From f7dc544add8acc98fb76696b9aa2cb0cfd54a684 Mon Sep 17 00:00:00 2001 From: foamyguy Date: Mon, 5 Aug 2024 18:15:36 -0500 Subject: [PATCH 1/5] adding Formatter and example including timestamp --- adafruit_logging.py | 68 ++++++++++++++++++++++++++- examples/logging_formatter_example.py | 33 +++++++++++++ 2 files changed, 100 insertions(+), 1 deletion(-) create mode 100644 examples/logging_formatter_example.py diff --git a/adafruit_logging.py b/adafruit_logging.py index 6394f16..4d0ae46 100644 --- a/adafruit_logging.py +++ b/adafruit_logging.py @@ -148,12 +148,71 @@ def _logRecordFactory(name, level, msg, args): return LogRecord(name, level, _level_for(level), msg, time.monotonic(), args) +class Formatter: + """ + Responsible for converting a LogRecord to an output string to be + interpreted by a human or external system. + + Only implements a sub-set of CPython logging.Formatter behavior, + but retains all the same arguments in order to match the API. + + The only init arguments currently supported are: fmt and defaults. + All others are currently ignored + + The only style value currently supported is '{'. CPython has support + for some others, but this implementation does not. Additionally, the + default value for style in this implementation is '{' whereas the default + style value in CPython is '%' + """ + + def __init__( # pylint: disable=too-many-arguments + self, fmt=None, datefmt=None, style="{", validate=True, defaults=None + ): + self.fmt = fmt + self.datefmt = datefmt + self.style = style + if self.style != "{": + raise ValueError("Only '{' formatting sytle is supported at this time.") + + self.validate = validate + self.defaults = defaults + + def format(self, record: LogRecord) -> str: + """ + Format the given LogRecord into an output string + """ + if self.fmt is None: + return record.msg + + vals = { + "name": record.name, + "levelno": record.levelno, + "levelname": record.levelname, + "message": record.msg, + "created": record.created, + "args": record.args, + } + if "{asctime}" in self.fmt: + now = time.localtime() + vals["asctime"] = ( + f"{now.tm_year}-{now.tm_mon:02d}-{now.tm_mday:02d} " + f"{now.tm_hour:02d}:{now.tm_min:02d}:{now.tm_sec:02d}" + ) + + if self.defaults: + for key, val in self.defaults.items(): + vals[key] = val + + return self.fmt.format(**vals) + + class Handler: """Base logging message handler.""" def __init__(self, level: int = NOTSET) -> None: """Create Handler instance""" self.level = level + self.formatter = None def setLevel(self, level: int) -> None: """ @@ -167,7 +226,8 @@ def format(self, record: LogRecord) -> str: :param record: The record (message object) to be logged """ - + if self.formatter: + return self.formatter.format(record) return f"{record.created:<0.3f}: {record.levelname} - {record.msg}" def emit(self, record: LogRecord) -> None: @@ -182,6 +242,12 @@ def emit(self, record: LogRecord) -> None: def flush(self) -> None: """Placeholder for flush function in subclasses.""" + def setFormatter(self, formatter: Formatter) -> None: + """ + Set the Formatter to be used by this Handler. + """ + self.formatter = formatter + # pylint: disable=too-few-public-methods class StreamHandler(Handler): diff --git a/examples/logging_formatter_example.py b/examples/logging_formatter_example.py new file mode 100644 index 0000000..5e35e37 --- /dev/null +++ b/examples/logging_formatter_example.py @@ -0,0 +1,33 @@ +# SPDX-FileCopyrightText: 2024 Tim Cocks +# SPDX-License-Identifier: MIT + + +"""Briefly exercise the logger and null logger.""" + +import adafruit_logging as logging +# To test on CPython, un-comment below and comment out above +# import logging + + +logger = logging.getLogger("example") +logger.setLevel(logging.INFO) +print_handler = logging.StreamHandler() +logger.addHandler(print_handler) + +default_formatter = logging.Formatter() +print_handler.setFormatter(default_formatter) +logger.info("Default formatter example") + + +timestamp_formatter = logging.Formatter( + fmt="{asctime} {levelname}: {message}", style="{" +) +print_handler.setFormatter(timestamp_formatter) +logger.info("Timestamp formatter example") + + +custom_vals_formatter = logging.Formatter( + fmt="{ip} {levelname}: {message}", style="{", defaults={"ip": "192.168.1.188"} +) +print_handler.setFormatter(custom_vals_formatter) +logger.info("Custom formatter example") From 8f53ae15afafde6bb0c3e7abefdc079d43e18296 Mon Sep 17 00:00:00 2001 From: foamyguy Date: Mon, 5 Aug 2024 18:33:48 -0500 Subject: [PATCH 2/5] update comment --- examples/logging_formatter_example.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/examples/logging_formatter_example.py b/examples/logging_formatter_example.py index 5e35e37..460e028 100644 --- a/examples/logging_formatter_example.py +++ b/examples/logging_formatter_example.py @@ -2,9 +2,11 @@ # SPDX-License-Identifier: MIT -"""Briefly exercise the logger and null logger.""" +"""Illustrate usage of default and custom Formatters including +one with timestamps.""" import adafruit_logging as logging + # To test on CPython, un-comment below and comment out above # import logging From 555981ac12e5937b3fa05560b214242ca277edc2 Mon Sep 17 00:00:00 2001 From: foamyguy Date: Mon, 5 Aug 2024 18:38:38 -0500 Subject: [PATCH 3/5] fix unsupported syntx --- adafruit_logging.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/adafruit_logging.py b/adafruit_logging.py index 4d0ae46..f2041b8 100644 --- a/adafruit_logging.py +++ b/adafruit_logging.py @@ -194,10 +194,10 @@ def format(self, record: LogRecord) -> str: } if "{asctime}" in self.fmt: now = time.localtime() - vals["asctime"] = ( - f"{now.tm_year}-{now.tm_mon:02d}-{now.tm_mday:02d} " - f"{now.tm_hour:02d}:{now.tm_min:02d}:{now.tm_sec:02d}" - ) + # pylint: disable=line-too-long + vals[ + "asctime" + ] = f"{now.tm_year}-{now.tm_mon:02d}-{now.tm_mday:02d} {now.tm_hour:02d}:{now.tm_min:02d}:{now.tm_sec:02d}" if self.defaults: for key, val in self.defaults.items(): From ef8c237e276e34af2a54b17a4e6d9eae899b5b9f Mon Sep 17 00:00:00 2001 From: foamyguy Date: Tue, 6 Aug 2024 10:52:17 -0500 Subject: [PATCH 4/5] implement % sytle and change default to it --- adafruit_logging.py | 35 ++++++++++++++++++--------- examples/logging_formatter_example.py | 21 +++++++++++++--- 2 files changed, 41 insertions(+), 15 deletions(-) diff --git a/adafruit_logging.py b/adafruit_logging.py index f2041b8..029b834 100644 --- a/adafruit_logging.py +++ b/adafruit_logging.py @@ -64,7 +64,7 @@ try: # pylint: disable=deprecated-class - from typing import Optional, Hashable + from typing import Optional, Hashable, Dict from typing_extensions import Protocol class WriteableStream(Protocol): @@ -156,23 +156,28 @@ class Formatter: Only implements a sub-set of CPython logging.Formatter behavior, but retains all the same arguments in order to match the API. - The only init arguments currently supported are: fmt and defaults. - All others are currently ignored + The only init arguments currently supported are: fmt, defaults and + style. All others are currently ignored - The only style value currently supported is '{'. CPython has support - for some others, but this implementation does not. Additionally, the - default value for style in this implementation is '{' whereas the default - style value in CPython is '%' + The only two styles currently supported are '%' and '{'. The default + style is '{' """ def __init__( # pylint: disable=too-many-arguments - self, fmt=None, datefmt=None, style="{", validate=True, defaults=None + self, + fmt: Optional[str] = None, + datefmt: Optional[str] = None, + style: str = "%", + validate: bool = True, + defaults: Dict = None, ): self.fmt = fmt self.datefmt = datefmt self.style = style - if self.style != "{": - raise ValueError("Only '{' formatting sytle is supported at this time.") + if self.style not in ("{", "%"): + raise ValueError( + "Only '%' and '{' formatting style are supported at this time." + ) self.validate = validate self.defaults = defaults @@ -192,7 +197,7 @@ def format(self, record: LogRecord) -> str: "created": record.created, "args": record.args, } - if "{asctime}" in self.fmt: + if "{asctime}" in self.fmt or "%(asctime)s" in self.fmt: now = time.localtime() # pylint: disable=line-too-long vals[ @@ -203,6 +208,14 @@ def format(self, record: LogRecord) -> str: for key, val in self.defaults.items(): vals[key] = val + if self.style not in ("{", "%"): + raise ValueError( + "Only '%' and '{' formatting style are supported at this time." + ) + + if self.style == "%": + return self.fmt % vals + return self.fmt.format(**vals) diff --git a/examples/logging_formatter_example.py b/examples/logging_formatter_example.py index 460e028..ccd2eea 100644 --- a/examples/logging_formatter_example.py +++ b/examples/logging_formatter_example.py @@ -17,19 +17,32 @@ logger.addHandler(print_handler) default_formatter = logging.Formatter() + print_handler.setFormatter(default_formatter) logger.info("Default formatter example") -timestamp_formatter = logging.Formatter( - fmt="{asctime} {levelname}: {message}", style="{" -) +timestamp_formatter = logging.Formatter(fmt="%(asctime)s %(levelname)s: %(message)s") print_handler.setFormatter(timestamp_formatter) logger.info("Timestamp formatter example") custom_vals_formatter = logging.Formatter( - fmt="{ip} {levelname}: {message}", style="{", defaults={"ip": "192.168.1.188"} + fmt="%(ip)s %(levelname)s: %(message)s", defaults={"ip": "192.168.1.188"} ) print_handler.setFormatter(custom_vals_formatter) logger.info("Custom formatter example") + + +bracket_timestamp_formatter = logging.Formatter( + fmt="{asctime} {levelname}: {message}", style="{" +) +print_handler.setFormatter(bracket_timestamp_formatter) +logger.info("Timestamp formatter bracket style example") + + +bracket_custom_vals_formatter = logging.Formatter( + fmt="{ip} {levelname}: {message}", style="{", defaults={"ip": "192.168.1.188"} +) +print_handler.setFormatter(bracket_custom_vals_formatter) +logger.info("Custom formatter bracket style example") From c9ae545190816800bb564892e1fd786ab7adba14 Mon Sep 17 00:00:00 2001 From: foamyguy Date: Tue, 6 Aug 2024 13:12:53 -0500 Subject: [PATCH 5/5] prevent defaults from clobbering --- adafruit_logging.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/adafruit_logging.py b/adafruit_logging.py index 029b834..d037b15 100644 --- a/adafruit_logging.py +++ b/adafruit_logging.py @@ -206,7 +206,8 @@ def format(self, record: LogRecord) -> str: if self.defaults: for key, val in self.defaults.items(): - vals[key] = val + if key not in vals: + vals[key] = val if self.style not in ("{", "%"): raise ValueError(