|
9 | 9 | import logging
|
10 | 10 | import os
|
11 | 11 |
|
12 |
| -# Third-Party |
13 |
| -from opentelemetry import trace |
14 |
| -from opentelemetry.sdk.resources import Resource |
15 |
| -from opentelemetry.sdk.trace import TracerProvider |
16 |
| -from opentelemetry.sdk.trace.export import BatchSpanProcessor, ConsoleSpanExporter, SimpleSpanProcessor |
17 |
| -from opentelemetry.trace import Status, StatusCode |
| 12 | +# Try to import OpenTelemetry core components - make them truly optional |
| 13 | +OTEL_AVAILABLE = False |
| 14 | +try: |
| 15 | + # Third-Party |
| 16 | + from opentelemetry import trace |
| 17 | + from opentelemetry.sdk.resources import Resource |
| 18 | + from opentelemetry.sdk.trace import TracerProvider |
| 19 | + from opentelemetry.sdk.trace.export import BatchSpanProcessor, ConsoleSpanExporter, SimpleSpanProcessor |
| 20 | + from opentelemetry.trace import Status, StatusCode |
| 21 | + |
| 22 | + OTEL_AVAILABLE = True |
| 23 | +except ImportError: |
| 24 | + # OpenTelemetry not installed - set to None for graceful degradation |
| 25 | + trace = None |
| 26 | + Resource = None |
| 27 | + TracerProvider = None |
| 28 | + BatchSpanProcessor = None |
| 29 | + ConsoleSpanExporter = None |
| 30 | + SimpleSpanProcessor = None |
| 31 | + Status = None |
| 32 | + StatusCode = None |
18 | 33 |
|
19 | 34 | # Try to import optional exporters
|
20 | 35 | try:
|
@@ -73,6 +88,12 @@ def init_telemetry():
|
73 | 88 | logger.info("Observability disabled via OTEL_ENABLE_OBSERVABILITY=false")
|
74 | 89 | return None
|
75 | 90 |
|
| 91 | + # Check if OpenTelemetry is available |
| 92 | + if not OTEL_AVAILABLE: |
| 93 | + logger.warning("OpenTelemetry not installed. Telemetry features will be disabled.") |
| 94 | + logger.info("To enable telemetry, install with: pip install mcp-contextforge-gateway[observability]") |
| 95 | + return None |
| 96 | + |
76 | 97 | # Get exporter type from environment
|
77 | 98 | exporter_type = os.getenv("OTEL_TRACES_EXPORTER", "otlp").lower()
|
78 | 99 |
|
@@ -223,6 +244,10 @@ def decorator(func):
|
223 | 244 | The wrapped function with tracing capabilities.
|
224 | 245 | """
|
225 | 246 |
|
| 247 | + # If OpenTelemetry is not available, return the function unchanged |
| 248 | + if not OTEL_AVAILABLE: |
| 249 | + return func |
| 250 | + |
226 | 251 | async def wrapper(*args, **kwargs):
|
227 | 252 | """Async wrapper that adds tracing to the decorated function.
|
228 | 253 |
|
@@ -280,8 +305,8 @@ def create_span(name: str, attributes: dict = None):
|
280 | 305 | # Your code here
|
281 | 306 | pass
|
282 | 307 | """
|
283 |
| - if not _TRACER: |
284 |
| - # Return a no-op context manager if tracing is not configured |
| 308 | + if not _TRACER or not OTEL_AVAILABLE: |
| 309 | + # Return a no-op context manager if tracing is not configured or available |
285 | 310 | return nullcontext()
|
286 | 311 |
|
287 | 312 | # Start span and return the context manager
|
@@ -337,12 +362,14 @@ def __exit__(self, exc_type, exc_val, exc_tb):
|
337 | 362 | # Record exception if one occurred
|
338 | 363 | if exc_type is not None and self.span:
|
339 | 364 | self.span.record_exception(exc_val)
|
340 |
| - self.span.set_status(Status(StatusCode.ERROR, str(exc_val))) |
| 365 | + if OTEL_AVAILABLE and Status and StatusCode: |
| 366 | + self.span.set_status(Status(StatusCode.ERROR, str(exc_val))) |
341 | 367 | self.span.set_attribute("error", True)
|
342 | 368 | self.span.set_attribute("error.type", exc_type.__name__)
|
343 | 369 | self.span.set_attribute("error.message", str(exc_val))
|
344 | 370 | elif self.span:
|
345 |
| - self.span.set_status(Status(StatusCode.OK)) |
| 371 | + if OTEL_AVAILABLE and Status and StatusCode: |
| 372 | + self.span.set_status(Status(StatusCode.OK)) |
346 | 373 | return self.span_context.__exit__(exc_type, exc_val, exc_tb)
|
347 | 374 |
|
348 | 375 | return SpanWithAttributes(span_context, attributes)
|
|
0 commit comments