2003-11-26 19:21:12 +01:00
|
|
|
#!/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.
|
|
|
|
###
|
|
|
|
|
|
|
|
__revision__ = "$Id$"
|
|
|
|
|
|
|
|
import fix
|
|
|
|
|
|
|
|
import os
|
|
|
|
import sys
|
2004-01-19 21:22:06 +01:00
|
|
|
import time
|
2003-12-03 23:32:33 +01:00
|
|
|
import types
|
2003-11-26 19:21:12 +01:00
|
|
|
import atexit
|
|
|
|
import logging
|
|
|
|
|
2003-12-01 16:38:42 +01:00
|
|
|
import ansi
|
2003-11-26 19:21:12 +01:00
|
|
|
import conf
|
2004-02-06 10:20:47 +01:00
|
|
|
import utils
|
2004-01-18 08:58:26 +01:00
|
|
|
import registry
|
|
|
|
|
2003-11-26 19:21:12 +01:00
|
|
|
deadlyExceptions = [KeyboardInterrupt, SystemExit]
|
|
|
|
|
|
|
|
class Formatter(logging.Formatter):
|
|
|
|
def formatTime(self, record, datefmt=None):
|
2004-04-14 04:37:17 +02:00
|
|
|
return timestamp(record.created)
|
2003-11-26 19:21:12 +01:00
|
|
|
|
|
|
|
def formatException(self, (E, e, tb)):
|
|
|
|
for exn in deadlyExceptions:
|
|
|
|
if issubclass(e.__class__, exn):
|
|
|
|
raise
|
2004-02-03 19:21:19 +01:00
|
|
|
return logging.Formatter.formatException(self, (E, e, tb))
|
2003-11-26 19:21:12 +01:00
|
|
|
|
2003-12-01 16:38:42 +01:00
|
|
|
|
2003-12-03 23:32:33 +01:00
|
|
|
class BetterStreamHandler(logging.StreamHandler):
|
|
|
|
def emit(self, record):
|
|
|
|
msg = self.format(record)
|
|
|
|
if not hasattr(types, "UnicodeType"): #if no unicode support...
|
|
|
|
self.stream.write("%s\n" % msg)
|
|
|
|
else:
|
|
|
|
try:
|
|
|
|
self.stream.write("%s\n" % msg)
|
|
|
|
except UnicodeError:
|
|
|
|
self.stream.write("%s\n" % msg.encode("UTF-8"))
|
|
|
|
self.flush()
|
2004-02-02 01:59:02 +01:00
|
|
|
|
2004-03-27 21:18:47 +01:00
|
|
|
|
2004-02-02 01:59:02 +01:00
|
|
|
class StdoutStreamHandler(BetterStreamHandler):
|
2004-04-06 11:32:09 +02:00
|
|
|
def disable(self):
|
|
|
|
self.setLevel(sys.maxint) # Just in case.
|
|
|
|
_logger.removeHandler(self)
|
|
|
|
logging._acquireLock()
|
|
|
|
try:
|
|
|
|
del logging._handlers[self]
|
|
|
|
finally:
|
|
|
|
logging._releaseLock()
|
|
|
|
|
2004-02-02 01:59:02 +01:00
|
|
|
def emit(self, record):
|
2004-04-18 07:56:31 +02:00
|
|
|
if conf.supybot.log.stdout() and not conf.daemonized:
|
2004-04-06 11:32:09 +02:00
|
|
|
try:
|
|
|
|
BetterStreamHandler.emit(self, record)
|
|
|
|
except ValueError, e: # Raised if sys.stdout is closed.
|
|
|
|
self.disable()
|
|
|
|
error('Error logging to stdout. Removing stdout handler.')
|
|
|
|
exception('Uncaught exception in StdoutStreamHandler:')
|
2004-02-02 01:59:02 +01:00
|
|
|
|
2003-12-03 23:32:33 +01:00
|
|
|
|
|
|
|
class BetterFileHandler(logging.FileHandler):
|
|
|
|
def emit(self, record):
|
|
|
|
msg = self.format(record)
|
|
|
|
if not hasattr(types, "UnicodeType"): #if no unicode support...
|
|
|
|
self.stream.write("%s\n" % msg)
|
|
|
|
else:
|
|
|
|
try:
|
|
|
|
self.stream.write("%s\n" % msg)
|
|
|
|
except UnicodeError:
|
|
|
|
self.stream.write("%s\n" % msg.encode("UTF-8"))
|
|
|
|
self.flush()
|
2004-03-25 13:17:48 +01:00
|
|
|
|
2003-12-03 23:32:33 +01:00
|
|
|
|
2003-12-01 16:38:42 +01:00
|
|
|
class ColorizedFormatter(Formatter):
|
|
|
|
def formatException(self, (E, e, tb)):
|
2004-01-18 08:58:26 +01:00
|
|
|
if conf.supybot.log.stdout.colorized():
|
2003-12-01 16:38:42 +01:00
|
|
|
return ''.join([ansi.BOLD, ansi.RED,
|
|
|
|
Formatter.formatException(self, (E, e, tb)),
|
|
|
|
ansi.RESET])
|
|
|
|
else:
|
|
|
|
return Formatter.formatException(self, (E, e, tb))
|
|
|
|
|
|
|
|
def format(self, record, *args, **kwargs):
|
2004-01-18 08:58:26 +01:00
|
|
|
if conf.supybot.log.stdout.colorized():
|
2003-12-01 16:38:42 +01:00
|
|
|
color = ''
|
|
|
|
if record.levelno == logging.CRITICAL:
|
|
|
|
color = ansi.WHITE + ansi.BOLD
|
|
|
|
elif record.levelno == logging.ERROR:
|
|
|
|
color = ansi.RED
|
|
|
|
elif record.levelno == logging.WARNING:
|
|
|
|
color = ansi.YELLOW
|
|
|
|
return ''.join([color,
|
|
|
|
Formatter.format(self, record, *args, **kwargs),
|
|
|
|
ansi.RESET])
|
|
|
|
else:
|
|
|
|
return Formatter.format(self, record, *args, **kwargs)
|
|
|
|
|
|
|
|
# These are public.
|
2003-11-26 19:21:12 +01:00
|
|
|
formatter = Formatter('%(levelname)s %(asctime)s %(message)s')
|
|
|
|
pluginFormatter = Formatter('%(levelname)s %(asctime)s %(name)s %(message)s')
|
|
|
|
|
|
|
|
# These are not.
|
|
|
|
_logger = logging.getLogger('supybot')
|
|
|
|
debug = _logger.debug
|
|
|
|
info = _logger.info
|
|
|
|
warning = _logger.warning
|
|
|
|
error = _logger.error
|
|
|
|
critical = _logger.critical
|
|
|
|
exception = _logger.exception
|
|
|
|
|
2004-01-13 19:03:01 +01:00
|
|
|
setLevel = _logger.setLevel
|
|
|
|
|
2003-11-26 19:21:12 +01:00
|
|
|
atexit.register(logging.shutdown)
|
|
|
|
|
|
|
|
def getPluginLogger(name):
|
2004-04-14 18:06:22 +02:00
|
|
|
if not conf.supybot.log.individualPluginLogfiles():
|
|
|
|
return _logger
|
2003-11-26 19:21:12 +01:00
|
|
|
log = logging.getLogger('supybot.plugins.%s' % name)
|
|
|
|
if not log.handlers:
|
|
|
|
filename = os.path.join(pluginLogDir, '%s.log' % name)
|
2003-12-03 23:32:33 +01:00
|
|
|
handler = BetterFileHandler(filename)
|
2004-01-20 11:46:18 +01:00
|
|
|
handler.setLevel(-1)
|
2003-11-26 19:21:12 +01:00
|
|
|
handler.setFormatter(pluginFormatter)
|
|
|
|
log.addHandler(handler)
|
2004-03-25 13:17:48 +01:00
|
|
|
if name in sys.modules:
|
|
|
|
# Let's log the version, this might be useful.
|
|
|
|
module = sys.modules[name]
|
|
|
|
try:
|
|
|
|
if hasattr(module, '__revision__'):
|
|
|
|
version = module.__revision__.split()[2]
|
|
|
|
log.info('Starting log for %s (revision %s)', name, version)
|
|
|
|
else:
|
|
|
|
debug('Module %s has no __revision__ string.', name)
|
|
|
|
log.info('Starting log for %s.', name)
|
|
|
|
except IndexError:
|
|
|
|
log.debug('Improper __revision__ string in %s.', name)
|
|
|
|
log.info('Starting log for %s.', name)
|
2003-11-26 19:21:12 +01:00
|
|
|
return log
|
|
|
|
|
2004-01-19 21:22:06 +01:00
|
|
|
def timestamp(when=None):
|
|
|
|
if when is None:
|
|
|
|
when = time.time()
|
|
|
|
format = conf.supybot.log.timestampFormat()
|
2004-04-14 04:37:17 +02:00
|
|
|
t = time.localtime(when)
|
|
|
|
if format:
|
|
|
|
return time.strftime(format, t)
|
|
|
|
else:
|
|
|
|
return str(int(time.mktime(t)))
|
2004-01-19 21:22:06 +01:00
|
|
|
|
2004-02-06 10:20:47 +01:00
|
|
|
def firewall(f, errorHandler=None):
|
|
|
|
def logException(self, s=None):
|
|
|
|
if s is None:
|
|
|
|
s = 'Uncaught exception'
|
|
|
|
if hasattr(self, 'log'):
|
|
|
|
self.log.exception('%s:', s)
|
|
|
|
else:
|
|
|
|
exception('%s in %s.%s:', s, self.__class__.__name__, f.func_name)
|
|
|
|
def m(self, *args, **kwargs):
|
|
|
|
try:
|
|
|
|
return f(self, *args, **kwargs)
|
|
|
|
except Exception, e:
|
|
|
|
logException(self)
|
|
|
|
if errorHandler is not None:
|
|
|
|
try:
|
|
|
|
errorHandler(self, *args, **kwargs)
|
|
|
|
except Exception, e:
|
|
|
|
logException(self, 'Uncaught exception in errorHandler')
|
|
|
|
|
|
|
|
m = utils.changeFunctionName(m, f.func_name, f.__doc__)
|
|
|
|
return m
|
|
|
|
|
|
|
|
class MetaFirewall(type):
|
|
|
|
def __new__(cls, name, bases, dict):
|
|
|
|
firewalled = {}
|
|
|
|
for base in bases:
|
|
|
|
if hasattr(base, '__firewalled__'):
|
|
|
|
firewalled.update(base.__firewalled__)
|
|
|
|
if '__firewalled__' in dict:
|
|
|
|
firewalled.update(dict['__firewalled__'])
|
|
|
|
for attr in firewalled:
|
|
|
|
if attr in dict:
|
|
|
|
try:
|
|
|
|
errorHandler = firewalled[attr]
|
|
|
|
except:
|
|
|
|
errorHandler = None
|
|
|
|
dict[attr] = firewall(dict[attr], errorHandler)
|
|
|
|
return type.__new__(cls, name, bases, dict)
|
|
|
|
|
2003-11-26 19:21:12 +01:00
|
|
|
|
2004-04-18 08:24:37 +02:00
|
|
|
class ValidLogLevel(registry.String):
|
|
|
|
"""This should be overridden."""
|
|
|
|
minimumLevel = -1
|
2004-01-20 13:10:18 +01:00
|
|
|
def set(self, s):
|
|
|
|
s = s.upper()
|
|
|
|
try:
|
2004-04-18 08:24:37 +02:00
|
|
|
level = getattr(logging, s)
|
|
|
|
if level < self.minimumLevel:
|
|
|
|
self.error()
|
|
|
|
self.setValue(level)
|
2004-01-20 13:10:18 +01:00
|
|
|
except AttributeError:
|
2004-04-13 07:19:56 +02:00
|
|
|
self.error()
|
|
|
|
|
2004-01-20 13:10:18 +01:00
|
|
|
def __str__(self):
|
|
|
|
return logging.getLevelName(self.value)
|
|
|
|
|
2004-04-18 08:24:37 +02:00
|
|
|
class LogLevel(ValidLogLevel):
|
|
|
|
"""Invalid log level. Value must be either DEBUG, INFO, WARNING, ERROR,
|
|
|
|
or CRITICAL."""
|
|
|
|
def setValue(self, v):
|
|
|
|
ValidLogLevel.setValue(self, v)
|
|
|
|
_logger.setLevel(self.value) # _logger defined later.
|
|
|
|
|
2004-01-20 13:10:18 +01:00
|
|
|
conf.supybot.directories.register('log', registry.String('logs', """Determines
|
|
|
|
what directory the bot will store its logfiles in."""))
|
|
|
|
|
2004-02-03 17:43:22 +01:00
|
|
|
conf.supybot.register('log')
|
2004-04-14 18:06:22 +02:00
|
|
|
conf.supybot.log.register('level', LogLevel(logging.INFO, """Determines what
|
|
|
|
the minimum priority level logged will be. Valid values are DEBUG, INFO,
|
|
|
|
WARNING, ERROR, and CRITICAL, in order of increasing priority."""))
|
|
|
|
conf.supybot.log.register('stdout', registry.Boolean(True, """Determines
|
|
|
|
whether the bot will log to stdout."""))
|
|
|
|
conf.supybot.log.register('individualPluginLogfiles', registry.Boolean(True,
|
|
|
|
"""Determines whether the bot will separate plugin logs into their own
|
|
|
|
individual logfiles."""))
|
2004-01-20 13:10:18 +01:00
|
|
|
|
|
|
|
class BooleanRequiredFalseOnWindows(registry.Boolean):
|
|
|
|
def set(self, s):
|
|
|
|
registry.Boolean.set(self, s)
|
|
|
|
if self.value and os.name == 'nt':
|
|
|
|
raise InvalidRegistryValue, 'Value cannot be true on Windows.'
|
|
|
|
|
|
|
|
conf.supybot.log.stdout.register('colorized',
|
|
|
|
BooleanRequiredFalseOnWindows(False, """Determines whether the bot's logs to
|
|
|
|
stdout (if enabled) will be colorized with ANSI color."""))
|
2004-02-03 19:21:19 +01:00
|
|
|
|
|
|
|
conf.supybot.log.register('timestampFormat',
|
|
|
|
registry.String('[%d-%b-%Y %H:%M:%S]',
|
|
|
|
"""Determines the format string for timestamps in logfiles. Refer to the
|
2004-04-14 04:37:17 +02:00
|
|
|
Python documentation for the time module to see what formats are accepted.
|
|
|
|
If you set this variable to the empty string, times will be logged in a
|
|
|
|
simple seconds-since-epoch format."""))
|
2004-01-20 13:10:18 +01:00
|
|
|
if not os.path.exists(conf.supybot.directories.log()):
|
|
|
|
os.mkdir(conf.supybot.directories.log(), 0755)
|
|
|
|
|
|
|
|
pluginLogDir = os.path.join(conf.supybot.directories.log(), 'plugins')
|
|
|
|
|
|
|
|
if not os.path.exists(pluginLogDir):
|
|
|
|
os.mkdir(pluginLogDir, 0755)
|
|
|
|
|
|
|
|
_handler = BetterFileHandler(os.path.join(conf.supybot.directories.log(),
|
|
|
|
'misc.log'))
|
|
|
|
_handler.setFormatter(formatter)
|
2004-04-13 02:59:19 +02:00
|
|
|
_handler.setLevel(-1)
|
2004-01-20 13:10:18 +01:00
|
|
|
_logger.addHandler(_handler)
|
2004-04-13 02:59:19 +02:00
|
|
|
_logger.setLevel(conf.supybot.log.level())
|
2004-01-20 13:10:18 +01:00
|
|
|
|
2004-04-18 07:56:31 +02:00
|
|
|
if not conf.daemonized:
|
|
|
|
_stdoutHandler = StdoutStreamHandler(sys.stdout)
|
|
|
|
_formatString = '%(name)s: %(levelname)s %(message)s'
|
|
|
|
_stdoutFormatter = ColorizedFormatter(_formatString)
|
|
|
|
_stdoutHandler.setFormatter(_stdoutFormatter)
|
|
|
|
_stdoutHandler.setLevel(-1)
|
|
|
|
_logger.addHandler(_stdoutHandler)
|
2004-01-20 13:10:18 +01:00
|
|
|
|
2004-04-12 01:12:36 +02:00
|
|
|
|
2003-11-26 19:21:12 +01:00
|
|
|
# vim:set shiftwidth=4 tabstop=8 expandtab textwidth=78:
|
|
|
|
|