Skip to content

Logs

When to Use

Use logs to record discrete events and diagnostic details. They can describe a request's outcome, but also startup, shutdown, or failures outside any request. Use metrics for numeric aggregates and spans for timed operations. A milestone needed only within a span is often better represented as a span event.

An OpenTelemetry log record contains a timestamp, severity, message body, and attributes. It also identifies the emitting service and logger, and can include trace and span IDs. Those IDs let an operator move from a log to the related trace.

A structured event gives an occurrence a stable name and defined attributes. Where the integration supports it, use the log data model for such events rather than adding a separate application event SDK.

Keep Python Logging

Keep named standard-library loggers in application code. A logging bridge converts their Python records into OpenTelemetry records. Configure that bridge and its destinations during application startup; libraries should not attach exporters or replace the host's logging configuration.

After logging and tracing are configured, a record emitted inside a span can be associated with that span:

import logging

from opentelemetry import trace

logger = logging.getLogger(__name__)
tracer = trace.get_tracer("checkout.telemetry")

with tracer.start_as_current_span("checkout.complete"):
    logger.info(
        "Checkout completed",
        extra={"checkout.outcome": "success", "checkout.item_count": 3},
    )

The bridge supplies the active trace and span IDs. Do not invent IDs or require the span to be sampled before emitting a necessary log. Use stable messages and approved structured attributes instead of interpolated payloads. Choose severity according to the operation's outcome; a handled exception does not necessarily mean the operation failed.

Choose One Delivery Path

Path Prefer when Requirements
Stdout/stderr JSON collected by an agent Container/platform logging already works Structured formatting, resource mapping, trace fields, Collector file/container receiver
Python logging bridge to OTLP Direct structured OTel log delivery is useful Logger provider, batched processor, exporter, one handler owner

If both paths send the same records to the same backend, operators may see duplicates. Choose one collection path unless deliberate deduplication is in place. Batched SDK export is best-effort delivery, not an audit ledger or a durable business event queue.

Current Logging Integration

The Python logs API and SDK are still evolving. The linked contrib documentation recommends opentelemetry-instrumentation-logging instead of the deprecated SDK LoggingHandler. The general manual-instrumentation guide still shows the older handler. For new applications, prefer the contrib integration supported by the installed package versions; for existing applications, consult release-matched documentation.

The following extends explicit SDK setup. Install opentelemetry-instrumentation-logging alongside the SDK and exporter, and reuse the resource created during application startup. The example does not replace the application's existing logging configuration:

import logging

from opentelemetry._logs import set_logger_provider
from opentelemetry.exporter.otlp.proto.http._log_exporter import OTLPLogExporter
from opentelemetry.instrumentation.logging import LoggingInstrumentor
from opentelemetry.sdk._logs import LoggerProvider
from opentelemetry.sdk._logs.export import BatchLogRecordProcessor

log_provider = LoggerProvider(resource=resource)
log_provider.add_log_record_processor(BatchLogRecordProcessor(OTLPLogExporter()))
set_logger_provider(log_provider)
LoggingInstrumentor().instrument(
    inject_trace_context=True,
    set_logging_format=False,
    log_handler_level=logging.INFO,
)

This example requires a compatible contrib release whose instrumentor installs an OTel handler, with OTEL_PYTHON_LOG_AUTO_INSTRUMENTATION enabled. The handler sends Python records through log_provider, which batches and exports them. set_logging_format=False leaves the console format unchanged.

Logger and handler levels are separate filters. Configure logger levels in the application's logging setup: an INFO handler cannot recover a record already suppressed by a WARNING logger. Do not add this setup when a zero-code launcher already owns logging integration.

The underscore-prefixed modules are the documented log imports, but their API does not have tracing's stability guarantees. Lock compatible SDK and contrib versions and verify these arguments against those releases. Shut down log_provider after the application's final log records.

Correlation Is Not Export

Correlation associates a record with a trace. Export delivers the record to a destination. Adding trace IDs to console output does not by itself send logs through OTLP.

Trace context injection adds otelTraceID, otelSpanID, otelTraceSampled, and otelServiceName to Python log records. To preserve them in console output, include them in the JSON or text formatter. The OTel bridge instead supplies context in exported OTel records.

Current logging instrumentation can both install the bridge and inject fields. For console-only collection, set OTEL_PYTHON_LOG_AUTO_INSTRUMENTATION=false before enabling context injection. Older releases may behave differently; check their handler and injection defaults.

Enable injection before a formatter that requires those fields is used, because earlier records may lack them. Records outside a span must still be valid without a nonzero trace or span ID. An unsampled trace may supply IDs, but the backend may have no retained trace to open.

Volume and Safety

Avoid an INFO record for every item in a high-volume loop. Use metrics for totals and controlled DEBUG logging for detailed diagnosis. Log an exception once where the application is responsible for reporting it, not in every layer that re-raises it.

Remove sensitive information from messages, stack traces, and attributes before export. A log-level filter controls volume, not confidentiality.

Keep exporter errors out of a loop where the same failing exporter tries to export its own failures. Preserve a local diagnostic destination and configure noisy loggers individually. Also check Python logger propagation: a handler on both a child logger and the root logger may export the same record twice.

What to Verify

Use a logger provider local to the test and an in-memory log exporter supported by the installed SDK. Inspect severity, body, attributes, resource identity, and trace and span IDs.

Verify one record per event, the intended level filters, valid records outside a span, and final export during shutdown. If collecting console output, test that path separately. Confirm the backend exposes structured attributes as fields rather than placing everything in one message string.