78 lines
2.6 KiB
Python
78 lines
2.6 KiB
Python
"""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])
|