--- name: python-logging description: 'Design, review, or refactor Python logging. Use when choosing logger names, levels, handlers, library/application boundaries, basicConfig, dictConfig, structured logs, or operational logging defaults.' x-personal-mcp: id: python-logging version: 1.0.0 tags: - logging - python - observability capabilities: - resource://skills/python-logging/document --- # Python Logging Use this skill to produce idiomatic Python logging guidance or a small logging setup for an application, library, CLI, worker, or web service. Load references only when needed: - Python logging overview, library guidance, handlers, and dictConfig schema: [Python logging references](./references/python-logging-docs.md) - Minimal network logging example with a receiver and queue-backed client: [Network logging minimal example](./references/network-logging-minimal-example.md) - HTTP JSON logging example with `httpx` and a queue-backed client: [HTTPX logging handler example](./references/httpx-logging-handler-example.md) ## When to Use - A project mixes `print`, root logger calls, scattered `basicConfig`, or ad hoc handlers. - You need to choose logging levels, destinations, formatter fields, or logger names. - You need a clear boundary between library logging and application logging configuration. - You need a centralized logging setup, including a `logging.config.dictConfig` section. - You are tuning framework or third-party loggers such as `uvicorn`, `sqlalchemy`, or HTTP clients. ## Inputs To Collect 1. Runtime type: script, library, CLI, web app, worker, service, or notebook. 2. Audience: humans in a terminal, operators in files, machines in JSON, or test assertions. 3. Destinations: stdout/stderr, file, rotating file, queue, syslog, external collector, or none for libraries. 4. Default level and verbosity controls: `INFO`, `DEBUG`, CLI flag, environment variable, or config file. 5. Operational constraints: async event loop, multiprocessing, container logs, sensitive data, or high-volume paths. If missing, assume: - application code, not a reusable library - stdout console logging - human-readable formatter - root level `INFO` - no file logging unless requested ## Procedure 1. Classify the project boundary first: application code configures logging; library code emits logs and avoids configuring handlers. 2. In modules, create loggers with `logger = logging.getLogger(__name__)` so logger names follow the package hierarchy. 3. Use level semantics consistently: `DEBUG` for diagnosis, `INFO` for normal milestones, `WARNING` for notable recoverable conditions, `ERROR` for failed operations, and `CRITICAL` for process-threatening failures. 4. Prefer parameterized logging calls such as `logger.info("Processed %s items", count)` so message formatting is deferred until the record is emitted. 5. Configure handlers and formatters once during application startup. For small scripts, `basicConfig` can be enough; for applications, prefer a centralized configuration function. 6. Keep third-party logger overrides explicit and narrow. Tune noisy loggers by name instead of muting broad logger hierarchies. 7. Smoke-check output at expected levels and destinations, including one suppressed `DEBUG` message and one exception path if errors are logged. ## Best Practices - Do not name a module `logging.py`; it shadows the standard library package. - Do not call `basicConfig` or attach handlers in every module. - Do not log to the root logger from libraries. Use named loggers and, only if needed, attach `logging.NullHandler()` to the library's top-level logger. - Do not create loggers per request, user, file, or connection. Use contextual fields, adapters, or filters instead. - Use `logger.exception(...)` only inside an exception handler when the traceback is useful. - For async or high-throughput code, avoid slow network or file handlers on the hot path; consider `QueueHandler` and a listener. - Avoid custom levels unless there is a strong interoperability reason. ## Using dictConfig Use `logging.config.dictConfig` when configuration should be centralized, data-driven, or richer than `basicConfig`. 1. Define one `LOGGING` dictionary in a startup-oriented module such as `logging_config.py`. 2. Include `version: 1` and usually set `disable_existing_loggers: False` so existing named loggers are not silently disabled. 3. Define formatters, then handlers, then logger routing with `root` and optional named `loggers`. 4. Call `logging.config.dictConfig(LOGGING)` once during application startup. 5. Keep application logging calls unchanged when adding new destinations or formats. ### Minimal dictConfig Baseline ```python title="logging_config.py" import logging.config LOGGING = { "version": 1, "disable_existing_loggers": False, "formatters": { "console": { "format": "%(asctime)s.%(msecs)03d %(levelname)s %(name)s %(message)s", "datefmt": "%Y-%m-%d %H:%M:%S", } }, "handlers": { "console": { "class": "logging.StreamHandler", "formatter": "console", "stream": "ext://sys.stdout", } }, "root": { "level": "INFO", "handlers": ["console"], }, } def configure_logging() -> None: logging.config.dictConfig(LOGGING) ``` ```python title="app.py" from .logging_config import configure_logging configure_logging() ``` ```python title="feature.py" import logging logger = logging.getLogger(__name__) def run(count: int) -> None: logger.info("Processing %s items", count) ``` ## Branching Guidance - If the code is a tiny script: use `basicConfig` once near the entry point and module loggers elsewhere. - If the code is a library: remove handlers and configuration calls; document logger names and optionally add `NullHandler` at the package root. - If structured logs are required: keep the same logger and handler topology, but switch formatter output to JSON or a structured formatter. - If console and file output are needed: add one file or rotating-file handler and attach it centrally. - If multiple processes write to one file: use a queue/listener or process-safe collection path rather than opening the same file independently in each process. - If logs must cross a network: send records to a receiver or collector from a queue-backed handler, keep the receiver responsible for final destinations, and avoid exposing unauthenticated logging ports. - If a framework logger is noisy: add a named logger override with a level and leave unrelated logger propagation alone. ## Completion Checks 1. Modules use `logging.getLogger(__name__)`. 2. Application startup configures logging once. 3. Libraries do not configure application handlers. 4. Levels match the severity semantics in this skill. 5. Logs include enough context to identify source, severity, and event without leaking secrets. 6. Expected destinations receive messages and suppressed levels stay quiet. 7. No source file or package is named `logging.py`.