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 124 125 126
|
"""
Logging of tracebacks and L{twisted.python.failure.Failure} instances,
as well as common utilities for handling exception logging.
"""
import traceback
import sys
from ._message import EXCEPTION_FIELD, REASON_FIELD
from ._util import safeunicode, load_module
from ._validation import MessageType, Field
from ._errors import _error_extraction
TRACEBACK_MESSAGE = MessageType(
"eliot:traceback",
[
Field(REASON_FIELD, safeunicode, "The exception's value."),
Field("traceback", safeunicode, "The traceback."),
Field(
EXCEPTION_FIELD,
lambda typ: "%s.%s" % (typ.__module__, typ.__name__),
"The exception type's FQPN.",
),
],
"An unexpected exception indicating a bug.",
)
# The fields here are actually subset of what you might get in practice,
# due to exception extraction, so we hackily modify the serializer:
TRACEBACK_MESSAGE._serializer.allow_additional_fields = True
def _writeTracebackMessage(logger, typ, exception, traceback):
"""
Write a traceback to the log.
@param typ: The class of the exception.
@param exception: The L{Exception} instance.
@param traceback: The traceback, a C{str}.
"""
msg = TRACEBACK_MESSAGE(reason=exception, traceback=traceback, exception=typ)
msg = msg.bind(**_error_extraction.get_fields_for_exception(logger, exception))
msg.write(logger)
# The default Python standard library traceback.py formatting functions
# involving reading source from disk. This is a potential performance hit
# since disk I/O can block. We therefore format the tracebacks with in-memory
# information only.
#
# Unfortunately, the easiest way to do this is... exciting.
def _get_traceback_no_io():
"""
Return a version of L{traceback} that doesn't do I/O.
"""
try:
module = load_module(str("_traceback_no_io"), traceback)
except NotImplementedError:
# Can't fix the I/O problem, oh well:
return traceback
class FakeLineCache(object):
def checkcache(self, *args, **kwargs):
None
def getline(self, *args, **kwargs):
return ""
def lazycache(self, *args, **kwargs):
return None
module.linecache = FakeLineCache()
return module
_traceback_no_io = _get_traceback_no_io()
def write_traceback(logger=None, exc_info=None):
"""
Write the latest traceback to the log.
This should be used inside an C{except} block. For example:
try:
dostuff()
except:
write_traceback(logger)
Or you can pass the result of C{sys.exc_info()} to the C{exc_info}
parameter.
"""
if exc_info is None:
exc_info = sys.exc_info()
typ, exception, tb = exc_info
traceback = "".join(_traceback_no_io.format_exception(typ, exception, tb))
_writeTracebackMessage(logger, typ, exception, traceback)
def writeFailure(failure, logger=None):
"""
Write a L{twisted.python.failure.Failure} to the log.
This is for situations where you got an unexpected exception and want to
log a traceback. For example, if you have C{Deferred} that might error,
you'll want to wrap it with a L{eliot.twisted.DeferredContext} and then add
C{writeFailure} as the error handler to get the traceback logged:
d = DeferredContext(dostuff())
d.addCallback(process)
# Final error handler.
d.addErrback(writeFailure)
@param failure: L{Failure} to write to the log.
@type logger: L{eliot.ILogger}. Will be deprecated at some point, so just
ignore it.
@return: None
"""
# Failure.getBriefTraceback does not include source code, so does not do
# I/O.
_writeTracebackMessage(
logger, failure.value.__class__, failure.value, failure.getBriefTraceback()
)
|