120 lines
4.6 KiB
Python
120 lines
4.6 KiB
Python
from __future__ import annotations
|
|
|
|
import logging
|
|
import os
|
|
from collections.abc import MutableMapping
|
|
from dataclasses import dataclass
|
|
from typing import Any
|
|
|
|
from fastapi import FastAPI
|
|
from opentelemetry import metrics, trace
|
|
from opentelemetry._logs import set_logger_provider
|
|
from opentelemetry.exporter.otlp.proto.grpc._log_exporter import OTLPLogExporter
|
|
from opentelemetry.exporter.otlp.proto.grpc.metric_exporter import OTLPMetricExporter
|
|
from opentelemetry.exporter.otlp.proto.grpc.trace_exporter import OTLPSpanExporter
|
|
from opentelemetry.instrumentation.fastapi import FastAPIInstrumentor
|
|
from opentelemetry.instrumentation.httpx import HTTPXClientInstrumentor
|
|
from opentelemetry.instrumentation.sqlalchemy import SQLAlchemyInstrumentor
|
|
from opentelemetry.propagate import extract, inject, set_global_textmap
|
|
from opentelemetry.sdk._logs import LoggerProvider, LoggingHandler
|
|
from opentelemetry.sdk._logs.export import BatchLogRecordProcessor
|
|
from opentelemetry.sdk.metrics import MeterProvider
|
|
from opentelemetry.sdk.metrics.export import PeriodicExportingMetricReader
|
|
from opentelemetry.sdk.resources import Resource
|
|
from opentelemetry.sdk.trace import TracerProvider
|
|
from opentelemetry.sdk.trace.export import BatchSpanProcessor
|
|
from opentelemetry.sdk.trace.sampling import ALWAYS_ON
|
|
from opentelemetry.trace.propagation.tracecontext import TraceContextTextMapPropagator
|
|
|
|
|
|
@dataclass(slots=True)
|
|
class TelemetryRuntime:
|
|
tracer_provider: TracerProvider
|
|
meter_provider: MeterProvider
|
|
logger_provider: LoggerProvider
|
|
logging_handler: LoggingHandler
|
|
|
|
def shutdown(self) -> None:
|
|
self.logger_provider.shutdown()
|
|
self.meter_provider.shutdown()
|
|
self.tracer_provider.shutdown()
|
|
|
|
|
|
_runtime: TelemetryRuntime | None = None
|
|
|
|
|
|
def init_telemetry() -> TelemetryRuntime | None:
|
|
global _runtime
|
|
if _runtime is not None:
|
|
return _runtime
|
|
endpoint = os.getenv("OTEL_EXPORTER_OTLP_ENDPOINT", "").strip()
|
|
if not endpoint:
|
|
return None
|
|
resource = Resource.create(
|
|
{
|
|
"service.name": os.getenv("OTEL_SERVICE_NAME", "bitrix-local-app"),
|
|
"service.namespace": "han-chat",
|
|
"service.version": os.getenv("RELEASE_VERSION", "unknown"),
|
|
"deployment.environment": os.getenv("APP_ENV", "production-like"),
|
|
}
|
|
)
|
|
insecure = endpoint.startswith("http://")
|
|
set_global_textmap(TraceContextTextMapPropagator())
|
|
tracer_provider = TracerProvider(resource=resource, sampler=ALWAYS_ON)
|
|
tracer_provider.add_span_processor(
|
|
BatchSpanProcessor(OTLPSpanExporter(endpoint=endpoint, insecure=insecure, timeout=3))
|
|
)
|
|
trace.set_tracer_provider(tracer_provider)
|
|
metric_reader = PeriodicExportingMetricReader(
|
|
OTLPMetricExporter(endpoint=endpoint, insecure=insecure, timeout=3),
|
|
export_interval_millis=30000,
|
|
export_timeout_millis=3000,
|
|
)
|
|
meter_provider = MeterProvider(resource=resource, metric_readers=[metric_reader])
|
|
metrics.set_meter_provider(meter_provider)
|
|
logger_provider = LoggerProvider(resource=resource)
|
|
logger_provider.add_log_record_processor(
|
|
BatchLogRecordProcessor(
|
|
OTLPLogExporter(endpoint=endpoint, insecure=insecure, timeout=3),
|
|
max_queue_size=2048,
|
|
max_export_batch_size=512,
|
|
export_timeout_millis=3000,
|
|
)
|
|
)
|
|
set_logger_provider(logger_provider)
|
|
logging_handler = LoggingHandler(level=logging.NOTSET, logger_provider=logger_provider)
|
|
HTTPXClientInstrumentor().instrument()
|
|
SQLAlchemyInstrumentor().instrument(enable_commenter=False)
|
|
_runtime = TelemetryRuntime(
|
|
tracer_provider, meter_provider, logger_provider, logging_handler
|
|
)
|
|
return _runtime
|
|
|
|
|
|
def instrument_fastapi(app: FastAPI) -> None:
|
|
FastAPIInstrumentor.instrument_app(app, excluded_urls="/health/live,/health/ready")
|
|
|
|
|
|
def add_trace_context(
|
|
_logger: Any, _method_name: str, event_dict: MutableMapping[str, Any]
|
|
) -> MutableMapping[str, Any]:
|
|
context = trace.get_current_span().get_span_context()
|
|
if context.is_valid:
|
|
event_dict["trace_id"] = format(context.trace_id, "032x")
|
|
event_dict["span_id"] = format(context.span_id, "016x")
|
|
return event_dict
|
|
|
|
|
|
def current_traceparent() -> str | None:
|
|
carrier: dict[str, str] = {}
|
|
inject(carrier)
|
|
return carrier.get("traceparent")
|
|
|
|
|
|
def origin_links(traceparent: str | None) -> list[trace.Link]:
|
|
if not traceparent:
|
|
return []
|
|
context = extract({"traceparent": traceparent})
|
|
span_context = trace.get_current_span(context).get_span_context()
|
|
return [trace.Link(span_context)] if span_context.is_valid else []
|