הראה קוד מקור ל observability_otel

"""
OpenTelemetry setup (safe, optional, idempotent).

This module configures tracing (OTLP exporter) and auto-instrumentation for:

- Flask (when a Flask app instance is provided)
- requests (HTTP client)
- PyMongo

Design goals:

- Fail-open: if OpenTelemetry packages are not installed or misconfigured,
  setup_telemetry silently returns without raising.
- Idempotent: multiple calls are safe; only the first successful init takes effect.
- Non-invasive: avoid changing existing structlog configuration; we rely on
  observability._add_otel_ids to inject trace_id/span_id when tracing is active.
"""
from __future__ import annotations

from typing import Any
import os

_OTEL_INITIALIZED: bool = False


def _str2bool(value: str | None) -> bool:
    if value is None:
        return False
    return value.strip().lower() in {"1", "true", "yes", "y", "on"}


[תיעוד] def setup_telemetry( *, service_name: str = "codebot-service", service_version: str | None = None, environment: str | None = None, flask_app: Any | None = None, ) -> None: """Initialize OpenTelemetry tracing and basic instrumentation. Parameters ---------- service_name: str Logical service name for resource attributes. service_version: Optional[str] Version string reported with resource. environment: Optional[str] Environment hint (e.g., production/staging/development). flask_app: Optional[Flask] Flask app instance for explicit instrumentation; if None, Flask instrumentation is skipped. """ global _OTEL_INITIALIZED if _OTEL_INITIALIZED: return # Import inside the function to keep the dependency optional try: from opentelemetry import trace, metrics from opentelemetry.sdk.trace import TracerProvider from opentelemetry.sdk.trace.export import BatchSpanProcessor from opentelemetry.sdk.resources import Resource from opentelemetry.exporter.otlp.proto.grpc.trace_exporter import ( OTLPSpanExporter, ) # Metrics are optional; configure provider if exporter(s) are available try: from opentelemetry.sdk.metrics import MeterProvider from opentelemetry.sdk.metrics.export import ( PeriodicExportingMetricReader, ) from opentelemetry.exporter.otlp.proto.grpc.metric_exporter import ( OTLPMetricExporter, ) _METRICS_AVAILABLE = True except Exception: _METRICS_AVAILABLE = False # Instrumentations (all optional) try: from opentelemetry.instrumentation.requests import ( RequestsInstrumentor, ) except Exception: RequestsInstrumentor = None try: from opentelemetry.instrumentation.flask import ( FlaskInstrumentor, ) except Exception: FlaskInstrumentor = None try: from opentelemetry.instrumentation.pymongo import ( PymongoInstrumentor, ) except Exception: PymongoInstrumentor = None # ----- Resource ----- resource_attrs = { "service.name": service_name, } if service_version: resource_attrs["service.version"] = service_version env_val = (environment or os.getenv("ENVIRONMENT") or os.getenv("ENV") or "production").strip() resource_attrs["deployment.environment"] = env_val resource_attrs["deployment.type"] = os.getenv("DEPLOYMENT_TYPE", "render") resource = Resource.create(resource_attrs) # ----- Tracing ----- # אין להשתמש ב-localhost כברירת מחדל בעננים (Render וכד') # אם לא הוגדר OTEL_EXPORTER_OTLP_ENDPOINT – דלג בשקט על אתחול OTel endpoint = os.getenv("OTEL_EXPORTER_OTLP_ENDPOINT") or "" insecure = _str2bool(os.getenv("OTEL_EXPORTER_INSECURE", "false")) if endpoint.strip(): span_exporter = OTLPSpanExporter(endpoint=endpoint, insecure=insecure) tracer_provider = TracerProvider(resource=resource) tracer_provider.add_span_processor(BatchSpanProcessor(span_exporter)) trace.set_tracer_provider(tracer_provider) # ----- Metrics (best-effort) ----- # Guard metrics initialization behind explicit feature flags. # # - OTLP metrics exporter requires a valid endpoint to avoid noisy retries to localhost:4317. # - Prometheus exporter does NOT require an OTLP endpoint (scraped via /metrics). if _METRICS_AVAILABLE: try: enable_metrics = _str2bool(os.getenv("ENABLE_METRICS", "false")) except Exception: enable_metrics = False try: enable_prometheus = _str2bool(os.getenv("ENABLE_PROMETHEUS_METRICS", "false")) or _str2bool( os.getenv("ENABLE_PROMETHEUS_OTEL_METRICS", "false") ) except Exception: enable_prometheus = False metric_readers = [] # OTLP exporter (push) if enable_metrics and endpoint.strip(): try: metric_exporter = OTLPMetricExporter(endpoint=endpoint, insecure=insecure) metric_reader = PeriodicExportingMetricReader( metric_exporter, export_interval_millis=60000 ) metric_readers.append(metric_reader) except Exception: # Metrics are optional – ignore exporter/transport errors pass # Prometheus exporter (pull/scrape) if enable_prometheus: try: from opentelemetry.exporter.prometheus import PrometheusMetricReader # type: ignore metric_readers.append(PrometheusMetricReader()) except Exception: # Prometheus exporter is optional and may not be installed pass if metric_readers: try: meter_provider = MeterProvider(resource=resource, metric_readers=metric_readers) metrics.set_meter_provider(meter_provider) except Exception: pass # ----- Auto-instrumentation ----- try: if RequestsInstrumentor is not None: RequestsInstrumentor().instrument() except Exception: pass try: if FlaskInstrumentor is not None and flask_app is not None: # Prefer explicit instrumentation of the passed app instance FlaskInstrumentor().instrument_app(flask_app) except Exception: pass try: if PymongoInstrumentor is not None: PymongoInstrumentor().instrument() except Exception: pass _OTEL_INITIALIZED = True except Exception: # Entire telemetry stack is optional – fail-open return
__all__ = ["setup_telemetry"]