Passante A Passante publication

En Passante

In Passing
Field Kit

A dozen log calls and no handler

Logging that is never configured is not quiet, it is absent. It costs the same to write, reads identically in review, and is worth exactly nothing at the moment a swallowed exception needed it.

What happened

A module called the logging factory in a dozen places and nothing had ever configured a handler, so every record went nowhere — while several error paths deliberately swallowed their exceptions and therefore left no trace at all.

2026-08-01 Python · a long-running job server measured: records reaching a handler, and remaining bare prints
2 prompts, in order
  1. Find out whether your logs go anywhere A failure happened, you went looking for the log line, and there was nothing.
  2. Name outputs after their input Generated files are landing with names that say nothing about where they came from.

1Find out whether your logs go anywhere

Run it when
A failure happened, you went looking for the log line, and there was nothing.
It asks you for
Where loggers are obtained, where handlers are configured, and what the swallowing error paths do.
You get back
Whether records reach a destination at all, and the specific paths that fail invisibly.
It refuses if
You cannot point at handler configuration. It will say the logging is unconfigured rather than assume a framework did it.
copy this into a model
<stance>
You have gone looking for a log line after an incident and found that the logger had been called correctly, in a dozen places, for months, into nothing. Nobody wrote a bug. Somebody obtained a logger and nobody ever attached a handler, and every review since read as fine because the call sites look exactly the same either way.

You care about the destination, not the call site count.
</stance>

<inputs>
1. Where the code obtains its loggers.
2. Where handlers or logging configuration are set up. If the answer is "a framework probably does it", say that is the answer and keep it.
3. Every error path that catches and does not re-raise — the deliberate swallows.
4. What operator-facing output currently uses: the logging system, or direct printing.
5. How this runs: a server under a supervisor, a CLI in a terminal, a scheduled job with output redirected.
</inputs>

<method>
1. Answer the destination question first and plainly: do records reach anything. Obtaining a logger is not configuration; a module can call the factory anywhere and it changes nothing on its own.
2. Pair each swallowing error path with whether its log record has a destination. Those pairs are the finding — a swallowed exception with no reachable log leaves no trace of a failure that already happened, and that combination is what turns a bug into a mystery.
3. Separate the two output channels and say which is which. Direct printing goes to a stream that a supervisor may discard and a scheduled job usually does; logging goes where a handler sends it. Code that mixes them has two channels with different fates and one of them is invisible.
4. Recommend configuring a handler at the entry point, not in the modules. Modules obtain loggers; the program decides where they go.
5. Say what to do about the remaining direct prints. Operator output that must survive belongs on the logging channel; anything that does not need to survive should say so.
6. Give the confirmation: trigger a swallowing path and look for its record. Do not accept a code reading as proof that logging works.
</method>

<deliver>
- DESTINATION — whether records reach anything, answered directly
- SWALLOW PAIRS — each silent error path, with whether its record is reachable
- TWO CHANNELS — what prints versus what logs, and where each ends up
- WHERE CONFIGURATION BELONGS — the entry point, named
- THE CONFIRMATION — the path to trigger, and what should appear
</deliver>

<refuse>
- Do not assume a framework configures logging. If nobody can point at the configuration, say it is unconfigured.
- Do not count call sites as evidence that logging works. Ask where the records go.
- Do not leave a swallowing error path paired with an unreachable log without naming it. That pair is the reason this matters.
- Do not recommend configuring handlers inside library modules.
</refuse>

2Name outputs after their input

Run it when
Generated files are landing with names that say nothing about where they came from.
It asks you for
How output names are derived, and what the working file is called at that moment.
You get back
Where the name is lost, and the fix, with the sanitising the input requires.
It refuses if
The name comes from a user-supplied value and you have not said what sanitising exists. It will not help name a file from unsanitised input.
copy this into a model
<stance>
You have opened a directory of outputs named after temporary files and been unable to tell which one belonged to which job. The pipeline was correct. Every name was unique. And the connection back to the source was gone, which is the only thing a name is for.

You also know that the fix — use the user's own filename — is where a path traversal walks in if nobody is paying attention.
</stance>

<inputs>
1. How output names are derived. The exact expression, if possible.
2. What the working file is called at the point that derivation runs.
3. Where that working file came from: a temporary file, a copy, an upload.
4. If a user supplied the original name, how it arrives and what is done to it.
5. Whether several jobs can be in flight at once.
</inputs>

<method>
1. Locate exactly where the meaningful name is lost. It is usually one step: the real name is taken, a temporary copy is made, and everything downstream derives from the copy's generated stem.
2. Say what that costs concretely. Several jobs produce several similarly-named outputs with nothing connecting any of them to their source, and the cost lands on whoever opens the directory later rather than on the code.
3. Recommend carrying the meaningful name forward to the working file, so the existing derivation keeps working unchanged. Changing one assignment beats changing every downstream consumer.
4. Treat the user-supplied name as hostile input, in the same breath, not as a later hardening step. It arrives from a client, and it is about to become a filesystem path. Say what to strip and what to reject.
5. Recommend a per-job directory rather than a shared one when several jobs can run at once. Uniqueness by directory beats uniqueness by mangled filename, and it keeps the readable name readable.
6. If the sanitising is unknown, stop before recommending the user's name be used for a path.
</method>

<deliver>
- WHERE THE NAME IS LOST — the specific step
- WHAT IT COSTS — stated in terms of what someone sees later
- THE FIX — carry the name to the working file, so derivation is untouched
- SANITISING — what to strip, what to reject, before it becomes a path
- ISOLATION — per-job directory, if concurrent jobs exist
</deliver>

<refuse>
- Do not recommend using a client-supplied filename as a path without saying what sanitising it needs. Say it in the same answer, not as a follow-up.
- Do not propose renaming outputs downstream when the name is recoverable upstream.
- Do not treat filename collisions as solved by adding a suffix when the real problem is that the name means nothing.
- If the derivation expression is not supplied, ask rather than guessing at it.
</refuse>
How you know it worked

Trigger one of the paths that swallows an exception and look for the record. If nothing appears anywhere, the logging is decorative regardless of how many call sites exist. Then count remaining bare prints: any that survive are operator output on a channel nothing can capture.

from a real incident, 2026-08-01 · stack Python · a long-running job server · verified by records reaching a handler, and remaining bare prints · loggingobservabilitysilent-failureerror-handling

← every Field Kit