PyAI Conf
Register now
/Pydantic Logfire

Observability with Logfire and OpenTelemetry Collector

Hasan Ramezani avatar
Hasan Ramezani
9 mins

Observability with Logfire and OpenTelemetry Collector

As applications grow in complexity and scale, managing observability data becomes increasingly challenging. The OpenTelemetry Collector provides a powerful solution for collecting, processing, and exporting telemetry data from multiple sources. When combined with Pydantic Logfire, you get a robust observability stack that scales with your infrastructure.

In this guide, we'll explore how to integrate Logfire with OpenTelemetry Collector to build a centralized observability pipeline that gives you complete visibility into your applications.

Pydantic Logfire is a developer-centric observability platform built on OpenTelemetry standards. It provides:

  • Automatic instrumentation for Python, JavaScript, Rust, and other languages
  • Distributed tracing across your entire stack, AI to API
  • Real-time monitoring with powerful SQL query capabilities
  • Beautiful, intuitive UI for exploring traces, logs, and metrics
  • Built-in security with automatic scrubbing of sensitive data

The OpenTelemetry Collector is a vendor-agnostic telemetry data pipeline that can:

  • Receive telemetry data via multiple protocols (OTLP, Jaeger, Zipkin, Prometheus)
  • Process data through filtering, sampling, batching, and enrichment
  • Export to one or multiple backends simultaneously
  • Scale horizontally to handle high-volume telemetry

While Logfire applications can send data directly to the Logfire backend, using OpenTelemetry Collector as an intermediary provides several benefits:

  1. Centralized Collection: Route telemetry from multiple services through a single collection point
  2. Reduced Application Overhead: Offload telemetry processing from your application
  3. Flexible Routing: Send data to multiple backends (Logfire, Prometheus, etc.) simultaneously
  4. Data Transformation: Filter, sample, or enrich telemetry before it reaches Logfire
  5. Network Resilience: Buffer telemetry during network issues or backend downtime
  6. Multi-tenant Support: Route data from different teams or environments appropriately

The easiest way to get started with OpenTelemetry Collector is using Docker:

docker pull otel/opentelemetry-collector:latest

docker run -d \
  --name otel-collector \
  -p 4317:4317 \
  -p 4318:4318 \
  -v $(pwd)/otel-collector-config.yaml:/etc/otel-collector-config.yaml \
  otel/opentelemetry-collector:latest \
  --config=/etc/otel-collector-config.yaml

The OpenTelemetry Collector is configured using YAML. A minimal configuration consists of three main sections: receivers, processors, and exporters.

Create a file named otel-collector-config.yaml:

receivers:
  otlp:
    protocols:
      grpc:
        endpoint: 0.0.0.0:4317
      http:
        endpoint: 0.0.0.0:4318

processors:
  batch:
    timeout: 10s
    send_batch_size: 1024

exporters:
  debug:
    verbosity: detailed

service:
  pipelines:
    traces:
      receivers: [otlp]
      processors: [batch]
      exporters: [debug]
    metrics:
      receivers: [otlp]
      processors: [batch]
      exporters: [debug]
    logs:
      receivers: [otlp]
      processors: [batch]
      exporters: [debug]

This basic configuration:

  • Receives OTLP data on ports 4317 (gRPC) and 4318 (HTTP)
  • Batches telemetry for efficient transmission
  • Exports to console (useful for testing)

Now let's configure the collector to send data to Logfire.

First, obtain your Logfire write token:

  1. Sign in to Logfire
  2. Navigate to your project settings
  3. Create and copy your write token from the project's settings under "Write Tokens"

Update your otel-collector-config.yaml to include the Logfire exporter:

receivers:
  otlp:
    protocols:
      grpc:
        endpoint: 0.0.0.0:4317
      http:
        endpoint: 0.0.0.0:4318

processors:
  batch:
    timeout: 10s
    send_batch_size: 1024

  # Add resource attributes
  resource:
    attributes:
      - key: deployment.environment
        value: production
        action: upsert

exporters:
  otlphttp/logfire:
    # Configure the US / EU endpoint for Logfire.
    # - US: https://logfire-us.pydantic.dev
    # - EU: https://logfire-eu.pydantic.dev
    endpoint: https://logfire-us.pydantic.dev
    headers:
      Authorization: "Bearer YOUR_LOGFIRE_TOKEN"
    compression: gzip

  # Keep debug exporter for debugging
  debug:
    verbosity: basic

service:
  pipelines:
    traces:
      receivers: [otlp]
      processors: [batch, resource]
      exporters: [otlphttp/logfire, debug]
    metrics:
      receivers: [otlp]
      processors: [batch, resource]
      exporters: [otlphttp/logfire, debug]
    logs:
      receivers: [otlp]
      processors: [batch, resource]
      exporters: [otlphttp/logfire, debug]

Replace YOUR_LOGFIRE_TOKEN with your actual Logfire write token.

Now update your application to send telemetry to the collector instead of directly to Logfire:

FastAPI Example

# /// script
# dependencies = [
#   "logfire[fastapi]",
#   "fastapi",
#   "uvicorn",
# ]
# ///

import logfire
from fastapi import FastAPI

logfire.configure(send_to_logfire=False)

app = FastAPI()

# Instrument FastAPI with Logfire
logfire.instrument_fastapi(app)


@app.get("/")
async def root():
    logfire.info("Root endpoint accessed")
    return {"message": "Hello World"}


