diff options
author | John Dennis <jdennis@redhat.com> | 2015-01-12 10:47:37 -0500 |
---|---|---|
committer | Simo Sorce <simo@redhat.com> | 2015-01-26 14:01:03 -0500 |
commit | 74194d5d36cfc1be67e92924585731e9b3894605 (patch) | |
tree | 8f1f6c953615957d7af9954b9a5f6d56c6eb2045 | |
parent | e85f190d8e081fbbfeadca24781266d1c3e1bba1 (diff) | |
download | ipsilon-74194d5d36cfc1be67e92924585731e9b3894605.tar.gz ipsilon-74194d5d36cfc1be67e92924585731e9b3894605.tar.xz ipsilon-74194d5d36cfc1be67e92924585731e9b3894605.zip |
Add source code context information to debug logs
The log.debug() function helpfully adds the name of the function
invoking it but in a complicated software package there are many
functions/methods which share the same name. Thus a debug message
like this:
DEBUG(__init__): xxx
does not give you much context, there are probably hundreds of
__init__ methods. It would help to qualify the method name which it's
class name, that gives a lot more context when reading the
log. Sometimes it's also helpful to know the file and line number.
This patch adds the class name to the function and included the
filename and line number as well. The file path is trimmed to the last
3 components, sufficient to give context but not too verbose. Now the
debug message might look like this instead:
DEBUG(ipsilon/providers/common.py:129 LoadProviders.__init__()): xxx
Also included is a config option 'stacktrace_on_error' which will
include a stacktrace when the log.error function is called. It can be
very useful to see a stacktrace when logging an error, it defaults to
off.
Signed-off-by: John Dennis <jdennis@redhat.com>
Reviewed-by: Simo Sorce <simo@redhat.com>
-rw-r--r-- | ipsilon/util/log.py | 67 |
1 files changed, 64 insertions, 3 deletions
diff --git a/ipsilon/util/log.py b/ipsilon/util/log.py index a010b16..857a5ba 100644 --- a/ipsilon/util/log.py +++ b/ipsilon/util/log.py @@ -3,8 +3,10 @@ # See the file named COPYING for the project license import cherrypy -import inspect import cStringIO +import inspect +import os +import traceback def log_request_response(): '''Log the contents of the request and subsequent response. @@ -246,10 +248,67 @@ cherrypy.tools.log_request_response = cherrypy.Tool('on_end_resource', log_reque class Log(object): + @staticmethod + def stacktrace(): + buf = cStringIO.StringIO() + + stack = traceback.extract_stack() + traceback.print_list(stack[:-2], file=buf) + + stacktrace_string = buf.getvalue() + buf.close() + return stacktrace_string + + @staticmethod + def get_class_from_frame(frame_obj): + ''' + Taken from: + http://stackoverflow.com/questions/2203424/ + python-how-to-retrieve-class-information-from-a-frame-object + + At the frame object level, there does not seem to be any way + to find the actual python function object that has been + called. + + However, if your code relies on the common convention of naming + the instance parameter of a method self, then you could do this. + ''' + + args, _, _, value_dict = inspect.getargvalues(frame_obj) + # Is the functions first parameter named 'self'? + if len(args) and args[0] == 'self': + # in that case, 'self' will be referenced in value_dict + instance = value_dict.get('self', None) + if instance: + # return its class + return getattr(instance, '__class__', None) + # return None otherwise + return None + + @staticmethod + def call_location(): + frame = inspect.stack()[2] + 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) + if cls: + location = '%s:%s %s.%s()' % \ + (filename, line_number, cls.__name__, func) + else: + location = '%s:%s %s()' % (filename, line_number, func) + return location + + def debug(self, fact): if cherrypy.config.get('debug', False): - s = inspect.stack() - cherrypy.log('DEBUG(%s): %s' % (s[1][3], fact)) + location = Log.call_location() + cherrypy.log('DEBUG(%s): %s' % (location, fact)) # for compatibility with existing code _debug = debug @@ -259,3 +318,5 @@ class Log(object): def error(self, fact): cherrypy.log.error('ERROR: %s' % fact) + if cherrypy.config.get('stacktrace_on_error', False): + cherrypy.log.error(Log.stacktrace()) |