From d8392367e5222dabd22353ad7ada67a6b3b0ffba Mon Sep 17 00:00:00 2001 From: Vladimir Kotal Date: Thu, 17 Nov 2022 13:10:13 +0100 Subject: [PATCH 1/8] cleanup pylint warnings --- adafruit_logging.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/adafruit_logging.py b/adafruit_logging.py index 203d7c3..21cae6b 100644 --- a/adafruit_logging.py +++ b/adafruit_logging.py @@ -156,9 +156,7 @@ def format(self, record: LogRecord) -> str: :param record: The record (message object) to be logged """ - return "{0:<0.3f}: {1} - {2}".format( - record.created, record.levelname, record.msg - ) + return f"{record.created:<0.3f}: {record.levelname} - {record.msg}" def emit(self, record: LogRecord) -> None: """Send a message where it should go. From 7bba4fc429ac323bb31d96d31a3e7f4e9757127a Mon Sep 17 00:00:00 2001 From: Vladimir Kotal Date: Sat, 19 Nov 2022 19:45:15 +0100 Subject: [PATCH 2/8] allow multiple handlers fixes #43 --- adafruit_logging.py | 41 +++++++++++++++++++++++++++++------------ 1 file changed, 29 insertions(+), 12 deletions(-) diff --git a/adafruit_logging.py b/adafruit_logging.py index 21cae6b..fb23f49 100644 --- a/adafruit_logging.py +++ b/adafruit_logging.py @@ -25,9 +25,8 @@ .. note:: This module has a few key differences compared to its CPython counterpart, notably - that loggers can only be assigned one handler at a time. Calling ``addHander()`` - replaces the currently stored handler for that logger. Additionally, the default - formatting for handlers is different. + that loggers do not form a hierarchy that allows record propagation. + Additionally, the default formatting for handlers is different. Attributes ---------- @@ -275,7 +274,8 @@ def __init__(self, name: Hashable, level: int = WARNING) -> None: self.name = name """The name of the logger, this should be unique for proper functionality of `getLogger()`""" - self._handler = None + self._handlers = [] + self.emittedNoHandlerWarning = False def setLevel(self, log_level: int) -> None: """Set the logging cutoff level. @@ -294,23 +294,40 @@ def getEffectiveLevel(self) -> int: return self._level def addHandler(self, hdlr: Handler) -> None: - """Sets the handler of this logger to the specified handler. - - *NOTE* This is slightly different from the CPython equivalent - which adds the handler rather than replacing it. + """Adds the handler to this logger. :param Handler hdlr: The handler to add """ - self._handler = hdlr + self._handlers.append(hdlr) + + def removeHandler(self, hdlr: Handler) -> None: + """Remove handler from this logger. + + :param Handler hdlr: The handler to remove + """ + self._handlers.remove(hdlr) def hasHandlers(self) -> bool: """Whether any handlers have been set for this logger""" - return self._handler is not None + return len(self._handlers) > 0 def _log(self, level: int, msg: str, *args) -> None: record = _logRecordFactory(self.name, level, msg % args, args) - if self._handler and level >= self._level: - self._handler.emit(record) + self.handle(record) + + def handle(self, record: LogRecord) -> None: + """Pass the record to all handlers registered with this logger. + + :param LogRecord record: log record + """ + if not self.hasHandlers() and not self.emittedNoHandlerWarning: + sys.stderr.write(f"Logger '{self.name}' has no handlers\n") + self.emittedNoHandlerWarning = True + return + + if record.levelno >= self._level: + for handler in self._handlers: + handler.emit(record) def log(self, level: int, msg: str, *args) -> None: """Log a message. From ab8b0e2f1373eeb3ba98c23887e232af904996cf Mon Sep 17 00:00:00 2001 From: Vladimir Kotal Date: Sat, 19 Nov 2022 20:12:04 +0100 Subject: [PATCH 3/8] allow handlers to have log level --- adafruit_logging.py | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/adafruit_logging.py b/adafruit_logging.py index fb23f49..6e660ed 100644 --- a/adafruit_logging.py +++ b/adafruit_logging.py @@ -146,7 +146,17 @@ def _logRecordFactory(name, level, msg, args): class Handler: - """Abstract logging message handler.""" + """Base logging message handler.""" + + def __init__(self, level: int = NOTSET) -> None: + """Create Handler instance""" + self.level = level + + def setLevel(self, level: int) -> None: + """ + Set the logging level of this handler. + """ + self.level = level # pylint: disable=no-self-use def format(self, record: LogRecord) -> str: @@ -327,7 +337,8 @@ def handle(self, record: LogRecord) -> None: if record.levelno >= self._level: for handler in self._handlers: - handler.emit(record) + if record.levelno >= handler.level: + handler.emit(record) def log(self, level: int, msg: str, *args) -> None: """Log a message. From 6d865f120082ae731ab092d2fd70d4510d9c1ea3 Mon Sep 17 00:00:00 2001 From: Vladimir Kotal Date: Sat, 19 Nov 2022 21:15:48 +0100 Subject: [PATCH 4/8] handle exceptions in handlers by default --- adafruit_logging.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/adafruit_logging.py b/adafruit_logging.py index 6e660ed..e2df557 100644 --- a/adafruit_logging.py +++ b/adafruit_logging.py @@ -140,6 +140,8 @@ def _level_for(value: int) -> str: - ``args`` - The additional positional arguments provided """ +raiseExceptions = False + def _logRecordFactory(name, level, msg, args): return LogRecord(name, level, _level_for(level), msg, time.monotonic(), args) @@ -338,7 +340,14 @@ def handle(self, record: LogRecord) -> None: if record.levelno >= self._level: for handler in self._handlers: if record.levelno >= handler.level: - handler.emit(record) + try: + handler.emit(record) + except Exception as e: # pylint: disable=broad-except + if raiseExceptions: + raise e + if sys.stderr: + sys.stderr.write(f"Handler {handler} produced exception: " + f"{repr(e)}\n") def log(self, level: int, msg: str, *args) -> None: """Log a message. From dcf122e7a089aca0c103f8cc45d083329e46d93f Mon Sep 17 00:00:00 2001 From: Vladimir Kotal Date: Sat, 19 Nov 2022 21:28:56 +0100 Subject: [PATCH 5/8] rework exception handling semantics The choice is now whether to print a warning. Previously this was about re-raising the exception. This is done for stability's sake. --- adafruit_logging.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/adafruit_logging.py b/adafruit_logging.py index e2df557..fea6312 100644 --- a/adafruit_logging.py +++ b/adafruit_logging.py @@ -140,7 +140,10 @@ def _level_for(value: int) -> str: - ``args`` - The additional positional arguments provided """ -raiseExceptions = False +printHandlerExceptions = True +""" +Whether to print exceptions caught during handler's emit(). +""" def _logRecordFactory(name, level, msg, args): @@ -343,11 +346,9 @@ def handle(self, record: LogRecord) -> None: try: handler.emit(record) except Exception as e: # pylint: disable=broad-except - if raiseExceptions: - raise e - if sys.stderr: - sys.stderr.write(f"Handler {handler} produced exception: " - f"{repr(e)}\n") + if sys.stderr and printHandlerExceptions: + sys.stderr.write(f"Handler {repr(handler)} produced exception: " + f"{str(e)}\n") def log(self, level: int, msg: str, *args) -> None: """Log a message. From 134605a7bcc7b9f16c8bc1fadb22768a11acf1a7 Mon Sep 17 00:00:00 2001 From: Vladimir Kotal Date: Sat, 19 Nov 2022 21:40:53 +0100 Subject: [PATCH 6/8] apply black --- adafruit_logging.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/adafruit_logging.py b/adafruit_logging.py index fea6312..7c84475 100644 --- a/adafruit_logging.py +++ b/adafruit_logging.py @@ -140,10 +140,8 @@ def _level_for(value: int) -> str: - ``args`` - The additional positional arguments provided """ +# Whether to print exceptions caught during handler's emit(). printHandlerExceptions = True -""" -Whether to print exceptions caught during handler's emit(). -""" def _logRecordFactory(name, level, msg, args): @@ -347,8 +345,9 @@ def handle(self, record: LogRecord) -> None: handler.emit(record) except Exception as e: # pylint: disable=broad-except if sys.stderr and printHandlerExceptions: - sys.stderr.write(f"Handler {repr(handler)} produced exception: " - f"{str(e)}\n") + sys.stderr.write( + f"Handler {handler} produced exception: {e}\n" + ) def log(self, level: int, msg: str, *args) -> None: """Log a message. From 01359e76a65ddb36ae147c0458ecde4183c8b66b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Kotal?= Date: Mon, 5 Dec 2022 12:25:52 +0100 Subject: [PATCH 7/8] allow the handler exceptions to propagate --- adafruit_logging.py | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/adafruit_logging.py b/adafruit_logging.py index 7c84475..6e660ed 100644 --- a/adafruit_logging.py +++ b/adafruit_logging.py @@ -140,9 +140,6 @@ def _level_for(value: int) -> str: - ``args`` - The additional positional arguments provided """ -# Whether to print exceptions caught during handler's emit(). -printHandlerExceptions = True - def _logRecordFactory(name, level, msg, args): return LogRecord(name, level, _level_for(level), msg, time.monotonic(), args) @@ -341,13 +338,7 @@ def handle(self, record: LogRecord) -> None: if record.levelno >= self._level: for handler in self._handlers: if record.levelno >= handler.level: - try: - handler.emit(record) - except Exception as e: # pylint: disable=broad-except - if sys.stderr and printHandlerExceptions: - sys.stderr.write( - f"Handler {handler} produced exception: {e}\n" - ) + handler.emit(record) def log(self, level: int, msg: str, *args) -> None: """Log a message. From 54ad09120e4e01e35e63cb15443b2d7ef1a7e4eb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Kotal?= Date: Mon, 12 Dec 2022 21:06:52 +0100 Subject: [PATCH 8/8] match CPython behavior w.r.t. default (last resort) logger --- adafruit_logging.py | 21 ++++++++++++++++++--- examples/logging_simpletest.py | 25 +++++++++++++++++-------- 2 files changed, 35 insertions(+), 11 deletions(-) diff --git a/adafruit_logging.py b/adafruit_logging.py index 6e660ed..9d97e0f 100644 --- a/adafruit_logging.py +++ b/adafruit_logging.py @@ -246,13 +246,13 @@ def emit(self, record: LogRecord) -> None: logger_cache = {} +_default_handler = StreamHandler() def _addLogger(logger_name: Hashable) -> None: """Adds the logger if it doesn't already exist""" if logger_name not in logger_cache: new_logger = Logger(logger_name) - new_logger.addHandler(StreamHandler()) logger_cache[logger_name] = new_logger @@ -330,15 +330,30 @@ def handle(self, record: LogRecord) -> None: :param LogRecord record: log record """ - if not self.hasHandlers() and not self.emittedNoHandlerWarning: - sys.stderr.write(f"Logger '{self.name}' has no handlers\n") + if ( + _default_handler is None + and not self.hasHandlers() + and not self.emittedNoHandlerWarning + ): + sys.stderr.write( + f"Logger '{self.name}' has no handlers and default handler is None\n" + ) self.emittedNoHandlerWarning = True return + emitted = False if record.levelno >= self._level: for handler in self._handlers: if record.levelno >= handler.level: handler.emit(record) + emitted = True + + if ( + not emitted + and _default_handler + and record.levelno >= _default_handler.level + ): + _default_handler.emit(record) def log(self, level: int, msg: str, *args) -> None: """Log a message. diff --git a/examples/logging_simpletest.py b/examples/logging_simpletest.py index 3eb258b..c8099a1 100644 --- a/examples/logging_simpletest.py +++ b/examples/logging_simpletest.py @@ -8,22 +8,31 @@ import adafruit_logging as logging -# This should produce an error output +# This should produce an info output via default handler. + +logger_default_handler = logging.getLogger("default_handler") +logger_default_handler.setLevel(logging.INFO) +logger_default_handler.info("Default handler: Info message") +assert not logger_default_handler.hasHandlers() + +# This should produce an error output via Stream Handler. logger = logging.getLogger("test") -print_logger = logging.StreamHandler() -logger.addHandler(print_logger) +print_handler = logging.StreamHandler() +logger.addHandler(print_handler) +assert logger.hasHandlers() logger.setLevel(logging.ERROR) -logger.info("Info message") -logger.error("Error message") +logger.info("Stream Handler: Info message") +logger.error("Stream Handler: Error message") -# This should produce no output +# This should produce no output at all. null_logger = logging.getLogger("null") null_handler = logging.NullHandler() null_logger.addHandler(null_handler) +assert null_logger.hasHandlers() null_logger.setLevel(logging.ERROR) -null_logger.info("Info message") -null_logger.error("Error message") +null_logger.info("Null Handler: Info message") +null_logger.error("Null Handler: Error message")