|
"""OpenTelemetry metrics bootstrap for Open WebUI. |
|
|
|
This module initialises a MeterProvider that sends metrics to an OTLP |
|
collector. The collector is responsible for exposing a Prometheus |
|
`/metrics` endpoint – WebUI does **not** expose it directly. |
|
|
|
Metrics collected: |
|
|
|
* http.server.requests (counter) |
|
* http.server.duration (histogram, milliseconds) |
|
|
|
Attributes used: http.method, http.route, http.status_code |
|
|
|
If you wish to add more attributes (e.g. user-agent) you can, but beware of |
|
high-cardinality label sets. |
|
""" |
|
|
|
from __future__ import annotations |
|
|
|
import time |
|
from typing import Dict, List, Sequence, Any |
|
|
|
from fastapi import FastAPI, Request |
|
from opentelemetry import metrics |
|
from opentelemetry.exporter.otlp.proto.grpc.metric_exporter import ( |
|
OTLPMetricExporter, |
|
) |
|
from opentelemetry.sdk.metrics import MeterProvider |
|
from opentelemetry.sdk.metrics.view import View |
|
from opentelemetry.sdk.metrics.export import ( |
|
PeriodicExportingMetricReader, |
|
) |
|
from opentelemetry.sdk.resources import SERVICE_NAME, Resource |
|
|
|
from open_webui.env import OTEL_SERVICE_NAME, OTEL_EXPORTER_OTLP_ENDPOINT |
|
|
|
|
|
_EXPORT_INTERVAL_MILLIS = 10_000 |
|
|
|
|
|
def _build_meter_provider() -> MeterProvider: |
|
"""Return a configured MeterProvider.""" |
|
|
|
|
|
readers: List[PeriodicExportingMetricReader] = [ |
|
PeriodicExportingMetricReader( |
|
OTLPMetricExporter(endpoint=OTEL_EXPORTER_OTLP_ENDPOINT), |
|
export_interval_millis=_EXPORT_INTERVAL_MILLIS, |
|
) |
|
] |
|
|
|
|
|
views: List[View] = [ |
|
View( |
|
instrument_name="http.server.duration", |
|
attribute_keys=["http.method", "http.route", "http.status_code"], |
|
), |
|
View( |
|
instrument_name="http.server.requests", |
|
attribute_keys=["http.method", "http.route", "http.status_code"], |
|
), |
|
] |
|
|
|
provider = MeterProvider( |
|
resource=Resource.create({SERVICE_NAME: OTEL_SERVICE_NAME}), |
|
metric_readers=list(readers), |
|
views=views, |
|
) |
|
return provider |
|
|
|
|
|
def setup_metrics(app: FastAPI) -> None: |
|
"""Attach OTel metrics middleware to *app* and initialise provider.""" |
|
|
|
metrics.set_meter_provider(_build_meter_provider()) |
|
meter = metrics.get_meter(__name__) |
|
|
|
|
|
request_counter = meter.create_counter( |
|
name="http.server.requests", |
|
description="Total HTTP requests", |
|
unit="1", |
|
) |
|
duration_histogram = meter.create_histogram( |
|
name="http.server.duration", |
|
description="HTTP request duration", |
|
unit="ms", |
|
) |
|
|
|
|
|
@app.middleware("http") |
|
async def _metrics_middleware(request: Request, call_next): |
|
start_time = time.perf_counter() |
|
response = await call_next(request) |
|
elapsed_ms = (time.perf_counter() - start_time) * 1000.0 |
|
|
|
|
|
route = request.scope.get("route") |
|
route_path = getattr(route, "path", request.url.path) |
|
|
|
attrs: Dict[str, str | int] = { |
|
"http.method": request.method, |
|
"http.route": route_path, |
|
"http.status_code": response.status_code, |
|
} |
|
|
|
request_counter.add(1, attrs) |
|
duration_histogram.record(elapsed_ms, attrs) |
|
|
|
return response |
|
|