From 39783cb020d8a88785ce1de6f516bf2d69300002 Mon Sep 17 00:00:00 2001 From: John Dennis Date: Sat, 10 Oct 2015 11:09:02 -0400 Subject: [PATCH] Properly identify code location of logging message The method Log.call_location() is used to add identifying infomation about the location in the code where a logging message is emitted from. It needs to walk up the stack to bypass calls involved in logging to find where the call to logging was made. Formerly the code has a hardcoded offset into the list of stack frame objects. But any change in the logging implementation perturbs that offset. This patch fixes that problem by walking up the stack until a non-logging function is identified. Ticket: 172 Signed-off-by: John Dennis Reviewed-by: Patrick Uiterwijk --- ipsilon/util/log.py | 64 ++++++++++++++++++++++++++++++++++++++------- 1 file changed, 55 insertions(+), 9 deletions(-) diff --git a/ipsilon/util/log.py b/ipsilon/util/log.py index 03d8c57..4350629 100644 --- a/ipsilon/util/log.py +++ b/ipsilon/util/log.py @@ -297,16 +297,62 @@ class Log(object): @staticmethod def call_location(): - frame = inspect.stack()[2] - frame_obj = frame[0] - filename = frame[1] - line_number = frame[2] - func = frame[3] + """Return string describing where in the code a logging call was made. + + When reading a log file it is very helpful to know where in the + code the message was emitted from. + + Returns a string of the form: + "filename:line_number class" + + The filename is a path truncated to the last 3 pathname + components to give just enough context without being too verbose. + + This function walks up the stack bypassing any functions that + appear to be part of the logging operation itself. The first + such frame not part of logging is assumed to be the location + of the code that emitted the log message. + + :return: string describing code location of logging call + """ + + # Each frame is a 6-tuple: + # + # the frame object, + # the filename, + # the line number of the current line, + # the function name, + # a list of lines of context from the source code, + # the index of the current line within that list + + # Walk up the stack until we find a function name which is not + # a logging function, that frame is the logging caller and hence + # the location where the call to logging was made. + try: + frame = None + frame_obj = None + stack = inspect.stack() + for frame in stack: + if frame[3] not in ('call_location', 'log', 'debug', 'error'): + break + + if not frame: + frame = stack[0] + + frame_obj = frame[0] + filename = frame[1] + line_number = frame[2] + func = frame[3] + + # Only report the last 3 components of the path + filename = os.sep.join(filename.split(os.sep)[-3:]) + + cls = Log.get_class_from_frame(frame_obj) + finally: + # Don't keep reference to frame object + del frame_obj + del stack - # Only report the last 3 components of the path - filename = os.sep.join(filename.split(os.sep)[-3:]) - - cls = Log.get_class_from_frame(frame_obj) if cls: location = '%s:%s %s.%s()' % \ (filename, line_number, cls.__name__, func) -- 2.20.1