Django
Quick Start

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-django

Then 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 install

opentelemetry-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=true

Your 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_HEADERS is a comma-separated list of key=value pairs, so the Bearer prefix'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:application

That'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 of manage.py and wsgi.py. See Manual Initialization below — using opentelemetry-instrument is 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 as http.route
  • Status codes — 2xx, 4xx, 5xx responses
  • Exceptions — unhandled exceptions are recorded on the request span via span.record_exception() and the span is marked ERROR
  • 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:

LibraryWhat it captures
psycopg2 / psycopg / mysqlclient / pymysql / sqlite3Every DB query — SQL statement, duration, error
redis / aioredisRedis commands (GET/SET/…)
requests / urllib / urllib3 / httpxOutbound HTTP — propagates W3C trace context
celeryProducer + consumer spans for queued tasks — see Tasks
loggingStandard 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,requests

Tuning the Django Instrumentation

The middleware reads a handful of environment variables at startup:

VariableDefaultPurpose
OTEL_PYTHON_DJANGO_INSTRUMENTTrueSet 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_POSITION0Index 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_hook and not request_hook? The request_hook runs before Django middleware, so middleware-populated attributes like request.user are still anonymous when it fires. Use response_hook for 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: F401

The 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 logging records to Traceway over OTLP
  • OpenTelemetry docs — how OTel traces, metrics, and logs map to Traceway