@@ -0,0 +1,77 @@
|
||||
"""OpenTelemetry metrics bootstrap and application instruments."""
|
||||
|
||||
from opentelemetry import metrics
|
||||
from opentelemetry.exporter.otlp.proto.grpc.metric_exporter import OTLPMetricExporter
|
||||
from opentelemetry.sdk.metrics import MeterProvider
|
||||
from opentelemetry.sdk.metrics.export import PeriodicExportingMetricReader
|
||||
from opentelemetry.sdk.resources import Resource
|
||||
|
||||
from app.config import ObservabilityConfig
|
||||
|
||||
|
||||
_METER_PROVIDER: MeterProvider | None = None
|
||||
_CACHE_METRICS: "CacheMetrics | None" = None
|
||||
|
||||
|
||||
class CacheMetrics:
|
||||
"""Metrics emitted by the Redis-backed price cache."""
|
||||
|
||||
def __init__(self) -> None:
|
||||
meter = metrics.get_meter("app.repositories.cache")
|
||||
self._requests = meter.create_counter(
|
||||
"cache_requests_total",
|
||||
unit="{request}",
|
||||
description="Number of cache operations by operation and outcome.",
|
||||
)
|
||||
self._errors = meter.create_counter(
|
||||
"cache_errors_total",
|
||||
unit="{error}",
|
||||
description="Number of failed cache operations by error type.",
|
||||
)
|
||||
self._hit_ratio = meter.create_histogram(
|
||||
"cache_hit_ratio",
|
||||
unit="1",
|
||||
description="Cache lookup result: 1 for a hit and 0 for a miss.",
|
||||
)
|
||||
|
||||
def record_request(self, *, operation: str, outcome: str) -> None:
|
||||
self._requests.add(1, {"operation": operation, "outcome": outcome})
|
||||
|
||||
def record_error(self, *, operation: str, error_type: str) -> None:
|
||||
self._errors.add(1, {"operation": operation, "error.type": error_type})
|
||||
|
||||
def record_hit(self, *, hit: bool) -> None:
|
||||
self._hit_ratio.record(1.0 if hit else 0.0)
|
||||
|
||||
|
||||
def configure_metrics(observability: ObservabilityConfig) -> None:
|
||||
"""Configure the process-wide OTLP metrics exporter once."""
|
||||
|
||||
global _METER_PROVIDER
|
||||
|
||||
if not observability.enabled or _METER_PROVIDER is not None:
|
||||
return
|
||||
|
||||
meter_provider = _build_meter_provider(observability)
|
||||
metrics.set_meter_provider(meter_provider)
|
||||
_METER_PROVIDER = meter_provider
|
||||
|
||||
|
||||
def get_cache_metrics() -> CacheMetrics:
|
||||
"""Return the process-wide cache instruments."""
|
||||
|
||||
global _CACHE_METRICS
|
||||
|
||||
if _CACHE_METRICS is None:
|
||||
_CACHE_METRICS = CacheMetrics()
|
||||
return _CACHE_METRICS
|
||||
|
||||
|
||||
def _build_meter_provider(observability: ObservabilityConfig) -> MeterProvider:
|
||||
exporter = OTLPMetricExporter(
|
||||
endpoint=observability.otlp_endpoint,
|
||||
insecure=observability.otlp_insecure,
|
||||
)
|
||||
reader = PeriodicExportingMetricReader(exporter)
|
||||
resource = Resource.create({"service.name": observability.service_name})
|
||||
return MeterProvider(resource=resource, metric_readers=[reader])
|
||||
Reference in New Issue
Block a user