Django
The official opentelemetry-instrumentation-django (opens in a new tab) package (from opentelemetry-python-contrib) automatically instruments your Django application and exports traces, metrics, and logs to Traceway's OTLP endpoints — no manual instrumentation needed.
Prerequisites
- Python 3.9+
- Django 4.x or 5.x
- pip
- A Traceway project (create one in the dashboard)
Step 1: Install Packages
pip install \
opentelemetry-distro \
opentelemetry-exporter-otlp \
opentelemetry-instrumentation-djangoThen let the OTel bootstrapper auto-install the rest of the instrumentation that matches your installed dependencies (Postgres / MySQL / Redis / requests / urllib3 / Celery / logging / …):
opentelemetry-bootstrap -a installopentelemetry-bootstrap inspects your site-packages and only installs instrumentation packages that have a matching library present — re-run it whenever you add or remove dependencies.
Step 2: Configure Environment Variables
Add the following to your .env (or your container / systemd unit / manage.py shell), replacing the endpoint and token with your project values:
OTEL_SERVICE_NAME=my-django-app
OTEL_TRACES_EXPORTER=otlp
OTEL_METRICS_EXPORTER=otlp
OTEL_LOGS_EXPORTER=otlp
OTEL_EXPORTER_OTLP_PROTOCOL=http/protobuf
OTEL_EXPORTER_OTLP_ENDPOINT=https://your-traceway-instance.com/api/otel
OTEL_EXPORTER_OTLP_HEADERS=Authorization=Bearer%20your-project-token
# Forward Python logging records (Django + your loggers) as OTel log records.
OTEL_PYTHON_LOGGING_AUTO_INSTRUMENTATION_ENABLED=trueYour Traceway dashboard includes a Connection page with a ready-made config snippet and your project token. Go to Connection in the sidebar to grab it.
Note:
OTEL_EXPORTER_OTLP_HEADERSis a comma-separated list ofkey=valuepairs, so theBearerprefix's space must be URL-encoded as%20. The exporter URL-decodes header values automatically.
Step 3: Launch Django Through the OTel Agent
Don't run manage.py runserver (or your WSGI/ASGI server) directly — wrap it with opentelemetry-instrument so the agent can patch Django, the database driver, and the HTTP client libraries before your app imports them:
# Development
opentelemetry-instrument python manage.py runserver
# Production (Gunicorn / WSGI)
opentelemetry-instrument gunicorn myproject.wsgi:application
# Production (Uvicorn / ASGI)
opentelemetry-instrument uvicorn myproject.asgi:applicationThat's it — DjangoInstrumentor injects its middleware at index 0 of the MIDDLEWARE list at startup, so every inbound HTTP request is now traced. No settings.py edits required.
Want to instrument manually instead of via the agent? You can call
DjangoInstrumentor().instrument()from a tiny entrypoint module (e.g.myproject/otel.py) that you import at the top ofmanage.pyandwsgi.py. See Manual Initialization below — usingopentelemetry-instrumentis strongly preferred because it also wires up exporters and resource detection for you.
What Gets Captured
Once configured, the Django instrumentation automatically captures:
- Endpoints — every HTTP request with method and route template (e.g.,
GET /users/<int:id>/), set ashttp.route - Status codes — 2xx, 4xx, 5xx responses
- Exceptions — unhandled exceptions are recorded on the request span via
span.record_exception()and the span is markedERROR - Active request counter — concurrent in-flight request count, exported as a metric
- Duration histogram — request latency histogram (
http.server.request.duration)
And once opentelemetry-bootstrap -a install has been run, the matching auto-instrumentations also capture:
| Library | What it captures |
|---|---|
psycopg2 / psycopg / mysqlclient / pymysql / sqlite3 | Every DB query — SQL statement, duration, error |
redis / aioredis | Redis commands (GET/SET/…) |
requests / urllib / urllib3 / httpx | Outbound HTTP — propagates W3C trace context |
celery | Producer + consumer spans for queued tasks — see Tasks |
logging | Standard logging records as OTel logs (when OTEL_PYTHON_LOGGING_AUTO_INSTRUMENTATION_ENABLED=true) |
boto3 / pika / kafka-python / pymongo / elasticsearch / … | Bundled in opentelemetry-bootstrap's registry; auto-installed if the library is present |
Disable any instrumentation you don't want by uninstalling its package, or by setting the matching OTEL_PYTHON_DISABLED_INSTRUMENTATIONS env var:
# Comma-separated entry-point names — e.g. disable Redis + outbound requests
OTEL_PYTHON_DISABLED_INSTRUMENTATIONS=redis,requestsTuning the Django Instrumentation
The middleware reads a handful of environment variables at startup:
| Variable | Default | Purpose |
|---|---|---|
OTEL_PYTHON_DJANGO_INSTRUMENT | True | Set to False to disable Django instrumentation while keeping the rest. |
OTEL_PYTHON_DJANGO_EXCLUDED_URLS | (unset) | Comma-separated regexes — matching paths are not traced. Example: healthcheck,client/.*/info. |
OTEL_PYTHON_DJANGO_TRACED_REQUEST_ATTRS | (unset) | Comma-separated HttpRequest attributes to attach as span attributes (e.g. path_info,content_type). |
OTEL_PYTHON_DJANGO_MIDDLEWARE_POSITION | 0 | Index in the MIDDLEWARE list where the OTel middleware is inserted. |
OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SERVER_REQUEST | (unset) | Request headers to capture (regex list). Captured as http.request.header.<lowercased_name>. |
OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SERVER_RESPONSE | (unset) | Response headers to capture (regex list). |
OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SANITIZE_FIELDS | (unset) | Regexes of header names whose values should be redacted (e.g. .*session.*,set-cookie). |
Request & Response Hooks
For attribute work that needs Django-specific context (the authenticated user, the resolved view name, the response body size), register a response_hook from the manual entrypoint:
# myproject/otel.py
from opentelemetry.instrumentation.django import DjangoInstrumentor
def response_hook(span, request, response):
if not span or not span.is_recording():
return
if hasattr(request, "user") and request.user.is_authenticated:
span.set_attribute("enduser.id", request.user.pk)
span.set_attribute("enduser.role", request.user.groups.values_list("name", flat=True).first() or "")
span.set_attribute("http.response.body.size", len(response.content) if hasattr(response, "content") else 0)
DjangoInstrumentor().instrument(response_hook=response_hook)Then import this module before Django is imported — typically at the very top of manage.py / wsgi.py / asgi.py:
# manage.py
import myproject.otel # noqa: F401 — must come first
# ...standard manage.py body...Why
response_hookand notrequest_hook? Therequest_hookruns before Django middleware, so middleware-populated attributes likerequest.userare still anonymous when it fires. Useresponse_hookfor anything that depends on auth, sessions, or view resolution.
Manual Initialization
If you can't use the opentelemetry-instrument agent (for example you're in a managed environment that forbids wrapping the command line), set up the SDK and instrumentation programmatically:
# myproject/otel.py
from opentelemetry import trace
from opentelemetry.sdk.resources import Resource
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import BatchSpanProcessor
from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter
from opentelemetry.instrumentation.django import DjangoInstrumentor
resource = Resource.create({"service.name": "my-django-app"})
provider = TracerProvider(resource=resource)
provider.add_span_processor(BatchSpanProcessor(OTLPSpanExporter()))
trace.set_tracer_provider(provider)
DjangoInstrumentor().instrument()Import this module before Django loads:
# manage.py
import myproject.otel # noqa: F401The OTLPSpanExporter() constructor reads OTEL_EXPORTER_OTLP_ENDPOINT / OTEL_EXPORTER_OTLP_HEADERS from the environment, so you still configure it the same way.
Test Your Integration
Add a test route to verify data is flowing:
# myapp/views.py
from django.http import HttpResponse
def testing(request):
raise RuntimeError("Test error from Traceway integration")# myproject/urls.py
from django.urls import path
from myapp import views
urlpatterns = [
path("testing/", views.testing),
]Visit /testing/ in your browser, then check the Traceway dashboard — the exception should appear within a few seconds.
Next Steps
- Exceptions — manually capture caught exceptions with context
- Spans — create custom spans to measure sub-operations
- Tasks — trace Celery tasks and Django management commands as background tasks
- Metrics — track custom counters, histograms, and gauges with the OTel Metrics API
- Logs — forward Django + Python
loggingrecords to Traceway over OTLP - OpenTelemetry docs — how OTel traces, metrics, and logs map to Traceway