144 lines
6.9 KiB
Markdown
144 lines
6.9 KiB
Markdown
---
|
|
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`.
|