agentleFS
narenaryannarenaryan/agent-skills/skills/python/python-logging-cookbook/SKILL.md

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

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
  1. Very specific:

    Before 3.14, call listener.start() / .stop() manually.

    line 37
  2. Very specific:

    Never call basicConfig, StreamHandler, or setLevel in library code — the application owns those decisions.

    line 88

≈ “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.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
SKILL.md

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/stops

Before 3.14, call listener.start() / .stop() manually.

Multi-process logging

One FileHandler per process → interleaved writes corrupt output.

PatternWhen
QueueHandler + mp.Queue + single listenerin-tree workers
SocketHandler + aggregatorcontainers / 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

MechanismModifiesBest for
LoggerAdapter.process(msg, kwargs)prefixes to messageper-call ad-hoc context
logging.Filter.filter(record)sets record.X attributesstructured fields used in %(X)s format
logging.setLogRecordFactoryevery LogRecord globallyuniversal attributes (pid, hostname)
contextvars.ContextVar inside a Filterasync/request-scopedweb 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 = False or remove the ancestor handler.
  • disable_existing_loggers defaults to True: third-party loggers go silent on first dictConfig. Set False explicitly.
  • 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.
  • FileHandler opens lazily: a config error (bad path) surfaces on first log, not at config time.
  • exc_info=True outside an except block: returns (None, None, None) — pass the exception explicitly or use logger.exception() inside except.
  • extra= key collision: keys like message, asctime, levelname silently drop; record-reserved names cannot be overwritten.

Download now (ZIP)

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
What did you use it for? e.g. Reviewing pull requests in a Rails app

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.