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
| Lines | Symbol | Role | gopy |
|---|---|---|---|
| 1-80 | Module imports, IDENTIFIER regex | Imports configparser, io, logging, re, socket, struct, threading, traceback; defines the valid Python identifier pattern used to validate config keys. | (stdlib pending) |
| 81-200 | fileConfig | Reads an INI file; creates formatters from [formatter_*] sections, handlers from [handler_*] sections, loggers from [logger_*] sections; optionally disables existing loggers. | (stdlib pending) |
| 201-350 | DictConfigurator.__init__, configure | Validates the schema version; calls configure_formatters, configure_filters, configure_handlers, configure_loggers in order; handles incremental and disable_existing_loggers. | (stdlib pending) |
| 351-500 | configure_formatter, configure_filter | Resolves the class key via logging._checkLevel / _resolve; instantiates the object; supports () key for factory callables. | (stdlib pending) |
| 501-700 | configure_handler, handler graph resolution | Topologically 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-800 | configure_logger, configure_root | Creates 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-900 | listen, LoggingConfigHandler, stopListening | Background 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.