Skip to content

Commit

Permalink
logging.py: CPython-compatible logging improvements.
Browse files Browse the repository at this point in the history
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 <[email protected]>
  • Loading branch information
ned-pcs committed Feb 19, 2024
1 parent 4cc6706 commit cd5bfd4
Showing 1 changed file with 40 additions and 6 deletions.
46 changes: 40 additions & 6 deletions python-stdlib/logging/logging.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
_stream = sys.stderr
_default_fmt = "%(levelname)s:%(name)s:%(message)s"
_default_datefmt = "%Y-%m-%d %H:%M:%S"
_default_formatter = None


class LogRecord:
Expand All @@ -43,25 +44,40 @@ def __init__(self, level=NOTSET):
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

def setFormatter(self, formatter):
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()

Expand Down Expand Up @@ -128,7 +144,7 @@ def log(self, level, msg, *args):
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)
Expand Down Expand Up @@ -161,7 +177,13 @@ def exception(self, msg, *args, exc_info=True):
self.log(ERROR, buf.getvalue())

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):
return len(self.handlers) > 0
Expand Down Expand Up @@ -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:
Expand All @@ -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)

0 comments on commit cd5bfd4

Please sign in to comment.