Use python logging in install / log cherrypy at right severity
[cascardo/ipsilon.git] / ipsilon / util / log.py
index a010b16..ae851af 100644 (file)
@@ -3,8 +3,12 @@
 # See the file named COPYING for the project license
 
 import cherrypy
-import inspect
 import cStringIO
+import inspect
+import os
+import traceback
+import logging
+
 
 def log_request_response():
     '''Log the contents of the request and subsequent response.
@@ -79,7 +83,7 @@ def log_request_response():
 
     '''
 
-    #--- Begin local functions ---
+    # --- Begin local functions ---
 
     def indent_text(text, level=0, indent='    '):
         '''
@@ -133,6 +137,21 @@ def log_request_response():
         f.close()
         return string
 
+    def print_param(name, value):
+        f = cStringIO.StringIO()
+
+        # Might be a multipart Part object, if so format it
+        if isinstance(value, cherrypy._cpreqbody.Part):  # pylint:disable=W0212
+            f.write(indent_text("%s:\n" % (name)))
+            f.write(indent_text(print_part(value), 1))
+        else:
+            # Not a mulitpart, just write it as a string
+            f.write(indent_text("%s: %s\n" % (name, value)))
+
+        string = f.getvalue()
+        f.close()
+        return string
+
     def collapse_body(body):
         '''The cherrypy response body can be:
 
@@ -168,7 +187,7 @@ def log_request_response():
         f.close()
         return string
 
-    #--- End local functions ---
+    # --- End local functions ---
 
     f = cStringIO.StringIO()
     request = cherrypy.serving.request
@@ -177,8 +196,8 @@ def log_request_response():
     #
     # Log the Request
     #
-    f.write(indent_text("<Request> [%s] %s\n" % \
-                      (remote.name or remote.ip, request.request_line), 0))
+    f.write(indent_text("<Request> [%s] %s\n" %
+                        (remote.name or remote.ip, request.request_line), 0))
 
     # Request Headers
     if request.headers:
@@ -194,16 +213,10 @@ def log_request_response():
             # Multi-valued paramater is in a list
             if isinstance(value, list):
                 for i, item in enumerate(value):
-                    # Might be a multipart Part object, if so format it
-                    if isinstance(item, cherrypy._cpreqbody.Part):
-                        f.write(indent_text("%s[%s]:\n" % (name, i), 2))
-                        f.write(indent_text(print_part(item), 3))
-                    else:
-                        # Not a mulitpart, just write it as a string
-                        f.write(indent_text("%s[%s]: %s\n" % (name, i, item), 2))
+                    f.write(indent_text(print_param("%s[%d]" % (name, i),
+                                                    item), 2))
             else:
-                # Just a string value
-                f.write(indent_text("%s: %s\n" % (name, value), 2))
+                f.write(indent_text(print_param(name, value), 2))
 
     # If the body is multipart format each of the parts
     if request.body.parts:
@@ -241,15 +254,72 @@ def log_request_response():
     f.close()
     print string
 
-cherrypy.tools.log_request_response = cherrypy.Tool('on_end_resource', log_request_response)
+cherrypy.tools.log_request_response = cherrypy.Tool('on_end_resource',
+                                                    log_request_response)
 
 
 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
@@ -258,4 +328,6 @@ class Log(object):
         cherrypy.log(fact)
 
     def error(self, fact):
-        cherrypy.log.error('ERROR: %s' % fact)
+        cherrypy.log.error('ERROR: %s' % fact, severity=logging.ERROR)
+        if cherrypy.config.get('stacktrace_on_error', False):
+            cherrypy.log.error(Log.stacktrace())