Shane Hathaway wrote:
I'd like to change Zope 3 to log exceptions using zope.exceptions.exceptionformatter. Zope's exceptionformatter formats tracebacks with information from __traceback_info__ and __traceback_supplement__ variables, which is very useful for debugging problems with page templates. I've made all the necessary changes in my own Zope 3 sandbox. I've also added an option to exceptionformatter for displaying module filenames rather than module names, so that old concern is gone.


However, the place where it's possible to properly install exceptionformatter as the traceback generator is inside ZConfig. AFAICT, ZConfig tries hard to not import anything from the zope package. So I don't know how to get my code installed without poking a hole in ZConfig. What should I do?

FWIW, here are the patches I'm proposing.

Shane
Index: zope/app/traversing/adapters.py
===================================================================
--- zope/app/traversing/adapters.py     (revision 33328)
+++ zope/app/traversing/adapters.py     (working copy)
@@ -146,6 +146,8 @@
     not provided.
 
     """
+    __traceback_info__ = (obj, name)
+
     if name == '.':
         return obj
 
Index: zope/exceptions/exceptionformatter.py
===================================================================
--- zope/exceptions/exceptionformatter.py       (revision 33328)
+++ zope/exceptions/exceptionformatter.py       (working copy)
@@ -19,6 +19,7 @@
 import sys
 import cgi
 import linecache
+import traceback
 
 DEBUG_EXCEPTION_FORMATTER = 1
 
@@ -27,14 +28,15 @@
     line_sep = '\n'
     show_revisions = 0
 
-    def __init__(self, limit=None):
+    def __init__(self, limit=None, with_filenames=False):
         self.limit = limit
+        self.with_filenames = with_filenames
 
     def escape(self, s):
         return s
 
     def getPrefix(self):
-        return 'Traceback (innermost last):'
+        return 'Traceback (most recent call last):'
 
     def getLimit(self):
         limit = self.limit
@@ -122,14 +124,16 @@
         name = co.co_name
         locals = f.f_locals
         globals = f.f_globals
-        modname = globals.get('__name__', filename)
 
-        s = '  Module %s, line %d' % (modname, lineno)
+        if self.with_filenames:
+            s = '  File "%s", line %d' % (filename, lineno)
+        else:
+            modname = globals.get('__name__', filename)
+            s = '  Module %s, line %d' % (modname, lineno)
+            revision = self.getRevision(globals)
+            if revision:
+                s = s + ', rev. %s' % revision
 
-        revision = self.getRevision(globals)
-        if revision:
-            s = s + ', rev. %s' % revision
-
         s = s + ', in %s' % name
 
         result = []
@@ -158,7 +162,6 @@
                 result.extend(self.formatSupplement(supp, tb))
             except:
                 if DEBUG_EXCEPTION_FORMATTER:
-                    import traceback
                     traceback.print_exc()
                 # else just swallow the exception.
 
@@ -168,14 +171,12 @@
                 result.append(self.formatTracebackInfo(tbi))
         except:
             if DEBUG_EXCEPTION_FORMATTER:
-                import traceback
                 traceback.print_exc()
             # else just swallow the exception.
 
         return self.line_sep.join(result)
 
     def formatExceptionOnly(self, etype, value):
-        import traceback
         return self.line_sep.join(
             traceback.format_exception_only(etype, value))
 
@@ -210,7 +211,7 @@
         return cgi.escape(s)
 
     def getPrefix(self):
-        return '<p>Traceback (innermost last):\r\n<ul>'
+        return '<p>Traceback (most recent call last):\r\n<ul>'
 
     def formatSupplementLine(self, line):
         return '<b>%s</b>' % self.escape(str(line))
@@ -233,13 +234,32 @@
 if hasattr(sys, 'tracebacklimit'):
     limit = min(limit, sys.tracebacklimit)
 
-text_formatter = TextExceptionFormatter(limit)
-html_formatter = HTMLExceptionFormatter(limit)
 
+def format_exception(t, v, tb, limit=None, as_html=False,
+                     with_filenames=False):
+    """Format a stack trace and the exception information.
 
-def format_exception(t, v, tb, limit=None, as_html=0):
+    See 'traceback.format_exception', but adds supplemental
+    information to the traceback and accepts two options, 'as_html'
+    and 'with_filenames'.
+    """
     if as_html:
-        fmt = html_formatter
+        fmt = HTMLExceptionFormatter(limit, with_filenames)
     else:
-        fmt = text_formatter
+        fmt = TextExceptionFormatter(limit, with_filenames)
     return fmt.formatException(t, v, tb)
+
+
+def print_exception(t, v, tb, limit=None, file=None, as_html=False,
+                    with_filenames=True):
+    """Print exception up to 'limit' stack trace entries from 'tb' to 'file'.
+
+    Similar to 'traceback.print_exception', but adds supplemental
+    information to the traceback and accepts two options, 'as_html'
+    and 'with_filenames'.
+    """
+    if file is None:
+        file = sys.stderr
+    lines = format_exception(t, v, tb, limit, as_html, with_filenames)
+    for line in lines:
+        file.write(line)
Index: zope/publisher/base.py
===================================================================
--- zope/publisher/base.py      (revision 33328)
+++ zope/publisher/base.py      (working copy)
@@ -18,12 +18,12 @@
 
 $Id$
 """
-import traceback
 from cStringIO import StringIO
 
 from zope.interface import implements, providedBy
 from zope.interface.common.mapping import IReadMapping, IEnumerableMapping
 from zope.publisher.interfaces import NotFound
+from zope.exceptions.exceptionformatter import print_exception
 
 from zope.publisher.interfaces import IPublication, IHeld
 from zope.publisher.interfaces import NotFound, DebugError, Unauthorized
@@ -68,7 +68,7 @@
 
     def handleException(self, exc_info):
         'See IPublisherResponse'
-        traceback.print_exception(
+        print_exception(
             exc_info[0], exc_info[1], exc_info[2], 100, self)
 
     def internalError(self):
Index: components/logger/handlers.xml
===================================================================
--- components/logger/handlers.xml      (revision 33328)
+++ components/logger/handlers.xml      (working copy)
@@ -12,6 +12,13 @@
     </description>
     <key name="dateformat"
          default="%Y-%m-%dT%H:%M:%S"/>
+    <key name="enhance-tracebacks" datatype="boolean" default="yes">
+      <description>
+        When enabled, logging uses zope.exceptions.exceptionformatter
+        to enhance exception tracebacks with information from
+        __traceback_info__ and __traceback_supplement__ variables.
+      </description>
+    </key>
     <key name="level"
          default="notset"
          datatype="ZConfig.components.logger.datatypes.logging_level"/>
Index: components/logger/handlers.py
===================================================================
--- components/logger/handlers.py       (revision 33328)
+++ components/logger/handlers.py       (working copy)
@@ -13,6 +13,7 @@
 ##############################################################################
 """ZConfig factory datatypes for log handlers."""
 
+import logging
 import sys
 
 from ZConfig.components.logger.factory import Factory
@@ -54,7 +55,23 @@
         value = value.replace(pattern, replacement)
     return value
 
+class EnhancedFormatter(logging.Formatter):
+    def formatException(self, ei):
+        """Format and return the specified exception information as a string.
 
+        Uses zope.exceptions.exceptionformatter to enhance the traceback.
+        """
+        import cStringIO
+        from zope.exceptions.exceptionformatter import print_exception
+
+        sio = cStringIO.StringIO()
+        print_exception(ei[0], ei[1], ei[2], file=sio, with_filenames=True)
+        s = sio.getvalue()
+        sio.close()
+        if s.endswith("\n"):
+            s = s[:-1]
+        return s
+
 class HandlerFactory(Factory):
     def __init__(self, section):
         Factory.__init__(self)
@@ -65,10 +82,12 @@
             "subclasses must override create_loghandler()")
 
     def create(self):
-        import logging
         logger = self.create_loghandler()
-        logger.setFormatter(logging.Formatter(self.section.format,
-                                              self.section.dateformat))
+        if self.section.enhance_tracebacks:
+            f = EnhancedFormatter
+        else:
+            f = logging.Formatter
+        logger.setFormatter(f(self.section.format, self.section.dateformat))
         logger.setLevel(self.section.level)
         return logger
 
_______________________________________________
Zope3-dev mailing list
Zope3-dev@zope.org
Unsub: http://mail.zope.org/mailman/options/zope3-dev/archive%40mail-archive.com

Reply via email to