exlab_wizard.cache.log_writer#

Append-only writer for wizard.<hostname>.log files. Backend Spec §11.5, §16.2.4.

Low-level companion to the logger handler chain in exlab_wizard/logging/. This module owns the canonical line shape and the on-disk append semantics so that both the high-level StructuredTagFormatter (§16.4) and any direct callers (e.g. the equipment-scoped file handler in §16.2.4) write identical bytes.

The line format follows §11.5 verbatim:

<UTC ISO 8601 timestamp> [<LEVEL:5>] [host:..] [equip:..] [proj:..] [kind:..] [run:..] <message>

Two public surfaces:

  • format_log_line() – pure function. Given a timestamp, level, message, and optional context tags, returns a single line WITHOUT a trailing newline. Truncates messages whose UTF-8 length exceeds LOG_LINE_MAX_BYTES with a literal ...[truncated] marker (Backend §4.5).

  • append_log_line() – side-effecting. Appends a single line to a wizard.<hostname>.log file, creating the parent .exlab-wizard directory if missing. Atomic up to PIPE_BUF on POSIX via O_APPEND; on Windows the mode="a" open flag passes FILE_APPEND_DATA to the OS, which makes a single short append serializable against any other mode="a" writer on the same file (Backend §4.5 same-equipment concurrency rule).

The writer here is intentionally synchronous and side-effecting; the non-blocking emit pipeline (QueueHandler + QueueListener; §16.2.5) wraps it so the asyncio event loop is not blocked on filesystem writes.

Functions

append_log_line(path, line)

Append a single line to a wizard.<hostname>.log file.

format_log_line(*, timestamp_utc, level, message)

Render a structured log line per Backend Spec §11.5 / §16.4.

exlab_wizard.cache.log_writer.append_log_line(path, line)[source]#

Append a single line to a wizard.<hostname>.log file.

Creates the parent .exlab-wizard directory if missing (the cache directory is allowed to not yet exist on first equipment-folder initialization). The file is opened in text-append mode with line buffering so each call ends up as one write() syscall.

Concurrency: the file is opened with mode="a". On POSIX this maps to O_APPEND, which makes a single write() of bytes ≤ PIPE_BUF (4096 on Linux) atomic against concurrent appenders. Lines longer than this cap are truncated upstream by format_log_line() to LOG_LINE_MAX_BYTES (1024) which is well under PIPE_BUF. On Windows mode="a" opens with FILE_APPEND_DATA; the OS serializes the actual append at the syscall boundary. See Backend §4.5 same-equipment concurrency rule.

The line is appended verbatim; this function adds a single trailing newline. Callers SHOULD pass a line already truncated to LOG_LINE_MAX_BYTES via format_log_line().

Parameters:
Return type:

None

exlab_wizard.cache.log_writer.format_log_line(*, timestamp_utc, level, message, host=None, equipment_id=None, project_short_id=None, run_kind=None, run_id=None)[source]#

Render a structured log line per Backend Spec §11.5 / §16.4.

Tags are omitted entirely when their argument is None. The level field is left-padded to 5 characters so columns line up across INFO / WARN / DEBUG / ERROR lines. The timestamp is rendered in UTC with a trailing Z (e.g. 2026-04-17T14:32:00Z) matching the example shapes in §11.5.

Lines whose UTF-8 length exceeds LOG_LINE_MAX_BYTES are truncated by trimming the message tail and appending the literal ...[truncated] marker. The truncation budget is computed against the full line length (timestamp + level + tags + message), so the marker is guaranteed to fit. Messages that are already short enough are returned untouched.

Returns the line WITHOUT a trailing newline; the writer adds the newline at append time.

Parameters:
Return type:

str