# How to Configure OpenTelemetry Tracing with Headroom: Complete Setup Guide

> Learn how to configure OpenTelemetry tracing with Headroom using this complete setup guide. Export spans to Langfuse easily by following our step by step instructions. Start tracing today!

- Repository: [Tejas Chopra/headroom](https://github.com/chopratejas/headroom)
- Tags: how-to-guide
- Published: 2026-06-10

---

**Headroom provides an optional OpenTelemetry tracing façade that exports spans to Langfuse via OTLP when you install the `otel` extra, set the required environment variables, and call `configure_langfuse_tracing()` at startup.**

Headroom ships with a lightweight tracing abstraction that remains in-process by default. To export distributed traces to an OpenTelemetry-compatible collector, you must enable the optional `otel` dependency and configure the Langfuse exporter using environment variables. This guide covers the complete configuration flow based on the Headroom source code.

## Install the OpenTelemetry Extra

Begin by installing Headroom with the optional OpenTelemetry dependencies. The `otel` extra, defined in [[`pyproject.toml`](https://github.com/chopratejas/headroom/blob/main/pyproject.toml)](https://github.com/chopratejas/headroom/blob/main/pyproject.toml) (line 153), pulls in the OpenTelemetry SDK and HTTP OTLP exporter.

```bash
pip install "headroom-ai[otel]"

```

Without this extra, Headroom falls back to a no-op tracer that discards spans locally.

## Configure Environment Variables

Tracing is driven entirely by environment variables. The [`LangfuseTracingConfig.from_env()`](https://github.com/chopratejas/headroom/blob/main/headroom/observability/tracing.py) method (lines 54-77) reads these values to construct the OTLP endpoint and authentication headers.

Set the following variables before starting your application:

- **`HEADROOM_LANGFUSE_ENABLED`** – Set to `true` to activate the exporter.
- **`LANGFUSE_PUBLIC_KEY`** / **`LANGFUSE_SECRET_KEY`** – Authentication credentials for the Langfuse collector.
- **`LANGFUSE_BASE_URL`** (or `LANGFUSE_OTEL_HOST`) – The base URL of your Langfuse instance (e.g., `https://cloud.langfuse.com`).
- **`HEADROOM_LANGFUSE_SERVICE_NAME`** – The service name that appears in trace metadata (defaults to `headroom`).
- **`HEADROOM_LANGFUSE_RESOURCE_ATTRIBUTES`** – Optional comma-separated `key=value` pairs added as resource attributes (e.g., `region=us-east,team=nlp`).

These variables configure the OTLP endpoint at `/api/public/otel/v1/traces` and the HTTP Basic Authentication header required by Langfuse.

## Activate the Exporter

Initialize the tracing system once at application startup. The [`configure_langfuse_tracing()`](https://github.com/chopratejas/headroom/blob/main/headroom/observability/tracing.py) function creates a `TracerProvider`, attaches a `BatchSpanProcessor` with an `OTLPSpanExporter`, and replaces the global Headroom tracer.

```python
from headroom.observability.tracing import configure_langfone_tracing

# Reads env vars and configures the global tracer

configure_langfuse_tracing()

```

If the `otel` extra is missing or required environment variables are absent, the function falls back to a no-op tracer and logs a warning (lines 149-158 of [`tracing.py`](https://github.com/chopratejas/headroom/blob/main/tracing.py)).

## Create and Export Spans

Once configured, use the [`get_headroom_tracer()`](https://github.com/chopratejas/headroom/blob/main/headroom/observability/tracing.py) function to obtain the tracer instance and start spans. The `HeadroomTracer` delegates to the underlying OpenTelemetry tracer, ensuring all spans export to the configured Langfuse endpoint.

```python
from headroom.observability.tracing import get_headroom_tracer

tracer = get_headroom_tracer()

def process_request():
    with tracer.start_as_current_span("process_request", attributes={"user_id": "12345"}):
        # Your business logic here

        pass

```

Standard OpenTelemetry context propagation applies, so nested spans automatically become children of the current active span.

## Advanced Configuration and Lifecycle Management

### Inspect Tracing Status

Verify the current configuration using [`get_langfuse_tracing_status()`](https://github.com/chopratejas/headroom/blob/main/headroom/observability/tracing.py) (lines 94-106):

```python
from headroom.observability.tracing import get_langfuse_tracing_status

status = get_langfuse_tracing_status()
print(status)

# Output: {'configured': True, 'enabled': True, 'service_name': 'headroom', ...}

```

### Clean Shutdown

Ensure all pending spans flush before process exit by calling [`shutdown_headroom_tracing()`](https://github.com/chopratejas/headroom/blob/main/headroom/observability/tracing.py) (lines 191-205):

```python
import atexit
from headroom.observability.tracing import shutdown_headroom_tracing

atexit.register(shutdown_headroom_tracing)

```

### Complete Example

Here is a minimal working example that combines installation, configuration, and instrumentation:

```python
import os
from headroom.observability.tracing import (
    configure_langfuse_tracing,
    get_headroom_tracer,
    shutdown_headroom_tracing
)

# 1. Configure environment

os.environ["HEADROOM_LANGFUSE_ENABLED"] = "true"
os.environ["LANGFUSE_PUBLIC_KEY"] = "pk-lf-..."
os.environ["LANGFUSE_SECRET_KEY"] = "sk-lf-..."
os.environ["HEADROOM_LANGFUSE_RESOURCE_ATTRIBUTES"] = "env=production,service=ai-gateway"

# 2. Initialize tracing

configure_langfuse_tracing()

# 3. Use the tracer

tracer = get_headroom_tracer()

with tracer.start_as_current_span("main_workflow"):
    print("Processing with distributed tracing enabled")

# 4. Optional: cleanup

shutdown_headroom_tracing()

```

## Summary

- **Install the extra**: Use `pip install "headroom-ai[otel]"` to include OpenTelemetry SDK dependencies.
- **Set environment variables**: Configure `HEADROOM_LANGFUSE_ENABLED`, `LANGFUSE_PUBLIC_KEY`, and `LANGFUSE_SECRET_KEY` to authenticate with the Langfuse collector.
- **Initialize at startup**: Call `configure_langfuse_tracing()` once to set up the `TracerProvider` and `BatchSpanProcessor`.
- **Instrument code**: Use `get_headroom_tracer()` to obtain the tracer and `start_as_current_span()` to create exportable spans.
- **Manage lifecycle**: Call `shutdown_headroom_tracing()` on exit to ensure all spans flush to the collector.

## Frequently Asked Questions

### What happens if I install Headroom without the `otel` extra?

The library defaults to a no-op tracer that records spans in-process only. If you call `configure_langfuse_tracing()` without the extra installed, the function catches the missing dependency and logs a warning, continuing execution without exporting traces (see lines 149-158 in [`headroom/observability/tracing.py`](https://github.com/chopratejas/headroom/blob/main/headroom/observability/tracing.py)).

### Can I export traces to a different OTLP endpoint instead of Langfuse?

Currently, Headroom only supports Langfuse as the OTLP destination through the `configure_langfuse_tracing()` helper. The function specifically constructs the Langfuse endpoint path (`/api/public/otel/v1/traces`) and authentication headers. For other collectors, you would need to implement a custom configuration using the underlying OpenTelemetry SDK directly.

### How do I add custom resource attributes to all spans?

Set the `HEADROOM_LANGFUSE_RESOURCE_ATTRIBUTES` environment variable to a comma-separated list of `key=value` pairs before calling `configure_langfuse_tracing()`. These attributes attach to the `Resource` associated with the `TracerProvider`, appearing on every span exported from the service.

### Is tracing enabled by default?

No. Tracing remains disabled until you explicitly set `HEADROOM_LANGFUSE_ENABLED=true` and call `configure_langfuse_tracing()`. This opt-in design prevents accidental data leakage to external collectors and ensures zero overhead when observability is not required.