Skip to content

Commit 1249ace

Browse files
feat: add OpenTelemetry integration (#9886)
* feat: initial otel integration * feat: add traceparent propagation via headers and meta tag * feat: add memcached instrumentation * fix: add more otel service attributes * style: Update dev/build/gunicorn.conf.py Co-authored-by: Jennifer Richards <[email protected]> * style: Update dev/build/gunicorn.conf.py Co-authored-by: Jennifer Richards <[email protected]> * style: Update dev/build/gunicorn.conf.py Co-authored-by: Jennifer Richards <[email protected]> * fix: Update traceparent meta tag to use otel variable * fix: Update traceparent_id context processor to use sub-object --------- Co-authored-by: Jennifer Richards <[email protected]>
1 parent bd6a160 commit 1249ace

File tree

6 files changed

+61
-2
lines changed

6 files changed

+61
-2
lines changed

dev/build/gunicorn.conf.py

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,16 @@
1-
# Copyright The IETF Trust 2024, All Rights Reserved
1+
# Copyright The IETF Trust 2024-2025, All Rights Reserved
2+
3+
import os
4+
import ietf
5+
from opentelemetry import trace
6+
from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter
7+
from opentelemetry.sdk.resources import Resource
8+
from opentelemetry.sdk.trace import TracerProvider
9+
from opentelemetry.sdk.trace.export import BatchSpanProcessor
10+
from opentelemetry.instrumentation.django import DjangoInstrumentor
11+
from opentelemetry.instrumentation.psycopg2 import Psycopg2Instrumentor
12+
from opentelemetry.instrumentation.pymemcache import PymemcacheInstrumentor
13+
from opentelemetry.instrumentation.requests import RequestsInstrumentor
214

315
# Configure security scheme headers for forwarded requests. Cloudflare sets X-Forwarded-Proto
416
# for us. Don't trust any of the other similar headers. Only trust the header if it's coming
@@ -119,3 +131,25 @@ def post_request(worker, req, environ, resp):
119131
in_flight = in_flight_by_pid.get(worker.pid, [])
120132
if request_description in in_flight:
121133
in_flight.remove(request_description)
134+
135+
def post_fork(server, worker):
136+
server.log.info("Worker spawned (pid: %s)", worker.pid)
137+
138+
resource = Resource.create(attributes={
139+
"service.name": "datatracker",
140+
"service.version": ietf.__version__,
141+
"service.instance.id": worker.pid,
142+
"service.namespace": "datatracker",
143+
"deployment.environment.name": os.environ.get("DATATRACKER_SERVICE_ENV", "dev")
144+
})
145+
146+
trace.set_tracer_provider(TracerProvider(resource=resource))
147+
otlp_exporter = OTLPSpanExporter(endpoint="https://heimdall-otlp.ietf.org/v1/traces")
148+
149+
trace.get_tracer_provider().add_span_processor(BatchSpanProcessor(otlp_exporter))
150+
151+
# Instrumentations
152+
DjangoInstrumentor().instrument()
153+
Psycopg2Instrumentor().instrument()
154+
PymemcacheInstrumentor().instrument()
155+
RequestsInstrumentor().instrument()

ietf/context_processors.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
from django.conf import settings
66
from django.utils import timezone
77
from ietf import __version__, __patch__, __release_branch__, __release_hash__
8+
from opentelemetry.propagate import inject
89

910
def server_mode(request):
1011
return {'server_mode': settings.SERVER_MODE}
@@ -51,3 +52,8 @@ def timezone_now(request):
5152
return {
5253
'timezone_now': timezone.now(),
5354
}
55+
56+
def traceparent_id(request):
57+
context_extras = {}
58+
inject(context_extras)
59+
return { "otel": context_extras }

ietf/middleware.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
from django.http import HttpResponsePermanentRedirect
99
from ietf.utils.log import log, exc_parts
1010
from ietf.utils.mail import log_smtp_exception
11+
from opentelemetry.propagate import inject
1112
import re
1213
import smtplib
1314
import unicodedata
@@ -99,3 +100,12 @@ def add_header(request):
99100
return response
100101

101102
return add_header
103+
104+
def add_otel_traceparent_header(get_response):
105+
"""Middleware to add the OpenTelemetry traceparent id header to the response"""
106+
def add_header(request):
107+
response = get_response(request)
108+
inject(response)
109+
return response
110+
111+
return add_header

ietf/settings.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -411,6 +411,7 @@ def skip_unreadable_post(record):
411411
],
412412
'OPTIONS': {
413413
'context_processors': [
414+
'ietf.context_processors.traceparent_id',
414415
'django.contrib.auth.context_processors.auth',
415416
'django.template.context_processors.debug', # makes 'sql_queries' available in templates
416417
'django.template.context_processors.i18n',
@@ -443,6 +444,7 @@ def skip_unreadable_post(record):
443444

444445

445446
MIDDLEWARE = [
447+
"ietf.middleware.add_otel_traceparent_header",
446448
"django.middleware.csrf.CsrfViewMiddleware",
447449
"corsheaders.middleware.CorsMiddleware", # see docs on CORS_REPLACE_HTTPS_REFERER before using it
448450
"django.middleware.common.CommonMiddleware",

ietf/templates/base.html

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
{% block title %}No title{% endblock %}
1616
</title>
1717
<meta name="viewport" content="width=device-width, initial-scale=1">
18+
<meta name="traceparent" content="{{ otel.traceparent }}">
1819
<link href="{{ settings.STATIC_IETF_ORG }}/fonts/inter/import.css" rel="stylesheet">
1920
<link href="{{ settings.STATIC_IETF_ORG }}/fonts/noto-sans-mono/import.css" rel="stylesheet">
2021
<link rel="stylesheet" href="{% static 'ietf/css/ietf.css' %}">
@@ -170,4 +171,4 @@
170171
</script>
171172
{% analytical_body_bottom %}
172173
</body>
173-
</html>
174+
</html>

requirements.txt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,12 @@ mock>=5.2.0 # should replace with unittest.mock and remove dependency
5252
types-mock>=5.2.0
5353
mypy~=1.7.0 # Version requirements determined by django-stubs.
5454
oic>=1.7.0 # Used only by tests
55+
opentelemetry-sdk>=1.38.0
56+
opentelemetry-instrumentation-django>=0.59b0
57+
opentelemetry-instrumentation-psycopg2>=0.59b0
58+
opentelemetry-instrumentation-pymemcache>=0.59b0
59+
opentelemetry-instrumentation-requests>=0.59b0
60+
opentelemetry-exporter-otlp-proto-http>=1.38.0
5561
pillow>=11.3.0
5662
psycopg2>=2.9.10
5763
pyang>=2.6.1

0 commit comments

Comments
 (0)