cadrumo.core.logging module

Logging configuration entry point.

Provides get_logger() as the consistent logger factory to avoid scattered bare logging instances, with configure_logging() installing the project defaults. The installed log-record factory reads cadrumo.core.observability.sink.JsonlRunSink state indirectly through contextvars, so every record automatically picks up the active run_id / step_id while a run context is bound.

This module attaches the log-record secret scrubber. Every handler attached through configure_logging() receives a SecretScrubbingFilter so sensitive fields are redacted before formatting. Shape-based NIF, URL, and bearer-token matching is delegated to redact_for_log(); this module keeps only logging-specific key-paired placeholders such as cookies, passphrases, and certificate serial suffixes. Per-run JSONL handlers are attached with attach_run_sink() so the same filter protects observability output.

Encoded document payloads are contained at two seams, because the diagnostic log is plaintext on disk and a scanned invoice that reaches it has left secure storage. _ThirdPartyDebugFilter holds non-Cadrumo loggers below DEBUG at every sink, so a model or HTTP client never gets to dump a request body carrying an image; SecretScrubbingFilter then redacts base64 payloads out of the records that remain, including a payload reachable only by rendering an opaque %s argument. The first seam needs no knowledge of the payload’s shape, and the second covers first-party records the first seam admits.

Logging is a diagnostic channel, not the CLI result contract. Operator-facing success payloads and typed Notice values are rendered through the JSON/text output stack; this module only prepares redacted log records and plaintext diagnostic log files rooted by settings.

SCRUB_FIELD_PATTERNS: tuple[str, ...]

ALWAYS_REDACT_KEY_TERMS (the tree-wide shared base) plus this module’s own generic logging-domain additions. See cadrumo.core.redaction.ALWAYS_REDACT_KEY_TERMS for why the base is never redeclared independently.

Type:

Log-message KEY-NAME redaction vocabulary

LogExtraValue

Closed union of stdlib-loggable scalar types accepted by LogExtra.

class LogExtra(root=PydanticUndefined, **data)[source]

Bases: RootModel[dict[str, Union[str, int, float, bool, NoneType]]]

Typed logging extra payload restricted to flat loggable scalars.

Upgrades the historical Mapping[str, object] return annotation on service-layer as_extra() log-field helpers (e.g. PerModeloAggregationLogFields, OperatorSurfaceContract’s log_fields): every current emitter only ever writes flat, non-secret scalars into logging.Logger.debug()’s extra= mapping, so accepting bare object values understated the real contract. A future field that accidentally carried a nested mapping, a Decimal, or another non-serialisable value now fails loudly at construction with a pydantic.ValidationError naming the offending field, instead of surfacing later as a JSONL-sink serialisation surprise.

Use for_logging() to materialise the plain dict the stdlib logging module’s extra= parameter expects; the model itself is not iterable the way logging.Logger.makeRecord() requires.

Parameters:

root (RootModelRootType)

for_logging()[source]

Return the payload as the plain dict logging extras expect.

Return type:

dict[str, str | int | float | bool | None]

class SecretScrubbingFilter(name='')[source]

Bases: Filter

Redact sensitive fields from log records before formatting.

The filter mutates each logging.LogRecord in place so handlers, stderr diagnostics, and JSONL run sinks see the same scrubbed record. It is deliberately narrower than CLI output redaction: structured command results still route through cadrumo.core.output_rendering or cadrumo.core.json_contract, while this filter protects logging-only message text, %-format args, exception text, and extra fields.

filter(record)[source]

Scrub sensitive values from record in-place and return True to allow it.

Parameters:

record (LogRecord) – The log record whose msg, args, exc_info, exc_text, and extra fields are scrubbed before formatting.

Return type:

bool

Returns:

Always True — every record is allowed through after scrubbing.

class DropRunEventFilter(name='')[source]

Bases: Filter

Suppress observability run_event records on the stderr handler.

Records carrying a run_event extra are the per-run JSONL sink’s diet — they’re already persisted to events.jsonl via JsonlRunSink. Echoing them on stderr as well would spam the console with one run.event NAVIGATION line per step; suppressing them here removes the noise while leaving the record intact for any other handler (including the JSONL sink).

filter(record)[source]

Determine if the specified record is to be logged.

Returns True if the record should be logged, or False otherwise. If deemed appropriate, the record may be modified in-place.

Return type:

bool

Parameters:

record (LogRecord)

OPERATOR_DOCUMENT_LOG_EXTRA

Extra key marking a record whose content the emitter has ALREADY written to the operator’s stream as a structured document. Pass it through extra on any log call that accompanies such a write.

class DropOperatorDocumentEchoFilter(name='')[source]

