--- name: python-logging-dictconfig description: 'Set up idiomatic Python logging with logging.config.dictConfig. Use when creating or refactoring logging setup, standardizing handlers/formatters, and enforcing centralized config.' argument-hint: 'Target context (single script, package, FastAPI app, or CLI) and desired log destinations' x-personal-mcp: id: python-logging-dictconfig version: 1.0.0 tags: - logging - python - observability capabilities: - resource://skills/python-logging-dictconfig/document depends_on: [] references: python-logging-docs: path: references/python-logging-docs.md mime_type: text/markdown title: Python Logging Docs --- # Idiomatic Python Logging with dictConfig Use this skill to produce a minimal, centralized logging setup using `logging.config.dictConfig`. Load references only when needed: - Python logging overview and hierarchy: [Python logging references](./references/python-logging-docs.md) ## When to Use - A project configures logging ad hoc with `basicConfig` across multiple modules. - You need one canonical logging configuration for app startup. - You need consistent formatting and levels across console/file handlers. - You want library modules to use named loggers without configuring logging themselves. ## Inputs To Collect 1. Runtime type: script, library, web app, worker, CLI. 2. Destinations: stdout only, file only, or both. 3. Desired default level: `INFO`, `DEBUG`, etc. 4. Whether third-party loggers should be tuned (for example `uvicorn`, `sqlalchemy`). If missing, assume: - stdout handler - human-readable formatter - root level `INFO` - `disable_existing_loggers: False` ## Procedure 1. Define a single `LOGGING` dictionary in one startup-oriented module (for example `logging_config.py`). 2. Include `version: 1` and set `disable_existing_loggers: False` unless there is a specific reason to silence existing loggers. 3. Define formatters first, then handlers, then logger routing (`root` and optional named `loggers`). 4. Use `logging.config.dictConfig(LOGGING)` exactly once during application startup. 5. In all modules, get loggers via `logger = logging.getLogger(__name__)` and never call `basicConfig`. 6. Keep libraries configuration-free: libraries should emit logs, applications decide routing. 7. Verify behavior with a quick smoke check at multiple levels (`DEBUG`, `INFO`, `WARNING`, `ERROR`). ## Minimal Baseline Templates ### Configuration !!! warning "Don't use the name `logging.py` because it will conflict ```python title="logging_config.py" import logging.config LOGGING = { "version": 1, "disable_existing_loggers": False, "formatters": { "basic": { "format": "%(asctime)s.%(msecs)03d [%(levelname)s] %(message)s", "datefmt": "%Y-%m-%d %H:%M:%S", } }, "handlers": { "console": { "class": "logging.StreamHandler", "formatter": "basic", "stream": "ext://sys.stdout", } }, "root": { "level": "INFO", "handlers": ["console"], }, } def configure_logging() -> None: logging.config.dictConfig(LOGGING) ``` ```python title="app.py" # app startup from .logging_config import configure_logging configure_logging() ``` ### Usage The preferred way of instantiating loggers is at the top of modules like this: ```python import logging logger = logging.getLogger(__name__) ``` ## Completion Checks 1. `dictConfig` is called once at startup, not per module. 2. No `basicConfig` calls remain. 3. Modules use `getLogger(__name__)`. 4. Logs appear at expected level and destination. 5. Third-party logger noise is intentionally configured or left at defaults. 6. No module named `logging.py` in the project. ## Branching Guidance - If structured logs are required: switch formatter output to JSON while keeping `dictConfig` topology unchanged. - If both console and file output are needed: add a file handler and attach it to `root`. - If a specific framework logger is too noisy: add a named logger override under `loggers`.