Skip to main content
This page wires the two most popular third-party Python logging libraries — structlog and loguru — into the OpenTelemetry log pipeline. It builds on the Python page: set up the SDK, OTLP exporter, and resource attributes there first. Both approaches reuse the OTel LoggingHandler, which also attaches trace_id and span_id automatically inside an active span.

structlog

Route structlog through the standard library logger (which carries the OTel LoggingHandler from the Python page) and render each event as JSON so its key/value pairs reach Bronto as structured fields:
import structlog

structlog.configure(
    logger_factory=structlog.stdlib.LoggerFactory(),
    wrapper_class=structlog.stdlib.BoundLogger,
    processors=[
        structlog.contextvars.merge_contextvars,
        structlog.stdlib.add_log_level,
        structlog.processors.TimeStamper(fmt="iso"),
        structlog.processors.JSONRenderer(),
    ],
)

log = structlog.get_logger()
log.info("user_login", user_id=123, ip="203.0.113.7")
structlog emits the rendered JSON to the stdlib logger, the OTel handler exports it, and Bronto parses the JSON body into searchable fields. For the full processor reference, see structlog’s standard-library docs.

loguru

loguru bypasses stdlib logging, so add the OTel LoggingHandler as a loguru sink. Use enqueue=True so logging never blocks your application:
import logging
from loguru import logger
from opentelemetry.sdk._logs import LoggingHandler

# logger_provider is the one configured on the Python page
otel_handler = LoggingHandler(level=logging.NOTSET, logger_provider=logger_provider)

logger.add(otel_handler, level="INFO", enqueue=True)

logger.info("Payment processed: {amount} {ccy}", amount=99.99, ccy="USD")
The LoggingHandler reads the active span context, so logs emitted inside a span are correlated to their trace automatically — no manual trace_id handling needed.

Verify in Bronto

Open Search and filter by the service.name you set on the Python page. Emit a log from your app and confirm it appears within a few seconds.

Troubleshooting

  • No logs? Confirm the base OTel setup from the Python page runs at startup (the LoggerProvider, exporter, and root-logger handler).
  • structlog output not structured? Keep JSONRenderer() as the final processor and use structlog.stdlib.LoggerFactory().
  • loguru logs blocking or lost on exit? Keep enqueue=True, and call logger.remove() on shutdown so the queue drains before the process exits.