logging skill updates
This commit is contained in:
@@ -17,9 +17,18 @@ x-personal-mcp:
|
||||
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)
|
||||
|
||||
[Python logging references](./references/python-logging-docs.md)
|
||||
: Python logging overview, library guidance, handlers, and dictConfig schema
|
||||
|
||||
[JSON file logging pattern](./references/json-file-logging.md)
|
||||
: Queue-backed rotating JSON file pattern for local machine-readable logs
|
||||
|
||||
[Network logging minimal example](./references/network-logging-minimal-example.md)
|
||||
: Minimal network logging example with a receiver and queue-backed client
|
||||
|
||||
[HTTPX logging handler example](./references/httpx-logging-handler-example.md)
|
||||
: HTTP JSON logging example with `httpx` and a queue-backed client
|
||||
|
||||
## When to Use
|
||||
|
||||
@@ -50,7 +59,7 @@ If missing, assume:
|
||||
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.
|
||||
5. Configure handlers and formatters once during application startup.
|
||||
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.
|
||||
|
||||
@@ -64,6 +73,111 @@ If missing, assume:
|
||||
- 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.
|
||||
|
||||
## Examples
|
||||
|
||||
### `logging.basicConfig`
|
||||
|
||||
```python title="Bare minimum"
|
||||
import logging
|
||||
|
||||
logging.basicConfig(level=logging.INFO, format="%(message)s")
|
||||
|
||||
logging.info("Hello, world!")
|
||||
```
|
||||
|
||||
```python title="With a little formatting"
|
||||
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`
|
||||
|
||||
```python title="Minimal dictConfig example"
|
||||
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`
|
||||
|
||||
```python title="composed config"
|
||||
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`.
|
||||
@@ -74,45 +188,35 @@ Use `logging.config.dictConfig` when configuration should be centralized, data-d
|
||||
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
|
||||
## Application Usage
|
||||
|
||||
```python title="logging_config.py"
|
||||
import logging.config
|
||||
Concrete examples of how logging should be configured and used.
|
||||
|
||||
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"],
|
||||
},
|
||||
}
|
||||
!!! warning "It's important to avoid the obvious name of `logging.py` to avoid weird clashes with IDEs and python internals."
|
||||
|
||||
=== "dictConfig"
|
||||
|
||||
def configure_logging() -> None:
|
||||
logging.config.dictConfig(LOGGING)
|
||||
```
|
||||
```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)
|
||||
```
|
||||
|
||||
```python title="app.py"
|
||||
from .logging_config import configure_logging
|
||||
|
||||
configure_logging()
|
||||
```
|
||||
|
||||
```python title="feature.py"
|
||||
import logging
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
@@ -122,12 +226,20 @@ def run(count: int) -> None:
|
||||
logger.info("Processing %s items", count)
|
||||
```
|
||||
|
||||
```python title="main.py"
|
||||
from app import run
|
||||
from logging_config import configure_logging
|
||||
|
||||
configure_logging()
|
||||
run(5)
|
||||
```
|
||||
|
||||
## 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 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](./references/json-file-logging.md).
|
||||
- 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.
|
||||
|
||||
Reference in New Issue
Block a user