Every ticket as the root of its provenance graph — goal, patch, approval, runs and (as the ticket-class build lands) mandate, manifestations, guarantees, journey, artifacts and children. Rendered through the <<<TicketGraphList>>> tag.
goal
THIS UNIT SUPERSEDES #300 — it is the SAME module and the same work, re-filed to be
SELF-CONTAINED. The superseded unit named its source only as a `git cat-file` command
against the forge's bare repos, which the sandboxed pair_loop worker that executes these
tickets cannot run, so it could not be done as filed (#346; #259 is the proof). A ticket body
is permanent in SQL (tg_tickets_immutable_identity, migrations/0029_ticket_authorization
.sql:111-117), so that body could not be repaired — this ticket carries the fix instead, and
the original is halted by a stop ticket rather than erased. Do the work HERE.
Rewrite ONE unique forge module as entirely new, behavior-preserving, installable,
control-config'd Python, per the FORGE-wide refactor standard #200 (#std).
This ticket covers ONE module — the one identified below — and nothing else. It was filed
against the module's DEDUPLICATED identity, so if that module was copy-pasted across the
forge, every copy is covered by this single ticket.
MODULE KEY : forge-rw:4c486357e89341dd
(the ticketer's idempotency marker — it lives in this ticket's TITLE. Do
not edit the title, or the module will be re-filed as a duplicate.)
IDENTITY : AST-NORMALIZED — codebean's `_ast_norm` (#183), i.e.
ast.dump(ast.parse(ast.unparse(ast.parse(src)))).
Copies of this module that differ only in comments, blank lines,
quoting or spacing carry the SAME module-sha and are covered by THIS
ticket. A differing docstring is real content, so a module whose
docstring differs is a DIFFERENT module with its own ticket.
SIZE : 8868 bytes
EXTENSION : .py
CANONICAL SOURCE (the location this ticket is named for):
repo : code__code__jazz__kokoro__dz_debug
path : dz_debug/log.py
ref : refs/heads/main (as recorded when this module was first filed)
blob : faeb483cdcf3d7d38aeeeab79ac61f135390eb0c (git blob sha1)
REFERENCE ONLY — you do NOT need this command. This module's bytes are embedded
COMPLETE in THE ORIGINAL SOURCE block below, and that is what the rewrite is based
on. For a reader who does have forge access:
git --git-dir=/Users/stevenpeterson/code/installed/jazz-project/config/forge/remotes/code__code__jazz__kokoro__dz_debug.git cat-file blob faeb483cdcf3d7d38aeeeab79ac61f135390eb0c
THE SAME MODULE ALSO LIVES AT THESE FORGE TIPS — 6 copy/copies. They SHARE THIS
ticket: this module is rewritten ONCE, and every location below is satisfied by
that one rewrite. Do not file a ticket per copy.
code__code__jazz__litellm__dz_debug:dz_debug/log.py
code__code__jazz__notes__dz_debug:dz_debug/log.py
dz_debug:dz_debug/log.py
gitlab__testmonkeyalpha-group__jazz-forge__dz_debug:dz_debug/log.py
gitlab__testmonkeyalpha__dz_debug:dz_debug/log.py
testmonkeyalpha-group__jazz-forge__dz_debug:dz_debug/log.py
HISTORICAL OCCURRENCES of this exact blob — every OTHER DISTINCT {repo, path}
the omni-git index (`occ`, joined on the git blob sha1) records this
byte-identical file at, with the index's own `ts` for the first row of each.
Any place this run enumerated LIVE is listed under the forge tips above, not
here. Context for the rewrite, not extra work:
testmonkeyalpha/dz_debug:dz_debug/log.py [occ.ts gitlab]
THE ORIGINAL SOURCE, EMBEDDED — YOU NEED NO FORGE ACCESS TO DO THIS TICKET.
The module's own bytes are reproduced below, inside this ticket. THEY are the
authoritative copy and the behavior-preserving basis: rewrite from them. The
`git --git-dir=... cat-file blob` command under CANONICAL SOURCE above is kept only as a
REFERENCE for a reader who happens to have forge access — running it is NOT part of this
ticket. (Why the bytes are here at all: the pair_loop worker that executes this ticket is
sandboxed to the ticket's own congruency worktree and cannot read the forge's bare repos.
Ticket #346 is the fix that put them on the ticket.)
The block BEGINS at the line
===ORIGINAL SOURCE (verbatim, the behavior-preserving basis)===
and ENDS at the first line
===END SOURCE===
Everything strictly between those two lines is the module — 8868 byte(s), byte for byte, with no re-indentation, no re-wrapping and nothing
elided.
===ORIGINAL SOURCE (verbatim, the behavior-preserving basis)===
# SPDX-License-Identifier: Apache-2.0
# Copyright 2026 testmonkeyalpha — Claude & Co.
"""
Service logger — writes timestamped lines to {log_root}/{build_id}/{service}.log
Behaviour is controlled by the CONFIG block below.
Set `profile` for the baseline, then flip individual channel booleans as needed.
"""
import json
import sys
import traceback
from datetime import datetime, timezone
from pathlib import Path
# ── Profiles ───────────────────────────────────────────────────────────────────
_PROFILES = {
"none": {"min_level": "ERROR"},
"standard": {"min_level": "INFO"},
"debug": {"min_level": "DEBUG"},
"trace": {"min_level": "DEBUG"},
}
# Which channels are on by default per profile
_PROFILE_CHANNELS = {
"none": {"payload": False, "perf": False, "process": False, "health": False, "ipc": False, "terminal": False},
"standard": {"payload": False, "perf": False, "process": True, "health": False, "ipc": True, "terminal": False},
"debug": {"payload": False, "perf": True, "process": True, "health": True, "ipc": True, "terminal": True},
"trace": {"payload": True, "perf": True, "process": True, "health": True, "ipc": True, "terminal": True},
}
# ── Configuration ──────────────────────────────────────────────────────────────
CONFIG = {
# Named profile — sets baseline level and channel defaults
# One of: "none", "standard", "debug", "trace"
"profile": "standard",
# Channel overrides — True/False flips a channel regardless of profile.
# Set to None to let the profile decide.
"payload": None,
"perf": None,
"process": None,
"health": None,
"ipc": None,
"terminal": None,
# Mirror every log line to stderr
"mirror_to_stderr": True,
# Root directory for log files
"log_root": Path.home() / "code" / "run" / "logs",
# Where to find build.json to auto-resolve build_id
"build_json": Path.home() / "code" / "run" / "clean" / "conf" / "env" / "build.json",
# Timestamp format
"timestamp_format": "%Y-%m-%dT%H:%M:%S",
# fatal(): how many characters of the exception to print to stderr before exit
"fatal_preview_chars": 500,
# Exit code used by fatal()
"fatal_exit_code": 1,
# Capture third-party stdlib logging (uvicorn, litellm, fastapi) into our log file.
# NOTE: requires touching Python's logging.Handler API — see _attach_stdlib_capture.
"capture_stdlib_logging": False,
"capture_stdlib_loggers": ["uvicorn", "litellm", "fastapi"],
}
# ── Level ordering ─────────────────────────────────────────────────────────────
_LEVELS = {"DEBUG": 0, "TERM ": 0, "INFO": 1, "WARN": 2, "ERROR": 3}
# ── Helpers ────────────────────────────────────────────────────────────────────
def _resolve_build_id(build_json: Path) -> str:
if build_json.exists():
return json.loads(build_json.read_text()).get("build_id", "unknown")
return "unknown"
def _timestamp(fmt: str) -> str:
now = datetime.now(timezone.utc)
ms = f"{now.microsecond // 1000:03d}"
return f"{now.strftime(fmt)}.{ms}Z"
def _resolve_channels(profile: str, overrides: dict) -> dict:
channels = dict(_PROFILE_CHANNELS[profile])
for ch, val in overrides.items():
if val is not None:
channels[ch] = val
return channels
# ── Logger ─────────────────────────────────────────────────────────────────────
class Logger:
"""
File logger scoped to a service and build ID.
Usage:
log = Logger("kokoro")
log.info("server started on port 4001")
log.debug("query took 12ms", tags=["perf"])
log.debug("raw text payload", tags=["payload"])
log.term("raw subprocess output line")
log.error("model failed", exc=e)
log.fatal("cannot continue", exc=e)
Override profile or channels at instantiation:
log = Logger("kokoro", config={"profile": "debug", "payload": True})
"""
def __init__(self, service: str, build_id: str = None, config: dict = None):
cfg = {**CONFIG, **(config or {})}
profile = cfg.get("profile", "standard")
if profile not in _PROFILES:
raise ValueError(f"Unknown profile '{profile}'. Choose: {list(_PROFILES)}")
if build_id is None:
build_id = _resolve_build_id(cfg["build_json"])
self._service = service
self._build_id = build_id
self._cfg = cfg
self._min_level = _LEVELS[_PROFILES[profile]["min_level"]]
self._channels = _resolve_channels(profile, {
ch: cfg.get(ch) for ch in ("payload", "perf", "process", "health", "ipc", "terminal")
})
log_dir = Path(cfg["log_root"]) / build_id
log_dir.mkdir(parents=True, exist_ok=True)
self._path = log_dir / f"{service}.log"
self._file = self._path.open("a", buffering=1)
if cfg.get("capture_stdlib_logging"):
self._attach_stdlib_capture(cfg.get("capture_stdlib_loggers", []))
# ── Public interface ───────────────────────────────────────────────
def info(self, msg: str, tags: list = None):
self._write("INFO", msg, tags)
def warn(self, msg: str, tags: list = None):
self._write("WARN", msg, tags)
def error(self, msg: str, exc: Exception = None, tags: list = None):
self._write("ERROR", msg, tags)
if exc:
self._write("ERROR", traceback.format_exc().strip(), tags)
def debug(self, msg: str, tags: list = None):
self._write("DEBUG", msg, tags)
def term(self, msg: str):
"""Log raw terminal/subprocess output. Controlled by the terminal channel."""
self._write("TERM ", msg, tags=["terminal"])
def fatal(self, msg: str, exc: Exception = None):
full = msg
if exc:
full += "\n" + traceback.format_exc().strip()
self._write("ERROR", full)
preview = full[:self._cfg.get("fatal_preview_chars", 500)]
print(f"\nFATAL [{self._service}]: {preview}\n", file=sys.stderr)
print(f"Full log: {self._path}", file=sys.stderr)
self.close()
sys.exit(self._cfg.get("fatal_exit_code", 1))
@property
def path(self) -> Path:
return self._path
@property
def channels(self) -> dict:
return dict(self._channels)
def close(self):
self._file.close()
# ── Private ────────────────────────────────────────────────────────
def _write(self, level: str, msg: str, tags: list = None):
if _LEVELS.get(level, 0) < self._min_level:
return
if tags:
if not any(self._channels.get(t, False) for t in tags):
return
line = f"{_timestamp(self._cfg['timestamp_format'])} [{self._service}] {level} {msg}\n"
self._file.write(line)
if self._cfg.get("mirror_to_stderr"):
print(line, end="", file=sys.stderr)
def _attach_stdlib_capture(self, logger_names: list):
# NOTE: This is the one place we must touch the stdlib `logging` API.
# uvicorn, litellm, and fastapi call logging.getLogger(__name__) internally
# and can only be intercepted by registering a logging.Handler on those loggers.
import logging
writer = self._write
class _Bridge(logging.Handler):
_MAP = {
logging.DEBUG: "DEBUG",
logging.INFO: "INFO",
logging.WARNING: "WARN",
logging.ERROR: "ERROR",
logging.CRITICAL: "ERROR",
}
def emit(self, record: logging.LogRecord):
writer(self._MAP.get(record.levelno, "INFO"), self.format(record))
handler = _Bridge()
handler.setFormatter(logging.Formatter("%(name)s: %(message)s"))
targets = logger_names if logger_names else [None]
for name in targets:
logging.getLogger(name).addHandler(handler)
===END SOURCE===
THE CALLABLE SURFACE — every callable in the original, lowered with codebean's
`lower_node` (#183). The rewrite must present THIS surface (same names, same
parameters, same defaults) so callers of the original keep working:
_resolve_build_id(build_json: Path) -> str
_timestamp(fmt: str) -> str
_resolve_channels(profile: str, overrides: dict) -> dict
Logger.__init__(self, service: str, build_id: str=None, config: dict=None)
Logger.info(self, msg: str, tags: list=None)
Logger.warn(self, msg: str, tags: list=None)
Logger.error(self, msg: str, exc: Exception=None, tags: list=None)
Logger.debug(self, msg: str, tags: list=None)
Logger.term(self, msg: str)
Logger.fatal(self, msg: str, exc: Exception=None)
Logger.path(self) -> Path
Logger.channels(self) -> dict
Logger.close(self)
Logger._write(self, level: str, msg: str, tags: list=None)
Logger._attach_stdlib_capture(self, logger_names: list)
CAPTURED BEHAVIORAL EXEMPLARS: none. no function in this module is minable — criteria_miner mines only
self-contained pure functions (it refuses anything that mentions a
world-touching or non-deterministic name, plus methods and no-arg
functions), and nothing here qualified
This does NOT weaken the ticket's bar — it MOVES the work: you must write the
equivalence exemplars by hand from the original's behavior before rewriting, and
they are what settles this ticket.
THE STANDARD — #200's four requirements, which this ticket exists to enforce. All four are
the standard's own words; none is optional:
1. BEHAVIOR-PRESERVING, VERIFIED NOT ASSERTED — the deliverable is ENTIRELY NEW source
code (rewritten, not copied). It must reproduce the ORIGINAL's behavior, and the proof
is the exemplars above (captured via codebean lower_* #183 + criteria_miner
IO-exemplars, exactly as #200 specifies).
2. A PYTHON MODULE — regardless of the source language. This module's source is `.py`;
the rewrite is Python either way.
3. INSTALLABLE — a proper package: pyproject.toml, `pip install -e .` works, console
entry points where applicable.
4. CONTROL-CONFIG'D — NO hardcoded paths or params. A JSON/registry config object drives
it (the install.json / registry.json idiom of this repo), and per note-for-claude a
tool that cannot see its config THROWS rather than guessing a default.
ACCEPTANCE (#200's, per rewrite ticket) — this ticket settles when ALL of:
a. the new source reproduces every captured exemplar above, plus the edge cases you add
for what sampling cannot reach (paste the runs as evidence);
b. it presents the callable surface above, so existing callers keep working;
c. `pip install -e .` installs it clean from its own pyproject.toml;
d. it runs off a config object with ZERO hardcoded settings, and throws without one.
Kind is `build` (a pair: coder + adversarial tester). This ticket hangs off #200 by a
`spawned` edge, so a stop ticket aimed at #200 brakes every rewrite in this programme.
Filed mechanically by checkouts/current/congruency/tools/forge_refactor_ticketer.py under ticket #201, from the forge bare repos at
/Users/stevenpeterson/code/installed/jazz-project/config/forge/remotes. Its original source is embedded above (#346), so this ticket is complete on its own.