logging.py: CPython-compatible logging improvements.

This commit allows for logging handlers to be added to the root logger
and then used by non-root loggers that don't have their own handlers.

It also adds the (CPython-compatible) handlers argument
to logging.basicConfig() to ease this initialization:

    import logging
    sh = logging.StreamHandler()
    fh = logging.FileHandler("my.log", mode="a")
    logging.basicConfig(handlers=[sh, fh])

    root_logger = logging.getLogger() # uses sh and fh
    another_logger = logging.getLogger("another") # inherits handlers


It also adds the Logger.removeHandler() method and avoids repeated handler
addition.

It also adds the flush() method to StreamHandler and its subclasses.

It also correctly calls the superclass constructor from the StreamHandler
constructor and uses a default formatter if a Handler has none
set (as in PR #710).

Signed-off-by: Ned Konz <ned@productcreationstudio.com>
pull/806/head
Ned Konz 2024-02-15 10:19:24 -08:00
rodzic 661efa48f0
commit 94a38ab297
1 zmienionych plików z 40 dodań i 6 usunięć

Wyświetl plik

@ -25,6 +25,7 @@ _loggers = {}
_stream = sys.stderr _stream = sys.stderr
_default_fmt = "%(levelname)s:%(name)s:%(message)s" _default_fmt = "%(levelname)s:%(name)s:%(message)s"
_default_datefmt = "%Y-%m-%d %H:%M:%S" _default_datefmt = "%Y-%m-%d %H:%M:%S"
_default_formatter = None
class LogRecord: class LogRecord:
@ -43,9 +44,19 @@ class Handler:
self.level = level self.level = level
self.formatter = None self.formatter = None
@staticmethod
def _default_formatter():
global _default_formatter
if _default_formatter is None:
_default_formatter = Formatter()
return _default_formatter
def close(self): def close(self):
pass pass
def flush(self):
pass
def setLevel(self, level): def setLevel(self, level):
self.level = level self.level = level
@ -53,15 +64,20 @@ class Handler:
self.formatter = formatter self.formatter = formatter
def format(self, record): def format(self, record):
return self.formatter.format(record) if self.formatter:
fmt = self.formatter
else:
fmt = self._default_formatter()
return fmt.format(record)
class StreamHandler(Handler): class StreamHandler(Handler):
def __init__(self, stream=None): def __init__(self, stream=None):
super().__init__()
self.stream = _stream if stream is None else stream self.stream = _stream if stream is None else stream
self.terminator = "\n" self.terminator = "\n"
def close(self): def flush(self):
if hasattr(self.stream, "flush"): if hasattr(self.stream, "flush"):
self.stream.flush() self.stream.flush()
@ -128,7 +144,7 @@ class Logger:
msg = msg % args msg = msg % args
self.record.set(self.name, level, msg) self.record.set(self.name, level, msg)
handlers = self.handlers handlers = self.handlers
if not handlers: if not self.hasHandlers():
handlers = getLogger().handlers handlers = getLogger().handlers
for h in handlers: for h in handlers:
h.emit(self.record) h.emit(self.record)
@ -161,7 +177,13 @@ class Logger:
self.log(ERROR, buf.getvalue()) self.log(ERROR, buf.getvalue())
def addHandler(self, handler): def addHandler(self, handler):
self.handlers.append(handler) if handler not in self.handlers:
self.handlers.append(handler)
def removeHandler(self, handler):
if handler in self.handlers:
handler.close()
self.handlers.remove(handler)
def hasHandlers(self): def hasHandlers(self):
return len(self.handlers) > 0 return len(self.handlers) > 0
@ -225,17 +247,28 @@ def basicConfig(
stream=None, stream=None,
encoding="UTF-8", encoding="UTF-8",
force=False, force=False,
handlers=None,
): ):
if "root" not in _loggers: if "root" not in _loggers:
_loggers["root"] = Logger("root") _loggers["root"] = Logger("root")
logger = _loggers["root"] logger = _loggers["root"]
if force or not logger.handlers: if force:
for h in logger.handlers: for h in logger.handlers:
h.close() h.close()
logger.handlers = [] logger.handlers = []
if len([arg for arg in (filename, stream, handlers) if arg is not None]) > 1:
raise ValueError("can only set one of 'filename', 'stream' or 'handlers'")
if handlers is not None:
for h in handlers:
if h.formatter is None:
h.setFormatter(Formatter(format, datefmt))
logger.addHandler(h)
if not logger.hasHandlers():
if filename is None: if filename is None:
handler = StreamHandler(stream) handler = StreamHandler(stream)
else: else:
@ -244,9 +277,10 @@ def basicConfig(
handler.setLevel(level) handler.setLevel(level)
handler.setFormatter(Formatter(format, datefmt)) handler.setFormatter(Formatter(format, datefmt))
logger.setLevel(level)
logger.addHandler(handler) logger.addHandler(handler)
logger.setLevel(level)
if hasattr(sys, "atexit"): if hasattr(sys, "atexit"):
sys.atexit(shutdown) sys.atexit(shutdown)