Skip to content

Commit 1a9f857

Browse files
fcarreno-collabChronoFrank
authored andcommitted
feature: add open telemetry integration
1 parent 50055de commit 1a9f857

5 files changed

Lines changed: 132 additions & 1 deletion

File tree

backend/.env.example

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,4 +28,13 @@ OAUTH2_CLIENT_SECRET=
2828
OAUTH2_ADD_SCOPE=config-values/add config-values/write
2929
OAUTH2_UPDATE_SCOPE=config-values/update config-values/write
3030
OAUTH2_DELETE_SCOPE=config-values/delete config-values/write
31-
OAUTH2_CLONE_SCOPE=config-values/clone config-values/write
31+
OAUTH2_CLONE_SCOPE=config-values/clone config-values/write
32+
33+
# open telemetry integration
34+
OTEL_INSTRUMENTATION_ENABLED=true
35+
OTEL_SERVICE_NAME=marketing-api
36+
OTEL_EXPORTER_OTLP_ENDPOINT=http://collector.fnvirtual.app:4318/v1/traces
37+
OTEL_PROPAGATORS=tracecontext,baggage
38+
OTEL_PYTHON_LOG_CORRELATION=true
39+
# set it to otel_endpoint, console or null (just tu run tests locally)
40+
OTEL_EXPORTER_MODE=otel_endpoint

backend/env_var_eval.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import os
2+
3+
def env_bool(name: str, default: bool = False) -> bool:
4+
value = os.getenv(name)
5+
if value is None:
6+
return default
7+
return value.strip().lower() in ("1", "true")

backend/otel_instrumentation.py

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
import os
2+
from opentelemetry import baggage as baggage_api
3+
from opentelemetry import trace
4+
from opentelemetry.instrumentation.django import DjangoInstrumentor
5+
from opentelemetry.instrumentation.mysqlclient import MySQLClientInstrumentor
6+
from opentelemetry.instrumentation.redis import RedisInstrumentor
7+
from opentelemetry.sdk.resources import Resource
8+
from opentelemetry.sdk.trace import TracerProvider
9+
from opentelemetry.sdk.trace.export import BatchSpanProcessor, ConsoleSpanExporter
10+
from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter
11+
12+
13+
OTEL_EXPORTER_OTLP_ENDPOINT = os.getenv('OTEL_EXPORTER_OTLP_ENDPOINT')
14+
OTEL_EXPORTER_MODE = os.getenv('OTEL_EXPORTER_MODE')
15+
16+
class DjangoTelemetry:
17+
18+
@staticmethod
19+
def request_hook(span, request):
20+
if not span.is_recording():
21+
return
22+
23+
# Attach CF-Ray header
24+
span.set_attribute("cf.ray_id", request.headers.get("Cf-Ray", ""))
25+
26+
# Attach baggage if present
27+
baggage_val = baggage_api.get_baggage("cf.ray_id")
28+
if baggage_val:
29+
span.set_attribute("baggage.cf.ray_id", baggage_val)
30+
31+
@staticmethod
32+
def response_hook(span, request, response):
33+
if span.is_recording() and hasattr(response, "content"):
34+
span.set_attribute("http.response.length", len(response.content))
35+
36+
@staticmethod
37+
def mysql_hook(span, instance, cursor, statement, parameters):
38+
"""Enrich MySQL spans with DB info"""
39+
if not span.is_recording():
40+
return
41+
try:
42+
span.set_attribute("db.system", "mysql")
43+
span.set_attribute("db.name", os.getenv('DB_NAME', 'db'))
44+
span.set_attribute("db.statement", statement)
45+
except Exception:
46+
pass
47+
48+
@staticmethod
49+
def redis_hook(span, instance, args, kwargs):
50+
"""Enrich Redis spans with command + keys"""
51+
if not span.is_recording():
52+
return
53+
try:
54+
cmd = args[0] if args else ""
55+
span.set_attribute("db.system", "redis")
56+
span.set_attribute("redis.command", cmd)
57+
if len(args) > 1:
58+
# Add first key only (avoid leaking big payloads)
59+
span.set_attribute("redis.key", str(args[1]))
60+
except Exception:
61+
pass
62+
63+
@classmethod
64+
def setup(cls, environment):
65+
if environment != "test":
66+
# set the OTEL_EXPORTER_MODE to null
67+
# No Exporter setup if the env is dev and you want to run the tests locally
68+
if OTEL_EXPORTER_MODE:
69+
resource = Resource.create({
70+
"service.name": os.getenv("OTEL_SERVICE_NAME", "marketing-api")
71+
})
72+
# Provider with resource
73+
provider = TracerProvider(resource=resource)
74+
trace.set_tracer_provider(provider)
75+
if OTEL_EXPORTER_MODE == "otel_endpoint":
76+
exporter = OTLPSpanExporter(endpoint=OTEL_EXPORTER_OTLP_ENDPOINT)
77+
provider.add_span_processor(BatchSpanProcessor(exporter))
78+
elif OTEL_EXPORTER_MODE == "console":
79+
exporter = ConsoleSpanExporter()
80+
provider.add_span_processor(BatchSpanProcessor(exporter))
81+
82+
# Django
83+
DjangoInstrumentor().instrument(
84+
request_hook=cls.request_hook,
85+
response_hook=cls.response_hook,
86+
)
87+
# MySQL
88+
MySQLClientInstrumentor().instrument(
89+
enable_commenter=True,
90+
cursor_instrumentation_enabled=True,
91+
span_callback=cls.mysql_hook,
92+
)
93+
# Redis
94+
RedisInstrumentor().instrument(
95+
tracer_provider=trace.get_tracer_provider(),
96+
request_hook=cls.redis_hook,
97+
)

backend/settings.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -339,6 +339,15 @@
339339
os.path.join(BASE_DIR, "backend/media"),
340340
]
341341

342+
DEV_EMAIL = os.getenv('DEV_EMAIL')
343+
344+
from backend.env_var_eval import env_bool
345+
OTEL_INSTRUMENTATION_ENABLED = env_bool('OTEL_INSTRUMENTATION_ENABLED', True)
346+
347+
if OTEL_INSTRUMENTATION_ENABLED:
348+
from .otel_instrumentation import DjangoTelemetry
349+
DjangoTelemetry.setup(ENV)
350+
342351
# Import local settings
343352
try:
344353
from .settings_local import *

requirements.txt

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,3 +46,12 @@ stevedore==1.32.0
4646
uritemplate==3.0.1
4747
urllib3==1.25.9
4848
wrapt==1.12.1
49+
50+
#open-telemetry integration
51+
opentelemetry-distro==0.48b0
52+
opentelemetry-sdk==1.27.0
53+
opentelemetry-exporter-otlp==1.27.0
54+
opentelemetry-instrumentation-django==0.48b0
55+
opentelemetry-instrumentation-requests==0.48b0
56+
opentelemetry-instrumentation-mysqlclient==0.48b0
57+
opentelemetry-instrumentation-redis==0.48b0

0 commit comments

Comments
 (0)