From 1cb876c594d251eeac00314646fda4a9af1b6c08 Mon Sep 17 00:00:00 2001
From: A_D <aunderscored@gmail.com>
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"<property {name} on {class_name}>"
+
+                        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`