From 4138272132e3bc00dd0a93ecf86440c3d1e87947 Mon Sep 17 00:00:00 2001 From: Pedro Figueiredo e Silva Date: Mon, 22 Jul 2019 15:52:35 +0300 Subject: [PATCH] Update logging (#95) This commit cleans up the logger setup by adding a class to handle the object creation. Besides the cleanup, this change fixes the duplicate logging to stdout by setting the logger propagation to false. --- .../wirepas_gateway/dbus_print_client.py | 6 +- .../wirepas_gateway/transport_service.py | 8 +- .../wirepas_gateway/utils/log_tools.py | 141 +++++++++++++++--- 3 files changed, 127 insertions(+), 28 deletions(-) diff --git a/python_transport/wirepas_gateway/dbus_print_client.py b/python_transport/wirepas_gateway/dbus_print_client.py index 6c2233e1..b0fd8310 100644 --- a/python_transport/wirepas_gateway/dbus_print_client.py +++ b/python_transport/wirepas_gateway/dbus_print_client.py @@ -7,7 +7,7 @@ from datetime import datetime from wirepas_gateway.dbus.dbus_client import BusClient -from wirepas_gateway.utils import setup_log +from wirepas_gateway.utils import LoggerHelper class PrintClient(BusClient): @@ -59,7 +59,9 @@ def main(log_name="print_client"): except KeyError: debug_level = "info" - logger = setup_log(log_name, level=debug_level) + log = LoggerHelper(module_name=__name__, level=debug_level) + logger = log.setup() + obj = PrintClient() obj.logger = logger obj.run() diff --git a/python_transport/wirepas_gateway/transport_service.py b/python_transport/wirepas_gateway/transport_service.py index 272c6e39..aa52b90c 100644 --- a/python_transport/wirepas_gateway/transport_service.py +++ b/python_transport/wirepas_gateway/transport_service.py @@ -13,7 +13,7 @@ from wirepas_gateway.protocol.topic_helper import TopicGenerator, TopicParser from wirepas_gateway.protocol.mqtt_wrapper import MQTTWrapper from wirepas_gateway.utils import ParserHelper -from wirepas_gateway.utils import setup_log +from wirepas_gateway.utils import LoggerHelper from wirepas_messaging.gateway.api import ( GatewayResultCode, GatewayState, @@ -21,6 +21,7 @@ ) from wirepas_gateway import __version__ as transport_version +from wirepas_gateway import __pkg_name__ # This constant is the actual API level implemented by this transport module (cf WP-RM-128) IMPLEMENTED_API_VERSION = 1 @@ -644,14 +645,15 @@ def main(): except KeyError: pass - logger = setup_log("transport_service", level=debug_level) + log = LoggerHelper(module_name=__pkg_name__, level=debug_level) + logger = log.setup() _update_parameters(settings, logger) # after this stage, mqtt deprecated argument cannot be used _check_parameters(settings, logger) - TransportService(settings, logger).run() + TransportService(settings=settings, logger=logger).run() if __name__ == "__main__": diff --git a/python_transport/wirepas_gateway/utils/log_tools.py b/python_transport/wirepas_gateway/utils/log_tools.py index 5b19aa47..5175401d 100644 --- a/python_transport/wirepas_gateway/utils/log_tools.py +++ b/python_transport/wirepas_gateway/utils/log_tools.py @@ -9,39 +9,134 @@ Copyright 2019 Wirepas Ltd licensed under Apache License, Version 2.0 See file LICENSE for full license details. """ + import sys +import ast import logging -def setup_log( - module, - level="debug", - log_format="%(asctime)s | [%(levelname)s] %(name)s@%(filename)s:%(lineno)d:%(message)s", -): +class LoggerHelper(object): """ - Prepares logging. - - Setups Python's logging and by default send up to LEVEL - logs to stdout. + LoggerHelper - Args: - level - logging level to enable. + The loggerhelper is a class to abstract the creation of logger + instances. """ - logger = logging.getLogger(module) - level = "{0}".format(level.upper()) + def __init__(self, module_name, level: str = "debug", **kwargs): + super(LoggerHelper, self).__init__() + + self._logger = logging.getLogger(module_name) + self._name = module_name + self._level = "{0}".format(level.upper()) + self._handlers = dict() + + self._log_format = dict() + self._log_format["stdout"] = logging.Formatter( + "%(asctime)s | [%(levelname)s] %(name)s@%(filename)s:%(lineno)d:%(message)s" + ) + + self._log_format["stderr"] = logging.Formatter( + "%(asctime)s | [%(levelname)s] %(name)s@%(filename)s:%(lineno)d:%(message)s" + ) + + try: + self._logger.setLevel(ast.literal_eval("logging.{0}".format(self._level))) + except Exception: + self._logger.setLevel(logging.DEBUG) + + @property + def level(self): + """ Return the logging level """ + return self._level + + @level.setter + def level(self, value): + """ Sets the log level """ + self._level = "{0}".format(value.upper()) + + try: + self._logger.setLevel(ast.literal_eval("logging.{0}".format(self._level))) + except Exception: + self._logger.setLevel(logging.DEBUG) + + def format(self, name): + """ Return the format for a known stream """ + return self._log_format[name] + + def add_stdout(self): + """ Adds a handler for stdout """ + try: + if self._handlers["stdout"]: + self._handlers["stdout"].close() + except KeyError: + self._handlers["stdout"] = None + + self._handlers["stdout"] = logging.StreamHandler(stream=sys.stdout) + self._handlers["stdout"].setFormatter(self.format("stdout")) + self._logger.addHandler(self._handlers["stdout"]) + + def add_stderr(self, value="error"): + """ Adds a handler for stderr """ + try: + if self._handlers["stderr"]: + self._handlers["stderr"].close() + except KeyError: + self._handlers["stderr"] = None + + self._handlers["stderr"] = logging.StreamHandler(stream=sys.stderr) + self._handlers["stderr"].setFormatter(self.format("stderr")) + # By default stderr handler limits logging level to error. + # In case logger itself has higher value, e.g. critical, + # it will limit the input of this handler. + try: + level = "{0}".format(value.upper()) + self._handlers["stderr"].setLevel( + ast.literal_eval("logging.{0}".format(level)) + ) + except Exception: + self._handlers["stderr"].setLevel(logging.ERROR) + self._logger.addHandler(self._handlers["stderr"]) + + def setup(self, level: str = None, propagate=False): + """ + Constructs the logger with the system arguments provided upon + the object creation. + """ + + if level is not None: + self.level = level + + self.add_stdout() + self._logger.propagate = propagate + + return self._logger + + def add_custom_level(self, debug_level_name, debug_level_number): + """ Add a custom debug level for log filtering purposes. + To set a logging level called sensitive please call + + self.add_custom_level(debug_level_name='sensitive', + debug_level_number=100) + + afterwards the method will be available to the logger as - try: - logger.setLevel(eval("logging.{0}".format(level))) - except: - logger.setLevel(logging.DEBUG) + logger.sensitive('my logging message') + """ - formatter = logging.Formatter(log_format) + logging.addLevelName(debug_level_number, debug_level_name.upper()) - # configures stdout - h = logging.StreamHandler(stream=sys.stdout) - h.setFormatter(formatter) + def cb(self, message, *pargs, **kws): + # Yes, logger takes its '*pargs' as 'args'. + if self.isEnabledFor(debug_level_number): + self._log(debug_level_number, message, pargs, **kws) - logger.addHandler(h) + setattr(logging.Logger, debug_level_name, cb) - return logger + def close(self): + """ Attempts to close log handlers """ + for _, handler in self._handlers.items(): + try: + handler.close() + except Exception as err: + self._logger.exception("Could not close logger {}".format(err))