diff --git a/adafruit_logging.py b/adafruit_logging.py index 117c1bf..dc3c652 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): @@ -148,12 +148,85 @@ 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, defaults and + style. All others are currently ignored + + The only two styles currently supported are '%' and '{'. The default + style is '{' + """ + + def __init__( # pylint: disable=too-many-arguments + 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 not in ("{", "%"): + raise ValueError( + "Only '%' and '{' formatting style are 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 or "%(asctime)s" in self.fmt: + now = time.localtime() + # 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(): + if key not in vals: + 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) + + 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 +240,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 +256,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..ccd2eea --- /dev/null +++ b/examples/logging_formatter_example.py @@ -0,0 +1,48 @@ +# SPDX-FileCopyrightText: 2024 Tim Cocks +# SPDX-License-Identifier: MIT + + +"""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 + + +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)s %(levelname)s: %(message)s") +print_handler.setFormatter(timestamp_formatter) +logger.info("Timestamp formatter example") + + +custom_vals_formatter = logging.Formatter( + 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")