python-logging-cookbook
Skill0 starsChanged 39 days agoMITView on GitHub
Use when configuring Python logging beyond basicConfig — non-blocking handlers for latency-sensitive paths, multi-process log aggregation, propagation control, injecting contextual attributes, dictConfig incremental updates, level routing per handler, or library authors avoiding handler conflicts; covers QueueHandler/QueueListener, SocketHandler, LoggerAdapter vs Filter, contextvars, NullHandler for libraries
In this repository66 other files it gives its agents.
- AGENTS.mdAGENTS.md
- postgres-locking-joinsSkill
- ai-bug-patternsSkill
- compressed-ndjson-boundariesSkill
- http-resume-integritySkill
- linux-io-latency-triageSkill
- macos-disk-space-recoverySkill
- minimal-sufficient-evidenceSkill
- modularize-large-filesSkill
- patch-reproducer-validitySkill
- prompt-cache-boundariesSkill
- proton-drive-selective-backupSkill
- transactional-outbox-deliverySkill
- conventional-commitsSkill
- git-advanced-mergingSkill
- git-bisect-forensicsSkill
- git-data-recoverySkill
- git-hooksSkill
- git-rerereSkill
- git-reset-mechanicsSkill
- git-revisions-syntaxSkill
- git-rewriting-historySkill
- git-submodulesSkill
- git-worktreesSkill
- github-ssh-keysSkill
- npm-publish-ci-pitfallsSkill
- sapling-stacked-pr-workflowSkill
- go-concurrency-patternsSkill
- go-data-idiomsSkill
- go-error-handlingSkill
- go-interfaces-embeddingSkill
- go-pprof-profilingSkill
- marketing-funnel-diagnosisSkill
- marketing-geo-experimentsSkill
- marketing-incrementalitySkill
- marketing-positioning-testsSkill
- python-descriptors-mroSkill
- python-scaling-regression-testsSkill
- python-unicode-gotchasSkill
- rust-compile-time-invariantsSkill
- rust-deterministic-concurrency-testsSkill
- rust-error-identity-boundariesSkill
- rust-regex-search-contractsSkill
- rust-runtime-bug-classesSkill
- rust-tokio-actor-deliverySkill
- rust-tokio-cancellation-safetySkill
- sales-discovery-qualificationSkill
- sales-mutual-action-plansSkill
- sales-pipeline-forecast-calibrationSkill
- sales-value-negotiationSkill
- ai-triage-evaluationSkill
- container-image-patchingSkill
- dependency-artifact-integritySkill
- dnssec-sentinel-diagnosticsSkill
- iam-policy-change-replaySkill
- rbvm-applicabilitySkill
- rbvm-asset-evidenceSkill
- rbvm-guarded-remediationSkill
- rbvm-priority-decisionsSkill
- rbvm-risk-exceptionsSkill
- The first 60 shown.
The file
We split this skill into the rules it gives an agent
2 rules. About 0 are common, 0 are distinctive, and 2 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈0
Distinctive · estimate
This author's own choices. Worth a look.
2
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Very specific: line 37
Before 3.14, call listener.start() / .stop() manually.
- Very specific: line 88
Never call basicConfig, StreamHandler, or setLevel in library code — the application owns those decisions.
≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
Python Logging Cookbook
A LogRecord propagates up the logger hierarchy (unless propagate=False). Each handler gates by its own level + filters; the logger's level is only an initial cutoff.
Handler routing with level caps
logger = logging.getLogger('app'); logger.setLevel(logging.DEBUG)
fh = logging.FileHandler('debug.log'); fh.setLevel(logging.DEBUG)
ch = logging.StreamHandler(); ch.setLevel(logging.ERROR)
class MaxLevel(logging.Filter):
def __init__(self, lvl): self.lvl = lvl
def filter(self, r): return r.levelno <= self.lvl
fh.addFilter(MaxLevel(logging.WARNING)) # file: DEBUG–WARNING only
logger.addHandler(fh); logger.addHandler(ch)QueueHandler + QueueListener (non-blocking)
Park slow handlers (SMTP, Socket, rotating-file under contention) behind a queue:
from logging.handlers import QueueHandler, QueueListener
from queue import Queue
que = Queue(-1)
logging.getLogger().addHandler(QueueHandler(que))
with QueueListener(que, slow_handler, respect_handler_level=True):
run_app() # Py3.14+: context manager auto-starts/stopsBefore 3.14, call listener.start() / .stop() manually.
Multi-process logging
One FileHandler per process → interleaved writes corrupt output.
| Pattern | When |
QueueHandler + mp.Queue + single listener | in-tree workers |
SocketHandler + aggregator | containers / separate hosts |
WatchedFileHandler + external rotator (logrotate) | Unix, single writer |
TimedRotatingFileHandler is unsafe across processes — the rollover rename leaves other processes writing to the renamed file.
Contextual info: Adapter vs Filter vs factory
| Mechanism | Modifies | Best for |
LoggerAdapter.process(msg, kwargs) | prefixes to message | per-call ad-hoc context |
logging.Filter.filter(record) | sets record.X attributes | structured fields used in %(X)s format |
logging.setLogRecordFactory | every LogRecord globally | universal attributes (pid, hostname) |
contextvars.ContextVar inside a Filter | async/request-scoped | web handlers, asyncio tasks |
Return False from Filter.filter to drop the record entirely.
dictConfig: incremental + preserve libraries
logging.config.dictConfig({
'version': 1,
'disable_existing_loggers': False, # keep library loggers alive
...
})
logging.config.dictConfig({
'version': 1,
'incremental': True, # only update levels/propagate; no new handlers
'loggers': {'urllib3': {'level': 'WARNING'}},
})incremental=True only updates logger levels, handler levels, and propagation — it cannot add/remove handlers or formatters.
Library author rules
# mylib/__init__.py
logger = logging.getLogger(__name__)
logger.addHandler(logging.NullHandler()) # avoid "No handlers could be found"Never call basicConfig, StreamHandler, or setLevel in library code — the application owns those decisions.
Pitfalls
- Double log lines: a child logger with its own handler still propagates to ancestors. Set
propagate = Falseor remove the ancestor handler. disable_existing_loggersdefaults to True: third-party loggers go silent on firstdictConfig. SetFalseexplicitly.- Same file, multiple handlers: interleaved writes corrupt lines. One handler, attach to multiple loggers.
- Per-connection logger names:
logging.getLogger(f'app.conn.{id}')leaks forever — use an Adapter or contextvars. FileHandleropens lazily: a config error (bad path) surfaces on first log, not at config time.exc_info=Trueoutside an except block: returns(None, None, None)— pass the exception explicitly or uselogger.exception()insideexcept.extra=key collision: keys likemessage,asctime,levelnamesilently drop; record-reserved names cannot be overwritten.
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

