tree-sitter-analyzer 1.6.1.2__py3-none-any.whl → 1.6.1.3__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of tree-sitter-analyzer might be problematic. Click here for more details.
- tree_sitter_analyzer/__init__.py +1 -1
- tree_sitter_analyzer/core/query.py +11 -9
- tree_sitter_analyzer/core/query_service.py +10 -13
- tree_sitter_analyzer/languages/python_plugin.py +51 -27
- tree_sitter_analyzer/logging_manager.py +361 -0
- tree_sitter_analyzer/mcp/server.py +1 -1
- tree_sitter_analyzer/mcp/tools/output_format_validator.py +147 -0
- tree_sitter_analyzer/mcp/tools/search_content_tool.py +41 -8
- tree_sitter_analyzer/mcp/utils/file_output_manager.py +74 -1
- tree_sitter_analyzer/mcp/utils/search_cache.py +9 -0
- tree_sitter_analyzer/utils.py +38 -203
- {tree_sitter_analyzer-1.6.1.2.dist-info → tree_sitter_analyzer-1.6.1.3.dist-info}/METADATA +5 -5
- {tree_sitter_analyzer-1.6.1.2.dist-info → tree_sitter_analyzer-1.6.1.3.dist-info}/RECORD +15 -13
- {tree_sitter_analyzer-1.6.1.2.dist-info → tree_sitter_analyzer-1.6.1.3.dist-info}/WHEEL +0 -0
- {tree_sitter_analyzer-1.6.1.2.dist-info → tree_sitter_analyzer-1.6.1.3.dist-info}/entry_points.txt +0 -0
tree_sitter_analyzer/__init__.py
CHANGED
|
@@ -10,7 +10,7 @@ import logging
|
|
|
10
10
|
import time
|
|
11
11
|
from typing import Any
|
|
12
12
|
|
|
13
|
-
from tree_sitter import Language, Node, Tree
|
|
13
|
+
from tree_sitter import Language, Node, Query, QueryCursor, Tree
|
|
14
14
|
|
|
15
15
|
from ..query_loader import get_query_loader
|
|
16
16
|
|
|
@@ -77,10 +77,11 @@ class QueryExecutor:
|
|
|
77
77
|
f"Query '{query_name}' not found", query_name=query_name
|
|
78
78
|
)
|
|
79
79
|
|
|
80
|
-
# Create and execute the query
|
|
80
|
+
# Create and execute the query using new API (tree-sitter 0.25.0+)
|
|
81
81
|
try:
|
|
82
|
-
query = language
|
|
83
|
-
|
|
82
|
+
query = Query(language, query_string)
|
|
83
|
+
cursor = QueryCursor(query)
|
|
84
|
+
captures = list(cursor.captures(tree.root_node))
|
|
84
85
|
|
|
85
86
|
# Process captures
|
|
86
87
|
try:
|
|
@@ -146,10 +147,11 @@ class QueryExecutor:
|
|
|
146
147
|
if language is None:
|
|
147
148
|
return self._create_error_result("Language is None") # type: ignore[unreachable]
|
|
148
149
|
|
|
149
|
-
# Create and execute the query
|
|
150
|
+
# Create and execute the query using new API (tree-sitter 0.25.0+)
|
|
150
151
|
try:
|
|
151
|
-
query = language
|
|
152
|
-
|
|
152
|
+
query = Query(language, query_string)
|
|
153
|
+
cursor = QueryCursor(query)
|
|
154
|
+
captures = list(cursor.captures(tree.root_node))
|
|
153
155
|
|
|
154
156
|
# Process captures
|
|
155
157
|
try:
|
|
@@ -373,8 +375,8 @@ class QueryExecutor:
|
|
|
373
375
|
if lang_obj is None:
|
|
374
376
|
return False
|
|
375
377
|
|
|
376
|
-
# Try to create the query
|
|
377
|
-
lang_obj
|
|
378
|
+
# Try to create the query using new API (tree-sitter 0.25.0+)
|
|
379
|
+
Query(lang_obj, query_string)
|
|
378
380
|
return True
|
|
379
381
|
|
|
380
382
|
except Exception as e:
|
|
@@ -9,6 +9,8 @@ Provides core tree-sitter query functionality including predefined and custom qu
|
|
|
9
9
|
import logging
|
|
10
10
|
from typing import Any
|
|
11
11
|
|
|
12
|
+
from tree_sitter import Query, QueryCursor
|
|
13
|
+
|
|
12
14
|
from ..encoding_utils import read_file_safe
|
|
13
15
|
from ..query_loader import query_loader
|
|
14
16
|
from .parser import Parser
|
|
@@ -80,23 +82,18 @@ class QueryService:
|
|
|
80
82
|
f"Query '{query_key}' not found for language '{language}'"
|
|
81
83
|
)
|
|
82
84
|
|
|
83
|
-
# Execute tree-sitter query
|
|
84
|
-
ts_query = language_obj
|
|
85
|
-
|
|
85
|
+
# Execute tree-sitter query using new API (tree-sitter 0.25.0+)
|
|
86
|
+
ts_query = Query(language_obj, query_string)
|
|
87
|
+
cursor = QueryCursor(ts_query)
|
|
88
|
+
matches = cursor.matches(tree.root_node)
|
|
86
89
|
|
|
87
|
-
# Process
|
|
90
|
+
# Process match results (new API returns list of (pattern_index, captures_dict))
|
|
88
91
|
results = []
|
|
89
|
-
|
|
90
|
-
#
|
|
91
|
-
for capture_name, nodes in
|
|
92
|
+
for pattern_index, captures_dict in matches:
|
|
93
|
+
# captures_dict is {capture_name: [node1, node2, ...]}
|
|
94
|
+
for capture_name, nodes in captures_dict.items():
|
|
92
95
|
for node in nodes:
|
|
93
96
|
results.append(self._create_result_dict(node, capture_name))
|
|
94
|
-
else:
|
|
95
|
-
# Old tree-sitter API returns list of tuples
|
|
96
|
-
for capture in captures:
|
|
97
|
-
if isinstance(capture, tuple) and len(capture) == 2:
|
|
98
|
-
node, name = capture
|
|
99
|
-
results.append(self._create_result_dict(node, name))
|
|
100
97
|
|
|
101
98
|
# Apply filters
|
|
102
99
|
if filter_expression and results:
|
|
@@ -15,6 +15,7 @@ if TYPE_CHECKING:
|
|
|
15
15
|
|
|
16
16
|
try:
|
|
17
17
|
import tree_sitter
|
|
18
|
+
from tree_sitter import Query, QueryCursor
|
|
18
19
|
|
|
19
20
|
TREE_SITTER_AVAILABLE = True
|
|
20
21
|
except ImportError:
|
|
@@ -113,17 +114,23 @@ class PythonElementExtractor(ElementExtractor):
|
|
|
113
114
|
|
|
114
115
|
language = tree.language if hasattr(tree, "language") else None
|
|
115
116
|
if language:
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
117
|
+
# Use new API (tree-sitter 0.25.0+)
|
|
118
|
+
query = Query(language, class_query)
|
|
119
|
+
cursor = QueryCursor(query)
|
|
120
|
+
matches = cursor.matches(tree.root_node)
|
|
121
|
+
|
|
122
|
+
# Process matches to get class bodies
|
|
123
|
+
class_bodies = []
|
|
124
|
+
for pattern_index, captures_dict in matches:
|
|
125
|
+
for capture_name, nodes in captures_dict.items():
|
|
126
|
+
if capture_name == "class.body":
|
|
127
|
+
class_bodies.extend(nodes)
|
|
128
|
+
|
|
129
|
+
# For each class body, extract attribute assignments
|
|
130
|
+
for class_body in class_bodies:
|
|
131
|
+
variables.extend(
|
|
132
|
+
self._extract_class_attributes(class_body, source_code)
|
|
133
|
+
)
|
|
127
134
|
|
|
128
135
|
except Exception as e:
|
|
129
136
|
log_warning(f"Could not extract Python class attributes: {e}")
|
|
@@ -664,20 +671,29 @@ class PythonElementExtractor(ElementExtractor):
|
|
|
664
671
|
language = tree.language if hasattr(tree, "language") else None
|
|
665
672
|
if language:
|
|
666
673
|
for query_string in import_queries:
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
674
|
+
# Use new API (tree-sitter 0.25.0+)
|
|
675
|
+
query = Query(language, query_string)
|
|
676
|
+
cursor = QueryCursor(query)
|
|
677
|
+
matches = cursor.matches(tree.root_node)
|
|
678
|
+
|
|
679
|
+
# Process matches to get statement nodes
|
|
680
|
+
statement_nodes = {}
|
|
681
|
+
for pattern_index, captures_dict in matches:
|
|
682
|
+
for capture_name, nodes in captures_dict.items():
|
|
683
|
+
if capture_name.endswith("statement"):
|
|
684
|
+
import_type = capture_name.split(".")[0]
|
|
685
|
+
if import_type not in statement_nodes:
|
|
686
|
+
statement_nodes[import_type] = []
|
|
687
|
+
statement_nodes[import_type].extend(nodes)
|
|
688
|
+
|
|
689
|
+
# Process different types of imports
|
|
690
|
+
for import_type, nodes in statement_nodes.items():
|
|
691
|
+
for node in nodes:
|
|
692
|
+
imp = self._extract_import_info(
|
|
693
|
+
node, source_code, import_type
|
|
694
|
+
)
|
|
695
|
+
if imp:
|
|
696
|
+
imports.append(imp)
|
|
681
697
|
|
|
682
698
|
except Exception as e:
|
|
683
699
|
log_warning(f"Could not extract Python imports: {e}")
|
|
@@ -1179,8 +1195,16 @@ class PythonPlugin(LanguagePlugin):
|
|
|
1179
1195
|
else:
|
|
1180
1196
|
return {"error": f"Unknown query: {query_name}"}
|
|
1181
1197
|
|
|
1182
|
-
|
|
1183
|
-
|
|
1198
|
+
# Use new API (tree-sitter 0.25.0+)
|
|
1199
|
+
query = Query(language, query_string)
|
|
1200
|
+
cursor = QueryCursor(query)
|
|
1201
|
+
matches = list(cursor.matches(tree.root_node))
|
|
1202
|
+
# Convert matches to legacy format for compatibility
|
|
1203
|
+
captures = []
|
|
1204
|
+
for pattern_index, captures_dict in matches:
|
|
1205
|
+
for capture_name, nodes in captures_dict.items():
|
|
1206
|
+
for node in nodes:
|
|
1207
|
+
captures.append((node, capture_name))
|
|
1184
1208
|
return {"captures": captures, "query": query_string}
|
|
1185
1209
|
|
|
1186
1210
|
except Exception as e:
|
|
@@ -0,0 +1,361 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
統一ログ管理システム
|
|
4
|
+
|
|
5
|
+
ログ出力の重複問題を解決するためのLoggerManagerクラスを提供します。
|
|
6
|
+
シングルトンパターンによりロガーインスタンスを一意に管理し、
|
|
7
|
+
重複ハンドラーを防止します。
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
import logging
|
|
11
|
+
import os
|
|
12
|
+
import sys
|
|
13
|
+
import tempfile
|
|
14
|
+
import threading
|
|
15
|
+
from pathlib import Path
|
|
16
|
+
from typing import Any, Dict, List, Optional
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class LoggerManager:
|
|
20
|
+
"""
|
|
21
|
+
統一されたロガー管理クラス
|
|
22
|
+
|
|
23
|
+
シングルトンパターンでロガーインスタンスを管理し、
|
|
24
|
+
重複ハンドラーを防止する。
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
_instance: Optional['LoggerManager'] = None
|
|
28
|
+
_lock: threading.Lock = threading.Lock()
|
|
29
|
+
_loggers: Dict[str, logging.Logger] = {}
|
|
30
|
+
_handler_registry: Dict[str, List[str]] = {}
|
|
31
|
+
_initialized: bool = False
|
|
32
|
+
_file_log_message_shown: bool = False
|
|
33
|
+
|
|
34
|
+
def __new__(cls) -> 'LoggerManager':
|
|
35
|
+
"""スレッドセーフなシングルトン実装"""
|
|
36
|
+
if cls._instance is None:
|
|
37
|
+
with cls._lock:
|
|
38
|
+
if cls._instance is None:
|
|
39
|
+
cls._instance = super().__new__(cls)
|
|
40
|
+
return cls._instance
|
|
41
|
+
|
|
42
|
+
def __init__(self) -> None:
|
|
43
|
+
"""初期化(シングルトンのため一度のみ実行)"""
|
|
44
|
+
if not self._initialized:
|
|
45
|
+
with self._lock:
|
|
46
|
+
if not self._initialized:
|
|
47
|
+
self._loggers = {}
|
|
48
|
+
self._handler_registry = {}
|
|
49
|
+
self._initialized = True
|
|
50
|
+
|
|
51
|
+
def get_logger(
|
|
52
|
+
self,
|
|
53
|
+
name: str = "tree_sitter_analyzer",
|
|
54
|
+
level: int | str = logging.WARNING
|
|
55
|
+
) -> logging.Logger:
|
|
56
|
+
"""
|
|
57
|
+
重複を防ぐロガー取得
|
|
58
|
+
|
|
59
|
+
Args:
|
|
60
|
+
name: ロガー名
|
|
61
|
+
level: ログレベル
|
|
62
|
+
|
|
63
|
+
Returns:
|
|
64
|
+
設定済みロガーインスタンス
|
|
65
|
+
"""
|
|
66
|
+
with self._lock:
|
|
67
|
+
if name not in self._loggers:
|
|
68
|
+
self._loggers[name] = self._create_logger(name, level)
|
|
69
|
+
else:
|
|
70
|
+
# 既存のロガーでもレベルを更新
|
|
71
|
+
numeric_level = self._convert_level(level)
|
|
72
|
+
|
|
73
|
+
# 環境変数からのレベル設定が優先
|
|
74
|
+
env_level = os.environ.get("LOG_LEVEL", "").upper()
|
|
75
|
+
if env_level and env_level in ["DEBUG", "INFO", "WARNING", "ERROR"]:
|
|
76
|
+
numeric_level = getattr(logging, env_level)
|
|
77
|
+
|
|
78
|
+
self._loggers[name].setLevel(numeric_level)
|
|
79
|
+
|
|
80
|
+
return self._loggers[name]
|
|
81
|
+
|
|
82
|
+
def _create_logger(self, name: str, level: int | str) -> logging.Logger:
|
|
83
|
+
"""
|
|
84
|
+
ロガー作成とハンドラー設定
|
|
85
|
+
|
|
86
|
+
Args:
|
|
87
|
+
name: ロガー名
|
|
88
|
+
level: ログレベル
|
|
89
|
+
|
|
90
|
+
Returns:
|
|
91
|
+
設定済みロガーインスタンス
|
|
92
|
+
"""
|
|
93
|
+
# レベル変換処理
|
|
94
|
+
numeric_level = self._convert_level(level)
|
|
95
|
+
|
|
96
|
+
# 環境変数からのレベル設定
|
|
97
|
+
env_level = os.environ.get("LOG_LEVEL", "").upper()
|
|
98
|
+
if env_level and env_level in ["DEBUG", "INFO", "WARNING", "ERROR"]:
|
|
99
|
+
numeric_level = getattr(logging, env_level)
|
|
100
|
+
|
|
101
|
+
logger = logging.getLogger(name)
|
|
102
|
+
|
|
103
|
+
# 重複ハンドラーチェック
|
|
104
|
+
if not self._has_required_handlers(logger, name):
|
|
105
|
+
self._setup_handlers(logger, name, numeric_level)
|
|
106
|
+
|
|
107
|
+
# ロガーレベル設定
|
|
108
|
+
logger.setLevel(numeric_level)
|
|
109
|
+
|
|
110
|
+
return logger
|
|
111
|
+
|
|
112
|
+
def _convert_level(self, level: int | str) -> int:
|
|
113
|
+
"""ログレベル文字列を数値に変換"""
|
|
114
|
+
if isinstance(level, str):
|
|
115
|
+
level_upper = level.upper()
|
|
116
|
+
level_map = {
|
|
117
|
+
"DEBUG": logging.DEBUG,
|
|
118
|
+
"INFO": logging.INFO,
|
|
119
|
+
"WARNING": logging.WARNING,
|
|
120
|
+
"ERROR": logging.ERROR,
|
|
121
|
+
}
|
|
122
|
+
return level_map.get(level_upper, logging.WARNING)
|
|
123
|
+
return level
|
|
124
|
+
|
|
125
|
+
def _has_required_handlers(self, logger: logging.Logger, name: str) -> bool:
|
|
126
|
+
"""
|
|
127
|
+
必要なハンドラーが既に設定されているかチェック
|
|
128
|
+
|
|
129
|
+
Args:
|
|
130
|
+
logger: チェック対象ロガー
|
|
131
|
+
name: ロガー名
|
|
132
|
+
|
|
133
|
+
Returns:
|
|
134
|
+
必要なハンドラーが設定済みの場合True
|
|
135
|
+
"""
|
|
136
|
+
if name in self._handler_registry:
|
|
137
|
+
# 既に管理されているロガーの場合は設定済みとみなす
|
|
138
|
+
return True
|
|
139
|
+
|
|
140
|
+
# 既存ハンドラーの有無をチェック
|
|
141
|
+
has_stream_handler = any(
|
|
142
|
+
isinstance(h, logging.StreamHandler) and not isinstance(h, logging.FileHandler)
|
|
143
|
+
for h in logger.handlers
|
|
144
|
+
)
|
|
145
|
+
|
|
146
|
+
if has_stream_handler:
|
|
147
|
+
# ハンドラー登録を記録
|
|
148
|
+
handler_types = [type(h).__name__ for h in logger.handlers]
|
|
149
|
+
self._handler_registry[name] = handler_types
|
|
150
|
+
return True
|
|
151
|
+
|
|
152
|
+
return False
|
|
153
|
+
|
|
154
|
+
def _setup_handlers(self, logger: logging.Logger, name: str, level: int) -> None:
|
|
155
|
+
"""
|
|
156
|
+
ロガーにハンドラーを設定
|
|
157
|
+
|
|
158
|
+
Args:
|
|
159
|
+
logger: 設定対象ロガー
|
|
160
|
+
name: ロガー名
|
|
161
|
+
level: ログレベル
|
|
162
|
+
"""
|
|
163
|
+
# メインハンドラー(stderr)の追加
|
|
164
|
+
if not self._has_stream_handler(logger):
|
|
165
|
+
stream_handler = SafeStreamHandler()
|
|
166
|
+
formatter = logging.Formatter(
|
|
167
|
+
"%(asctime)s - %(name)s - %(levelname)s - %(message)s"
|
|
168
|
+
)
|
|
169
|
+
stream_handler.setFormatter(formatter)
|
|
170
|
+
logger.addHandler(stream_handler)
|
|
171
|
+
|
|
172
|
+
# ファイルログハンドラーの追加(環境変数で有効化)
|
|
173
|
+
enable_file_log = (
|
|
174
|
+
os.environ.get("TREE_SITTER_ANALYZER_ENABLE_FILE_LOG", "").lower() == "true"
|
|
175
|
+
)
|
|
176
|
+
|
|
177
|
+
if enable_file_log and not self._has_file_handler(logger):
|
|
178
|
+
file_handler = self._create_file_handler(level)
|
|
179
|
+
if file_handler:
|
|
180
|
+
logger.addHandler(file_handler)
|
|
181
|
+
|
|
182
|
+
# ハンドラー登録を記録
|
|
183
|
+
handler_types = [type(h).__name__ for h in logger.handlers]
|
|
184
|
+
self._handler_registry[name] = handler_types
|
|
185
|
+
|
|
186
|
+
def _has_stream_handler(self, logger: logging.Logger) -> bool:
|
|
187
|
+
"""StreamHandlerの存在チェック"""
|
|
188
|
+
return any(
|
|
189
|
+
isinstance(h, logging.StreamHandler) and not isinstance(h, logging.FileHandler)
|
|
190
|
+
for h in logger.handlers
|
|
191
|
+
)
|
|
192
|
+
|
|
193
|
+
def _has_file_handler(self, logger: logging.Logger) -> bool:
|
|
194
|
+
"""FileHandlerの存在チェック"""
|
|
195
|
+
return any(isinstance(h, logging.FileHandler) for h in logger.handlers)
|
|
196
|
+
|
|
197
|
+
def _create_file_handler(self, level: int) -> Optional[logging.FileHandler]:
|
|
198
|
+
"""
|
|
199
|
+
ファイルハンドラーの作成
|
|
200
|
+
|
|
201
|
+
Args:
|
|
202
|
+
level: ログレベル
|
|
203
|
+
|
|
204
|
+
Returns:
|
|
205
|
+
作成されたFileHandlerまたはNone
|
|
206
|
+
"""
|
|
207
|
+
try:
|
|
208
|
+
# ログディレクトリの決定
|
|
209
|
+
log_dir = os.environ.get("TREE_SITTER_ANALYZER_LOG_DIR")
|
|
210
|
+
if log_dir:
|
|
211
|
+
log_path = Path(log_dir) / "tree_sitter_analyzer.log"
|
|
212
|
+
Path(log_dir).mkdir(parents=True, exist_ok=True)
|
|
213
|
+
else:
|
|
214
|
+
temp_dir = tempfile.gettempdir()
|
|
215
|
+
log_path = Path(temp_dir) / "tree_sitter_analyzer.log"
|
|
216
|
+
|
|
217
|
+
# ファイルログレベルの決定
|
|
218
|
+
file_log_level_str = os.environ.get(
|
|
219
|
+
"TREE_SITTER_ANALYZER_FILE_LOG_LEVEL", ""
|
|
220
|
+
).upper()
|
|
221
|
+
file_log_level = level # デフォルトはメインレベル
|
|
222
|
+
|
|
223
|
+
if file_log_level_str in ["DEBUG", "INFO", "WARNING", "ERROR"]:
|
|
224
|
+
file_log_level = getattr(logging, file_log_level_str)
|
|
225
|
+
|
|
226
|
+
# ファイルハンドラー作成
|
|
227
|
+
file_handler = logging.FileHandler(str(log_path), encoding="utf-8")
|
|
228
|
+
formatter = logging.Formatter(
|
|
229
|
+
"%(asctime)s - %(name)s - %(levelname)s - %(message)s"
|
|
230
|
+
)
|
|
231
|
+
file_handler.setFormatter(formatter)
|
|
232
|
+
file_handler.setLevel(file_log_level)
|
|
233
|
+
|
|
234
|
+
# ファイルパス情報を出力(1回のみ)
|
|
235
|
+
if not LoggerManager._file_log_message_shown:
|
|
236
|
+
LoggerManager._file_log_message_shown = True
|
|
237
|
+
if hasattr(sys, "stderr") and hasattr(sys.stderr, "write"):
|
|
238
|
+
try:
|
|
239
|
+
sys.stderr.write(
|
|
240
|
+
f"[LoggerManager] File logging enabled: {log_path}\n"
|
|
241
|
+
)
|
|
242
|
+
except Exception:
|
|
243
|
+
pass
|
|
244
|
+
|
|
245
|
+
return file_handler
|
|
246
|
+
|
|
247
|
+
except Exception as e:
|
|
248
|
+
# ファイルハンドラー作成に失敗してもメインの動作は継続
|
|
249
|
+
if hasattr(sys, "stderr") and hasattr(sys.stderr, "write"):
|
|
250
|
+
try:
|
|
251
|
+
sys.stderr.write(
|
|
252
|
+
f"[LoggerManager] File handler creation failed: {e}\n"
|
|
253
|
+
)
|
|
254
|
+
except Exception:
|
|
255
|
+
pass
|
|
256
|
+
return None
|
|
257
|
+
|
|
258
|
+
def reset_for_testing(self) -> None:
|
|
259
|
+
"""
|
|
260
|
+
テスト用リセット機能
|
|
261
|
+
|
|
262
|
+
Note:
|
|
263
|
+
本番環境では使用しないこと
|
|
264
|
+
"""
|
|
265
|
+
with self._lock:
|
|
266
|
+
# 全ハンドラーのクリーンアップ
|
|
267
|
+
for logger in self._loggers.values():
|
|
268
|
+
for handler in logger.handlers[:]:
|
|
269
|
+
try:
|
|
270
|
+
handler.close()
|
|
271
|
+
logger.removeHandler(handler)
|
|
272
|
+
except Exception:
|
|
273
|
+
pass
|
|
274
|
+
|
|
275
|
+
self._loggers.clear()
|
|
276
|
+
self._handler_registry.clear()
|
|
277
|
+
LoggerManager._file_log_message_shown = False
|
|
278
|
+
|
|
279
|
+
|
|
280
|
+
class SafeStreamHandler(logging.StreamHandler):
|
|
281
|
+
"""
|
|
282
|
+
安全なStreamHandler実装
|
|
283
|
+
|
|
284
|
+
MCPプロトコルのstdio通信やテスト環境での
|
|
285
|
+
ストリームクローズ問題に対応。
|
|
286
|
+
"""
|
|
287
|
+
|
|
288
|
+
def __init__(self, stream=None):
|
|
289
|
+
# デフォルトでstderrを使用(stdoutはMCP用に保持)
|
|
290
|
+
super().__init__(stream if stream is not None else sys.stderr)
|
|
291
|
+
|
|
292
|
+
def emit(self, record: Any) -> None:
|
|
293
|
+
"""
|
|
294
|
+
レコードの安全な出力
|
|
295
|
+
|
|
296
|
+
Args:
|
|
297
|
+
record: ログレコード
|
|
298
|
+
"""
|
|
299
|
+
try:
|
|
300
|
+
# ストリームの状態チェック
|
|
301
|
+
if hasattr(self.stream, "closed") and self.stream.closed:
|
|
302
|
+
return
|
|
303
|
+
|
|
304
|
+
if not hasattr(self.stream, "write"):
|
|
305
|
+
return
|
|
306
|
+
|
|
307
|
+
# pytest環境での特別処理
|
|
308
|
+
stream_name = getattr(self.stream, "name", "")
|
|
309
|
+
if stream_name is None or "pytest" in str(type(self.stream)).lower():
|
|
310
|
+
try:
|
|
311
|
+
super().emit(record)
|
|
312
|
+
return
|
|
313
|
+
except (ValueError, OSError, AttributeError, UnicodeError):
|
|
314
|
+
return
|
|
315
|
+
|
|
316
|
+
# 通常のストリーム書き込み可能性チェック
|
|
317
|
+
try:
|
|
318
|
+
if hasattr(self.stream, "writable") and not self.stream.writable():
|
|
319
|
+
return
|
|
320
|
+
except (ValueError, OSError, AttributeError, UnicodeError):
|
|
321
|
+
return
|
|
322
|
+
|
|
323
|
+
super().emit(record)
|
|
324
|
+
|
|
325
|
+
except (ValueError, OSError, AttributeError, UnicodeError):
|
|
326
|
+
# I/Oエラーは静かに無視(シャットダウン時やpytestキャプチャ時)
|
|
327
|
+
pass
|
|
328
|
+
except Exception:
|
|
329
|
+
# その他の予期しないエラーも静かに無視
|
|
330
|
+
pass
|
|
331
|
+
|
|
332
|
+
|
|
333
|
+
# グローバルインスタンス
|
|
334
|
+
_logger_manager = LoggerManager()
|
|
335
|
+
|
|
336
|
+
|
|
337
|
+
def get_logger_manager() -> LoggerManager:
|
|
338
|
+
"""
|
|
339
|
+
LoggerManagerのグローバルインスタンス取得
|
|
340
|
+
|
|
341
|
+
Returns:
|
|
342
|
+
LoggerManagerインスタンス
|
|
343
|
+
"""
|
|
344
|
+
return _logger_manager
|
|
345
|
+
|
|
346
|
+
|
|
347
|
+
def get_unified_logger(
|
|
348
|
+
name: str = "tree_sitter_analyzer",
|
|
349
|
+
level: int | str = logging.WARNING
|
|
350
|
+
) -> logging.Logger:
|
|
351
|
+
"""
|
|
352
|
+
統一されたロガー取得関数
|
|
353
|
+
|
|
354
|
+
Args:
|
|
355
|
+
name: ロガー名
|
|
356
|
+
level: ログレベル
|
|
357
|
+
|
|
358
|
+
Returns:
|
|
359
|
+
設定済みロガーインスタンス
|
|
360
|
+
"""
|
|
361
|
+
return _logger_manager.get_logger(name, level)
|
|
@@ -67,7 +67,7 @@ from .tools.read_partial_tool import ReadPartialTool
|
|
|
67
67
|
from .tools.search_content_tool import SearchContentTool
|
|
68
68
|
from .tools.table_format_tool import TableFormatTool
|
|
69
69
|
|
|
70
|
-
# Set up logging
|
|
70
|
+
# Set up logging using unified LoggerManager
|
|
71
71
|
logger = setup_logger(__name__)
|
|
72
72
|
|
|
73
73
|
|