* find_caller_frame() to do the frame walk.
* munge_module_name() to fix up for plugins.
NB: caller_attributes() now has a noqa on CCR001 as I don't think it
can sensibly be made any less complex. Pulling out the 'if frame:'
section just results in *that* new method then being labelled as too
complex.:244
* Settled on `plugins.internal` and `<plugins>.found` as the format.
* A PyCharm recommendation was to use 'cls' instead of 'self' on class
methods, so the class detection code needs to cater for that.
Technically a developer could use any string for the "myself" member
name, but we'll assume just these two.
* Found will always have at least one folder level within plugin_dir
* Internal should always have *no* folder within internal_plugin_dir,
but cater for it just in case in future.
1. This makes setting up logging everywhere slightly more involved.
2. If I then want to change, say, %(module)s value I'll end up needing
to stack walk again.
So this might be better done in a filter. But these commits for the
record, and to come back to if needs be.
* Log messages propagate up Parent.Child chains, so we don't need a
channel on the plugin logger.
* But it still needs the filter to define qualname and class for
formatting.
* <module> works.
* top-level function in <module> works, presumably also any other file.
* Call from within classes works.
* Extra, commented out, test cases in EDMarketConnector.py
* Technically %(class)s can be e.g. A.B not just 'B' so say "name(s)".
* To not confuse EDMCLogging.getLogger() with logging.getLogger() it's
been renamed to get_logger().
* Note how we signal errors with finding class and/or qualname.
* Call out EDMCLogging.py in Contributing.md.
* Added/fleshed out docstrings on file, classes and functions.
* No need to use a function for the stack frame getting.
* Check if LogRecord has class or qualname before setting, allowing
upstream to implement them.
* Use setattr()/getattr() rather than __dict__ fiddling.
* Force an error string into class/qualname if we have issues finding
them, rather than failing silently to ''.
* Also now providers single caller_class_and_qualname() method to get
both strings, returned as a Tuple[str, str]. Either could be empty
if something went wrong.
* Rather than a fragile 'skip' this now:
1. Looks for the first up-stack frame with self of logging.Logger
1. Then looks for the next up-stack frame with self NOT of
logging.Logger. This should be the call site we want.