108 lines
3.7 KiB
Markdown
108 lines
3.7 KiB
Markdown
---
|
|
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'
|
|
---
|
|
|
|
# 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`.
|