Limnoria/src/debug.py

216 lines
6.2 KiB
Python

#!/usr/bin/env python
###
# Copyright (c) 2002, Jeremiah Fincher
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice,
# this list of conditions, and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions, and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
# * Neither the name of the author of this software nor the name of
# contributors to this software may be used to endorse or promote products
# derived from this software without specific prior written consent.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
###
from fix import *
import sys
import os
import os.path
import sys
import time
import cgitb
import traceback
import ansi
try:
import conf
except ImportError:
class conf:
logDir = '.'
detailedTracebacks = True
import world
###
# CONFIGURATION
###
# Names of logfiles.
if not os.path.exists(conf.logDir):
os.mkdir(conf.logDir)
errorfile = os.path.join(conf.logDir, 'error.log')
debugfile = os.path.join(conf.logDir, 'debug.log')
tracefile = os.path.join(conf.logDir, 'trace.log')
# stderr: True if messages should be written to stderr as well as logged.
stderr = True
# colorterm: True if the terminal run on is color.
if sys.platform == 'win32':
colorterm = False
else:
colorterm = True
# printf: True if printf debugging messages should be printed.
printf = False
# minimumDebugPriority: Lowest priority logged;
# One of {'verbose', 'low', 'normal', 'high'}.
minimumDebugPriority = 'verbose'
# deadlyExceptions: Exceptions that should cause immediate failure.
deadlyExceptions = [KeyboardInterrupt, SystemExit]
###
# END CONFIGURATION
###
_errorfd = file(errorfile, 'a')
_debugfd = file(debugfile, 'a')
_tracefd = file(tracefile, 'w')
priorities = { 'verbose': 0,
'low': 1,
'normal': 2,
'high': 3 }
priorityColors = { 'verbose': ansi.BLUE,
'low': ansi.CYAN,
'normal': ansi.GREEN,
'high': ansi.BOLD }
priorityColors.setdefault('')
# This is a queue of the time of the last 10 calls to recoverableException.
# If the most recent time is
lastTimes = [time.time()-1] * 10
def exit(i=-1):
class E(Exception):
pass
if deadlyExceptions:
# Just to be safe, we'll make it a subclass of *all* the deadly
# exceptions :)
for exn in deadlyExceptions:
class E(exn, E):
pass
raise E
else:
os._exit(i)
def _writeNewline(fd, s):
fd.write(s)
if s[-len(os.linesep):] != os.linesep:
fd.write(os.linesep)
def recoverableError(msg):
"""Called with errors that are not critical.
"""
if stderr:
if colorterm:
sys.stderr.write(ansi.BOLD + ansi.RED)
_writeNewline(sys.stderr, msg)
if colorterm:
sys.stderr.write(ansi.RESET)
_writeNewline(_errorfd, msg)
_errorfd.flush()
def unrecoverableError(msg):
recoverableError(msg)
exit(-1)
def recoverableException(type='detailed'):
"""Logs an exception that's recoverable.
The 'type' argument can be either 'detailed' (for a nice cgitb traceback),
'normal' (for a normal traceback), or 'terse' (for a short line stating
the exception raised).
"""
if world.testing:
return
(E, e, tb) = sys.exc_info()
for exn in deadlyExceptions:
if issubclass(e.__class__, exn):
raise
lastTimes.append(time.time())
if lastTimes[-1] - lastTimes[0] < 0.50:
msg('Too many exceptions too quickly. Bailing out.', 'high')
exit()
else:
del lastTimes[0]
try:
if type == 'detailed':
if conf.detailedTracebacks:
text = cgitb.text((E, e, tb))
else:
text = ''.join(traceback.format_exception(E, e, tb))
elif type == 'normal':
text = ''.join(traceback.format_exception(E, e, tb))
elif type == 'terse':
text = exnToString(e)
except:
text = ''.join(traceback.format_exception(E, e, tb))
del tb # just to be safe.
if stderr:
if colorterm:
sys.stderr.write(ansi.BOLD + ansi.RED)
sys.stderr.write(text)
if colorterm:
sys.stderr.write(ansi.RESET)
_errorfd.write(text)
_errorfd.flush()
def unrecoverableException():
recoverableException()
exit(-1)
def msg(s, priority='low'):
if priorities[priority] >= priorities[minimumDebugPriority]:
if stderr:
if colorterm:
sys.stderr.write(priorityColors.get(priority))
_writeNewline(sys.stderr, s)
if colorterm:
sys.stderr.write(ansi.RESET)
s = '%s: %s' % (priority.upper(), s)
_writeNewline(_debugfd, s)
_debugfd.flush()
def printf(msg):
if printf:
print '*** ' + str(msg)
def methodNamePrintf(obj, methodName):
printf('%s: %s' % (obj.__class__.__name__, methodName))
def exnToString(e):
return '%s: %s' % (e.__class__.__name__, e)
def tracer(frame, event, _):
if event == 'call':
s = '%s: %s\n' % (frame.f_code.co_filename, frame.f_code.co_name)
_tracefd.write(s)
_tracefd.flush()
# vim:set shiftwidth=4 tabstop=8 expandtab textwidth=78: