|
56 | 56 | # django.contrib.auth) you may enable sending PII data. |
57 | 57 | send_default_pii=True, |
58 | 58 | ) |
| 59 | + |
| 60 | + |
| 61 | +# OpenTelemetry Configuration |
| 62 | +OTEL_ENABLED = config("OTEL_ENABLED", default=False, cast=bool) |
| 63 | +OTEL_SERVICE_NAME = config("OTEL_SERVICE_NAME", default="karrio-api") |
| 64 | +OTEL_EXPORTER_OTLP_ENDPOINT = config("OTEL_EXPORTER_OTLP_ENDPOINT", default=None) |
| 65 | +OTEL_EXPORTER_OTLP_PROTOCOL = config("OTEL_EXPORTER_OTLP_PROTOCOL", default="grpc") |
| 66 | +OTEL_EXPORTER_OTLP_HEADERS = config("OTEL_EXPORTER_OTLP_HEADERS", default="") |
| 67 | +OTEL_TRACES_EXPORTER = config("OTEL_TRACES_EXPORTER", default="otlp") |
| 68 | +OTEL_METRICS_EXPORTER = config("OTEL_METRICS_EXPORTER", default="otlp") |
| 69 | +OTEL_LOGS_EXPORTER = config("OTEL_LOGS_EXPORTER", default="otlp") |
| 70 | +OTEL_RESOURCE_ATTRIBUTES = config("OTEL_RESOURCE_ATTRIBUTES", default="") |
| 71 | +OTEL_ENVIRONMENT = config("OTEL_ENVIRONMENT", default=config("ENV", default="production")) |
| 72 | + |
| 73 | +# Only initialize OpenTelemetry if enabled and endpoint is configured |
| 74 | +if OTEL_ENABLED and OTEL_EXPORTER_OTLP_ENDPOINT: |
| 75 | + import logging |
| 76 | + from opentelemetry import trace, metrics |
| 77 | + from opentelemetry.sdk.trace import TracerProvider |
| 78 | + from opentelemetry.sdk.trace.export import BatchSpanProcessor |
| 79 | + from opentelemetry.sdk.metrics import MeterProvider |
| 80 | + from opentelemetry.sdk.metrics.export import PeriodicExportingMetricReader |
| 81 | + from opentelemetry.sdk.resources import Resource, SERVICE_NAME, SERVICE_VERSION |
| 82 | + from opentelemetry.instrumentation.django import DjangoInstrumentor |
| 83 | + from opentelemetry.instrumentation.requests import RequestsInstrumentor |
| 84 | + from opentelemetry.instrumentation.logging import LoggingInstrumentor |
| 85 | + from opentelemetry.instrumentation.psycopg2 import Psycopg2Instrumentor |
| 86 | + from opentelemetry.instrumentation.redis import RedisInstrumentor |
| 87 | + |
| 88 | + # Import appropriate exporter based on protocol |
| 89 | + if OTEL_EXPORTER_OTLP_PROTOCOL == "grpc": |
| 90 | + from opentelemetry.exporter.otlp.proto.grpc.trace_exporter import OTLPSpanExporter |
| 91 | + from opentelemetry.exporter.otlp.proto.grpc.metric_exporter import OTLPMetricExporter |
| 92 | + else: # http/protobuf |
| 93 | + from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter |
| 94 | + from opentelemetry.exporter.otlp.proto.http.metric_exporter import OTLPMetricExporter |
| 95 | + |
| 96 | + # Parse headers if provided |
| 97 | + headers = {} |
| 98 | + if OTEL_EXPORTER_OTLP_HEADERS: |
| 99 | + for header_pair in OTEL_EXPORTER_OTLP_HEADERS.split(","): |
| 100 | + if "=" in header_pair: |
| 101 | + key, value = header_pair.split("=", 1) |
| 102 | + headers[key.strip()] = value.strip() |
| 103 | + |
| 104 | + # Parse resource attributes |
| 105 | + resource_attributes = { |
| 106 | + SERVICE_NAME: OTEL_SERVICE_NAME, |
| 107 | + SERVICE_VERSION: config("VERSION", default="unknown"), |
| 108 | + "environment": OTEL_ENVIRONMENT, |
| 109 | + "deployment.environment": OTEL_ENVIRONMENT, |
| 110 | + } |
| 111 | + |
| 112 | + if OTEL_RESOURCE_ATTRIBUTES: |
| 113 | + for attr_pair in OTEL_RESOURCE_ATTRIBUTES.split(","): |
| 114 | + if "=" in attr_pair: |
| 115 | + key, value = attr_pair.split("=", 1) |
| 116 | + resource_attributes[key.strip()] = value.strip() |
| 117 | + |
| 118 | + # Create resource |
| 119 | + resource = Resource(attributes=resource_attributes) |
| 120 | + |
| 121 | + # Configure Trace Provider |
| 122 | + trace_provider = TracerProvider(resource=resource) |
| 123 | + trace.set_tracer_provider(trace_provider) |
| 124 | + |
| 125 | + # Configure span exporter |
| 126 | + span_exporter = OTLPSpanExporter( |
| 127 | + endpoint=OTEL_EXPORTER_OTLP_ENDPOINT, |
| 128 | + headers=headers if headers else None, |
| 129 | + ) |
| 130 | + span_processor = BatchSpanProcessor(span_exporter) |
| 131 | + trace_provider.add_span_processor(span_processor) |
| 132 | + |
| 133 | + # Configure Metrics Provider |
| 134 | + metric_exporter = OTLPMetricExporter( |
| 135 | + endpoint=OTEL_EXPORTER_OTLP_ENDPOINT, |
| 136 | + headers=headers if headers else None, |
| 137 | + ) |
| 138 | + metric_reader = PeriodicExportingMetricReader( |
| 139 | + exporter=metric_exporter, |
| 140 | + export_interval_millis=30000, # Export metrics every 30 seconds |
| 141 | + ) |
| 142 | + meter_provider = MeterProvider( |
| 143 | + resource=resource, |
| 144 | + metric_readers=[metric_reader], |
| 145 | + ) |
| 146 | + metrics.set_meter_provider(meter_provider) |
| 147 | + |
| 148 | + # Instrument Django |
| 149 | + DjangoInstrumentor().instrument( |
| 150 | + is_sql_commentor_enabled=True, # Add trace context to SQL queries |
| 151 | + request_hook=lambda span, request: span.set_attribute("http.client_ip", request.META.get("REMOTE_ADDR", "")), |
| 152 | + response_hook=lambda span, request, response: span.set_attribute("http.response.size", len(response.content) if hasattr(response, 'content') else 0), |
| 153 | + ) |
| 154 | + |
| 155 | + # Instrument other libraries |
| 156 | + RequestsInstrumentor().instrument() # HTTP client requests |
| 157 | + LoggingInstrumentor().instrument(set_logging_format=True) # Add trace context to logs |
| 158 | + |
| 159 | + # Instrument database if PostgreSQL is used |
| 160 | + if config("DATABASE_ENGINE", default="").endswith("postgresql"): |
| 161 | + try: |
| 162 | + Psycopg2Instrumentor().instrument() |
| 163 | + except Exception: |
| 164 | + pass # Psycopg2 might not be installed |
| 165 | + |
| 166 | + # Instrument Redis if configured |
| 167 | + if config("REDIS_HOST", default=None): |
| 168 | + try: |
| 169 | + RedisInstrumentor().instrument() |
| 170 | + except Exception: |
| 171 | + pass # Redis might not be installed |
| 172 | + |
| 173 | + # Instrument Huey task queue (temporarily disabled due to compatibility issues) |
| 174 | + # try: |
| 175 | + # from karrio.server.lib.otel_huey import instrument_huey |
| 176 | + # instrument_huey() |
| 177 | + # except Exception as e: |
| 178 | + # logger = logging.getLogger(__name__) |
| 179 | + # logger.warning(f"Failed to instrument Huey: {e}") |
| 180 | + |
| 181 | + # Log that OpenTelemetry is enabled |
| 182 | + logger = logging.getLogger(__name__) |
| 183 | + logger.info(f"OpenTelemetry enabled: Service={OTEL_SERVICE_NAME}, Endpoint={OTEL_EXPORTER_OTLP_ENDPOINT}") |
0 commit comments