Expert guidance for configuring and deploying the OpenTelemetry Collector. Use when setting up a Collector pipeline, configuring receivers, exporters, or processors, deploying a Collector to Kubernetes or Docker, or forwarding telemetry to Dash0. Triggers on requests involving collector, pipeline, OTLP receiver, exporter, or Dash0 collector setup.
100
100%
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Advisory
Suggest reviewing before use
Instrument Python applications to generate traces, logs, and metrics for deep insights into behavior and performance.
pip install opentelemetry-distro opentelemetry-exporter-otlp
opentelemetry-bootstrap -a installThe opentelemetry-distro package includes dependencies for auto-instrumentation, the OTLP exporter, and the SDK.
The opentelemetry-bootstrap command detects installed libraries and installs the corresponding instrumentation packages automatically.
Note: Installing the packages alone is insufficient—you must activate the SDK AND configure exporters.
All environment variables that control the SDK behavior:
| Variable | Required | Default | Description |
|---|---|---|---|
OTEL_SERVICE_NAME | Yes | unknown_service | Identifies your service in telemetry data |
OTEL_TRACES_EXPORTER | No | otlp | Trace exporter (defaults to otlp, unlike Node.js) |
OTEL_METRICS_EXPORTER | No | none | Set to otlp to export metrics |
OTEL_LOGS_EXPORTER | No | otlp | Log exporter (defaults to otlp, unlike Node.js) |
OTEL_EXPORTER_OTLP_ENDPOINT | Yes | http://localhost:4317 | OTLP collector endpoint |
OTEL_EXPORTER_OTLP_HEADERS | No | - | Headers for authentication (e.g., Authorization=Bearer TOKEN) |
OTEL_EXPORTER_OTLP_PROTOCOL | No | grpc | Protocol: grpc, http/protobuf, or http/json |
OTEL_RESOURCE_ATTRIBUTES | No | - | Additional resource attributes (e.g., deployment.environment=production) |
Note: Unlike Node.js, the Python SDK defaults OTEL_TRACES_EXPORTER and OTEL_LOGS_EXPORTER to otlp, so traces and logs are exported without explicitly setting these variables.
https://<region>.your-platform.comorder-api, checkout-service)The SDK is activated by running your application with the opentelemetry-instrument command:
opentelemetry-instrument python main.pyFlask:
opentelemetry-instrument flask --app myapp runDjango:
opentelemetry-instrument python manage.py runserverexport OTEL_SERVICE_NAME="my-service"Traces and logs default to otlp, so you only need to enable metrics explicitly:
# Optional: also export metrics
export OTEL_METRICS_EXPORTER="otlp"export OTEL_EXPORTER_OTLP_ENDPOINT="https://<OTLP_ENDPOINT>"
export OTEL_EXPORTER_OTLP_HEADERS="Authorization=Bearer YOUR_AUTH_TOKEN"export OTEL_EXPORTER_OTLP_HEADERS="Authorization=Bearer YOUR_AUTH_TOKEN,Dash0-Dataset=my-dataset"# Service identification
export OTEL_SERVICE_NAME="my-service"
# Enable metrics exporter (traces and logs default to otlp)
export OTEL_METRICS_EXPORTER="otlp"
# Configure endpoint
export OTEL_EXPORTER_OTLP_ENDPOINT="https://<OTLP_ENDPOINT>"
export OTEL_EXPORTER_OTLP_HEADERS="Authorization=Bearer YOUR_AUTH_TOKEN"
# Run with auto-instrumentation
opentelemetry-instrument python main.pyPython does not automatically load .env files.
Use a library like python-dotenv or export variables in your shell before running:
.env:
OTEL_SERVICE_NAME=my-service
OTEL_METRICS_EXPORTER=otlp
OTEL_EXPORTER_OTLP_ENDPOINT=https://<OTLP_ENDPOINT>
OTEL_EXPORTER_OTLP_HEADERS=Authorization=Bearer YOUR_AUTH_TOKENRun with:
# Using shell export
set -a && source .env && set +a
opentelemetry-instrument python main.pyAdd instrumented targets to your Makefile:
run:
python main.py
run-otel:
set -a && source .env && set +a && opentelemetry-instrument python main.py
run-otel-console:
OTEL_SERVICE_NAME=my-service OTEL_TRACES_EXPORTER=console OTEL_LOGS_EXPORTER=console opentelemetry-instrument python main.pyUsage:
make run-otel # Run with OTLP export to backend
make run-otel-console # Run with console output (no collector needed)For development without a collector, use the console exporter to see telemetry in your terminal:
export OTEL_SERVICE_NAME="my-service"
export OTEL_TRACES_EXPORTER="console"
export OTEL_METRICS_EXPORTER="console"
export OTEL_LOGS_EXPORTER="console"
opentelemetry-instrument python main.pyThis prints spans, metrics, and logs directly to stdout—useful for verifying instrumentation works before configuring a remote backend.
If you use the default otlp exporter but have no collector running, you'll see connection errors. This is expected behavior:
Failed to export batch. UNAVAILABLE: failed to connect to all addressesOptions:
console exporter during development (recommended for quick testing)Set service.name, service.version, and deployment.environment.name for every deployment.
See resource attributes for the full list of required and recommended attributes.
See Kubernetes deployment for pod metadata injection, resource attributes, and Dash0 Kubernetes Operator guidance.
The auto-instrumentation packages automatically instrument:
| Category | Libraries |
|---|---|
| HTTP | Flask, Django, FastAPI, requests, urllib3, aiohttp |
| Database | psycopg2, mysql-connector, pymongo, redis |
| ORM | SQLAlchemy |
| Messaging | Celery, pika (RabbitMQ), confluent-kafka |
| AWS | boto3, botocore |
| Logging | logging (stdlib) |
| gRPC | grpcio |
| AI/LLM | OpenLLMetry, OpenLit (generative AI, Langchain observability) |
Refer to OpenTelemetry documentation for the complete list.
Add business context to auto-instrumented traces:
from opentelemetry import trace
from opentelemetry.trace import StatusCode
tracer = trace.get_tracer("my-service")
def process_order(order):
with tracer.start_as_current_span("order.process") as span:
try:
span.set_attribute("order.id", order["id"])
span.set_attribute("order.total", order["total"])
result = save_order(order)
return result
except Exception as error:
span.set_status(StatusCode.ERROR, str(error))
ctx = span.get_span_context()
logger.error("order.process.failed", extra={
"trace_id": format(ctx.trace_id, "032x"),
"span_id": format(ctx.span_id, "016x"),
"exception.type": type(error).__name__,
"exception.message": str(error),
"exception.stacktrace": traceback.format_exc(),
})
raiseAuto-instrumentation creates spans you do not control directly (e.g., the SERVER span for an HTTP request).
To enrich these spans with business context or set their status, retrieve the active span from the current context.
See adding attributes to auto-instrumented spans for when to use this pattern.
from opentelemetry import trace
@app.route("/api/orders", methods=["POST"])
def create_order():
span = trace.get_current_span()
span.set_attribute("order.id", request.json["order_id"])
span.set_attribute("tenant.id", request.headers.get("X-Tenant-Id"))
# ... handler logictrace.get_current_span() returns a non-recording span if no span is active.
Calling set_attribute or set_status on a non-recording span is a no-op, so no guard is needed.
See span status code for the full rules. This section shows how to apply them in Python.
ERRORThe second argument to set_status is the status message.
It must contain the error type and a short explanation — enough to understand the failure without opening the full trace.
from opentelemetry.trace import StatusCode
# BAD: no status message
span.set_status(StatusCode.ERROR)
# BAD: generic message with no diagnostic value
span.set_status(StatusCode.ERROR, "something went wrong")
# GOOD: specific message with error type and context
span.set_status(StatusCode.ERROR, f"TimeoutError: upstream payment service did not respond within 5s")Do not include tracebacks in the status message.
Record those in a log record with exception.stacktrace instead.
import traceback
# BAD: traceback in the status message
span.set_status(StatusCode.ERROR, traceback.format_exc())
# GOOD: short message only
span.set_status(StatusCode.ERROR, str(error))OK only for confirmed successSet status to OK when application logic has explicitly verified the operation succeeded.
Leave status UNSET if the code simply did not encounter an error.
# GOOD: explicit confirmation from downstream
response = requests.get(url)
if response.ok:
span.set_status(StatusCode.OK)
# BAD: setting OK speculatively
span.set_status(StatusCode.OK)
return some_function() # might still fail after this pointConfigure your logging framework to serialize exceptions into a single structured field so that stack traces do not break the one-line-per-record contract. See logs for general guidance on structured logging and exception stack traces.
The standard logging module prints multi-line stack traces by default.
Use python-json-logger to output single-line JSON with the stack trace captured in a structured field.
import logging
from pythonjsonlogger.json import JsonFormatter
handler = logging.StreamHandler()
handler.setFormatter(JsonFormatter(
fmt="%(asctime)s %(levelname)s %(name)s %(message)s",
rename_fields={"levelname": "level", "asctime": "timestamp"},
))
logger = logging.getLogger(__name__)
logger.addHandler(handler)
logger.setLevel(logging.INFO)
try:
process_order(order)
except Exception:
logger.exception("order.failed", extra={"order_id": order_id})logger.exception() automatically captures the stack trace.
The JSON formatter serializes it into an exc_info field as a single escaped string, keeping the log record on one line.
structlog produces single-line JSON output by default when configured with its JSON renderer.
import structlog
structlog.configure(
processors=[
structlog.processors.add_log_level,
structlog.processors.TimeStamper(fmt="iso"),
structlog.processors.format_exc_info,
structlog.processors.JSONRenderer(),
],
)
logger = structlog.get_logger()
try:
process_order(order)
except Exception:
logger.exception("order.failed", order_id=order_id)The format_exc_info processor converts the stack trace into a single string field before JSON serialization.
The opentelemetry-instrument command registers an atexit hook automatically.
When the process exits normally (including unhandled exceptions in most WSGI/ASGI servers), the hook flushes all pending spans, metrics, and log records before termination.
No additional code is needed for the auto-instrumented setup.
If you use a programmatic SDK setup (without opentelemetry-instrument), register a shutdown hook manually:
import atexit
atexit.register(tracer_provider.shutdown)
atexit.register(meter_provider.shutdown)
atexit.register(logger_provider.shutdown)shutdown() flushes pending batches and releases resources.
The call blocks until export completes or the timeout expires (default 30 seconds).
Check the exporter configuration:
echo $OTEL_TRACES_EXPORTER # Defaults to "otlp"; check it is not set to "none"Verify bootstrap installed instrumentations:
opentelemetry-bootstrap -a requirementsThis lists the instrumentation packages that should be installed based on your project's dependencies.
If packages are missing, re-run opentelemetry-bootstrap -a install.
Verify SDK is active:
Ensure you are running your application with the opentelemetry-instrument command, not just python.
Failed to export batch. UNAVAILABLE: failed to connect to all addressesThis means the SDK is working but cannot reach the collector:
OTEL_TRACES_EXPORTER=consoleOTEL_EXPORTER_OTLP_ENDPOINT is correctEnable debug-level logging to see detailed SDK output:
export OTEL_LOG_LEVEL="debug"
opentelemetry-instrument python main.pyThis reveals exporter activity, span creation, and configuration issues.
Symptom: SDK loads but specific libraries are not instrumented
Fix: Run the bootstrap command to detect and install missing instrumentation packages:
opentelemetry-bootstrap -a installEnsure the bootstrap command runs in the same virtual environment as your application.