Jump to content

Connect Leeroopedia MCP: Equip your AI agents to search best practices, build plans, verify code, diagnose failures, and look up hyperparameter defaults.

Implementation:Apache Airflow BaseTracer Protocol

From Leeroopedia


Knowledge Sources
Domains Observability, Tracing
Last Updated 2026-02-08 21:00 GMT

Overview

Tracer protocol and no-op fallback implementation that defines the tracing interface for type checking and provides EmptyTrace/EmptySpan as safe fallbacks when tracing is not configured.

Description

The base_tracer module establishes the tracing contract for Airflow's distributed tracing subsystem. It contains three key components:

  • Tracer (Protocol): Defines the interface that any tracing backend must implement. Methods include start_span, start_root_span, start_child_span, use_span, get_current_span, inject, and extract. This protocol is used for static type checking only and is not instantiated directly.
  • EmptyTrace: A concrete no-op implementation of the Tracer interface. All span-creation methods return EMPTY_SPAN, inject returns an empty dict, and extract returns EMPTY_CTX. This serves as the fallback when no tracing backend (e.g., OpenTelemetry) is configured.
  • EmptySpan: A no-op span that supports context manager usage (with blocks), decorator usage (__call__), and attribute/event operations that silently do nothing. Its is_recording() method always returns False.
  • EmptyContext: A minimal span context with default trace_id=1 and span_id=1.

The module also provides gen_context() and gen_links_from_kv_list() as lazy-import wrappers that delegate to the OTel tracer implementation when available.

Usage

The Tracer protocol is used for type annotations throughout Airflow's tracing code. EmptyTrace is the default tracer when OpenTelemetry is not installed or not configured. Components can safely call tracing methods without checking whether tracing is active.

Code Reference

Source Location

  • Repository: Apache Airflow
  • File: shared/observability/src/airflow_shared/observability/traces/base_tracer.py (232 lines)

Signature

class EmptyContext:
    """If no Tracer is configured, EmptyContext is used as a fallback."""
    trace_id = 1
    span_id = 1


class EmptySpan:
    """If no Tracer is configured, EmptySpan is used as a fallback."""

    def __enter__(self) -> Self: ...
    def __exit__(self, *args, **kwargs): ...
    def __call__(self, obj): ...
    def get_span_context(self): ...
    def set_attribute(self, key, value) -> None: ...
    def set_attributes(self, attributes) -> None: ...
    def is_recording(self) -> bool: ...  # Always returns False
    def add_event(self, name: str, attributes=None, timestamp=None) -> None: ...
    def add_link(self, context, attributes=None) -> None: ...
    def end(self, end_time=None, *args, **kwargs) -> None: ...


class Tracer(Protocol):
    """Protocol defining the tracing interface for type checking."""
    instance: Tracer | EmptyTrace | None = None

    @classmethod
    def get_tracer(cls, component): ...

    @classmethod
    def start_span(cls, span_name: str, component=None, parent_sc=None,
                   span_id=None, links=None, start_time=None): ...

    @classmethod
    def use_span(cls, span): ...

    @classmethod
    def get_current_span(self): ...

    @classmethod
    def start_root_span(cls, span_name=None, component=None,
                        start_time=None, start_as_current=True): ...

    @classmethod
    def start_child_span(cls, span_name=None, parent_context=None,
                         component=None, links=None, start_time=None,
                         start_as_current=True): ...

    @classmethod
    def inject(cls) -> dict: ...

    @classmethod
    def extract(cls, carrier) -> EmptyContext: ...


class EmptyTrace:
    """No-op tracer fallback. All methods return EMPTY_SPAN or EMPTY_CTX."""
    ...

Import

from airflow_shared.observability.traces.base_tracer import Tracer, EmptyTrace, EmptySpan
from airflow_shared.observability.traces.base_tracer import EMPTY_SPAN, EMPTY_CTX

I/O Contract

Tracer Protocol Methods

Method Parameters Return Type Description
get_tracer component: str Tracer Obtain a tracer for a named component
start_span span_name, component, parent_sc, span_id, links, start_time Span Start a new span with optional parent context
start_root_span span_name, component, start_time, start_as_current Span Start a root span (no parent)
start_child_span span_name, parent_context, component, links, start_time, start_as_current Span Start a child span under an existing parent
use_span span ContextManager Set a span as the current active span
get_current_span (none) Span Retrieve the currently active span
inject (none) dict Inject trace context into a carrier dict for propagation
extract carrier: dict Context Extract trace context from a carrier dict

EmptySpan Interface

Method Behavior
__enter__ / __exit__ No-op context manager
__call__(obj) Returns obj unchanged (no-op decorator)
set_attribute / set_attributes Silently ignored
add_event / add_link Silently ignored
is_recording() Always returns False
end() No-op
get_span_context() Returns EMPTY_CTX

Usage Examples

Safe Tracing with Fallback

from airflow_shared.observability.traces.base_tracer import EmptyTrace

# When tracing is not configured, EmptyTrace is used
tracer = EmptyTrace

with tracer.start_span("process_dag", component="scheduler") as span:
    span.set_attribute("dag_id", "example_dag")
    # Work proceeds normally; span operations are no-ops

Context Propagation

# Inject context for cross-process propagation
carrier = tracer.inject()  # Returns {} when using EmptyTrace

# Extract context from incoming request
context = tracer.extract(carrier)  # Returns EmptyContext

Related Pages

Implements Principle

Related Implementations

Page Connections

Double-click a node to navigate. Hold to expand connections.
Principle
Implementation
Heuristic
Environment