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 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 Jan 3, 2024
1 parent 7cdf708 commit dfdc686
Showing 1 changed file with 36 additions and 5 deletions.
41 changes: 36 additions & 5 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,6 +44,13 @@ 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

Expand All @@ -53,11 +61,16 @@ 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"

Expand Down Expand Up @@ -128,7 +141,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 +174,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 +244,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 +274,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 dfdc686

Please sign in to comment.