4.9 KiB
Logging Instructions
This document describes how to write and refactor logging across the AniWorld codebase to make logs human-readable, debug-friendly, and noise-free.
✅ Goal: Logs should help a developer understand what happened, why it happened, and what to inspect next — without overwhelming them with duplicates or irrelevant details.
1. Principles for Great Logs
1.1 Use the Right Log Level
DEBUG: Detailed internal state useful when debugging a specific issue (e.g., decision points, returned values, request/response payloads). Not for normal operation.INFO: High-level events that represent what the system is doing (e.g., "Import started", "New series added", "Config reloaded"). Use sparingly.WARNING: Something unexpected happened, but the system can continue (e.g., missing optional file, fallback behavior).ERROR: An operation failed and needs attention (e.g., exception caught, failed database write).CRITICAL: The system is in an unusable state (e.g., config corruption, failed startup).
1.2 Keep Logs Human-Readable
- Write messages in a clear, descriptive sentence-style format.
- Avoid cryptic codes or single-word log messages.
- Prefer
logger.debug("... %s", value)-style formatting over f-strings to avoid unnecessary work when the log level is disabled.
1.3 Avoid Log Spam
- Don’t log inside hot loops unless you explicitly aggregate and log a summary (e.g., "Processed 124 files, 3 failures").
- Avoid repeated/logging the same event at the same level (e.g., do not log "Retrying" 10 times at INFO; log once at INFO and then use DEBUG for each retry).
- Use rate limiting or debounce patterns for logs that can fire rapidly (e.g., external service health checks).
- Prefer a single higher-level log with context rather than many low-level logs that clutter output.
1.4 Log Objects Usefully
- When logging objects, log the minimal useful representation (e.g., ID, name, status) rather than the full object or its memory address.
- If an object has a
.dict(),.to_dict(), or.as_dict()helper (common in Pydantic models), log that rather than relying onrepr(). - Add a
__repr__or__str__implementation to domain models that returns a helpful, concise string with key identifiers. - Use structured logging (e.g.,
logger.info("Series added", extra={"series_id": series.id, "title": series.title})) where supported. - For exceptions, prefer
logger.exception("Failed to ...")to capture stack traces.
2. Refactoring Existing Logs
When improving or refactoring existing log statements, aim to make them:
- Actionable: A developer reading the log should know what happened and what to check next.
- Non-redundant: Remove duplicates and ensure only one log records the same high-level event at a given level.
- Context-rich: Include identifiers (e.g.,
series_id,file_path,user_id) and key state that explains why a decision was made. - Level-appropriate: Downgrade noisy INFO logs to DEBUG, and elevate critical failures to ERROR/CRITICAL.
2.1 Refactor Checklist
- Locate noisy logs: Search for repeated messages (e.g., "Start", "Done") and determine whether they should be DEBUG or removed.
- Replace ad-hoc prints: Remove
print()statements orprint(obj)and replace withlogger.*calls. - Use structured context: If a function logs multiple related messages, include the same context in each (e.g.,
extra={"series_id": series.id}) or use a context manager that attaches it. - Validate object output: Ensure any logged object produces a useful representation (add methods or translate to dict). If not, log the key fields explicitly.
- Batch repetitive events: If a loop logs per item, consider collecting stats and logging a summary at the end.
3. Adding New Logs
When adding logs to new code paths:
- Log important state transitions (e.g., "Queue started", "Download completed", "Config reloaded").
- For error paths, include what failed and why (e.g., "Could not load config from X: {exc}").
- Prefer logging at the boundaries of operations, not deep inside utility functions unless it aids debugging.
- Write logs in full sentences, with a clear subject, verb, and object.
4. Example Patterns
logger.info("Import completed", extra={"series_id": series.id, "count": len(imported)})
logger.debug(
"Fetched feed items",
extra={"feed_url": feed.url, "item_count": len(items)},
)
try:
result = download_episode(episode)
except Exception:
logger.exception("Failed to download episode %s", episode.id)
💡 When in doubt, favor fewer, richer logs over many noisy logs.
5. Logging Audit Task List
For a guided checklist of files and logging improvements, see docs/tasks.md. This is where we track which files have been reviewed and which logging items still need attention.
✅ After applying the guidelines above, update
docs/tasks.mdto indicate which tasks are complete.