Last active
December 2, 2024 22:37
-
-
Save angstwad/358c1cd937e933c01d3f72ad4abe2e7b to your computer and use it in GitHub Desktop.
Opinionated, standardized logging for Python CLIs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# An opinionated approach to logging in Python | |
import logging | |
import sys | |
def setup_logger(name: str | None = None, | |
level: str | int = 'WARN', | |
log_format: str | None = None) -> logging.Logger: | |
"""Sets up an opinionated logger bifurcating on log level to send | |
error and warning logs to stderr, info and debug to stdout | |
Args: | |
name: name of the logger to get; default `None` returns root logger | |
level: level attribute in logging as a string | |
log_format: optional log format; should be a valid format string, | |
see std lib logging docs for more info | |
Returns: | |
configured `logging.Logger` | |
""" | |
logger = logging.getLogger(name) | |
if logger.handlers: | |
return logger | |
if isinstance(level, int): | |
logger.setLevel(level) | |
else: | |
logger.setLevel(getattr(logging, level)) | |
if log_format is None: | |
log_format = "%(name)-10s %(levelname)-7s %(message)s" | |
formatter = logging.Formatter(log_format) | |
h1 = logging.StreamHandler(sys.stdout) | |
h1.setLevel(logging.DEBUG) | |
h1.setFormatter(formatter) | |
h1.addFilter(lambda record: record.levelno <= logging.INFO) | |
h2 = logging.StreamHandler() | |
h2.setLevel(logging.WARNING) | |
h2.setFormatter(formatter) | |
logger.addHandler(h1) | |
logger.addHandler(h2) | |
return logger | |
class LoggingMixin: | |
""" Mixin class which adds logging functionality to a class. Not intended to be used as a | |
subclass. To use, call `self._setup_logger; this creates an instance-internal logger at | |
`self._logger`. When setting up a logger, this expects this app's primary logger, the | |
`cli` logger, to be already set up, and uses its level as the internal logger's level. | |
Then, call `self.log` as a convenience method or, alternatively, the internal logger directly. | |
""" | |
_logger: logging.Logger | |
def _setup_logger(self, name: str, msg_prefix: str | None = None) -> None: | |
"""Sets up an internal logger object using a prescriptive convention | |
Note that this tries to match the log level of the hydrate logger, | |
because that's what is set up using CLI args. Changes to the hydrate | |
logger will need to be set here | |
""" | |
self._prefix = msg_prefix | |
self._logger = setup_logger(name, level=logging.getLogger('cli').level) | |
def log(self, msg: str, lvl: str = 'error', **kwargs) -> None: | |
"""Convenience method for logging to the internal logger | |
Args: | |
msg: log message as a string | |
lvl: log level as a string name, should be a method name off a | |
`logging.Logger` object; i.e. `debug`, `info`, `warn` | |
Returns: | |
Result of logger method call; expected to be None | |
""" | |
meth = getattr(self._logger, lvl) | |
meth(f"{self._prefix + ": " if self._prefix else ""}{msg}", **kwargs) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Copyright 2024 Paul Durivage <[email protected]> | |
# Licensed under the Apache License, Version 2.0 (the "License"); | |
# you may not use this file except in compliance with the License. | |
# You may obtain a copy of the License at | |
# http://www.apache.org/licenses/LICENSE-2.0 | |
# Unless required by applicable law or agreed to in writing, software | |
# distributed under the License is distributed on an "AS IS" BASIS, | |
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
# See the License for the specific language governing permissions and | |
# limitations under the License. |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment