9.4 KiB
9.4 KiB
name, description, x-personal-mcp
| name | description | x-personal-mcp | ||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| python-logging | Design, review, or refactor Python logging. Use when choosing logger names, levels, handlers, library/application boundaries, basicConfig, dictConfig, structured logs, or operational logging defaults. |
|
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 references
- Python logging overview, library guidance, handlers, and dictConfig schema
- JSON file logging pattern
- Queue-backed rotating JSON file pattern for local machine-readable logs
- Network logging minimal example
- Minimal network logging example with a receiver and queue-backed client
- HTTPX logging handler example
- HTTP JSON logging example with
httpxand a queue-backed client
When to Use
- A project mixes
print, root logger calls, scatteredbasicConfig, 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.dictConfigsection. - You are tuning framework or third-party loggers such as
uvicorn,sqlalchemy, or HTTP clients.
Inputs To Collect
- Runtime type: script, library, CLI, web app, worker, service, or notebook.
- Audience: humans in a terminal, operators in files, machines in JSON, or test assertions.
- Destinations: stdout/stderr, file, rotating file, queue, syslog, external collector, or none for libraries.
- Default level and verbosity controls:
INFO,DEBUG, CLI flag, environment variable, or config file. - 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
- Classify the project boundary first: application code configures logging; library code emits logs and avoids configuring handlers.
- In modules, create loggers with
logger = logging.getLogger(__name__)so logger names follow the package hierarchy. - Use level semantics consistently:
DEBUGfor diagnosis,INFOfor normal milestones,WARNINGfor notable recoverable conditions,ERRORfor failed operations, andCRITICALfor process-threatening failures. - Prefer parameterized logging calls such as
logger.info("Processed %s items", count)so message formatting is deferred until the record is emitted. - Configure handlers and formatters once during application startup.
- Keep third-party logger overrides explicit and narrow. Tune noisy loggers by name instead of muting broad logger hierarchies.
- Smoke-check output at expected levels and destinations, including one suppressed
DEBUGmessage 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
basicConfigor 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
QueueHandlerand a listener. - Avoid custom levels unless there is a strong interoperability reason.
Examples
logging.basicConfig
import logging
logging.basicConfig(level=logging.INFO, format="%(message)s")
logging.info("Hello, world!")
import logging
logging.basicConfig(
level=logging.DEBUG,
format="%(asctime)s.%(msecs)03d %(levelname)-8s %(message)s",
datefmt="%Y-%m-%d %H:%M:%S",
)
logging.info("Hello, world!")
logging.config.dictConfig
import logging
import logging.config
logging.config.dictConfig(
{
"version": 1,
"disable_existing_loggers": False,
"formatters": {
"console": {
"format": "%(asctime)s.%(msecs)03d %(levelname)-8s %(message)s",
"datefmt": "%Y-%m-%dT%H:%M:%S",
}
},
"handlers": {
"console": {
"class": "logging.StreamHandler",
"formatter": "console",
}
},
"root": {"level": "INFO", "handlers": ["console"]},
}
)
logging.info("Hello world")
Config Composition
Example function suitable for merging dicts for dictConfig
from collections.abc import Iterable
from collections.abc import Mapping
from collections.abc import Sequence
from copy import copy
from functools import reduce
BASE = ...
CONSOLE = ...
JSON_FILE = ...
RICH = ...
def merge(a: Mapping, b: Mapping) -> Mapping:
"""Recursively merge config dicts"""
a = dict(a)
for k, v in b.items():
match a.get(k), v:
case Mapping() as inner, Mapping():
a[k] = merge(inner, v)
case Sequence() as inner, Iterable():
new = list(copy(inner))
a[k] = new + [sub_v for sub_v in v if sub_v not in new]
case _:
a[k] = v
return a
def configure_logging(
base_config: dict | None = None,
*,
enable_console: bool = True,
enable_file: bool = True,
enable_rich: bool = True,
) -> dict:
"""Configure logging using the merged configuration."""
configs = [base_config or BASE]
if enable_console:
configs.append(CONSOLE)
if enable_file:
configs.append(JSON_FILE)
if enable_rich:
configs.append(RICH)
final_config = dict(reduce(merge, configs))
logging.config.dictConfig(final_config)
return final_config
Using dictConfig
Use logging.config.dictConfig when configuration should be centralized, data-driven, or richer than basicConfig.
- Define one
LOGGINGdictionary in a startup-oriented module such aslogging_config.py. - Include
version: 1and usually setdisable_existing_loggers: Falseso existing named loggers are not silently disabled. - Define formatters, then handlers, then logger routing with
rootand optional namedloggers. - Call
logging.config.dictConfig(LOGGING)once during application startup. - Keep application logging calls unchanged when adding new destinations or formats.
Application Usage
Concrete examples of how logging should be configured and used.
!!! warning "It's important to avoid the obvious name of logging.py to avoid weird clashes with IDEs and python internals."
=== "dictConfig"
```python title="logging_config.py"
import logging.config
LOGGING = ...
def configure_logging() -> None:
logging.config.dictConfig(LOGGING)
```
=== "basicConfig"
```python title="logging_config.py"
import logging.config
LOGGING = ...
def configure_logging() -> None:
logging.basicConfig(**LOGGING)
```
import logging
logger = logging.getLogger(__name__)
def run(count: int) -> None:
logger.info("Processing %s items", count)
from app import run
from logging_config import configure_logging
configure_logging()
run(5)
Branching Guidance
- If the code is a tiny script: use
basicConfigonce 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
NullHandlerat 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. For a queue-backed JSON file setup, use the JSON file logging pattern.
- 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
- Modules use
logging.getLogger(__name__). - Application startup configures logging once.
- Libraries do not configure application handlers.
- Levels match the severity semantics in this skill.
- Logs include enough context to identify source, severity, and event without leaking secrets.
- Expected destinations receive messages and suppressed levels stay quiet.
- No source file or package is named
logging.py.