Quickstart

On this page

This page walks through the three ways you'll actually use this module, in the order you're likely to reach for them.

1. Turn on a backend, change nothing else

If your chains / RAG pipelines / routers already call Tracer.span() or Tracer.record_external_span() internally (that's the intended integration point for the rest of Fennec Community), then tracing is entirely opt-in from the outside: register an exporter and everything starts flowing.

Python · 3 lines
from zerda.observability import enable_langsmith

enable_langsmith(api_key="ls__...", project_name="Customer Support")
Python · 3 lines
from zerda.observability import enable_console

enable_console(verbose=True)  # prints input/output/metadata too
Python · 3 lines
from zerda.observability import enable_json

enable_json(path="traces.jsonl")  # append-only JSON Lines file

Each enable_* call registers an exporter with the global Tracer singleton, which also turns tracing on (see Tracer.register_exporter()). You can register more than one — a span is delivered to every registered exporter.

2. Wrap your own code in a span

Use Tracer.span() as a context manager anywhere you want a traced unit of work — a tool call, a retrieval step, a manual LLM call:

Python · 6 lines
from zerda.observability import get_tracer

with get_tracer().span("MyTool", type="tool") as span:
    span.set_input(query)
    result = do_work(query)
    span.set_output(result)

If an exception is raised inside the with block, the span is automatically marked as failed (span.error, span.stack_trace are populated) and the exception still propagates — tracing never swallows your errors. See Tracer.span() for the full behavior.

3. Trace a whole function with a decorator

Python · 5 lines
from zerda.observability import trace

@trace("MyTool", type="tool")
def run_tool(query: str) -> str:
    return do_work(query)

This is sugar over step 2: it opens a span named "MyTool" around the call, sets the span's output to the function's return value, and returns that value unchanged. See trace().

Multiple backends at once

Python · 4 lines
from zerda.observability import get_tracer, ConsoleExporter, JSONExporter

get_tracer().register_exporter(ConsoleExporter())
get_tracer().register_exporter(JSONExporter(path="traces.jsonl"))

Every span is now printed to stdout and appended to traces.jsonl.

Turning tracing off again

Python · 4 lines
from zerda.observability import disable_tracing, is_tracing_enabled

disable_tracing()
is_tracing_enabled()  # False

disable_tracing() keeps your registered exporters in place — a later get_tracer().enable() resumes exporting to them without re-registering anything. See Tracer.disable().

What "no exporter registered" costs you

Until an exporter is registered, get_tracer().span(...) returns a Span that is never tracked, nested, or exported — cheap enough to leave sprinkled through hot paths unconditionally. See Architecture → No-op when disabled for exactly what that path does and doesn't do.

Next: Architecture for how the pieces fit together, or jump straight to the API Reference.

Related pages