From 1cb876c594d251eeac00314646fda4a9af1b6c08 Mon Sep 17 00:00:00 2001 From: A_D Date: Mon, 21 Dec 2020 12:27:19 +0200 Subject: [PATCH] Added support for logging from properties This is some best effort support for using logging in properties. This works by using the (as suggested by reporter) inspect `getattr_static` method, and failing that (as it can possibly fail), wrapping a `getattr` in a try/catch for a RecursionError--don't want to catch other things, probably best if that explodes on its own. From there as the `property` object will not have location information, we rebuild as best we can to an approximation of what the path would be. With a healthy dash of defensive programming "Just in case". I don't think that this will have any adverse effects to other logging methods, as all the new code should only be touched if we hit a property object. Closes #808 --- EDMCLogging.py | 29 ++++++++++++++++++++++++++--- EDMarketConnector.py | 10 ++++++++-- 2 files changed, 34 insertions(+), 5 deletions(-) diff --git a/EDMCLogging.py b/EDMCLogging.py index 308e85fc..4df210f8 100644 --- a/EDMCLogging.py +++ b/EDMCLogging.py @@ -296,9 +296,32 @@ class EDMCContextFilter(logging.Filter): name = f'_{frame_class.__class__.__name__}{frame_info.function}' # Find __qualname__ of the caller - fn = getattr(frame_class, name, None) - if fn and fn.__qualname__: - caller_qualname = fn.__qualname__ + fn = inspect.getattr_static(frame_class, name, None) + if fn is None: + # For some reason getattr_static cant grab this. Try and grab it with getattr, bail out + # if we get a RecursionError indicating a property + try: + fn = getattr(frame_class, name, None) + except RecursionError: + print( + "EDMCLogging:EDMCContextFilter:caller_attributes():" + "Failed to get attribute for function info. Bailing out" + ) + return "??", "??", "??" + + if fn is not None: + if isinstance(fn, property): + class_name = str(frame_class) + # If somehow you make your __class__ or __class__.__qualname__ recursive, I'll be impressed. + if hasattr(frame_class, '__class__') and hasattr(frame_class.__class__, "__qualname__"): + class_name = frame_class.__class__.__qualname__ + caller_qualname = f"{class_name}.{name}(property)" + + else: + caller_qualname = f"" + + elif fn.__qualname__: + caller_qualname = fn.__qualname__ # Find containing class name(s) of caller, if any if frame_class.__class__ and frame_class.__class__.__qualname__: diff --git a/EDMarketConnector.py b/EDMarketConnector.py index b1b2a55f..8e30cba5 100755 --- a/EDMarketConnector.py +++ b/EDMarketConnector.py @@ -1238,7 +1238,6 @@ executable: {sys.executable} sys.path: {sys.path}''' ) - # We prefer a UTF-8 encoding gets set, but older Windows versions have # issues with this. From Windows 10 1903 onwards we can rely on the # manifest ActiveCodePage to set this, but that is silently ignored on @@ -1287,7 +1286,8 @@ sys.path: {sys.path}''' logger.exception(f"Could not set LC_ALL to ('{locale_startup[0]}', 'UTF_8')") except Exception: - logger.exception(f"Exception other than locale.Error on setting LC_ALL=('{locale_startup[0]}', 'UTF_8')") + logger.exception( + f"Exception other than locale.Error on setting LC_ALL=('{locale_startup[0]}', 'UTF_8')") else: log_locale('After switching to UTF-8 encoding (same language)') @@ -1303,10 +1303,16 @@ sys.path: {sys.path}''' def __init__(self): logger.debug('A call from A.B.__init__') self.__test() + _ = self.test_prop def __test(self): logger.debug("A call from A.B.__test") + @property + def test_prop(self): + logger.debug("test log from property") + return "Test property is testy" + # abinit = A.B() # Plain, not via `logger`