if __name__ == "__main__":
    import uvicorn

    logfire.info("Starting FastAPI application")
    uvicorn.run(app, host="0.0.0.0", port=8000)

Run the application:

# Using uv (automatically installs dependencies from script header)
OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4318 uv run app.py

# Or install manually and run with python
pip install 'logfire[fastapi]' uvicorn
OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4318 python app.py

Test the endpoints:

# Test root endpoint
curl http://localhost:8000/

Let's explore more advanced configurations that enhance your observability setup.

Sampling

Reduce data volume by sampling traces:

processors:
  probabilistic_sampler:
    sampling_percentage: 10  # Sample 10% of traces

service:
  pipelines:
    traces:
      receivers: [otlp]
      processors: [probabilistic_sampler, batch, resource]
      exporters: [otlphttp/logfire]

Filtering

Filter out unwanted telemetry:

processors:
  filter:
    traces:
      span:
        - 'attributes["http.target"] == "/health"'
        - 'attributes["http.target"] == "/metrics"'

service:
  pipelines:
    traces:
      receivers: [otlp]
      processors: [filter, batch, resource]
      exporters: [otlphttp/logfire]

Enrichment

Add environment context to all telemetry:

processors:
  resource/add_environment:
    attributes:
      - key: environment
        value: production
        action: insert
      - key: region
        value: us-east-1
        action: insert
      - key: cluster
        value: primary
        action: insert

service:
  pipelines:
    traces:
      receivers: [otlp]
      processors: [resource/add_environment, batch]
      exporters: [otlphttp/logfire, debug]

Multi-Backend Export

Send data to multiple backends simultaneously:

exporters:
  otlphttp/logfire:
    endpoint: https://logfire-us.pydantic.dev
    headers:
      authorization: "Bearer YOUR_LOGFIRE_TOKEN"

  prometheus:
    endpoint: "localhost:9090"

  jaeger:
    endpoint: "jaeger:14250"
    tls:
      insecure: true

service:
  pipelines:
    traces:
      receivers: [otlp]
      processors: [batch, resource]
      exporters: [otlphttp/logfire, jaeger]

    metrics:
      receivers: [otlp]
      processors: [batch, resource]
      exporters: [otlphttp/logfire, prometheus]

At Logfire, we use this multi-backend export feature for our demo project to send traces and metrics simultaneously to both our US and EU production environments. This ensures data redundancy and allows users in different regions to access the demo data with low latency. You can see our production configuration in the Logfire demo repository.

The collector exposes metrics about its own operation. Configure Prometheus to scrape these metrics:

service:
  telemetry:
    metrics:
      level: detailed
      address: 0.0.0.0:8888

Then configure Prometheus:

scrape_configs:
  - job_name: 'otel-collector'
    static_configs:
      - targets: ['localhost:8888']

Key metrics to monitor:

  • otelcol_receiver_accepted_spans - Spans received
  • otelcol_receiver_refused_spans - Spans rejected
  • otelcol_exporter_sent_spans - Spans successfully exported
  • otelcol_exporter_send_failed_spans - Export failures
  • otelcol_processor_batch_batch_send_size - Batch sizes

Begin with a basic configuration and add complexity as needed. Don't over-engineer your initial setup.

Always enable the batch processor to reduce network overhead and improve throughput.

The collector is a critical component. Monitor its health, resource usage, and throughput.

Configure retry logic and queuing to handle temporary failures without data loss.

Use TLS and authentication to protect your collector endpoints, especially in production.

Keep your collector configuration in version control and treat it like application code.

Validate configuration changes in a staging environment before deploying to production.

Add consistent resource attributes to identify the source of telemetry across your infrastructure.

First, verify the collector is running and healthy by sending a test request:

# Test the collector's HTTP endpoint
curl -X POST http://localhost:4318/v1/traces \
  -H "Content-Type: application/json" \
  -d '{"resourceSpans":[]}'

Healthy response: {"partialSuccess":{}} - The collector is running and accepting data.

If this fails, check that:

  • The collector container is running: docker ps
  • The ports are properly exposed: 4317 (gRPC) and 4318 (HTTP)
  • The configuration file is valid and loaded correctly

Once the collector is confirmed healthy, verify your application is configured to send to the correct endpoint (http://localhost:4317 for gRPC or http://localhost:4318 for HTTP).

Enable debug logging in the collector:

exporters:
  debug:
    verbosity: detailed

service:
  pipelines:
    traces:
      exporters: [debug, otlp/logfire]

Configure memory limits:

processors:
  memory_limiter:
    check_interval: 1s
    limit_mib: 512

Verify your Logfire write token is correct and hasn't expired by testing it directly:

# For US production stack
curl -H "Authorization: Bearer YOUR_TOKEN" \
  https://logfire-us.pydantic.dev/v1/traces

# For EU production stack
curl -H "Authorization: Bearer YOUR_TOKEN" \
  https://logfire-eu.pydantic.dev/v1/traces

If you see unknown token%, your write token is invalid.

Integrating Pydantic Logfire with OpenTelemetry Collector gives you a flexible, scalable observability pipeline. You gain centralized control over your telemetry data, can route to multiple backends, and have the processing power to filter, sample, and enrich data before it reaches Logfire.

Whether you're running a small application or a large distributed system, this combination provides the foundation for comprehensive observability that grows with your needs.

Ready to get started? Sign up for Pydantic Logfire and check out the OpenTelemetry Collector documentation for more details.