95 lines
4.9 KiB
Markdown
95 lines
4.9 KiB
Markdown
# 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 on `repr()`.
|
||
- 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
|
||
|
||
1. **Locate noisy logs**: Search for repeated messages (e.g., "Start", "Done") and determine whether they should be DEBUG or removed.
|
||
2. **Replace ad-hoc prints**: Remove `print()` statements or `print(obj)` and replace with `logger.*` calls.
|
||
3. **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.
|
||
4. **Validate object output**: Ensure any logged object produces a useful representation (add methods or translate to dict). If not, log the key fields explicitly.
|
||
5. **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
|
||
|
||
```python
|
||
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.md` to indicate which tasks are complete.
|