> ## 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 changing your existing logging code.

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.
