Expose OTel metrics on :9464 for Prometheus to scrape

Alloy 404s OTLP metrics, so each service gets a MeterProvider with a
PrometheusMetricReader (same Resource as traces) served by
prometheus_client on its own port instead of a FastAPI route: gateway's
port 8000 is publicly routed by Traefik, and the audit worker has no HTTP
app. Host/port come from OTEL_EXPORTER_PROMETHEUS_HOST/PORT. Setting the
provider before the instrumentors also turns on their built-in HTTP and
DB pool metrics.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Movses Aslanyan
2026-10-13 21:56:06 +03:30
co-authored by Claude Opus 5.5
parent 44d7801617
commit 7cb3231226
2 changed files with 20 additions and 1 deletions
+18 -1
View File
@@ -1,10 +1,15 @@
from opentelemetry import trace import os
from opentelemetry import metrics, trace
from opentelemetry.sdk.metrics import MeterProvider
from opentelemetry.sdk.resources import SERVICE_NAME, Resource from opentelemetry.sdk.resources import SERVICE_NAME, Resource
from opentelemetry.sdk.trace import TracerProvider from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import BatchSpanProcessor from opentelemetry.sdk.trace.export import BatchSpanProcessor
from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter
from opentelemetry.exporter.prometheus import PrometheusMetricReader
from opentelemetry.instrumentation.httpx import HTTPXClientInstrumentor from opentelemetry.instrumentation.httpx import HTTPXClientInstrumentor
from opentelemetry.instrumentation.redis import RedisInstrumentor from opentelemetry.instrumentation.redis import RedisInstrumentor
from prometheus_client import start_http_server
def setup_telemetry(service_name: str): def setup_telemetry(service_name: str):
@@ -14,6 +19,18 @@ def setup_telemetry(service_name: str):
provider.add_span_processor(processor) provider.add_span_processor(processor)
trace.set_tracer_provider(provider) trace.set_tracer_provider(provider)
# Pull, not push: Alloy 404s OTLP metrics, so Prometheus scrapes each
# service instead. The reader renders current values on every scrape.
# It serves on its own port rather than a FastAPI route so /metrics is
# never reachable through gateway's public Traefik router, and so the
# audit worker (no HTTP app) exposes metrics the same way.
reader = PrometheusMetricReader()
metrics.set_meter_provider(MeterProvider(resource=resource, metric_readers=[reader]))
start_http_server(
port=int(os.environ.get("OTEL_EXPORTER_PROMETHEUS_PORT", "9464")),
addr=os.environ.get("OTEL_EXPORTER_PROMETHEUS_HOST", "0.0.0.0"),
)
# Process-wide patches on the client classes, so they also cover clients # Process-wide patches on the client classes, so they also cover clients
# created at import time (gateway's _proxy_client, common.redis_client). # created at import time (gateway's _proxy_client, common.redis_client).
HTTPXClientInstrumentor().instrument() HTTPXClientInstrumentor().instrument()
+2
View File
@@ -11,3 +11,5 @@ python-multipart==0.0.20
structlog==25.5.0 structlog==25.5.0
opentelemetry-distro==0.66b1 opentelemetry-distro==0.66b1
opentelemetry-exporter-otlp==1.45.1 opentelemetry-exporter-otlp==1.45.1
opentelemetry-exporter-prometheus==0.66b1
prometheus-client==0.26.0