Skip to main content

Lib/logging/config.py

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

logging.config provides two configuration front-ends and one remote configuration server. fileConfig parses an INI file using configparser and is the older API, present since Python 2.3. dictConfig (introduced in Python 2.7 / 3.2) accepts a plain dict and is now the recommended approach. Both ultimately call the same logging API to create handlers, formatters, filters, and loggers.

DictConfigurator is the class that implements dictConfig. It resolves the configuration in a specific order: formatters, filters, handlers (with a dependency-resolution pass for handlers that reference other handlers), and finally loggers. The root logger is configured last. An incremental flag allows partial updates to an already-running configuration without tearing it down.

listen() starts a background thread that accepts connections on a localhost socket and applies new dictConfig payloads on the fly. This is the standard mechanism for reconfiguring logging in a running daemon without a restart.

Map

LinesSymbolRolegopy
1-80Module imports, IDENTIFIER regexImports configparser, io, logging, re, socket, struct, threading, traceback; defines the valid Python identifier pattern used to validate config keys.(stdlib pending)
81-200fileConfigReads an INI file; creates formatters from [formatter_*] sections, handlers from [handler_*] sections, loggers from [logger_*] sections; optionally disables existing loggers.(stdlib pending)
201-350DictConfigurator.__init__, configureValidates the schema version; calls configure_formatters, configure_filters, configure_handlers, configure_loggers in order; handles incremental and disable_existing_loggers.(stdlib pending)
351-500configure_formatter, configure_filterResolves the class key via logging._checkLevel / _resolve; instantiates the object; supports () key for factory callables.(stdlib pending)
501-700configure_handler, handler graph resolutionTopologically orders handlers so that a handler referencing another handler (e.g., MemoryHandler.target) is created after its dependency; sets formatter, filters, level on the result.(stdlib pending)
701-800configure_logger, configure_rootCreates or retrieves the Logger; sets level, propagate, attaches handlers; removes handlers not named in the config when disable_existing_loggers is true.(stdlib pending)
801-900listen, LoggingConfigHandler, stopListeningBackground thread accepting TCP connections; reads a 4-byte length-prefixed JSON blob; calls dictConfig under the logging lock; stopListening sends a sentinel to shut the thread down.(stdlib pending)

Reading

DictConfigurator.configure (lines 201 to 350)

cpython 3.14 @ ab2d84fe1023/Lib/logging/config.py#L201-350

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:
handlers = config.get('handlers', EMPTY_DICT)
for name in handlers:
try:
handler = logging._handlers[name]
handler_config = handlers[name]
level = handler_config.get('level', None)
if level:
handler.setLevel(logging._checkLevel(level))
except Exception as e:
raise ValueError('Unable to configure handler '
'%r' % name) from e
loggers = config.get('loggers', EMPTY_DICT)
for name in loggers:
try:
self.configure_logger(name, loggers[name], True)
except Exception as e:
raise ValueError('Unable to configure logger '
'%r' % name) from e
root = config.get('root', None)
if root:
try:
self.configure_root(root, True)
except Exception as e:
raise ValueError('Unable to configure root logger') from e
else:
# Do everything at once so that the configuration is atomic-ish.
formatters = config.get('formatters', EMPTY_DICT)
for name in formatters:
try:
self.configure_formatter(formatters[name])
except Exception as e:
raise ValueError('Unable to configure '
'formatter %r' % name) from e
...

In incremental mode, configure only adjusts levels on existing handlers and loggers. It does not create or destroy any objects, so the update is safe to apply to a running system with active handler references. The non-incremental path reconstructs everything. The logging._handlers and logging._handlerList module-level dicts are the shared state that both paths read and write, under the logging module's internal _acquireLock.

The version key is mandatory and must be 1; this is a forward compatibility hook. Future versions of logging.config can introduce a new schema without breaking existing code that passes version: 1 dicts.

Handler graph resolution (lines 501 to 700)

cpython 3.14 @ ab2d84fe1023/Lib/logging/config.py#L501-700

def configure_handler(self, config):
"""Configure a handler from a dictionary."""
config_copy = dict(config) # for restoring in case of error
formatter = config.pop('formatter', None)
if formatter:
try:
formatter = self.config['formatters'][formatter]
except Exception as e:
raise ValueError('Unable to set formatter '
'%r' % formatter) from e
try:
formatter = self.configure_formatter(formatter)
except Exception as e:
raise ValueError('Unable to configure '
'formatter %r' % formatter) from e
level = config.pop('level', None)
filters = config.pop('filters', None)
# factory is a callable which is invoked with the config dict
# as its argument. The key '()' in the config dict is used to
# denote the callable.
factory = config.pop('class')
...
kwargs = {k: config[k] for k in config if valid_ident(k)}
try:
result = factory(**kwargs)
except TypeError as te:
...
raise
...
return result

