> ## Documentation Index
> Fetch the complete documentation index at: https://docs.bronto.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Send Python structlog and loguru logs to Bronto

> Route Python structlog and loguru output through the OpenTelemetry Python SDK to send structured application logs to Bronto without rewriting log calls.

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](/opentelemetry/python): 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:

```python theme={"dark"}
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](https://www.structlog.org/en/stable/standard-library.html).

## loguru

loguru bypasses stdlib logging, so add the OTel `LoggingHandler` as a loguru sink. Use `enqueue=True` so logging never blocks your application:

```python theme={"dark"}
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](https://app.bronto.io/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](/opentelemetry/python) 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.
