An error log without a traceback is the hardest kind of bug report to act on.
Inside an except block, log.exception("message") logs at ERROR and appends the current exception's traceback. It's equivalent to log.error("message", exc_info=True).
Use exc_info=True with other levels when the failure isn't really an error, for example a retried call: log.warning("retrying", exc_info=True). You can also pass an exception instance: exc_info=err.
Guidelines:
- Call
log.exceptiononly inside anexceptblock; elsewhere there is no current exception and you getNoneType: Noneas the "traceback". - Log or re-raise, not both at every layer, or a single failure appears five times. Log where you handle the error (for example the top-level request handler).
- Include identifiers in the message:
log.exception("charge failed for order %s", order_id).
stack_info=True adds the current call stack (not an exception) to a record, useful for "who called this deprecated function?". stacklevel=2 attributes the record's funcName and lineno to the caller, which is what you want inside logging helper functions.
Exception chains (raise ... from err) are rendered in full, the same as an uncaught exception.
Example
import logging, sys
logging.basicConfig(stream=sys.stdout, format="%(levelname)s %(message)s")
log = logging.getLogger("billing")
def charge(order_id, amount):
try:
return 100 / amount
except ZeroDivisionError:
log.exception("charge failed for order %s", order_id)
return None
charge("A-17", 0)
# ERROR charge failed for order A-17
# Traceback (most recent call last):
# File "...", line 7, in charge
# return 100 / amount
# ZeroDivisionError: division by zeroRemember
- log.exception() = log.error() plus the current traceback
- exc_info=True attaches a traceback at any level
- Log an error once, where it is handled
Watch out for
- Logging and re-raising at every layer, so one failure produces many identical tracebacks.