Files
Aniworld/docs/InstructionsLogging.md

4.6 KiB
Raw Blame History

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

  • Dont 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

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.

importent

note which file is done.