LY001: Exception exc_info
Enforce exc_info=True in exception handlers
Aka: "The rule that will save your career at 3 AM"
Problem
Log exceptions without exc_info=True and watch your future self suffer. You've just thrown away your debugging lifeline.
try:
result = api.fetch_user(user_id)
except Exception:
logger.error("Failed to fetch user") # ❌ Congratulations, you broke debugging
When this fails in production at 2:47 AM, your log will scream:
And you'll have absolutely no idea:
- What exception type actually blew up (ConnectionError? TypeError? A ghost in the machine?)
- Where exactly in api.fetch_user() things went sideways
- What the call stack looked like (your fortune cookie of debugging wisdom)
- How deep this rabbit hole goes
Solution
Add exc_info=True and let the healing begin:
try:
result = api.fetch_user(user_id)
except Exception:
logger.error("Failed to fetch user", exc_info=True) # ✅ Now you're being smart
Now your logs actually tell a story:
ERROR - Failed to fetch user
Traceback (most recent call last):
File "app.py", line 42, in fetch_user
response = requests.get(url)
...
ConnectionError: Connection refused
BOOM. You just went from "I have no idea what happened" to "I see exactly what went wrong."
Impact
Without stack traces (aka "the nightmare path"): - Incident resolution time: 2-4 hours - 3 engineers huddled around a desk, sweating - Someone has to reproduce the issue. In prod. On a Friday at 6 PM. - Career advancement: delayed
With stack traces (aka "the hero path"): - Incident resolution time: 15-30 minutes - One engineer, one terminal, one coffee - Root cause visible in the first glance at logs - You leave the office on time. Your family remembers your name.
Configuration
exception_exc_info:
levels: [error] # Check error logs only
severity: fail # Block CI if violations found
Options
levels: Which log levels to enforce
[error]- Only error logs (recommended)[error, warning]- Error and warning logs[any]- All log levels
severity: Violation handling
fail- Exit with error (recommended for CI)warn- Print warning onlyinfo- Informational
Examples
✅ Pass (The Responsible Developer)
# With exc_info=True - the safe bet
try:
connect_db()
except Exception:
logger.error("DB connection failed", exc_info=True) # Gold standard
# With exception() - Python's built-in shortcut
try:
connect_db()
except Exception:
logger.exception("DB connection failed") # Equivalent to exc_info=True
# Non-exception context - you don't need exc_info here, dummy
if not user:
logger.error("User not found") # Not in an exception handler, so it's fine
❌ Fail (The Regret Path)
# Missing exc_info in exception handler - future you will hate present you
try:
process_payment()
except Exception:
logger.error("Payment failed") # 3 AM: "WHY NO STACK TRACE?!?!"
# Also fails with string formatting - you just moved the error into the message
try:
load_config()
except Exception as e:
logger.error(f"Config error: {e}") # You removed the stack trace! For what?!
When to Use
Always, without exception (pun intended), use in production code: - Exception handlers that log errors (duh) - Background job failures (yes, those too) - API request failures (especially those) - Anywhere an exception happens and you log it
Optional in development: - Exploratory logging (you're debugging locally anyway) - Known/expected exceptions (use your judgment) - Rate-limited error handlers (be reasonable)
Alternative: logger.exception()
Python's logger.exception() is the lazy developer's blessing—it automatically includes exc_info=True:
try:
risky_operation()
except Exception:
logger.exception("Operation failed") # She's included by default! No questions!
It's equivalent to:
Use whichever makes you happy. Both are correct. Pick one and be consistent.
Real-World Example
From our battle-test analysis of real-world codebases (aka "code that broke in production"):
Celery codebase - Missing exc_info in async backend:
# celery/backends/asynchronous.py:130
try:
self._shutdown.set()
except RuntimeError as e:
logging.error(f"Failed to set shutdown event: {e}") # ❌ Stack trace? Gone. With the wind.
When this breaks at 3:47 AM, you're left with just the exception message, no context. No way to understand where it came from. Just pure chaos.
← Back to Policies - Go read about LY002 while you're at it.