From debc7f97d3916feec4e941e71b55b5e58e7a0250 Mon Sep 17 00:00:00 2001 From: Athanasius <github@miggy.org> Date: Fri, 24 Jul 2020 20:42:06 +0100 Subject: [PATCH] Use a logging.Filter to implement %(class)s in formatting. --- EDMarketConnector.py | 80 ++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 78 insertions(+), 2 deletions(-) diff --git a/EDMarketConnector.py b/EDMarketConnector.py index bc7762b9..6d0ff021 100755 --- a/EDMarketConnector.py +++ b/EDMarketConnector.py @@ -18,6 +18,8 @@ import _strptime # Workaround for http://bugs.python.org/issue7980 from calendar import timegm import webbrowser import logging +import traceback +from typing import Any, Optional from config import appname, applongname, appversion, appversion_nobuild, copyright, config @@ -941,8 +943,68 @@ def enforce_single_instance() -> None: EnumWindows(enumwindowsproc, 0) +########################################################################### +# Logging + # Has to be here to be defined for other modules importing logger = logging.getLogger(appname) + +class EDMCContextFilter(logging.Filter): + """ + logging.Filter sub-class to place the calling __class__ in the record. + """ + def filter(self, record: logging.LogRecord) -> bool: + """ + + :param record: + :return: bool - True for this record to be logged. + """ + record.__dict__['class'] = self.caller_class() + return True + + def caller_class(self, skip=5) -> str: + """ + Figure out our caller class. + + Ref: <https://gist.github.com/techtonik/2151727#gistcomment-2333747> + + :param skip: How many stack frames above to look. + :return: str: The class name. + """ + import inspect + + def stack_(frame): + framelist = [] + while frame: + framelist.append(frame) + frame = frame.f_back + return framelist + + stack = stack_(sys._getframe(1)) + start = 0 + skip + if len(stack) < start + 1: + return '' + + class_name = [] + frame = stack[start] + module = inspect.getmodule(frame) + # `modname` can be None when frame is executed directly in console + # TODO(techtonik): consider using __main__ + #if module: + # class_name.append(module.__name__) + # detect classname + if 'self' in frame.f_locals: + # I don't know any way to detect call from the object method + # XXX: there seems to be no way to detect static method call - it will + # be just a function call + class_name.insert(0, frame.f_locals['self'].__class__.__qualname__) + codename = frame.f_code.co_name + #if codename != '<module>': # top level usually + # class_name.append(codename) # function or a method + + return ".".join(class_name) +########################################################################### + # Run the app if __name__ == "__main__": @@ -954,19 +1016,33 @@ if __name__ == "__main__": ########################################################################### # Set up a logging instance - logger_default_loglevel = logging.INFO + logger_default_loglevel = logging.DEBUG logger.setLevel(logger_default_loglevel) + + # Set up filter for adding class name + logger_f = EDMCContextFilter() + logger.addFilter(logger_f) + logger_ch = logging.StreamHandler() logger_ch.setLevel(logger_default_loglevel) - logger_formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(module)s:%(lineno)d:%(funcName)s: %(message)s') + + logger_formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(module)s<.%(class)s>.%(funcName)s:%(lineno)d: %(message)s') logger_formatter.default_time_format = '%Y-%m-%d %H:%M:%S' logger_formatter.default_msec_format = '%s.%03d' + logger_ch.setFormatter(logger_formatter) logger.addHandler(logger_ch) ########################################################################### # Plain, not via `logger` print(f'{applongname} {appversion}') + logger.info('Logging test from __main__') + class A(object): + class B(object): + def __init__(self): + logger.info('Test from A.B.__init__') + + ab = A.B() Translations.install(config.get('language') or None) # Can generate errors so wait til log set up