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