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:
# 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:
# 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
| 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:
#!/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
| 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
-
launchagent-log-rotation skill - RotatingFileHandler for daemons
-
platformdirs reference - Cross-platform directories
-
loguru patterns - Advanced loguru configuration
-
migration guide - From print() to structured logging
Anti-Patterns to Avoid
-
Unbounded logs - Always configure rotation
-
print() for logging - Use structured logger
-
Bare except - Catch specific exceptions, log them
-
Silent failures - Log errors before suppressing
-
Hardcoded paths - Use platformdirs for cross-platform