# Logging Patterns

> Logging conventions -- level usage, formatting style, structured output.

- Skill: `jartan-llc/logging-patterns` (Agent Skill)
- Install (CLI): `npx skillmds@latest add jartan-llc/logging-patterns`
- Raw SKILL.md: https://api.skillmd.com/api/skills/jartan-llc/logging-patterns/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: Jartan-LLC (https://skillmd.com/u/jartan-llc)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/jartan-llc/logging-patterns

---


# Logging Conventions

## Setup

One logger per module, at module level:

```python
import logging

logger = logging.getLogger(__name__)
```

## Formatting

Use `%s`-style formatting arguments, not f-strings -- the message template is preserved for structured aggregator queries:

```python
logger.info("Cleaned up %d expired sessions", count)  # yes
logger.exception("SMTP send failed for %s", email)    # yes (in an except handler)
logger.info(f"Cleaned up {count} expired sessions")   # no
```

`%s` deferral is stdlib-specific -- `structlog` uses kwargs, not `%s`. See `pythonica:python-observability`.

## Level Conventions

| Level | Use for |
|---|---|
| `DEBUG` | Cache hit/miss, slow-path internals (opt-in only) |
| `INFO` | Startup/shutdown, admin bootstrap, cleanup counts, rate limit hits |
| `WARNING` | Recoverable anomalies, swallowed exceptions, degraded operation |
| `ERROR` | Unexpected exceptions on operational paths -- use `logger.exception(...)` |
| `CRITICAL` | Reserved for unusable state |

## Output

Log to **stderr** when stdout carries the program's own output -- the common case: CLIs, filters, pipeline stages (also Python's `logging.StreamHandler` default). Use **stdout** only for pure log-shipping services that emit nothing else. One record per line either way. Container runtimes (Docker, k8s) capture both streams, so the choice isolates logs from program output, not runtime visibility -- no file sinks or log rotation in-app.

Support two formats via config:

- **`plain`** -- Readable for local dev
- **`json`** -- Stable single-line object per record for log aggregators (Loki, Datadog, ELK, CloudWatch)

Timestamps in UTC in both formats.

