Skip to main content

airflow-otel

CI PyPI version Python versions License: MIT

OpenTelemetry instrumentation for Apache Airflow tasks.

Wraps Airflow task execution with traces, metrics, and logs exported via OTLP. Designed to work with any OpenTelemetry-compatible backend (Jaeger, Tempo, Dash0, Honeycomb, etc.) — point it at your collector and go.

The following screenshots are of the example Network Rail DAG running in a kubernetes environment and sending data back to Dash0 via the Open Telemetry Collector:

The Airflow Console View The data in Dash0's Service Map

Full details of how to use this library can be found below and in the example README.

Features

  • Creates a CONSUMER root span per task execution named {dag_id}.{task_id}
  • Cross-task trace linking via W3C Trace Context propagation through XCom — tasks in a DAG run appear as a single connected trace in Dash0, Grafana App O11y, Jaeger, etc.
  • Sets ERROR status and emits a correlated log record on exception
  • Supports both the TaskFlow API (decorator) and traditional PythonOperator (context manager)
  • Cardinality-safe: high-cardinality values (run_id, try_number, logical_date) go on span/log attributes, never on resource attributes
  • get_tracer() / get_meter() give you access for custom child spans and metrics inside tasks

Compatibility

Airflow Python Status
2.5 – 2.x 3.9 – 3.13 Supported
3.x 3.9 – 3.13 Supported

Installation

uv add airflow-otel

Or with pip:

pip install airflow-otel

Configuration

Configuration is via environment variables — no code changes needed to switch environments.

Variable Default Description
OTEL_EXPORTER_OTLP_ENDPOINT http://localhost:4318 OTLP HTTP endpoint
OTEL_EXPORTER_OTLP_HEADERS (none) Comma-separated key=value headers (e.g. for auth tokens)
OTEL_RESOURCE_ATTRIBUTES (none) Extra resource attributes as comma-separated key=value pairs
AIRFLOW_ENV / ENV production Sets deployment.environment.name on the resource

Usage

TaskFlow API — @instrument_task decorator

Apply @instrument_task below @task. The decorator handles OTel setup and teardown automatically.

from airflow.decorators import dag, task
from airflow_otel import instrument_task, get_tracer, get_meter
from datetime import datetime

@dag(schedule=None, start_date=datetime(2024, 1, 1))
def my_pipeline():

    @task
    @instrument_task
    def process_records():
        tracer = get_tracer()
        meter = get_meter()

        records_counter = meter.create_counter(
            "records.processed",
            description="Number of records processed",
        )

        with tracer.start_as_current_span("fetch") as span:
            records = fetch_from_source()
            span.set_attribute("records.fetched", len(records))

        with tracer.start_as_current_span("transform"):
            transformed = [transform(r) for r in records]

        records_counter.add(len(transformed))
        return len(transformed)

    process_records()

dag_instance = my_pipeline()

Traditional PythonOperator — instrument_task_context context manager

from airflow import DAG
from airflow.operators.python import PythonOperator
from airflow_otel import instrument_task_context, get_tracer
from datetime import datetime

def process_records(**context):
    with instrument_task_context(context) as span:
        tracer = get_tracer()

        with tracer.start_as_current_span("fetch") as child:
            records = fetch_from_source()
            child.set_attribute("records.fetched", len(records))

        # Add attributes to the root span
        span.set_attribute("records.processed", len(records))

with DAG("my_dag", start_date=datetime(2024, 1, 1), schedule=None) as dag:
    PythonOperator(
        task_id="process_records",
        python_callable=process_records,
    )

What gets emitted

Resource attributes

These are stable for the lifetime of the task process and safe for metric cardinality:

Attribute Value
service.name task_id
service.namespace dag_id
service.instance.id Random UUID v4 per process
deployment.environment.name AIRFLOW_ENV / ENV env var, or production

Root span attributes

High-cardinality values are placed on the span (not the resource) to avoid unbounded metric time series:

Attribute Value
airflow.dag_id DAG identifier
airflow.task_id Task identifier
airflow.run_id Run identifier
airflow.try_number Attempt number
airflow.logical_date Logical execution date (if available)

Cross-task trace linking

Each task automatically propagates the W3C traceparent header through Airflow's XCom under the key __otel_trace_context__.

  • Before starting the root span: the library pulls context from each upstream task's XCom and uses it as the parent, linking this task's span into the same trace.
  • After the root span starts: the library injects the current context into XCom so downstream tasks can pick it up.

The result is that all tasks in a DAG run appear as a single connected trace in your observability tool's service graph, with each task as a child span of its upstream.

For fan-in patterns (multiple upstream tasks), the first upstream task that has a stored context is used as the parent.

Development

# Install with dev dependencies
uv sync --extra dev

# Run tests
uv run pytest

# Run tests with coverage
uv run pytest --cov=airflow_otel

Tests use an in-memory span exporter — no running collector required.

License

MIT — see LICENSE.

Metadata

Release files for airflow-otel 0.2.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for airflow-otel 0.2.0
File Size Uploaded
airflow_otel-0.2.0.tar.gz 630.6 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for airflow-otel 0.2.0
File Interpreter ABI Platform
airflow_otel-0.2.0-py3-none-any.whl Python 3 none any Details

Total release size: 642.1 kB

Release files / airflow_otel-0.2.0.tar.gz

Download URL airflow_otel-0.2.0.tar.gz
Size 630.6 kB
Tags Source
SHA-256 checksum
How to use checksums
9f13957dd389beccf77bd7feb1985b04e6a27be4e423aad93027e81c0025d294
BLAKE2b-256 checksum
How to use checksums
38afd392850a6e0b0020ea588ef97d0b76c92d9f7e82815ab6a83dccc5b553c8
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.12

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Jun 17, 2026.

Transparency log

Release files / airflow_otel-0.2.0-py3-none-any.whl

Download URL airflow_otel-0.2.0-py3-none-any.whl
Size 11.5 kB
Tags Python 3
SHA-256 checksum
How to use checksums
1b5995ea972288a29e6786f0d68753cd234259cea52287062d19dff5f6071d34
BLAKE2b-256 checksum
How to use checksums
b262242465ccc97c525b1fbca189361b055f27c8c382e3d35f752f15543b0fbf
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.12

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Jun 17, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.2.0 This release

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page