mirror of
https://git.linux-kernel.at/oliver/ivatar.git
synced 2025-11-17 05:28:03 +00:00
Add OpenTelemetry integration
This commit is contained in:
233
ivatar/opentelemetry_config.py
Normal file
233
ivatar/opentelemetry_config.py
Normal file
@@ -0,0 +1,233 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
OpenTelemetry configuration for ivatar project.
|
||||
|
||||
This module provides OpenTelemetry setup and configuration for the ivatar
|
||||
Django application, including tracing, metrics, and logging integration.
|
||||
"""
|
||||
|
||||
import os
|
||||
import logging
|
||||
|
||||
from opentelemetry import trace, metrics
|
||||
from opentelemetry.sdk.trace import TracerProvider
|
||||
from opentelemetry.sdk.trace.export import BatchSpanProcessor
|
||||
from opentelemetry.sdk.metrics import MeterProvider
|
||||
from opentelemetry.sdk.metrics.export import PeriodicExportingMetricReader
|
||||
from opentelemetry.sdk.resources import Resource
|
||||
from opentelemetry.exporter.otlp.proto.grpc.trace_exporter import OTLPSpanExporter
|
||||
from opentelemetry.exporter.otlp.proto.grpc.metric_exporter import OTLPMetricExporter
|
||||
from opentelemetry.exporter.prometheus import PrometheusMetricReader
|
||||
from opentelemetry.instrumentation.django import DjangoInstrumentor
|
||||
from opentelemetry.instrumentation.psycopg2 import Psycopg2Instrumentor
|
||||
from opentelemetry.instrumentation.pymysql import PyMySQLInstrumentor
|
||||
from opentelemetry.instrumentation.requests import RequestsInstrumentor
|
||||
from opentelemetry.instrumentation.urllib3 import URLLib3Instrumentor
|
||||
|
||||
# Note: Memcached instrumentation not available in OpenTelemetry Python
|
||||
|
||||
logger = logging.getLogger("ivatar")
|
||||
|
||||
|
||||
class OpenTelemetryConfig:
|
||||
"""
|
||||
OpenTelemetry configuration manager for ivatar.
|
||||
|
||||
Handles setup of tracing, metrics, and instrumentation for the Django application.
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
self.enabled = self._is_enabled()
|
||||
self.service_name = self._get_service_name()
|
||||
self.environment = self._get_environment()
|
||||
self.resource = self._create_resource()
|
||||
|
||||
def _is_enabled(self) -> bool:
|
||||
"""Check if OpenTelemetry is enabled via environment variable and Django settings."""
|
||||
# First check Django settings (for F/LOSS deployments)
|
||||
try:
|
||||
from django.conf import settings
|
||||
from django.core.exceptions import ImproperlyConfigured
|
||||
|
||||
try:
|
||||
if getattr(settings, "ENABLE_OPENTELEMETRY", False):
|
||||
return True
|
||||
except ImproperlyConfigured:
|
||||
# Django settings not configured yet, fall back to environment variable
|
||||
pass
|
||||
except ImportError:
|
||||
# Django not available yet, fall back to environment variable
|
||||
pass
|
||||
|
||||
# Then check OpenTelemetry-specific environment variable
|
||||
return os.environ.get("OTEL_ENABLED", "false").lower() in ("true", "1", "yes")
|
||||
|
||||
def _get_service_name(self) -> str:
|
||||
"""Get service name from environment or default."""
|
||||
return os.environ.get("OTEL_SERVICE_NAME", "ivatar")
|
||||
|
||||
def _get_environment(self) -> str:
|
||||
"""Get environment name (production, development, etc.)."""
|
||||
return os.environ.get("OTEL_ENVIRONMENT", "development")
|
||||
|
||||
def _create_resource(self) -> Resource:
|
||||
"""Create OpenTelemetry resource with service information."""
|
||||
return Resource.create(
|
||||
{
|
||||
"service.name": self.service_name,
|
||||
"service.version": os.environ.get("IVATAR_VERSION", "1.8.0"),
|
||||
"service.namespace": "libravatar",
|
||||
"deployment.environment": self.environment,
|
||||
"service.instance.id": os.environ.get("HOSTNAME", "unknown"),
|
||||
}
|
||||
)
|
||||
|
||||
def setup_tracing(self) -> None:
|
||||
"""Set up OpenTelemetry tracing."""
|
||||
if not self.enabled:
|
||||
logger.info("OpenTelemetry tracing disabled")
|
||||
return
|
||||
|
||||
try:
|
||||
# Set up tracer provider
|
||||
trace.set_tracer_provider(TracerProvider(resource=self.resource))
|
||||
tracer_provider = trace.get_tracer_provider()
|
||||
|
||||
# Configure OTLP exporter if endpoint is provided
|
||||
otlp_endpoint = os.environ.get("OTEL_EXPORTER_OTLP_ENDPOINT")
|
||||
if otlp_endpoint:
|
||||
otlp_exporter = OTLPSpanExporter(endpoint=otlp_endpoint)
|
||||
span_processor = BatchSpanProcessor(otlp_exporter)
|
||||
tracer_provider.add_span_processor(span_processor)
|
||||
logger.info(
|
||||
f"OpenTelemetry tracing configured with OTLP endpoint: {otlp_endpoint}"
|
||||
)
|
||||
else:
|
||||
logger.info("OpenTelemetry tracing configured without OTLP exporter")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to setup OpenTelemetry tracing: {e}")
|
||||
self.enabled = False
|
||||
|
||||
def setup_metrics(self) -> None:
|
||||
"""Set up OpenTelemetry metrics."""
|
||||
if not self.enabled:
|
||||
logger.info("OpenTelemetry metrics disabled")
|
||||
return
|
||||
|
||||
try:
|
||||
# Configure metric readers
|
||||
metric_readers = []
|
||||
|
||||
# Configure Prometheus exporter for metrics
|
||||
prometheus_endpoint = os.environ.get(
|
||||
"OTEL_PROMETHEUS_ENDPOINT", "0.0.0.0:9464"
|
||||
)
|
||||
prometheus_reader = PrometheusMetricReader()
|
||||
metric_readers.append(prometheus_reader)
|
||||
|
||||
# Configure OTLP exporter if endpoint is provided
|
||||
otlp_endpoint = os.environ.get("OTEL_EXPORTER_OTLP_ENDPOINT")
|
||||
if otlp_endpoint:
|
||||
otlp_exporter = OTLPMetricExporter(endpoint=otlp_endpoint)
|
||||
metric_reader = PeriodicExportingMetricReader(otlp_exporter)
|
||||
metric_readers.append(metric_reader)
|
||||
logger.info(
|
||||
f"OpenTelemetry metrics configured with OTLP endpoint: {otlp_endpoint}"
|
||||
)
|
||||
|
||||
# Set up meter provider with readers
|
||||
meter_provider = MeterProvider(
|
||||
resource=self.resource, metric_readers=metric_readers
|
||||
)
|
||||
metrics.set_meter_provider(meter_provider)
|
||||
|
||||
logger.info(
|
||||
f"OpenTelemetry metrics configured with Prometheus endpoint: {prometheus_endpoint}"
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to setup OpenTelemetry metrics: {e}")
|
||||
self.enabled = False
|
||||
|
||||
def setup_instrumentation(self) -> None:
|
||||
"""Set up OpenTelemetry instrumentation for various libraries."""
|
||||
if not self.enabled:
|
||||
logger.info("OpenTelemetry instrumentation disabled")
|
||||
return
|
||||
|
||||
try:
|
||||
# Django instrumentation
|
||||
DjangoInstrumentor().instrument()
|
||||
logger.info("Django instrumentation enabled")
|
||||
|
||||
# Database instrumentation
|
||||
Psycopg2Instrumentor().instrument()
|
||||
PyMySQLInstrumentor().instrument()
|
||||
logger.info("Database instrumentation enabled")
|
||||
|
||||
# HTTP client instrumentation
|
||||
RequestsInstrumentor().instrument()
|
||||
URLLib3Instrumentor().instrument()
|
||||
logger.info("HTTP client instrumentation enabled")
|
||||
|
||||
# Note: Memcached instrumentation not available in OpenTelemetry Python
|
||||
# Cache operations will be traced through Django instrumentation
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to setup OpenTelemetry instrumentation: {e}")
|
||||
self.enabled = False
|
||||
|
||||
def get_tracer(self, name: str) -> trace.Tracer:
|
||||
"""Get a tracer instance."""
|
||||
return trace.get_tracer(name)
|
||||
|
||||
def get_meter(self, name: str) -> metrics.Meter:
|
||||
"""Get a meter instance."""
|
||||
return metrics.get_meter(name)
|
||||
|
||||
|
||||
# Global OpenTelemetry configuration instance (lazy-loaded)
|
||||
_ot_config = None
|
||||
|
||||
|
||||
def get_ot_config():
|
||||
"""Get the global OpenTelemetry configuration instance."""
|
||||
global _ot_config
|
||||
if _ot_config is None:
|
||||
_ot_config = OpenTelemetryConfig()
|
||||
return _ot_config
|
||||
|
||||
|
||||
def setup_opentelemetry() -> None:
|
||||
"""
|
||||
Set up OpenTelemetry for the ivatar application.
|
||||
|
||||
This function should be called during Django application startup.
|
||||
"""
|
||||
logger.info("Setting up OpenTelemetry...")
|
||||
|
||||
ot_config = get_ot_config()
|
||||
ot_config.setup_tracing()
|
||||
ot_config.setup_metrics()
|
||||
ot_config.setup_instrumentation()
|
||||
|
||||
if ot_config.enabled:
|
||||
logger.info("OpenTelemetry setup completed successfully")
|
||||
else:
|
||||
logger.info("OpenTelemetry setup skipped (disabled)")
|
||||
|
||||
|
||||
def get_tracer(name: str) -> trace.Tracer:
|
||||
"""Get a tracer instance for the given name."""
|
||||
return get_ot_config().get_tracer(name)
|
||||
|
||||
|
||||
def get_meter(name: str) -> metrics.Meter:
|
||||
"""Get a meter instance for the given name."""
|
||||
return get_ot_config().get_meter(name)
|
||||
|
||||
|
||||
def is_enabled() -> bool:
|
||||
"""Check if OpenTelemetry is enabled."""
|
||||
return get_ot_config().enabled
|
||||
Reference in New Issue
Block a user