[Zope3-dev] zope.exceptions.exceptionformatter

Shane Hathaway shane at hathawaymix.org
Tue Jul 19 13:34:28 EDT 2005


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
-------------- next part --------------
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):
-------------- next part --------------
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
 


More information about the Zope3-dev mailing list