JSONExporter
On this page
Overview
Serializes every finished span to JSON, either appended to a JSONL file on disk, or accumulated in an in-memory buffer — handy for tests and for feeding a custom dashboard.
Constructor
def __init__(self, path: Optional[Union[str, Path]] = None) -> None
Internally stores self._path (as a pathlib.Path, or None), a
threading.Lock, and self._buffer: List[Dict[str, Any]] (used only in
the in-memory mode).
export
def export(self, span: Span) -> None
Calls span.to_dict() to get a plain-dict record
(note: this is the one built-in exporter that goes through to_dict(),
so it is subject to the 2000-character _safe_repr truncation on
input/output — unlike ConsoleExporter and
LangSmithExporter).
Under the internal lock:
- If
pathwas given: opens the file in append ("a") text mode and writesjson.dumps(record, default=str) + "\n"— one JSON object per line (JSONL). Thedefault=strfallback means any value thatjson.dumpscan't natively serialize is stringified rather than raising. - If
pathwas omitted: appends the record dict toself._buffer.
The file is opened and closed on every single call to export()
(there is no persistent open file handle) — this trades a small amount of
per-span overhead for the guarantee that partial writes/crashes don't
corrupt an open handle, and that the file is always in a readable state
between writes.
Side effects: file I/O (append mode) when path is set; in-memory
list mutation otherwise. No network requests, no background jobs beyond
what Tracer's own drain thread already provides.
Thread safety: export(), spans, to_json(), and clear() all
acquire the same threading.Lock, so this exporter is safe to use
concurrently — though in practice, only the single Tracer drain thread
ever calls export() (see Architecture); the
lock matters most for the read/clear methods, which a user might call
from the main thread while the drain thread is concurrently writing.
Properties and methods (in-memory mode)
spans
@property
def spans(self) -> List[Dict[str, Any]]
Returns a copy of the in-memory buffer (only populated when no
path was given — if path was given, this stays an empty list, since
records go to the file instead of the buffer).
to_json
def to_json(self, indent: int = 2) -> str
Returns json.dumps(self._buffer, indent=indent, default=str) — a
pretty-printed JSON array of every buffered span. Like export(), only
meaningful in in-memory mode.
clear
def clear(self) -> None
Empties self._buffer in place. Has no effect on a file written via
path (there's no method on this class to truncate or clear the file).
Examples
File mode (JSONL):
from zerda.observability import enable_json
enable_json(path="traces.jsonl")
$ cat traces.jsonl
{"id": "3f2a9c1b...", "parent_id": null, "trace_id": "3f2a9c1b...", "name": "Fetch", ...}
{"id": "7b1e44aa...", "parent_id": "3f2a9c1b...", "trace_id": "3f2a9c1b...", "name": "Parse", ...}
In-memory mode, for tests or a dashboard:
from zerda.observability import get_tracer, JSONExporter
exporter = JSONExporter() # no path
get_tracer().register_exporter(exporter)
with get_tracer().span("Fetch", type="tool") as span:
span.set_output("done")
get_tracer().flush()
print(exporter.spans) # list[dict]
print(exporter.to_json()) # pretty-printed JSON string
exporter.clear()
Related: BaseExporter · Span.to_dict · enable_json