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

Python
def __init__(self, path: Optional[Union[str, Path]] = None) -> None
ParameterTypeDefaultNotes
pathOptional[Union[str, Path]]NoneIf given, each exported span is appended as one JSON line to this file. If omitted, spans accumulate in an in-memory list instead.

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

Python
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 path was given: opens the file in append ("a") text mode and writes json.dumps(record, default=str) + "\n" — one JSON object per line (JSONL). The default=str fallback means any value that json.dumps can't natively serialize is stringified rather than raising.
  • If path was omitted: appends the record dict to self._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

Python · 2 lines
@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

Python
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

Python
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):

Python · 3 lines
from zerda.observability import enable_json

enable_json(path="traces.jsonl")
code · 3 lines
$ 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:

Python · 12 lines
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

Related pages