104 lines
3.8 KiB
Python
104 lines
3.8 KiB
Python
from __future__ import annotations
|
|
|
|
import logging
|
|
from dataclasses import dataclass
|
|
from typing import Any
|
|
|
|
from fastapi import FastAPI
|
|
from opentelemetry import metrics, trace
|
|
from opentelemetry.baggage.propagation import W3CBaggagePropagator
|
|
from opentelemetry.exporter.otlp.proto.http.metric_exporter import OTLPMetricExporter
|
|
from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter
|
|
from opentelemetry.instrumentation.fastapi import FastAPIInstrumentor
|
|
from opentelemetry.instrumentation.httpx import HTTPXClientInstrumentor
|
|
from opentelemetry.instrumentation.logging import LoggingInstrumentor
|
|
from opentelemetry.instrumentation.sqlalchemy import SQLAlchemyInstrumentor
|
|
from opentelemetry.propagate import set_global_textmap
|
|
from opentelemetry.propagators.composite import CompositePropagator
|
|
from opentelemetry.sdk.metrics import MeterProvider
|
|
from opentelemetry.sdk.metrics.export import PeriodicExportingMetricReader
|
|
from opentelemetry.sdk.resources import Resource
|
|
from opentelemetry.sdk.trace import TracerProvider
|
|
from opentelemetry.sdk.trace.export import BatchSpanProcessor
|
|
from opentelemetry.trace.propagation.tracecontext import TraceContextTextMapPropagator
|
|
|
|
try:
|
|
from opentelemetry.instrumentation.system_metrics import SystemMetricsInstrumentor
|
|
except ImportError: # pragma: no cover - defensive fallback for minimal envs
|
|
SystemMetricsInstrumentor = None # type: ignore[assignment]
|
|
|
|
from app.core.config import Settings
|
|
|
|
LOGGER = logging.getLogger(__name__)
|
|
|
|
|
|
@dataclass
|
|
class TelemetryProviders:
|
|
tracer_provider: TracerProvider
|
|
meter_provider: MeterProvider
|
|
|
|
|
|
def configure_otel(settings: Settings) -> TelemetryProviders:
|
|
set_global_textmap(
|
|
CompositePropagator([TraceContextTextMapPropagator(), W3CBaggagePropagator()])
|
|
)
|
|
resource = Resource.create(
|
|
{
|
|
"service.name": settings.otel_service_name,
|
|
"service.namespace": settings.otel_service_namespace,
|
|
"deployment.environment": settings.app_env,
|
|
}
|
|
)
|
|
|
|
trace_exporter = OTLPSpanExporter(
|
|
endpoint=f"{settings.otel_collector_endpoint}/v1/traces",
|
|
timeout=settings.otel_export_timeout_ms / 1000,
|
|
)
|
|
tracer_provider = TracerProvider(resource=resource)
|
|
tracer_provider.add_span_processor(BatchSpanProcessor(trace_exporter))
|
|
trace.set_tracer_provider(tracer_provider)
|
|
|
|
metric_reader = PeriodicExportingMetricReader(
|
|
exporter=OTLPMetricExporter(
|
|
endpoint=f"{settings.otel_collector_endpoint}/v1/metrics",
|
|
timeout=settings.otel_export_timeout_ms / 1000,
|
|
),
|
|
export_interval_millis=10000,
|
|
)
|
|
meter_provider = MeterProvider(resource=resource, metric_readers=[metric_reader])
|
|
metrics.set_meter_provider(meter_provider)
|
|
|
|
LoggingInstrumentor().instrument(set_logging_format=True)
|
|
if SystemMetricsInstrumentor is not None:
|
|
SystemMetricsInstrumentor().instrument()
|
|
else:
|
|
LOGGER.warning(
|
|
"System metrics instrumentor not available, runtime host metrics disabled."
|
|
)
|
|
LOGGER.info("OpenTelemetry providers configured")
|
|
return TelemetryProviders(
|
|
tracer_provider=tracer_provider, meter_provider=meter_provider
|
|
)
|
|
|
|
|
|
def instrument_fastapi(app: FastAPI) -> None:
|
|
FastAPIInstrumentor.instrument_app(app)
|
|
|
|
|
|
def instrument_sqlalchemy_engines(engines: dict[str, Any]) -> None:
|
|
for engine in engines.values():
|
|
SQLAlchemyInstrumentor().instrument(engine=engine)
|
|
|
|
|
|
def instrument_httpx_clients() -> None:
|
|
HTTPXClientInstrumentor().instrument()
|
|
|
|
|
|
def shutdown_otel(providers: TelemetryProviders) -> None:
|
|
HTTPXClientInstrumentor().uninstrument()
|
|
if SystemMetricsInstrumentor is not None:
|
|
SystemMetricsInstrumentor().uninstrument()
|
|
LoggingInstrumentor().uninstrument()
|
|
providers.meter_provider.shutdown()
|
|
providers.tracer_provider.shutdown()
|