ConsoleExporter

On this page

Overview

Writes one line per finished span to a stream (stdout by default). Described in its own source docstring as "useful for local development and as the simplest possible reference implementation of BaseExporter" — read this file first if you're about to write your own exporter.

Constructor

Python
def __init__(self, stream: TextIO = sys.stdout, verbose: bool = False) -> None
ParameterTypeDefaultNotes
streamTextIOsys.stdoutAny writable text stream — e.g. sys.stderr, an in-memory io.StringIO() for tests, or an open file handle.
verboseboolFalseWhen True, also prints input, output, and metadata for each span (each only if non-empty/non-None).

export

Python
def export(self, span: Span) -> None

Format (exact, from the source):

[OK ] {indent}{span.name} (type={span.type}, {duration_ms:.1f}ms, trace={span.trace_id[:8]})

or, for a failed span:

[ERR] {indent}{span.name} (type={span.type}, {duration_ms:.1f}ms, trace={span.trace_id[:8]}) error={span.error}
  • indent is two spaces (" ") if span.parent_id is set, else empty — a simple, single-level visual nesting cue (does not recurse to visually reflect deeper nesting levels beyond one indent).
  • duration_ms is (span.duration or 0.0) * 1000, formatted to one decimal place.
  • trace shows only the first 8 characters of span.trace_id.
  • Status is "OK " (three characters, note the trailing space to align with "ERR") if span.success is True, else "ERR".

If verbose=True was passed to the constructor, three additional indented lines are written, each only if the corresponding value is truthy:

code · 3 lines
    input:  {span.input}
    output: {span.output}
    meta:   {span.metadata}

These are printed via plain {value} interpolation — i.e. str(value) — with no truncation, unlike Span.to_dict()'s 2000-character _safe_repr limit. Large inputs/outputs will print in full when verbose mode is on.

Every call ends with self._stream.flush().

Side effects: writes to the configured stream; no state mutation, caching, file I/O beyond the stream itself, network requests, or background jobs.

Thread safety: not explicitly synchronized — since export() is only ever called from the single Tracer background drain thread (see Architecture), this is not a practical concern for the built-in usage pattern, but writing to the same stream from elsewhere concurrently is not guarded against.

Examples

Basic — default stdout:

Python · 6 lines
from zerda.observability import get_tracer, ConsoleExporter

get_tracer().register_exporter(ConsoleExporter())
with get_tracer().span("Fetch", type="tool"):
    ...
# [OK ] Fetch (type=tool, 12.4ms, trace=3f2a9c1b)

Verbose, to capture output in a test:

Python · 10 lines
import io
from zerda.observability import get_tracer, ConsoleExporter

buf = io.StringIO()
get_tracer().register_exporter(ConsoleExporter(stream=buf, verbose=True))
with get_tracer().span("Fetch", type="tool") as span:
    span.set_input("query")
    span.set_output("result")

print(buf.getvalue())

Via the module-level shortcut:

Python · 2 lines
from zerda.observability import enable_console
enable_console(verbose=True)

Related: BaseExporter · JSONExporter · Span · enable_console

Related pages