> ## 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 logs, metrics, and traces to Bronto

> Instrument a Python application with the OpenTelemetry Python SDK to send logs, metrics, and traces to Bronto over OTLP/HTTP, via an OTel Collector or directly.

One initialisation block sends **logs, metrics, and traces** from your Python application to Bronto. Logs are bridged from the standard `logging` module, metrics flow through a meter provider, and traces through a tracer provider — all three sharing one resource identity and one OTLP/HTTP exporter configuration.

The application exports to an [OpenTelemetry Collector](/agent-setup/open-telemetry), which forwards to Bronto. If you don't run a Collector, [export directly to Bronto](#direct-export-to-bronto) instead — the code is identical, only two environment variables change.

## Prerequisites

* Python 3.9 or later, and pip
* An OTel Collector reachable from your application, with `logs`, `metrics`, and `traces` pipelines forwarding to Bronto — see [Connect OpenTelemetry Collector to Bronto](/agent-setup/open-telemetry)

## Install the SDK

```bash theme={"dark"}
pip install \
  opentelemetry-api \
  opentelemetry-sdk \
  opentelemetry-exporter-otlp-proto-http
```

## Configure the environment

The SDK reads its endpoint and identity from environment variables, so nothing in the code below is environment-specific.

```bash theme={"dark"}
export OTEL_EXPORTER_OTLP_ENDPOINT="http://localhost:4318"
export OTEL_EXPORTER_OTLP_PROTOCOL="http/protobuf"
export OTEL_SERVICE_NAME="my-service"
export OTEL_RESOURCE_ATTRIBUTES="service.namespace=my-team,deployment.environment=production"
```

The SDK appends the signal path (`/v1/logs`, `/v1/metrics`, `/v1/traces`) to `OTEL_EXPORTER_OTLP_ENDPOINT` automatically.

Two resource attributes determine how Bronto organises your data:

| OTel attribute      | Bronto concept | Set with                   |
| ------------------- | -------------- | -------------------------- |
| `service.name`      | Dataset        | `OTEL_SERVICE_NAME`        |
| `service.namespace` | Collection     | `OTEL_RESOURCE_ATTRIBUTES` |

<Note>
  `http://localhost:4318` is the standard OTLP/HTTP address for a Collector on the same host. Use the address reachable from your application if the Collector runs in another container, pod, or host. No authentication is needed between the application and the Collector — the Collector holds the Bronto API key.
</Note>

## Initialise the SDK

Add this module to your project and call `configure_otel()` once, before your first log statement or span.

```python otel.py theme={"dark"}
import logging

from opentelemetry import metrics, trace
from opentelemetry.exporter.otlp.proto.http._log_exporter import OTLPLogExporter
from opentelemetry.exporter.otlp.proto.http.metric_exporter import OTLPMetricExporter
from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter
from opentelemetry.sdk._logs import LoggerProvider, LoggingHandler
from opentelemetry.sdk._logs.export import BatchLogRecordProcessor
from opentelemetry.sdk.metrics import MeterProvider
from opentelemetry.sdk.metrics.export import PeriodicExportingMetricReader
from opentelemetry.sdk.resources import Resource
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import BatchSpanProcessor


def configure_otel():
    # Picks up OTEL_SERVICE_NAME and OTEL_RESOURCE_ATTRIBUTES.
    resource = Resource.create()

    # Logs — bridges the standard logging module into OTel.
    logger_provider = LoggerProvider(resource=resource)
    logger_provider.add_log_record_processor(BatchLogRecordProcessor(OTLPLogExporter()))
    logging.getLogger().addHandler(LoggingHandler(logger_provider=logger_provider))
    logging.getLogger().setLevel(logging.INFO)

    # Metrics
    reader = PeriodicExportingMetricReader(OTLPMetricExporter())
    metrics.set_meter_provider(MeterProvider(resource=resource, metric_readers=[reader]))

    # Traces
    tracer_provider = TracerProvider(resource=resource)
    tracer_provider.add_span_processor(BatchSpanProcessor(OTLPSpanExporter()))
    trace.set_tracer_provider(tracer_provider)
```

The handler is attached to the root logger, so every logger in the process inherits it and **existing log statements are unchanged**. Pass a name to `logging.getLogger("my_app")` to instrument only part of your application.

## Instrument your application

```python app.py theme={"dark"}
import logging

from opentelemetry import metrics, trace

from otel import configure_otel

configure_otel()

logger = logging.getLogger(__name__)
tracer = trace.get_tracer("my-service")
request_counter = metrics.get_meter("my-service").create_counter("app.requests")

with tracer.start_as_current_span("handle-request") as span:
    span.set_attribute("http.request.method", "GET")
    request_counter.add(1, {"http.request.method": "GET"})
    logger.info("Handling request")  # trace_id and span_id attached automatically
```

Any log emitted inside an active span automatically carries that span's `trace_id` and `span_id`, so you can jump from a log line to its trace in Bronto with no manual context propagation.

