|
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