foundry-mcp 0.3.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.
- foundry_mcp/__init__.py +7 -0
- foundry_mcp/cli/__init__.py +80 -0
- foundry_mcp/cli/__main__.py +9 -0
- foundry_mcp/cli/agent.py +96 -0
- foundry_mcp/cli/commands/__init__.py +37 -0
- foundry_mcp/cli/commands/cache.py +137 -0
- foundry_mcp/cli/commands/dashboard.py +148 -0
- foundry_mcp/cli/commands/dev.py +446 -0
- foundry_mcp/cli/commands/journal.py +377 -0
- foundry_mcp/cli/commands/lifecycle.py +274 -0
- foundry_mcp/cli/commands/modify.py +824 -0
- foundry_mcp/cli/commands/plan.py +633 -0
- foundry_mcp/cli/commands/pr.py +393 -0
- foundry_mcp/cli/commands/review.py +652 -0
- foundry_mcp/cli/commands/session.py +479 -0
- foundry_mcp/cli/commands/specs.py +856 -0
- foundry_mcp/cli/commands/tasks.py +807 -0
- foundry_mcp/cli/commands/testing.py +676 -0
- foundry_mcp/cli/commands/validate.py +982 -0
- foundry_mcp/cli/config.py +98 -0
- foundry_mcp/cli/context.py +259 -0
- foundry_mcp/cli/flags.py +266 -0
- foundry_mcp/cli/logging.py +212 -0
- foundry_mcp/cli/main.py +44 -0
- foundry_mcp/cli/output.py +122 -0
- foundry_mcp/cli/registry.py +110 -0
- foundry_mcp/cli/resilience.py +178 -0
- foundry_mcp/cli/transcript.py +217 -0
- foundry_mcp/config.py +850 -0
- foundry_mcp/core/__init__.py +144 -0
- foundry_mcp/core/ai_consultation.py +1636 -0
- foundry_mcp/core/cache.py +195 -0
- foundry_mcp/core/capabilities.py +446 -0
- foundry_mcp/core/concurrency.py +898 -0
- foundry_mcp/core/context.py +540 -0
- foundry_mcp/core/discovery.py +1603 -0
- foundry_mcp/core/error_collection.py +728 -0
- foundry_mcp/core/error_store.py +592 -0
- foundry_mcp/core/feature_flags.py +592 -0
- foundry_mcp/core/health.py +749 -0
- foundry_mcp/core/journal.py +694 -0
- foundry_mcp/core/lifecycle.py +412 -0
- foundry_mcp/core/llm_config.py +1350 -0
- foundry_mcp/core/llm_patterns.py +510 -0
- foundry_mcp/core/llm_provider.py +1569 -0
- foundry_mcp/core/logging_config.py +374 -0
- foundry_mcp/core/metrics_persistence.py +584 -0
- foundry_mcp/core/metrics_registry.py +327 -0
- foundry_mcp/core/metrics_store.py +641 -0
- foundry_mcp/core/modifications.py +224 -0
- foundry_mcp/core/naming.py +123 -0
- foundry_mcp/core/observability.py +1216 -0
- foundry_mcp/core/otel.py +452 -0
- foundry_mcp/core/otel_stubs.py +264 -0
- foundry_mcp/core/pagination.py +255 -0
- foundry_mcp/core/progress.py +317 -0
- foundry_mcp/core/prometheus.py +577 -0
- foundry_mcp/core/prompts/__init__.py +464 -0
- foundry_mcp/core/prompts/fidelity_review.py +546 -0
- foundry_mcp/core/prompts/markdown_plan_review.py +511 -0
- foundry_mcp/core/prompts/plan_review.py +623 -0
- foundry_mcp/core/providers/__init__.py +225 -0
- foundry_mcp/core/providers/base.py +476 -0
- foundry_mcp/core/providers/claude.py +460 -0
- foundry_mcp/core/providers/codex.py +619 -0
- foundry_mcp/core/providers/cursor_agent.py +642 -0
- foundry_mcp/core/providers/detectors.py +488 -0
- foundry_mcp/core/providers/gemini.py +405 -0
- foundry_mcp/core/providers/opencode.py +616 -0
- foundry_mcp/core/providers/opencode_wrapper.js +302 -0
- foundry_mcp/core/providers/package-lock.json +24 -0
- foundry_mcp/core/providers/package.json +25 -0
- foundry_mcp/core/providers/registry.py +607 -0
- foundry_mcp/core/providers/test_provider.py +171 -0
- foundry_mcp/core/providers/validation.py +729 -0
- foundry_mcp/core/rate_limit.py +427 -0
- foundry_mcp/core/resilience.py +600 -0
- foundry_mcp/core/responses.py +934 -0
- foundry_mcp/core/review.py +366 -0
- foundry_mcp/core/security.py +438 -0
- foundry_mcp/core/spec.py +1650 -0
- foundry_mcp/core/task.py +1289 -0
- foundry_mcp/core/testing.py +450 -0
- foundry_mcp/core/validation.py +2081 -0
- foundry_mcp/dashboard/__init__.py +32 -0
- foundry_mcp/dashboard/app.py +119 -0
- foundry_mcp/dashboard/components/__init__.py +17 -0
- foundry_mcp/dashboard/components/cards.py +88 -0
- foundry_mcp/dashboard/components/charts.py +234 -0
- foundry_mcp/dashboard/components/filters.py +136 -0
- foundry_mcp/dashboard/components/tables.py +195 -0
- foundry_mcp/dashboard/data/__init__.py +11 -0
- foundry_mcp/dashboard/data/stores.py +433 -0
- foundry_mcp/dashboard/launcher.py +289 -0
- foundry_mcp/dashboard/views/__init__.py +12 -0
- foundry_mcp/dashboard/views/errors.py +217 -0
- foundry_mcp/dashboard/views/metrics.py +174 -0
- foundry_mcp/dashboard/views/overview.py +160 -0
- foundry_mcp/dashboard/views/providers.py +83 -0
- foundry_mcp/dashboard/views/sdd_workflow.py +255 -0
- foundry_mcp/dashboard/views/tool_usage.py +139 -0
- foundry_mcp/prompts/__init__.py +9 -0
- foundry_mcp/prompts/workflows.py +525 -0
- foundry_mcp/resources/__init__.py +9 -0
- foundry_mcp/resources/specs.py +591 -0
- foundry_mcp/schemas/__init__.py +38 -0
- foundry_mcp/schemas/sdd-spec-schema.json +386 -0
- foundry_mcp/server.py +164 -0
- foundry_mcp/tools/__init__.py +10 -0
- foundry_mcp/tools/unified/__init__.py +71 -0
- foundry_mcp/tools/unified/authoring.py +1487 -0
- foundry_mcp/tools/unified/context_helpers.py +98 -0
- foundry_mcp/tools/unified/documentation_helpers.py +198 -0
- foundry_mcp/tools/unified/environment.py +939 -0
- foundry_mcp/tools/unified/error.py +462 -0
- foundry_mcp/tools/unified/health.py +225 -0
- foundry_mcp/tools/unified/journal.py +841 -0
- foundry_mcp/tools/unified/lifecycle.py +632 -0
- foundry_mcp/tools/unified/metrics.py +777 -0
- foundry_mcp/tools/unified/plan.py +745 -0
- foundry_mcp/tools/unified/pr.py +294 -0
- foundry_mcp/tools/unified/provider.py +629 -0
- foundry_mcp/tools/unified/review.py +685 -0
- foundry_mcp/tools/unified/review_helpers.py +299 -0
- foundry_mcp/tools/unified/router.py +102 -0
- foundry_mcp/tools/unified/server.py +580 -0
- foundry_mcp/tools/unified/spec.py +808 -0
- foundry_mcp/tools/unified/task.py +2202 -0
- foundry_mcp/tools/unified/test.py +370 -0
- foundry_mcp/tools/unified/verification.py +520 -0
- foundry_mcp-0.3.3.dist-info/METADATA +337 -0
- foundry_mcp-0.3.3.dist-info/RECORD +135 -0
- foundry_mcp-0.3.3.dist-info/WHEEL +4 -0
- foundry_mcp-0.3.3.dist-info/entry_points.txt +3 -0
- foundry_mcp-0.3.3.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,374 @@
|
|
|
1
|
+
"""Structured logging configuration with automatic context injection.
|
|
2
|
+
|
|
3
|
+
This module provides logging utilities that automatically inject request context
|
|
4
|
+
(correlation ID, client ID, etc.) into log records, enabling unified log
|
|
5
|
+
correlation across the foundry-mcp codebase.
|
|
6
|
+
|
|
7
|
+
Usage:
|
|
8
|
+
from foundry_mcp.core.logging_config import (
|
|
9
|
+
get_logger,
|
|
10
|
+
configure_logging,
|
|
11
|
+
ContextFilter,
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
# Get a logger with automatic context injection
|
|
15
|
+
logger = get_logger(__name__)
|
|
16
|
+
|
|
17
|
+
# Within a request context, logs automatically include correlation_id
|
|
18
|
+
with sync_request_context() as ctx:
|
|
19
|
+
logger.info("Processing request") # Includes correlation_id in record
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
from __future__ import annotations
|
|
23
|
+
|
|
24
|
+
import json
|
|
25
|
+
import logging
|
|
26
|
+
import sys
|
|
27
|
+
from datetime import datetime, timezone
|
|
28
|
+
from typing import Any, Dict, Optional, TextIO, Union
|
|
29
|
+
|
|
30
|
+
from foundry_mcp.core.context import (
|
|
31
|
+
get_client_id,
|
|
32
|
+
get_correlation_id,
|
|
33
|
+
get_start_time,
|
|
34
|
+
get_trace_context,
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
__all__ = [
|
|
38
|
+
"ContextFilter",
|
|
39
|
+
"StructuredFormatter",
|
|
40
|
+
"configure_logging",
|
|
41
|
+
"get_logger",
|
|
42
|
+
"add_context_filter",
|
|
43
|
+
]
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class ContextFilter(logging.Filter):
|
|
47
|
+
"""Logging filter that injects request context into log records.
|
|
48
|
+
|
|
49
|
+
This filter adds the following attributes to every log record:
|
|
50
|
+
- correlation_id: Current request correlation ID
|
|
51
|
+
- client_id: Current client identifier
|
|
52
|
+
- elapsed_ms: Milliseconds since request start
|
|
53
|
+
- trace_id: W3C trace ID if available
|
|
54
|
+
|
|
55
|
+
Example:
|
|
56
|
+
handler = logging.StreamHandler()
|
|
57
|
+
handler.addFilter(ContextFilter())
|
|
58
|
+
logger.addHandler(handler)
|
|
59
|
+
"""
|
|
60
|
+
|
|
61
|
+
def filter(self, record: logging.LogRecord) -> bool:
|
|
62
|
+
"""Add context attributes to the log record.
|
|
63
|
+
|
|
64
|
+
Args:
|
|
65
|
+
record: Log record to enrich
|
|
66
|
+
|
|
67
|
+
Returns:
|
|
68
|
+
Always True (allows all records through)
|
|
69
|
+
"""
|
|
70
|
+
# Always inject context, even if empty
|
|
71
|
+
record.correlation_id = get_correlation_id() or "-"
|
|
72
|
+
record.client_id = get_client_id() or "anonymous"
|
|
73
|
+
|
|
74
|
+
# Calculate elapsed time
|
|
75
|
+
start_time = get_start_time()
|
|
76
|
+
if start_time > 0:
|
|
77
|
+
import time
|
|
78
|
+
|
|
79
|
+
record.elapsed_ms = round((time.time() - start_time) * 1000, 2)
|
|
80
|
+
else:
|
|
81
|
+
record.elapsed_ms = 0.0
|
|
82
|
+
|
|
83
|
+
# Add trace context if available
|
|
84
|
+
trace_ctx = get_trace_context()
|
|
85
|
+
if trace_ctx:
|
|
86
|
+
record.trace_id = trace_ctx.trace_id
|
|
87
|
+
record.parent_id = trace_ctx.parent_id
|
|
88
|
+
record.trace_sampled = trace_ctx.is_sampled
|
|
89
|
+
else:
|
|
90
|
+
record.trace_id = "-"
|
|
91
|
+
record.parent_id = "-"
|
|
92
|
+
record.trace_sampled = False
|
|
93
|
+
|
|
94
|
+
return True
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
class StructuredFormatter(logging.Formatter):
|
|
98
|
+
"""JSON-structured log formatter for machine-readable output.
|
|
99
|
+
|
|
100
|
+
Produces newline-delimited JSON logs suitable for log aggregation
|
|
101
|
+
systems like Elasticsearch, Splunk, or CloudWatch.
|
|
102
|
+
|
|
103
|
+
Example output:
|
|
104
|
+
{"timestamp":"2024-01-15T10:30:45.123Z","level":"INFO",
|
|
105
|
+
"logger":"foundry_mcp.tools.specs","message":"Spec loaded",
|
|
106
|
+
"correlation_id":"req_a1b2c3d4e5f6","client_id":"user123",
|
|
107
|
+
"elapsed_ms":42.5}
|
|
108
|
+
"""
|
|
109
|
+
|
|
110
|
+
def __init__(
|
|
111
|
+
self,
|
|
112
|
+
*,
|
|
113
|
+
include_extra: bool = True,
|
|
114
|
+
include_exception: bool = True,
|
|
115
|
+
timestamp_format: str = "iso",
|
|
116
|
+
):
|
|
117
|
+
"""Initialize the structured formatter.
|
|
118
|
+
|
|
119
|
+
Args:
|
|
120
|
+
include_extra: Include extra record attributes
|
|
121
|
+
include_exception: Include exception info in output
|
|
122
|
+
timestamp_format: "iso" for ISO 8601, "unix" for Unix timestamp
|
|
123
|
+
"""
|
|
124
|
+
super().__init__()
|
|
125
|
+
self.include_extra = include_extra
|
|
126
|
+
self.include_exception = include_exception
|
|
127
|
+
self.timestamp_format = timestamp_format
|
|
128
|
+
|
|
129
|
+
# Standard attributes to exclude from "extra"
|
|
130
|
+
self._standard_attrs = {
|
|
131
|
+
"name",
|
|
132
|
+
"msg",
|
|
133
|
+
"args",
|
|
134
|
+
"levelname",
|
|
135
|
+
"levelno",
|
|
136
|
+
"pathname",
|
|
137
|
+
"filename",
|
|
138
|
+
"module",
|
|
139
|
+
"lineno",
|
|
140
|
+
"funcName",
|
|
141
|
+
"created",
|
|
142
|
+
"msecs",
|
|
143
|
+
"relativeCreated",
|
|
144
|
+
"thread",
|
|
145
|
+
"threadName",
|
|
146
|
+
"processName",
|
|
147
|
+
"process",
|
|
148
|
+
"message",
|
|
149
|
+
"exc_info",
|
|
150
|
+
"exc_text",
|
|
151
|
+
"stack_info",
|
|
152
|
+
# Context attributes (handled separately)
|
|
153
|
+
"correlation_id",
|
|
154
|
+
"client_id",
|
|
155
|
+
"elapsed_ms",
|
|
156
|
+
"trace_id",
|
|
157
|
+
"parent_id",
|
|
158
|
+
"trace_sampled",
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
def format(self, record: logging.LogRecord) -> str:
|
|
162
|
+
"""Format the log record as JSON.
|
|
163
|
+
|
|
164
|
+
Args:
|
|
165
|
+
record: Log record to format
|
|
166
|
+
|
|
167
|
+
Returns:
|
|
168
|
+
JSON-formatted log line
|
|
169
|
+
"""
|
|
170
|
+
# Build base log entry
|
|
171
|
+
log_entry: Dict[str, Any] = {}
|
|
172
|
+
|
|
173
|
+
# Timestamp
|
|
174
|
+
if self.timestamp_format == "iso":
|
|
175
|
+
log_entry["timestamp"] = datetime.fromtimestamp(
|
|
176
|
+
record.created, tz=timezone.utc
|
|
177
|
+
).isoformat()
|
|
178
|
+
else:
|
|
179
|
+
log_entry["timestamp"] = record.created
|
|
180
|
+
|
|
181
|
+
# Core fields
|
|
182
|
+
log_entry["level"] = record.levelname
|
|
183
|
+
log_entry["logger"] = record.name
|
|
184
|
+
log_entry["message"] = record.getMessage()
|
|
185
|
+
|
|
186
|
+
# Location (optional, useful for debugging)
|
|
187
|
+
log_entry["location"] = {
|
|
188
|
+
"file": record.filename,
|
|
189
|
+
"line": record.lineno,
|
|
190
|
+
"function": record.funcName,
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
# Context fields (injected by ContextFilter)
|
|
194
|
+
log_entry["correlation_id"] = getattr(record, "correlation_id", "-")
|
|
195
|
+
log_entry["client_id"] = getattr(record, "client_id", "anonymous")
|
|
196
|
+
log_entry["elapsed_ms"] = getattr(record, "elapsed_ms", 0.0)
|
|
197
|
+
|
|
198
|
+
# Trace context
|
|
199
|
+
trace_id = getattr(record, "trace_id", "-")
|
|
200
|
+
if trace_id and trace_id != "-":
|
|
201
|
+
log_entry["trace"] = {
|
|
202
|
+
"trace_id": trace_id,
|
|
203
|
+
"parent_id": getattr(record, "parent_id", "-"),
|
|
204
|
+
"sampled": getattr(record, "trace_sampled", False),
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
# Exception info
|
|
208
|
+
if self.include_exception and record.exc_info:
|
|
209
|
+
log_entry["exception"] = self.formatException(record.exc_info)
|
|
210
|
+
|
|
211
|
+
# Extra attributes
|
|
212
|
+
if self.include_extra:
|
|
213
|
+
extra = {}
|
|
214
|
+
for key, value in record.__dict__.items():
|
|
215
|
+
if key not in self._standard_attrs:
|
|
216
|
+
try:
|
|
217
|
+
# Ensure value is JSON-serializable
|
|
218
|
+
json.dumps(value)
|
|
219
|
+
extra[key] = value
|
|
220
|
+
except (TypeError, ValueError):
|
|
221
|
+
extra[key] = str(value)
|
|
222
|
+
if extra:
|
|
223
|
+
log_entry["extra"] = extra
|
|
224
|
+
|
|
225
|
+
return json.dumps(log_entry, default=str)
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
class HumanReadableFormatter(logging.Formatter):
|
|
229
|
+
"""Human-readable formatter with context prefix.
|
|
230
|
+
|
|
231
|
+
Produces logs in format:
|
|
232
|
+
[LEVEL] [correlation_id] logger: message
|
|
233
|
+
|
|
234
|
+
Example:
|
|
235
|
+
[INFO] [req_a1b2c3] foundry_mcp.tools.specs: Spec loaded successfully
|
|
236
|
+
"""
|
|
237
|
+
|
|
238
|
+
def __init__(
|
|
239
|
+
self,
|
|
240
|
+
*,
|
|
241
|
+
include_timestamp: bool = True,
|
|
242
|
+
include_location: bool = False,
|
|
243
|
+
color: bool = False,
|
|
244
|
+
):
|
|
245
|
+
"""Initialize the human-readable formatter.
|
|
246
|
+
|
|
247
|
+
Args:
|
|
248
|
+
include_timestamp: Include timestamp in output
|
|
249
|
+
include_location: Include file:line in output
|
|
250
|
+
color: Use ANSI colors (disabled by default per project standards)
|
|
251
|
+
"""
|
|
252
|
+
super().__init__()
|
|
253
|
+
self.include_timestamp = include_timestamp
|
|
254
|
+
self.include_location = include_location
|
|
255
|
+
self.color = color
|
|
256
|
+
|
|
257
|
+
def format(self, record: logging.LogRecord) -> str:
|
|
258
|
+
"""Format the log record for human reading.
|
|
259
|
+
|
|
260
|
+
Args:
|
|
261
|
+
record: Log record to format
|
|
262
|
+
|
|
263
|
+
Returns:
|
|
264
|
+
Formatted log line
|
|
265
|
+
"""
|
|
266
|
+
parts = []
|
|
267
|
+
|
|
268
|
+
# Timestamp
|
|
269
|
+
if self.include_timestamp:
|
|
270
|
+
ts = datetime.fromtimestamp(record.created).strftime("%Y-%m-%d %H:%M:%S")
|
|
271
|
+
parts.append(ts)
|
|
272
|
+
|
|
273
|
+
# Level
|
|
274
|
+
parts.append(f"[{record.levelname}]")
|
|
275
|
+
|
|
276
|
+
# Correlation ID
|
|
277
|
+
corr_id = getattr(record, "correlation_id", "-")
|
|
278
|
+
if corr_id and corr_id != "-":
|
|
279
|
+
parts.append(f"[{corr_id}]")
|
|
280
|
+
|
|
281
|
+
# Logger name (shortened)
|
|
282
|
+
logger_name = record.name
|
|
283
|
+
if logger_name.startswith("foundry_mcp."):
|
|
284
|
+
logger_name = logger_name[12:] # Remove "foundry_mcp."
|
|
285
|
+
parts.append(f"{logger_name}:")
|
|
286
|
+
|
|
287
|
+
# Message
|
|
288
|
+
parts.append(record.getMessage())
|
|
289
|
+
|
|
290
|
+
# Location
|
|
291
|
+
if self.include_location:
|
|
292
|
+
parts.append(f"({record.filename}:{record.lineno})")
|
|
293
|
+
|
|
294
|
+
result = " ".join(parts)
|
|
295
|
+
|
|
296
|
+
# Exception
|
|
297
|
+
if record.exc_info:
|
|
298
|
+
result += "\n" + self.formatException(record.exc_info)
|
|
299
|
+
|
|
300
|
+
return result
|
|
301
|
+
|
|
302
|
+
|
|
303
|
+
def configure_logging(
|
|
304
|
+
*,
|
|
305
|
+
level: Union[int, str] = logging.INFO,
|
|
306
|
+
format: str = "structured", # "structured" or "human"
|
|
307
|
+
stream: Optional[TextIO] = None,
|
|
308
|
+
add_context: bool = True,
|
|
309
|
+
) -> logging.Logger:
|
|
310
|
+
"""Configure the root foundry_mcp logger.
|
|
311
|
+
|
|
312
|
+
Args:
|
|
313
|
+
level: Log level (default: INFO)
|
|
314
|
+
format: Output format ("structured" for JSON, "human" for readable)
|
|
315
|
+
stream: Output stream (default: stderr)
|
|
316
|
+
add_context: Add ContextFilter for automatic context injection
|
|
317
|
+
|
|
318
|
+
Returns:
|
|
319
|
+
Configured root logger for foundry_mcp
|
|
320
|
+
"""
|
|
321
|
+
logger = logging.getLogger("foundry_mcp")
|
|
322
|
+
logger.setLevel(level)
|
|
323
|
+
|
|
324
|
+
# Remove existing handlers
|
|
325
|
+
logger.handlers.clear()
|
|
326
|
+
|
|
327
|
+
# Create handler
|
|
328
|
+
handler = logging.StreamHandler(stream or sys.stderr)
|
|
329
|
+
handler.setLevel(level)
|
|
330
|
+
|
|
331
|
+
# Add formatter
|
|
332
|
+
if format == "structured":
|
|
333
|
+
handler.setFormatter(StructuredFormatter())
|
|
334
|
+
else:
|
|
335
|
+
handler.setFormatter(HumanReadableFormatter())
|
|
336
|
+
|
|
337
|
+
# Add context filter
|
|
338
|
+
if add_context:
|
|
339
|
+
handler.addFilter(ContextFilter())
|
|
340
|
+
|
|
341
|
+
logger.addHandler(handler)
|
|
342
|
+
|
|
343
|
+
return logger
|
|
344
|
+
|
|
345
|
+
|
|
346
|
+
def get_logger(name: str) -> logging.Logger:
|
|
347
|
+
"""Get a logger with automatic context injection.
|
|
348
|
+
|
|
349
|
+
The logger inherits from the foundry_mcp root logger, which should
|
|
350
|
+
be configured via configure_logging(). Context injection happens
|
|
351
|
+
automatically via the ContextFilter on the root handler.
|
|
352
|
+
|
|
353
|
+
Args:
|
|
354
|
+
name: Logger name (typically __name__)
|
|
355
|
+
|
|
356
|
+
Returns:
|
|
357
|
+
Logger instance
|
|
358
|
+
"""
|
|
359
|
+
# Ensure name is under foundry_mcp namespace
|
|
360
|
+
if not name.startswith("foundry_mcp"):
|
|
361
|
+
name = f"foundry_mcp.{name}"
|
|
362
|
+
|
|
363
|
+
return logging.getLogger(name)
|
|
364
|
+
|
|
365
|
+
|
|
366
|
+
def add_context_filter(handler: logging.Handler) -> None:
|
|
367
|
+
"""Add ContextFilter to an existing handler.
|
|
368
|
+
|
|
369
|
+
Useful for adding context injection to handlers created elsewhere.
|
|
370
|
+
|
|
371
|
+
Args:
|
|
372
|
+
handler: Handler to add filter to
|
|
373
|
+
"""
|
|
374
|
+
handler.addFilter(ContextFilter())
|