111 lines
		
	
	
		
			3.3 KiB
		
	
	
	
		
			Python
		
	
	
	
		
		
			
		
	
	
			111 lines
		
	
	
		
			3.3 KiB
		
	
	
	
		
			Python
		
	
	
	
|  | """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 |