Canonical Log Lines & Structured Logging

logging_instance emits a fixed pipe-delimited human string with no JSON option and no trace/span fields, but OTel log-trace correlation needs trace_id / span_id on each record. This adds a Stripe-style canonical log line (one wide field-bag emitted per run) and a JSON logging.Formatter that carries trace context.

Pure standard library (json / logging); imports no PySide6. The timer clock is injectable, so durations are deterministic in CI.

Headless API

from je_auto_control import (
    CanonicalLogLine, bind_trace_context, JSONLogFormatter,
    new_root_context,
)

line = CanonicalLogLine({"event": "run_suite"})
bind_trace_context(line, new_root_context())
with line.timer("execute"):
    run()
line.add("ok", True).emit(logger.info)         # one wide line per run

# structured logging with trace correlation:
handler.setFormatter(JSONLogFormatter())

CanonicalLogLine accumulates request-scoped fields (add / update), times a block into {name}_ms via timer (injectable clock), and render / emit the wide line as JSON. bind_trace_context attaches a SpanContext’s trace_id / span_id. JSONLogFormatter is a logging.Formatter that emits one JSON object per record including trace_id / span_id and any extra= fields — the log-trace correlation counterpart to trace_context.

Executor command

AC_canonical_log builds a canonical line from a fields object and returns {line, json}. It is exposed as the MCP tool ac_canonical_log and as a Script Builder command under Report.