Skip to main content

Lib/logging/config.py

Source:

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

Map

LinesSymbolPurpose
1–50module header, importsconfigparser, socket, struct, threading, json
51–200fileConfigINI-file configuration reader
201–260valid_ident, _resolveName resolution helpers used by both config paths
261–400DictConfigurator.__init__, configuredictConfig entry point and top-level orchestration
401–530DictConfigurator.configure_formatterFormatter factory: class dispatch and () callable support
531–640DictConfigurator.configure_filterFilter factory
641–800DictConfigurator.configure_handlerHandler factory: class resolution, args, formatter, filters
801–890DictConfigurator.configure_logger, configure_rootLogger/root wiring: level, handlers, propagate
891–940dictConfigPublic thin wrapper around DictConfigurator
941–1000listen, stopListening, ConfigSocketReceiverRuntime reconfiguration over a TCP socket

Reading

fileConfig: ConfigParser-based configuration

fileConfig reads an INI file with [loggers], [handlers], and [formatters] sections. Each named object has its own section ([logger_root], [handler_hand01], etc.) whose keys map directly to constructor arguments. The function first disables all existing loggers not mentioned in the file (unless disable_existing=False is passed) to prevent stale loggers from accumulating across repeated calls.

# CPython: Lib/logging/config.py:80 fileConfig
def fileConfig(fname, defaults=None, disable_existing_loggers=True,
encoding=None):
import configparser
if isinstance(fname, str):
cp = configparser.ConfigParser(defaults)
if hasattr(fname, 'readline'):
cp.read_file(fname)
else:
encoding = io.text_encoding(encoding)
cp.read(fname, encoding=encoding)
else:
cp = fname
formatters = _create_formatters(cp)
logging._acquireLock()
try:
_clearExistingHandlers()
handlers = _install_handlers(cp, formatters)
_install_loggers(cp, handlers, disable_existing_loggers)
finally:
logging._releaseLock()

_install_loggers iterates loggers keys, looks up each [logger_xxx] section, resolves the handlers comma list, and attaches them. The root logger is handled separately via [logger_root].

dictConfig entry point and DictConfigurator

dictConfig passes the configuration dict to DictConfigurator, which validates the schema version (version must be 1), then processes formatters, filters, handlers, and loggers in that dependency order. Formatters and filters carry no forward references, so they are instantiated first. Handlers reference formatters and filters by name, so they come next. Loggers reference handlers by name, so they are wired last.

# CPython: Lib/logging/config.py:813 DictConfigurator.configure
def configure(self):
config = self.config
if 'version' not in config:
raise ValueError("dictionary doesn't specify a version")
if config['version'] != 1:
raise ValueError("Unsupported version: %s" % config['version'])
incremental = config.pop('incremental', False)
INCREMENTAL = 'incremental'
if incremental:
...
return
...
formatters = config.get('formatters', EMPTY_DICT)
for name in formatters:
...
self.config['formatters'][name] = self.configure_formatter(
formatters[name])
...

The () key convention lets any factory accept a fully qualified callable path instead of a class name, enabling custom formatter or handler subclasses without subclassing DictConfigurator.

configure_handler factory dispatch

Handler configuration supports two forms. The plain form uses class plus explicit keyword args (level, formatter, filters, and handler-specific keys). The () form passes all remaining keys as keyword arguments to the named callable, enabling handler factories that do not follow the standard __init__ signature.

# CPython: Lib/logging/config.py:686 DictConfigurator.configure_handler
def configure_handler(self, config):
factory = config.pop('class', None)
kwargs = {}
if factory is None:
factory = config.pop('()', None)
if factory is None:
raise StandardError("Unable to determine factory for handler")
...
else:
factory = self.resolve(factory)
props = config.copy()
formatter = props.pop('formatter', None)
if formatter:
...
props['formatter'] = self.config['formatters'][formatter]
filters = props.pop('filters', None)
kwargs = props
handler = factory(**kwargs)
...
return handler

_resolve splits dotted names and walks importlib.import_module plus getattr chains to locate the target class, so any installed Python class can be named in the config dict.

Incremental config merging and listen() socket server

When incremental: true is present in the dict, DictConfigurator.configure only updates log levels and handler levels on existing objects, skipping all instantiation. This lets a running process adjust verbosity without tearing down and rebuilding the handler graph.

listen() starts a ConfigSocketReceiver thread that binds a TCP socket (default port logging.DEFAULT_TCP_LOGGING_PORT, which is 9030). Each connection sends a 4-byte big-endian length followed by a JSON or INI payload. The receiver calls fileConfig (for INI) or dictConfig (for JSON dicts) on the received bytes, applying the new config live.

# CPython: Lib/logging/config.py:967 listen
def listen(port=DEFAULT_TCP_LOGGING_PORT, verify=None):
if not thread:
raise NotImplementedError("listen() needs threading to work")
class ConfigStreamHandler(StreamRequestHandler):
def handle(self):
rv = True
while rv:
try:
chunk = self.connection.recv(4)
if len(chunk) == 4:
slen = struct.unpack('>L', chunk)[0]
chunk = b''
while len(chunk) < slen:
chunk += self.connection.recv(slen - len(chunk))
if verify is not None:
chunk = verify(chunk)
if chunk is not None:
try:
jsonConfig = json.loads(chunk.decode('utf-8'))
dictConfig(jsonConfig)
except Exception:
fileConfig(io.StringIO(chunk.decode('utf-8')))
...

stopListening() sets a flag that causes the server thread to exit after the current request cycle. The returned thread object from listen() must be started by the caller.

gopy notes

Status: not yet ported.

Planned package path: module/logging/ (config.go within the same package, mirroring CPython's logging/config.py sub-module).

fileConfig depends on the configparser module port (not yet started). dictConfig is self-contained and can be ported earlier using a Go map[string]any as the configuration dict. The listen() socket server maps to a net.Listen("tcp", ...) goroutine with encoding/json for payload parsing. Incremental merging only touches level fields and can be implemented as a simple pass that skips the factory dispatch paths.