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
| Lines | Symbol | Role | gopy |
|---|---|---|---|
| 1-100 | Module imports, level constants, addLevelName, getLevelName | Defines DEBUG, INFO, WARNING, ERROR, CRITICAL and the two-way name/number mapping. | (stdlib pending) |
| 101-300 | LogRecord, makeLogRecord | Captures call site via sys._getframe, formats exc_info and stack_info, stores all fields used by Formatter. | (stdlib pending) |
| 301-500 | PercentStyle, StrFormatStyle, StringTemplateStyle, Formatter | Three string-formatting backends selected by %/{/$ style; Formatter.format calls formatTime, formatException, formatStack. | (stdlib pending) |
| 501-700 | Filter, Filterer | Filter accepts records whose name starts with name; Filterer.callFilters short-circuits on first rejection. | (stdlib pending) |
| 701-1100 | Handler, StreamHandler, FileHandler | Base handler with thread locking; emit is abstract; StreamHandler.emit writes to self.stream; FileHandler opens/closes the file. | (stdlib pending) |
| 1101-1600 | Logger, debug/info/warning/error/critical/exception/log, _log, callHandlers | Logger methods create a LogRecord via makeRecord then call handle; callHandlers walks the ancestor chain. | (stdlib pending) |
| 1601-1800 | Manager, PlaceHolder, Logger.manager, getLogger | The Manager maintains the logger tree in a flat dict; PlaceHolder marks intermediate nodes that have no Logger object yet. | (stdlib pending) |
| 1801-2000 | RootLogger, 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-2300 | basicConfig, captureWarnings, shutdown | basicConfig 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.