agentsclimarketplace

Python logging cookbook

Skill narenaryan/agent-skills/skills/python/python-logging-cookbook

Byte-sized agent skills for giving advanced knowledge to AI agents

Install
npx -y skills add narenaryan/agent-skills --skill python-logging-cookbook

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

One thing to look at

  • 0 stars0 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.

What its author says it does

Copied from the file, not written here

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

SKILL.md

4.3 KB, as published. Nobody here has run it

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.

Keep looking

Skills are one crate of 328,083. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.