Handler creation uses a factory pattern. The class key is resolved via _resolve(factory), which does a dotted attribute lookup (e.g., logging.handlers.RotatingFileHandler). All remaining keys that match the Python identifier regex are passed as keyword arguments to the factory. Keys that start with () are reserved for callable-factory syntax and are not passed as kwargs.

The () syntax enables configuring third-party handlers that require a non-standard constructor signature. A config dict with "()": "mypackage.MyHandler" calls MyHandler directly with the remaining dict as its sole argument, bypassing the keyword-argument expansion.

Handlers that reference other handlers (e.g., MemoryHandler needs its target handler to exist first) are resolved by a two-pass approach: the first pass creates handlers in dict iteration order, and a deferred queue holds those whose dependencies are not yet available. The second pass retries the deferred handlers until either all succeed or a pass makes no progress (which indicates a cycle and raises ValueError).

fileConfig INI parsing (lines 81 to 200)

cpython 3.14 @ ab2d84fe1023/Lib/logging/config.py#L81-200

def fileConfig(fname, defaults=None, disable_existing_loggers=True,
encoding=None):
import configparser
if isinstance(fname, str):
if not os.path.exists(fname):
raise FileNotFoundError(f"{fname} doesn't exist")
elif not os.path.isfile(fname):
raise ValueError(f"{fname} is not a regular file")
cp = configparser.ConfigParser(defaults)
if hasattr(fname, 'readline'):
cp.read_file(fname)
else:
cp.read(fname, encoding=encoding)
formatters = _create_formatters(cp)
# critical section
logging._acquireLock()
try:
logging._handlers.clear()
logging._handlerList[:] = []
# Handlers add themselves to logging._handlers
handlers = _install_handlers(cp, formatters)
_install_loggers(cp, handlers, disable_existing_loggers)
finally:
logging._releaseLock()

fileConfig wraps the entire handler and logger installation in the logging module lock so that no log records can be emitted against a half-configured state. _create_formatters runs outside the lock because Formatter construction has no side effects on shared state.

The disable_existing_loggers flag (default True) disables every Logger that is not mentioned in the INI file. This is a deliberate design choice: an INI file should fully describe the desired logging state rather than composing with previously registered loggers. For library code it is usually better to use dictConfig with disable_existing_loggers: false.

listen() socket-based remote config (lines 801 to 900)

cpython 3.14 @ ab2d84fe1023/Lib/logging/config.py#L801-900

def listen(port=DEFAULT_LOGGING_CONFIG_PORT, verify=None):
if not thread:
raise NotImplementedError(
"listen() needs threading to work")

class ConfigSocketReceiver(ThreadingTCPServer):
allow_reuse_address = True

def __init__(self, host='localhost', port=DEFAULT_LOGGING_CONFIG_PORT,
handler=None):
ThreadingTCPServer.__init__(self, (host, port), handler)
self.abort = 0
self.timeout = 1
self.logname = None

def serve_until_stopped(self):
import select
abort = 0
while not abort:
rd, wr, ex = select.select([self.socket.fileno()],
[], [], self.timeout)
if rd:
self.handle_request()
abort = self.abort

class LoggingConfigHandler(StreamRequestHandler):
def handle(self):
try:
conn = self.connection
chunk = conn.recv(4)
if len(chunk) == 4:
slen = struct.unpack(">L", chunk)[0]
chunk = b""
while len(chunk) < slen:
chunk += conn.recv(slen - len(chunk))
if verify is not None:
chunk = verify(chunk)
if chunk is not None:
try:
import json
d = json.loads(chunk.decode('utf-8'))
assert isinstance(d, dict)
dictConfig(d)
except Exception:
traceback.print_exc()
except OSError:
pass
...

listen uses socketserver.ThreadingTCPServer bound to localhost. The wire format is a 4-byte big-endian length followed by that many bytes of UTF-8 JSON. The verify callable, if provided, receives the raw bytes and must return the (possibly modified) bytes to apply, or None to reject the payload. This is the extension point for adding authentication or signature verification in production deployments.

The background thread uses select with a 1-second timeout so that serve_until_stopped can check self.abort even when no connections arrive. stopListening sets self.abort = 1 and joins the thread.

gopy mirror

logging.config depends on logging, configparser, socket, struct, threading, json, traceback, and os. The highest-value entry point is dictConfig since it is the modern recommended API. A gopy port would implement DictConfigurator as a Go struct, with configure_handler as the most complex method due to the handler graph resolution logic.