logging
logging
¶
File-backed best-effort logger — never raises, never disrupts callers.
BestEffortLogger binds a
destination path on construction so any subsystem in any terok-*
package can spin up its own log file with one shared, audited idiom.
Writes soft-fail: a logging error must never take down the caller, so
every write is wrapped and swallowed. Operator-facing stderr output
is run through sanitize_tty so
attacker-influenced strings can't smuggle terminal escapes (CWE-150);
the file-side write keeps the original bytes for forensic review.
__all__ = ['BestEffortLogger', 'configure']
module-attribute
¶
BestEffortLogger(log_path_fn)
¶
Append timestamped lines to a state-file log; soft-fail on any error.
The destination is supplied as a callable rather than an eager
Path so XDG / env-var overrides applied between construction
and write time still take effect.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
log_path_fn
|
Callable[[], Path]
|
Zero-arg callable returning the destination path.
Called on every write so tests overriding |
required |
Bind the destination resolver.
Source code in src/terok_util/logging.py
log(message, *, level='DEBUG')
¶
Append one [timestamp] LEVEL: message line. Never raises.
File creation goes through os.open with mode 0o600 so the
log lands owner-only by construction — atomically, without
relying on the process umask. The mode bits are honoured by
the kernel only on creation; existing files keep whatever perms
they were created with.
Source code in src/terok_util/logging.py
debug(message)
¶
warning(message)
¶
warn_user(component, message)
¶
Print a structured warning to stderr and append it to the log file.
Stderr output is run through
sanitize_tty so attacker
bytes in component / message (e.g. originating from foreign
config files) can't smuggle terminal escapes into the operator's
terminal. The file-side write is unsanitised so the log keeps
the original bytes for forensic review.
Source code in src/terok_util/logging.py
configure(identifier, *, level=logging.INFO, fmt=_DEFAULT_FORMAT, stream=None, stderr=False)
¶
Install the unified log handler(s) on the root logger — the one call a package makes.
When journald is present the root logger's records go to it (tagged
SYSLOG_IDENTIFIER=identifier); otherwise they fall back to a
stderr StreamHandler formatted with fmt.
Containers have no journal socket, so an in-container daemon always
takes the stderr branch — preserving the pattern where a wrapper
redirects that stderr to a file.
Set stderr when the process's stderr is deliberately consumed by a parent (a launched daemon whose logs the launcher reads): a stderr handler is then installed in addition to journald, so the parent's pipe keeps receiving records even on a journald host.
Idempotent by construction: handlers previously installed by
configure are removed first, so re-invoking it never stacks
duplicates. Every module that already does
logging.getLogger(__name__) is captured with no call-site change,
because all such loggers propagate to the root.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
identifier
|
str
|
|
required |
level
|
int
|
Root log level. |
INFO
|
fmt
|
str
|
|
_DEFAULT_FORMAT
|
stream
|
TextIO | None
|
Stderr stream to use (defaults to :data: |
None
|
stderr
|
bool
|
Also emit to stderr even when journald is the primary sink — for daemons whose stderr a parent process consumes. |
False
|
Returns:
| Type | Description |
|---|---|
list[Handler]
|
The installed handler(s), primary first (for tests / re-wiring). |