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
def __init__(self, stream: TextIO = sys.stdout, verbose: bool = False) -> None
export
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}
indentis two spaces (" ") ifspan.parent_idis set, else empty — a simple, single-level visual nesting cue (does not recurse to visually reflect deeper nesting levels beyond one indent).duration_msis(span.duration or 0.0) * 1000, formatted to one decimal place.traceshows only the first 8 characters ofspan.trace_id.- Status is
"OK "(three characters, note the trailing space to align with"ERR") ifspan.successisTrue, else"ERR".
If verbose=True was passed to the constructor, three additional
indented lines are written, each only if the corresponding value is
truthy:
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:
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:
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:
from zerda.observability import enable_console
enable_console(verbose=True)
Related: BaseExporter · JSONExporter · Span · enable_console