Python logging with loguru and platformdirs. TRIGGERS - loguru, structured logging, JSONL logs, log rotation, XDG directories.
Scanned 2/12/2026
Install via CLI
openskills install terrylica/cc-skills---
name: python-logging-best-practices
description: Python logging with loguru and platformdirs. TRIGGERS - loguru, structured logging, JSONL logs, log rotation, XDG directories.
allowed-tools: Read, Bash, Grep, Edit, Write
---
# Python Logging Best Practices
## When to Use This Skill
Use this skill when:
- Setting up Python logging with loguru
- Configuring structured JSONL logging for analysis
- Implementing log rotation
- Using platformdirs for cross-platform log directories
## Overview
Unified reference for Python logging patterns optimized for machine readability (Claude Code analysis) and operational reliability.
## MANDATORY Best Practices
### 1. Log Rotation (ALWAYS CONFIGURE)
Prevent unbounded log growth - configure rotation for ALL log files:
```python
# Loguru pattern (recommended for modern scripts)
from loguru import logger
logger.add(
log_path,
rotation="10 MB", # Rotate at 10MB
retention="7 days", # Keep 7 days
compression="gz" # Compress old logs
)
# RotatingFileHandler pattern (stdlib-only)
from logging.handlers import RotatingFileHandler
handler = RotatingFileHandler(
log_path,
maxBytes=100 * 1024 * 1024, # 100MB
backupCount=5 # Keep 5 backups (~500MB max)
)
```
### 2. JSONL Format (Machine-Readable)
Use JSONL (`.jsonl`) for logs that Claude Code or other tools will analyze:
```python
# One JSON object per line - jq-parseable
{"timestamp": "2026-01-14T12:45:23.456Z", "level": "info", "message": "..."}
{"timestamp": "2026-01-14T12:45:24.789Z", "level": "error", "message": "..."}
```
**File extension**: Always use `.jsonl` (not `.json` or `.log`)
**Validation**: `cat file.jsonl | jq -c .`
**Terminology**: JSONL is canonical. Equivalent terms: NDJSON, JSON Lines.
## When to Use Which Approach
| Approach | Use Case | Pros | Cons |
| --------------------- | -------------------------------- | ------------------------------------------ | ------------------- |
| `loguru` | Modern scripts, CLI tools | Zero-config, async-safe, built-in rotation | External dependency |
| `RotatingFileHandler` | LaunchAgent daemons, stdlib-only | No dependencies | More setup |
| `logger_setup.py` | Rich terminal apps | Beautiful output | Complex setup |
## Complete Loguru + platformdirs Pattern
Cross-platform log directory handling with structured JSONL output:
```python
#!/usr/bin/env python3
# /// script
# requires-python = ">=3.11"
# dependencies = ["loguru", "platformdirs"]
# ///
import json
import sys
from pathlib import Path
from uuid import uuid4
import platformdirs
from loguru import logger
def json_formatter(record) -> str:
"""JSONL formatter for Claude Code analysis."""
log_entry = {
"timestamp": record["time"].strftime("%Y-%m-%dT%H:%M:%S.%f")[:-3] + "Z",
"level": record["level"].name.lower(),
"component": record["function"],
"operation": record["extra"].get("operation", "unknown"),
"operation_status": record["extra"].get("status", None),
"trace_id": record["extra"].get("trace_id"),
"message": record["message"],
"context": {k: v for k, v in record["extra"].items()
if k not in ("operation", "status", "trace_id", "metrics")},
"metrics": record["extra"].get("metrics", {}),
"error": None
}
if record["exception"]:
exc_type, exc_value, _ = record["exception"]
log_entry["error"] = {
"type": exc_type.__name__ if exc_type else "Unknown",
"message": str(exc_value) if exc_value else "Unknown error",
}
return json.dumps(log_entry)
def setup_logger(app_name: str = "my-app"):
"""Configure Loguru for machine-readable JSONL output."""
logger.remove()
# Console output (JSONL to stderr)
logger.add(sys.stderr, format=json_formatter, level="INFO")
# Cross-platform log directory
# macOS: ~/Library/Logs/{app_name}/
# Linux: ~/.local/state/{app_name}/log/
log_dir = Path(platformdirs.user_log_dir(
appname=app_name,
ensure_exists=True
))
# File output with rotation
logger.add(
str(log_dir / f"{app_name}.jsonl"),
format=json_formatter,
rotation="10 MB",
retention="7 days",
compression="gz",
level="DEBUG"
)
return logger
# Usage
setup_logger("my-app")
trace_id = str(uuid4())
logger.info(
"Operation started",
operation="my_operation",
status="started",
trace_id=trace_id
)
logger.info(
"Operation complete",
operation="my_operation",
status="success",
trace_id=trace_id,
metrics={"duration_ms": 150, "items_processed": 42}
)
```
## Semantic Fields Reference
| Field | Type | Purpose |
| ------------------ | --------------- | ------------------------------------- |
| `timestamp` | ISO 8601 with Z | Event ordering |
| `level` | string | debug/info/warning/error/critical |
| `component` | string | Module/function name |
| `operation` | string | What action is being performed |
| `operation_status` | string | started/success/failed/skipped |
| `trace_id` | UUID4 | Correlation for async operations |
| `message` | string | Human-readable description |
| `context` | object | Operation-specific metadata |
| `metrics` | object | Quantitative data (counts, durations) |
| `error` | object/null | Exception details if failed |
## Related Resources
- [Python logging.handlers](https://docs.python.org/3/library/logging.handlers.html#rotatingfilehandler) - RotatingFileHandler for log rotation
- [platformdirs reference](./references/platformdirs-xdg.md) - Cross-platform directories
- [loguru patterns](./references/loguru-patterns.md) - Advanced loguru configuration
- [migration guide](./references/migration-guide.md) - From print() to structured logging
## Anti-Patterns to Avoid
1. **Unbounded logs** - Always configure rotation
2. **print() for logging** - Use structured logger
3. **Bare except** - Catch specific exceptions, log them
4. **Silent failures** - Log errors before suppressing
5. **Hardcoded paths** - Use platformdirs for cross-platform
---
## Troubleshooting
| Issue | Cause | Solution |
| ------------------------- | ----------------------- | -------------------------------------- |
| loguru not found | Not installed | Run `uv add loguru` |
| Logs not appearing | Wrong log level | Set level to DEBUG for troubleshooting |
| Log rotation not working | Missing rotation config | Add rotation param to logger.add() |
| platformdirs import error | Not installed | Run `uv add platformdirs` |
| JSONL parse errors | Malformed log line | Check for unescaped special characters |
| Logs in wrong directory | Using hardcoded path | Use platformdirs.user_log_dir() |
No comments yet. Be the first to comment!