logger — per-step log accumulator + dotted-key NPZ IO
Purpose
Loggeraccumulates per-step payloads into aRecursiveNamespaceofLogEntryseries;LogStore
serialises/deserialises them to dotted-key NPZ files and enforces the scoreboard-completeness
contract (a log that can’t be judged is a hard-fail on write, a loud warn on read).
Role in the system
- The L in the one-way pipeline
pre_run_loader → runner → logger → ((NPZ)) → data_analyzer → plotter / star_reporter. - The controller (
run_control_loop) hands aPackageper step toLogger.append_payload; runner / orchestrator own the call. LogStore.save_raw_logis the only sanctioned NPZ writer;LogStore.load_log_from_npzthe only reader.- On load it reads the per-series payload straight from the NPZ (dropping
RAW_LOG_EXCLUDED_KEYS) and rebuilds a data_analyzerLogView-shapedLogger. - The completeness bar is the scoreboard set from
ScoreboardSpecLoader(parameter_loader /pre_run_loader), not the fullmetrics.yamlcatalog.
Inputs / Outputs
- In (write): a live
Loggerwhose.logstree holdsLogEntryseries (per-step samples, optional paired_desired). - Out (write): a dotted-key NPZ (
np.savez), per-step time series only — no scalars/config knobs; aRawLogSaveResult(path, skipped_keys). - In (read): one NPZ path (lists are rejected — map comparison lists onto contexts first).
- Out (read): a reconstructed
Logger, with a coverage warning if scoreboard metrics are missing.
Key methods/functions
scoreboard_metric_coverage— split raw keys into (present, missing) vs the scoreboard set, by leaf name —analysis/logger.py:19Logger.append_payload— recursively fold a step payload into the log tree (Packagenests,Noneskips) —analysis/logger.py:81LogStore.save_raw_log— write the dotted-key NPZ;require_complete=Truehard-fails on gaps —analysis/logger.py:228LogStore.load_log_from_npz— load one NPZ (schema-compat applied) →Logger—analysis/logger.py:112LogStore.raw_log_payload— flatten aLoggerto{dotted-key: array}, pairing_desiredsecondaries —analysis/logger.py:253LogStore.populate_logger_from_raw_payload— rebuild the tree, pairing<key>_desiredas<key>’s secondary —analysis/logger.py:144LogStore.log_entry_for_path— resolve/create theLogEntryleaf for a dotted key —analysis/logger.py:174LogStore.raw_series_values— return a key’s series array; raise on a 0-d (scalar metadata) entry —analysis/logger.py:212LogStore.iter_log_entries— walk the tree, yielding(path-tuple, LogEntry)per leaf —analysis/logger.py:238
Footguns
_desiredkeys are loaded as secondaries, not as their own entries
populate_logger_from_raw_payloadskips any key ending in_desiredduring iteration — it is
attached as the paired (secondary) series of its primary<key>. Loading it again would create a
duplicateLogEntry. (analysis/INSIGHTS.md[footgun])
The dotted key space must be a proper tree —
LogEntryonly at leaves
log_entry_for_pathraisesRuntimeErrorif a key would nest under an existingLogEntry(e.g.
a.bexists as aLogEntry, thena.b.cis requested). (analysis/INSIGHTS.md[footgun])
NPZ logs are per-step series ONLY — no scalars
raw_series_valueshard-rejects 0-d entries with a clear error. Scalars/config knobs (suppress, dt,
cap, wall) live in the run-spec YAML /config_overrides/parameters.yamland are rebuilt from cfg
at analysis time. (analysis/INSIGHTS.md[io])
Completeness bar = the scoreboard set, NOT the full catalog
Coverage is matched against
YAMLs_by_domain/scoreboard.yaml(the judgment contract), by leaf name.
The fullmetrics.yamlcatalog hasinclude:truemetrics that are config-conditional (e.g. reactive
scorer scores absent in POSE mode), so it can’t serve as a universal bar.require_complete=Trueon
write blocks any log that the scoreboard can’t fully judge. (analysis/INSIGHTS.md[io])
Pseudocode
# write (run → NPZ)
raw = raw_log_payload(logger) # walk leaves; skip t, empty, RAW_LOG_EXCLUDED_KEYS
for each LogEntry leaf:
stack samples → array # ragged / object-dtype → record skip, drop key
if has_secondary: emit <key>_desired # paired actual/desired
if require_complete and missing: raise # scoreboard contract
np.savez(path, **raw) → RawLogSaveResult(path, skipped_keys)
# read (NPZ → run)
raw = {k: d[k] for k in np.load(path).files if k not in RAW_LOG_EXCLUDED_KEYS} # per-series payload
report_metric_coverage(raw) # warn (not raise) on missing scoreboard metrics
for key, series in raw: # _desired folded into its primary's secondary
log_entry_for_path(key).append(...)
Related
runner · orchestrator · data_analyzer · detached_run · parameter_loader · infra · terminology