diff --git a/pyproject.toml b/pyproject.toml index a058042..4e44f79 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -90,6 +90,7 @@ extract = [ "striprtf>=0.0.26", "chardet>=5.2", "python-magic>=0.4.27", + "opentelemetry-instrumentation-httpx>=0.45b0", ] analyze = [ { include-group = "db" }, @@ -114,6 +115,7 @@ notify = [ { include-group = "mq" }, { include-group = "obs" }, "aiosmtplib>=3.0", + "opentelemetry-instrumentation-httpx>=0.45b0", ] billing = [ { include-group = "db" }, diff --git a/src/contract_check/api/app.py b/src/contract_check/api/app.py index 7f3ab80..363a731 100644 --- a/src/contract_check/api/app.py +++ b/src/contract_check/api/app.py @@ -147,6 +147,15 @@ def create_app() -> FastAPI: ) -> RedirectResponse: return RedirectResponse(url=f"/admin/login?reason={exc.reason}", status_code=303) + # Emit request traces to the OTLP collector. Must happen after all routes + # and middleware are registered so spans cover the full request lifecycle. + try: + from opentelemetry.instrumentation.fastapi import FastAPIInstrumentor + + FastAPIInstrumentor.instrument_app(app) + except Exception: # pragma: no cover - optional instrumentor + pass + return app diff --git a/src/contract_check/core/telemetry.py b/src/contract_check/core/telemetry.py index 39eba66..9cd10bf 100644 --- a/src/contract_check/core/telemetry.py +++ b/src/contract_check/core/telemetry.py @@ -97,9 +97,10 @@ def setup_telemetry(service_name: str | None = None) -> None: resource = Resource.create({"service.name": service_name or settings.otel_service_name}) - # Traces + # Traces. Exporters read OTEL_EXPORTER_OTLP_ENDPOINT and append the + # signal-specific path (/v1/traces, /v1/logs, /v1/metrics) automatically. trace_provider = TracerProvider(resource=resource) - trace_provider.add_span_processor(BatchSpanProcessor(OTLPSpanExporter(endpoint=endpoint))) + trace_provider.add_span_processor(BatchSpanProcessor(OTLPSpanExporter())) set_tracer_provider(trace_provider) _trace_provider = trace_provider @@ -107,9 +108,7 @@ def setup_telemetry(service_name: str | None = None) -> None: # through the stdlib logging tree; this handler unwraps the structured # event dicts so OpenObserve receives the message body and fields. log_provider = LoggerProvider(resource=resource) - log_provider.add_log_record_processor( - BatchLogRecordProcessor(OTLPLogExporter(endpoint=endpoint)) - ) + log_provider.add_log_record_processor(BatchLogRecordProcessor(OTLPLogExporter())) set_logger_provider(log_provider) _log_provider = log_provider @@ -119,7 +118,7 @@ def setup_telemetry(service_name: str | None = None) -> None: # Metrics infrastructure (OTel metrics → OTLP). Existing prometheus_client # metrics are still scraped by Prometheus; use remote_write for those. - metric_reader = PeriodicExportingMetricReader(OTLPMetricExporter(endpoint=endpoint)) + metric_reader = PeriodicExportingMetricReader(OTLPMetricExporter()) meter_provider = MeterProvider(resource=resource, metric_readers=[metric_reader]) set_meter_provider(meter_provider) _meter_provider = meter_provider diff --git a/uv.lock b/uv.lock index 9763c9d..8dd2bad 100644 --- a/uv.lock +++ b/uv.lock @@ -751,6 +751,7 @@ extract = [ { name = "mammoth" }, { name = "minio" }, { name = "opentelemetry-exporter-otlp" }, + { name = "opentelemetry-instrumentation-httpx" }, { name = "opentelemetry-sdk" }, { name = "pillow" }, { name = "prometheus-client" }, @@ -770,6 +771,7 @@ notify = [ { name = "alembic" }, { name = "asyncpg" }, { name = "opentelemetry-exporter-otlp" }, + { name = "opentelemetry-instrumentation-httpx" }, { name = "opentelemetry-sdk" }, { name = "prometheus-client" }, { name = "sentry-sdk" }, @@ -918,6 +920,7 @@ extract = [ { name = "mammoth", specifier = ">=1.8" }, { name = "minio", specifier = ">=7.2" }, { name = "opentelemetry-exporter-otlp", specifier = ">=1.24" }, + { name = "opentelemetry-instrumentation-httpx", specifier = ">=0.45b0" }, { name = "opentelemetry-sdk", specifier = ">=1.24" }, { name = "pillow", specifier = ">=10" }, { name = "prometheus-client", specifier = ">=0.20" }, @@ -935,6 +938,7 @@ notify = [ { name = "alembic", specifier = ">=1.13" }, { name = "asyncpg", specifier = ">=0.29" }, { name = "opentelemetry-exporter-otlp", specifier = ">=1.24" }, + { name = "opentelemetry-instrumentation-httpx", specifier = ">=0.45b0" }, { name = "opentelemetry-sdk", specifier = ">=1.24" }, { name = "prometheus-client", specifier = ">=0.20" }, { name = "sentry-sdk", specifier = ">=2" },