open-webui/backend/open_webui/utils/telemetry/metrics.py

111 lines
3.3 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""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 # 10 seconds
def _build_meter_provider() -> MeterProvider:
"""Return a configured MeterProvider."""
# Periodic reader pushes metrics over OTLP/gRPC to collector
readers: List[PeriodicExportingMetricReader] = [
PeriodicExportingMetricReader(
OTLPMetricExporter(endpoint=OTEL_EXPORTER_OTLP_ENDPOINT),
export_interval_millis=_EXPORT_INTERVAL_MILLIS,
)
]
# Optional view to limit cardinality: drop user-agent etc.
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__)
# Instruments
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",
)
# FastAPI middleware
@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 template e.g. "/items/{item_id}" instead of real path.
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