Skip to main content

Lib/logging/__init__.py

cpython 3.14 @ ab2d84fe1023/Lib/logging/__init__.py

logging is a pure-Python module implementing a hierarchical logger tree, a pluggable handler/formatter pipeline, and a module-level API that mirrors Python's built-in print-style convenience. It is one of the most widely used stdlib modules in production systems.

The core data model has four objects. A LogRecord holds all the facts about one log event (message, level, filename, line number, thread id, time). A Logger decides whether to create a LogRecord and passes it to its handlers. A Handler delivers the record to a destination (file, stream, socket). A Formatter turns a LogRecord into a string.

Loggers form a tree rooted at the root logger. Each logger's name is a dotted string; a.b.c is a child of a.b. When Logger.callHandlers walks the tree, it moves from the child toward the root, calling each ancestor's handlers as long as logger.propagate is true.

Map

LinesSymbolRolegopy
1-100Module imports, level constants, addLevelName, getLevelNameDefines DEBUG, INFO, WARNING, ERROR, CRITICAL and the two-way name/number mapping.(stdlib pending)
101-300LogRecord, makeLogRecordCaptures call site via sys._getframe, formats exc_info and stack_info, stores all fields used by Formatter.(stdlib pending)
301-500PercentStyle, StrFormatStyle, StringTemplateStyle, FormatterThree string-formatting backends selected by %/{/$ style; Formatter.format calls formatTime, formatException, formatStack.(stdlib pending)
501-700Filter, FiltererFilter accepts records whose name starts with name; Filterer.callFilters short-circuits on first rejection.(stdlib pending)
701-1100Handler, StreamHandler, FileHandlerBase handler with thread locking; emit is abstract; StreamHandler.emit writes to self.stream; FileHandler opens/closes the file.(stdlib pending)
1101-1600Logger, debug/info/warning/error/critical/exception/log, _log, callHandlersLogger methods create a LogRecord via makeRecord then call handle; callHandlers walks the ancestor chain.(stdlib pending)
1601-1800Manager, PlaceHolder, Logger.manager, getLoggerThe Manager maintains the logger tree in a flat dict; PlaceHolder marks intermediate nodes that have no Logger object yet.(stdlib pending)
1801-2000RootLogger, root logger instance, module-level debug/info/...Module-level convenience functions call through to the root logger; initialized lazily so import cost is low.(stdlib pending)
2001-2300basicConfig, captureWarnings, shutdownbasicConfig installs a StreamHandler or FileHandler on the root logger; shutdown flushes and closes all registered handlers at interpreter exit.(stdlib pending)

Reading

Logger.callHandlers propagation (lines 1101 to 1600)

cpython 3.14 @ ab2d84fe1023/Lib/logging/__init__.py#L1101-1600

def callHandlers(self, record):
c = self
found = 0
while c:
for hdlr in c.handlers:
found = found + 1
if record.levelno >= hdlr.level:
hdlr.handle(record)
if not c.propagate:
c = None # break out
else:
c = c.parent
if (found == 0):
if lastResort:
if record.levelno >= lastResort.level:
lastResort.handle(record)
elif raiseExceptions and not self.manager.emittedNoHandlerWarning:
sys.stderr.write("No handlers could be found for logger"
" \"%s\"\n" % self.name)
self.manager.emittedNoHandlerWarning = True

callHandlers walks the logger parent chain. Each logger in the chain may have zero or more handlers attached. The level check is done per handler (record.levelno >= hdlr.level), not per logger, so a parent logger can have handlers at finer granularity than the child that emitted the record.

Propagation stops when c.propagate is false. Setting logger.propagate = False is the standard way to prevent a library logger from forwarding records to the root logger.

lastResort is a StreamHandler(sys.stderr) at WARNING level, introduced in Python 3.2 to silently handle the common case where no handlers are configured rather than printing a confusing "No handlers" warning.

Formatter.format with exception and stack info (lines 301 to 500)

cpython 3.14 @ ab2d84fe1023/Lib/logging/__init__.py#L301-500

def format(self, record):
record.message = record.getMessage()
if self.usesTime():
record.asctime = self.formatTime(record, self.datefmt)
s = self.formatMessage(record)
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)
if record.exc_text:
if s[-1:] != "\n":
s = s + "\n"
s = s + record.exc_text
if record.stack_info:
if s[-1:] != "\n":
s = s + "\n"
s = s + self.formatStack(record.stack_info)
return s

def formatTime(self, record, datefmt=None):
ct = self.converter(record.created)
if datefmt:
s = time.strftime(datefmt, ct)
else:
t = time.strftime(self.default_time_format, ct)
s = self.default_msec_format % (t, record.msecs)
return s

Formatter.format assembles the final string in three steps. First, getMessage() interpolates record.args into record.msg using % formatting (or returns msg unchanged if args is empty). Second, formatMessage applies the style template (%, {, or $). Third, if the record carries exception info or stack info, those are appended as separate newline-separated sections.

record.exc_text caches the formatted traceback. The first Handler to call format on a record populates this cache; subsequent handlers reuse it. This matters for MemoryHandler and QueueHandler, which may re-deliver the same LogRecord object to multiple downstream handlers.

logging.basicConfig (lines 2001 to 2300)

cpython 3.14 @ ab2d84fe1023/Lib/logging/__init__.py#L2001-2300

def basicConfig(**kwargs):
_acquireLock()
try:
force = kwargs.pop('force', False)
encoding = kwargs.pop('encoding', None)
errors = kwargs.pop('errors', 'backslashreplace')
if force:
for h in root.handlers[:]:
root.removeHandler(h)
h.close()
if len(root.handlers) == 0:
handlers = kwargs.pop("handlers", None)
if handlers is None:
if "stream" in kwargs and "filename" in kwargs:
raise ValueError("'stream' and 'filename' should not be "
"specified together")
else:
if "stream" in kwargs or "filename" in kwargs:
raise ValueError("'stream' or 'filename' should not be "
"specified together with 'handlers'")
if handlers is None:
filename = kwargs.pop("filename", None)
if filename:
mode = kwargs.pop("filemode", 'a')
h = FileHandler(filename, mode, encoding=encoding,
errors=errors)
else:
stream = kwargs.pop("stream", None)
h = StreamHandler(stream)
handlers = [h]
dfs = kwargs.pop("datefmt", None)
style = kwargs.pop("style", '%')
...
fs = kwargs.pop("format", BASIC_FORMAT)
fmt = Formatter(fs, dfs, style=style)
for handler in handlers:
if handler.formatter is None:
handler.setFormatter(fmt)
root.addHandler(handler)
level = kwargs.pop("level", None)
if level is not None:
root.setLevel(level)
finally:
_releaseLock()

basicConfig is idempotent by default: if the root logger already has handlers, it returns immediately. The force=True keyword overrides that by removing and closing all existing handlers first. This is useful in test suites that need to reconfigure logging mid-run.

The handlers keyword lets callers pass a pre-built list and skip the stream/filename logic entirely. Only one of handlers, stream, or filename may be specified; any combination raises ValueError.

gopy mirror

logging is listed in stdlib/MANIFEST.txt as pending. The module has no C extension dependency; it only uses os, sys, time, traceback, io, string.Template, weakref, and atexit. A gopy port needs Formatter, Handler, Logger, Manager, and basicConfig as the core set, with handlers.py following separately.