Have you ever tried to log an exception of unknown type ? What's in it ? How to fetch stack trace ? Will str(e) return plain ascii or international chars ? Is logger ready for it ? This recipe provides a formatting function.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 | #!/usr/bin/env python
# -*- coding: iso-8859-1 -*-
################################################################################
#
# Utility functions for formatting exceptions and stack traces so that they are
# guaranteed to fit in a single line and contain only chars in specified encoding.
# Very useful for logging and handling dead end exceptions.
#
# Written by Dmitry Dvoinikov <dmitry@targeted.org> (c) 2005
# Distributed under MIT license.
#
# Sample (test.py), line numbers added for clarity:
#
# 1. from exc_string import *
# 2: set_exc_string_encoding("ascii")
# 3: class foo(object):
# 4: def __init__(self):
# 5: raise Exception("z\xffz\n") # note non-ascii char in the middle and newline
# 6: try:
# 7: foo()
# 8: except:
# 9: assert exc_string() == "Exception(\"z?z \") in __init__() (test.py:5) <- ?() (test.py:7)"
#
# The (2 times longer) source code with self-tests is available from:
# http://www.targeted.org/python/recipes/exc_string.py
#
# (c) 2005 Dmitry Dvoinikov <dmitry@targeted.org>
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights to
# use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
# of the Software, and to permit persons to whom the Software is furnished to do
# so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
#
################################################################################
__all__ = [ "exc_string", "trace_string", "force_string",
"get_exc_string_encoding", "set_exc_string_encoding" ]
###############################################################################
from sys import exc_info
from traceback import extract_stack, extract_tb
from os import path
###############################################################################
exc_string_encoding = "windows-1251"
def get_exc_string_encoding():
return exc_string_encoding
def set_exc_string_encoding(encoding):
global exc_string_encoding
exc_string_encoding = encoding
###############################################################################
force_string_translate_map = " ????????\t ?? ??????????????????" + "".join([ chr(i) for i in range(32, 256) ])
def force_string(v):
if isinstance(v, str):
v = v.decode(exc_string_encoding, "replace").encode(exc_string_encoding, "replace")
return v.translate(force_string_translate_map)
elif isinstance(v, unicode):
v = v.encode(exc_string_encoding, "replace")
return v.translate(force_string_translate_map)
else:
try:
v = str(v)
except:
return "unable to convert %s to string, str() failed" % v.__class__.__name__
else:
return force_string(v)
###############################################################################
def _reversed(r):
result = list(r)
result.reverse()
return result
def trace_string(tb = None):
return " <- ".join([ force_string("%s() (%s:%s)" % (m, path.split(f)[1], n))
for f, n, m, u in _reversed(tb or extract_stack()[:-1]) ])
###############################################################################
def exc_string():
try:
t, v, tb = exc_info()
if t is None:
return "no exception"
if v is not None:
v = force_string(v)
else:
v = force_string(t)
if hasattr(t, "__name__"):
t = t.__name__
else:
t = type(t).__name__
return "%s(\"%s\") in %s" % (t, v, trace_string(extract_tb(tb)))
except:
return "exc_string() failed to extract exception string"
################################################################################
# EOF
|
It's always painful to try to log an exception. You have no idea what's in it and str() won't help, because (1) str() returns a string in unknown encoding likely unsuitable for your logger-of-the-week or worse (2) str() fails itself. Besides, you need to fetch stack trace from somewhere else and it's huge and ugly.
This recipe provides an exc_string() function which hides all this and returns a pretty single line with exception text and stack trace all in specified encoding.
Odd but cool. Your snippet is odd since we must invoke a global function in except scope OTOH it's pretty handy and encoding awareness is rare.
Maybe we could use this into a metaclass hook, that would be kinda cool. Thanx for share it.