Bases: Filter

Suppress records already rendered as an operator document, on stderr only.

stderr is the machine-readable error channel: the CLI’s terminal boundary writes the JSON error document there, and an agent or script parses that stream. A diagnostic log record about the same failure is therefore a duplicate on stderr, and a harmful one — the record carries the raw exception and its traceback, so echoing it prepends internal frames and the unredacted exception text to a document whose whole purpose is to present a translated, redacted message instead.

Suppressing the echo here keeps the record intact for every other handler, so the rotating file sink still receives the full traceback. That matters: the INTERNAL document tells the operator to consult the diagnostic logs, so the traceback has to actually be in them.

filter(record)[source]

Determine if the specified record is to be logged.

Returns True if the record should be logged, or False otherwise. If deemed appropriate, the record may be modified in-place.

Return type:

bool

Parameters:

record (LogRecord)

default_log_file_path()[source]

Return the file path for non-interactive project logs.

Resolved through the taxonomy accessor rather than by reading cadrumo_log_dir here. Both answer the same today, because the settings field is what the accessor consults first – but only one of them stays correct if the member’s resolution ever gains a case. A call site that reads the field directly has quietly opted out of whatever the accessor decides, and it does so invisibly, by continuing to work.

An explicit CADRUMO_LOG_DIR override still wins, and still keeps the log isolated per workspace rather than mixing every session’s records into one system-wide file.

The filename itself is read off the taxonomy’s LOG_FILE member rather than declared as an untethered string literal here.

Return type:

Path

configure_logging()[source]

Configure the project-wide diagnostic logging defaults.

Installs settings-derived stderr/file handlers, the run-context record factory, and SecretScrubbingFilter on the root logger plus every configured handler. The file handler writes redacted diagnostic plaintext under default_log_file_path(); this module does not encrypt logs or persist them through secure-object repositories.

When the diagnostic log directory cannot be created (an inaccessible CADRUMO_LOCAL_STORAGE_ROOT / CADRUMO_LOG_DIR), logging degrades to stderr-only and records an instructive diagnostic naming the likely remedy — it never crashes CLI startup with a raw traceback.

The function is idempotent, so hosts and the first-record placeholder can both reach it without duplicating handlers.

Return type:

None

set_log_level(level, *, file_level=10)[source]

Apply level to the root logger and every attached handler.

The root logger itself is always set to logging.DEBUG so no record is discarded before reaching a handler; each handler then applies its own level gate. FileHandler instances receive file_level (default DEBUG) to keep the diagnostic log comprehensive. All other handlers (typically the stderr stream handler) receive level.

configure_logging() is called first so the dictConfig contract is in place before any level mutation.

Parameters:
  • level (int) – The effective level for non-file handlers (e.g. logging.INFO for verbose mode).

  • file_level (int) – The level applied to logging.FileHandler instances (default logging.DEBUG).

Return type:

None

attach_run_sink(sink)[source]

Install SecretScrubbingFilter on sink then attach it to root.

Ensures every record flowing through the JSONL run sink is scrubbed before it reaches the serialiser, even when the root-logger filter has already scrubbed the shared record in-place. The filter is idempotent: a second call with the same sink is a no-op because the guard checks root_logger.handlers for an existing instance.

Parameters:

sink (Handler) – The logging.Handler (typically cadrumo.core.observability.sink.JsonlRunSink) to attach to the root logger.

Return type:

None

The sink is a diagnostic observability target. It receives redacted log records, not CLI result payloads or secure-storage records.

detach_run_sink(sink)[source]

Remove sink from the root logger and perform symmetric teardown.

Reverses every side-effect of attach_run_sink(): the handler is removed from the root logger, the SecretScrubbingFilter instances that attach_run_sink() installed on the sink are removed, and the sink is flushed so in-flight records reach their destination before the handle is released.

The caller is responsible for closing the sink after detach; this function deliberately does not call close() so a caller can flush output and inspect state before teardown.

Parameters:

sink (Handler) – The logging.Handler previously attached by attach_run_sink().

Return type:

None

defer_logging_configuration()[source]

Keep CLI parse/preflight state-free until dispatch is authorized.

Return type:

None

resume_logging_configuration()[source]

Release a prior startup deferral without configuring by itself.

Return type:

None

get_logger(name)[source]

Return the project logger for the given module name.

Preferred over direct logging.getLogger() in production modules because it attaches SecretScrubbingFilter directly to the returned logger. It never configures logging: hosts call configure_logging() at their process boundary, and a record that reaches the root first installs the configuration itself.

Parameters:

name (str) – The name of the module, typically __name__.

Return type:

Logger

Returns:

The named logging.Logger instance.