Repository navigation
Description
Documentation
Hey.
The documentation of LogRecord describes the exc_info attribute as:
exc_info (tuple[type[BaseException], BaseException, types.TracebackType] | None) – An exception tuple with the current exception information, as returned by sys.exc_info(), or None if no exception information is available.
However, records created through the normal logger methods can contain values that don’t match this type:
import logging
class H(logging.Handler):
def emit(self, record):
print(repr(record.exc_info))
logger = logging.getLogger()
logger.addHandler(H())
logger.propagate = False
logger.error("b", exc_info=True) # outside of an except-block
logger.error("c", exc_info=ValueError()) # exception that was never raisedwhich gives as of CPython 3.14.10:
(None, None, None)
(<class 'ValueError'>, ValueError(), None)
A very recent commit (938144e) already fixed the case of a false truth value, which as per “If exc_info does not evaluate as false, …” is IMO allowed.
Now:
- Outside an
except-block it would be(None, None, None). - If an exception instance is passed, the tuple is built from its traceback, which is None for exceptions that were never raised, while the documented type requires types.TracebackType.
I think typeshed already documents it properly:
https://github.com/python/typeshed/blob/80f7beabfbb71a634cc3dbca8e418a5da790142c/stdlib/logging/__init__.pyi#L63
So far I'd say this is a documentation issue.
Now I can create valid LogRecord-objects that don’t go through _log() and its normalisation:
>>> import logging
>>> l = logging.getLogger()
>>> r = logging.makeLogRecord({"exc_info": True})
>>> r.exc_info
True
>>> r = l.makeRecord("z", 40, "f", 1, "m", None, exc_info=False)
>>> r.exc_info
False
>>>
For makeLogRecord and makeRecord… it’s IMO not really clear what values these allow (de jure) for exc_info.
If they’d follow the spirit of “If exc_info does not evaluate as false, …” then the the above type description would be even more wrong, as then exc_info could be any object.
This would then IMO also make the types on typeshed wrong, cause while logging.Formatter.format() itself would call formatException() only if exc_info has a true truth value:
cpython/Lib/logging/__init__.py
Lines 716 to 720 in ecefd40
| if record.exc_info: | |
| # Cache the traceback text to avoid converting it multiple times | |
| # (it's constant anyway) | |
| if not record.exc_text: | |
| record.exc_text = self.formatException(record.exc_info) |
that may be still any object, there's nothing for the false case and both format() and formatException() are public, so any subclass may call formatException() without the if record.exc_info:.
Last but not least:
formatException() is more or less documented to an even more restrictive type:
Formats the specified exception information (a standard exception tuple as returned by sys.exc_info()) as a string.
So not sure,... at least some documentation seems to be not exact... but maybe there also needs to be code changes if the normalisation of exc_info should happen not only via LogRecord objects created through _log()... and if not, typeshed might need some updates to account for values that may be given.
Cheers,
Chris
Metadata
Metadata
Assignees
Labels
Projects
- StatusShow more project fieldsNo status
- StatusShow more project fieldsTodo
Activity
calestyo commentedon Nov 13, 2026
Perhaps to clarify on the potential effect on typeshed, which currently has:
But when created via
makeLogRecordormakeRecord, no normalisation seems to happen andexc_infomight beobject.