Errors & Logging Highlights Summary
Every highlight bullet from the 10 pages in this section, gathered on one page and grouped by the page it came from.
- Exceptions are a control-flow channel: they unwind the call stack until something catches them
- Logging is an observability channel: it's a side effect that never changes what code runs next
- The exception hierarchy exists so callers can catch a family of failures without knowing every subtype
- raise ... from and exception groups both preserve causality - one cause per exception, or many at once
- Catch Exception, not BaseException, in application code
- Catch the narrowest exception type that fits your error
- Order except handlers from most specific to most general
- Use else clause to avoid catching success-only code exceptions
- finally block guarantees resource cleanup on any exit path
- Bare raise re-propagates active exception with stack intact
- Wrap third-party exceptions with raise NewError from exc at module boundaries
- Use bare raise inside except to re-raise preserving original traceback context
- Apply from None at API edges to hide implementation details from callers
- Domain exceptions should carry business identifiers for handlers and logs
- Log exception info then re-raise with bare raise to keep traces clean
- Never lose root cause—always use from exc when wrapping exceptions
- One package-specific base exception beats dozens of unrelated subclasses
- Subclass Exception not BaseException for application errors
- Keep hierarchy shallow with base plus 2-3 levels maximum
- Wrap internal exceptions at boundaries to prevent API leaks
- Attach attributes on init before calling super().init
- Avoid broad except Exception handlers that hide KeyboardInterrupt
- Python 3.11+ raises ExceptionGroup when multiple errors occur together in structured concurrency
- except* matches sub-exceptions inside a group without flattening unrelated failures
- plain except catches the ExceptionGroup itself, not members inside it - use except* instead
- Unhandled sub-exceptions propagate in a new group after except* removes matched types
- Flatten nested groups at service boundaries to avoid complexity in application handlers
- Log all exception members by iterating eg.exceptions and calling log.exception on each
- Retry transient failures with exponential backoff and jitter, not validation errors
- Only retry idempotent operations to avoid duplicate charges or side effects on retry
- Use exponential backoff capped with jitter to prevent thundering herd during recovery
- Classify exceptions: 429/503 retries yes, 400/422 validation errors never retry
- Use tenacity decorators for stop_after_attempt and wait_exponential retry patterns
- Always raise with context when max retries exhausted; never swallow exceptions
- Configure logging at application entry point, never in library modules
- Use log.exception inside except blocks to include tracebacks automatically
- Lazy format with log.info(msg, arg) instead of f-strings to avoid eager evaluation
- Logger names mirror package structure via dotted names and form inheritance hierarchies
- Set logger.propagate False or attach handlers only to root to prevent duplicate logs
- basicConfig is a no-op if root already configured, use dictConfig for multiple handlers
- Emit JSON one-object-per-line for centralized log aggregation, search, and alerts
- Attach request-scoped data via contextvars without threading parameters through code
- Bind correlation IDs from X-Request-ID headers for distributed trace correlation
- Use structlog for production apps; it merges context, event dict, and processors
- Redact PII, limit cardinality, prefix custom LogRecord keys to avoid parsing errors
- Contextvars propagate context in asyncio; copy them on thread pool submit for workers
- Use warnings.warn() to signal deprecations without raising exceptions
- Set stacklevel=2 in warn() so deprecation points to caller, not wrapper
- DeprecationWarning hides by default in REPL; test with pytest.warns()
- Escalate warnings to CI errors with -W error to catch deprecations early
- Include version removal target in deprecation messages so users can plan
- Reserve warnings for deprecations only; using them for control flow masks issues
- Never use bare except: pass; catch specific types and chain with raise from
- Configure logging once at application entry, only use getLogger in libraries
- Use lazy % formatting in log calls, never f-strings to avoid string overhead
- Emit JSON or key=value logs with bound correlation IDs for request tracing
- Call log.exception inside except blocks once per incident, never duplicate
- Retry only transient idempotent failures with exponential backoff and jitter
Reviewed by Chris St. John·Last updated Jul 31, 2026