<Tip>
  **Framework spans without code changes.** Install `opentelemetry-instrumentation-<framework>` (for example `opentelemetry-instrumentation-flask`, `-django`, `-fastapi`, `-sqlalchemy`, `-requests`) and run your app with `opentelemetry-instrument python app.py`. The CLI configures the providers itself from the same `OTEL_*` variables above, so use it *instead of* `otel.py` — and add `opentelemetry-instrumentation-logging` so standard-library logs are still bridged. See [Python zero-code instrumentation](https://opentelemetry.io/docs/zero-code/python/).
</Tip>

<Tip>
  Using **structlog** or **loguru**? See [Python: structlog / loguru](/opentelemetry/python-structlog-loguru) to route them through the same pipeline.
</Tip>

## Verify

Run your application, then check each signal in Bronto, filtering by the `service.name` you set:

* **Logs** — the [Search](https://app.bronto.io/search) page, in the dataset named after your service
* **Metrics** — the Metric Explorer
* **Traces** — the Explore Traces page

If nothing arrives:

* Confirm the Collector is running and reachable at `OTEL_EXPORTER_OTLP_ENDPOINT`, and that its pipelines include an `otlp` receiver and the Bronto exporters.
* The batch processors export on a background thread. Short-lived scripts can exit before the first flush — for those, swap in `SimpleLogRecordProcessor` and `SimpleSpanProcessor`, or call `logger_provider.shutdown()` and `tracer_provider.shutdown()` before exit.

For signal-specific reference material, see [Send Metrics to Bronto](/metrics/send-metrics) and [Send Traces to Bronto](/tracing/send-traces).

## Direct export to Bronto

Without a Collector, the application exports straight to Bronto over OTLP/HTTP. The code in `otel.py` is unchanged — point the endpoint at your Bronto region and add your API key:

```bash theme={"dark"}
export OTEL_EXPORTER_OTLP_ENDPOINT="https://ingestion.eu.bronto.io"   # or ingestion.us.bronto.io
export OTEL_EXPORTER_OTLP_HEADERS="x-bronto-api-key=<YOUR_API_KEY>"
```

| Region | Base endpoint                    |
| ------ | -------------------------------- |
| EU     | `https://ingestion.eu.bronto.io` |
| US     | `https://ingestion.us.bronto.io` |

See [API Keys](/Account-Management/API-Keys) for how to create a key with ingestion permissions.

<Tip>
  Prefer the Collector for multi-service environments — it batches, filters, and enriches telemetry, and keeps the API key out of every application. Direct export suits single services and simple architectures.
</Tip>

## GenAI semantic conventions

If your application calls an LLM, OpenTelemetry's [GenAI semantic conventions](https://github.com/open-telemetry/semantic-conventions-genai/blob/main/docs/gen-ai/gen-ai-spans.md) define `gen_ai.*` span attributes for model, token usage, and prompt and response content. Python has the richest GenAI auto-instrumentation of any OTel SDK.

Install the instrumentation for your provider and run under the zero-code wrapper:

```bash theme={"dark"}
pip install opentelemetry-instrumentation-openai-v2   # or -botocore for Amazon Bedrock via boto3
opentelemetry-instrument python app.py
```

Each model call then produces a span such as `chat gpt-4o-mini` in place of a plain HTTP client span:

| Attribute                        | Example       |
| -------------------------------- | ------------- |
| `gen_ai.provider.name`           | `openai`      |
| `gen_ai.request.model`           | `gpt-4o-mini` |
| `gen_ai.usage.input_tokens`      | `33`          |
| `gen_ai.usage.output_tokens`     | `74`          |
| `gen_ai.response.finish_reasons` | `["stop"]`    |

### Capture prompts and responses

Content capture is off by default. Two environment variables enable it:

```bash theme={"dark"}
export OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT=span_only
export OTEL_SEMCONV_STABILITY_OPT_IN=gen_ai_latest_experimental
```

`span_only` writes content to `gen_ai.input.messages` and `gen_ai.output.messages` on the span, which Bronto surfaces as searchable trace fields. The legacy `true` setting emits it as separate log records correlated by `trace_id`, where it is not queryable on the span itself — prefer `span_only` where your instrumentation supports it. These conventions are still experimental, so verify the attributes emitted by the version you deploy.

### Manual spans

Where no instrumentation package exists for your provider, set the same attributes yourself:

```python theme={"dark"}
with tracer.start_as_current_span("chat gpt-4o-mini") as span:
    span.set_attribute("gen_ai.provider.name", "openai")
    span.set_attribute("gen_ai.request.model", "gpt-4o-mini")
    span.set_attribute("gen_ai.usage.input_tokens", 33)
    span.set_attribute("gen_ai.usage.output_tokens", 74)
    # Bronto flattens array attributes — this is searchable as finish_reasons.0
    span.set_attribute("gen_ai.response.finish_reasons", ["stop"])
```

See the [GenAI span conventions](https://github.com/open-telemetry/semantic-conventions-genai/blob/main/docs/gen-ai/gen-ai-spans.md) for the full attribute list.

For the recommended attribute set and Bronto search queries, see [LLM Observability](/ai-features/llm-observability). For Anthropic and LangChain instrumentation, see [OpenLLMetry](/integrations/openllmetry) and [LangChain](/integrations/langchain).
