123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332 |
- ##############################################################################
- #
- # Copyright (c) 2007 Agendaless Consulting and Contributors.
- # All Rights Reserved.
- #
- # This software is subject to the provisions of the BSD-like license at
- # http://www.repoze.org/LICENSE.txt. A copy of the license should accompany
- # this distribution. THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL
- # EXPRESS OR IMPLIED WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO,
- # THE IMPLIED WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND
- # FITNESS FOR A PARTICULAR PURPOSE
- #
- ##############################################################################
- """
- Logger implementation loosely modeled on PEP 282. We don't use the
- PEP 282 logger implementation in the stdlib ('logging') because it's
- idiosyncratic and a bit slow for our purposes (we don't use threads).
- """
- # This module must not depend on any non-stdlib modules to
- # avoid circular import problems
- import os
- import errno
- import sys
- import time
- import traceback
- class LevelsByName:
- CRIT = 50 # messages that probably require immediate user attention
- ERRO = 40 # messages that indicate a potentially ignorable error condition
- WARN = 30 # messages that indicate issues which aren't errors
- INFO = 20 # normal informational output
- DEBG = 10 # messages useful for users trying to debug configurations
- TRAC = 5 # messages useful to developers trying to debug plugins
- BLAT = 3 # messages useful for developers trying to debug supervisor
- class LevelsByDescription:
- critical = LevelsByName.CRIT
- error = LevelsByName.ERRO
- warn = LevelsByName.WARN
- info = LevelsByName.INFO
- debug = LevelsByName.DEBG
- trace = LevelsByName.TRAC
- blather = LevelsByName.BLAT
- def _levelNumbers():
- bynumber = {}
- for name, number in LevelsByName.__dict__.items():
- bynumber[number] = name
- return bynumber
- LOG_LEVELS_BY_NUM = _levelNumbers()
- def getLevelNumByDescription(description):
- num = getattr(LevelsByDescription, description, None)
- return num
- class Handler:
- fmt = '%(message)s'
- level = LevelsByName.INFO
- def setFormat(self, fmt):
- self.fmt = fmt
- def setLevel(self, level):
- self.level = level
- def flush(self):
- try:
- self.stream.flush()
- except IOError, why:
- # if supervisor output is piped, EPIPE can be raised at exit
- if why[0] != errno.EPIPE:
- raise
- def close(self):
- if hasattr(self.stream, 'fileno'):
- fd = self.stream.fileno()
- if fd < 3: # don't ever close stdout or stderr
- return
- self.stream.close()
- def emit(self, record):
- try:
- msg = self.fmt % record.asdict()
- try:
- self.stream.write(msg)
- except UnicodeError:
- self.stream.write(msg.encode("UTF-8"))
- self.flush()
- except:
- self.handleError(record)
- def handleError(self, record):
- ei = sys.exc_info()
- traceback.print_exception(ei[0], ei[1], ei[2], None, sys.stderr)
- del ei
- class FileHandler(Handler):
- """File handler which supports reopening of logs.
- """
- def __init__(self, filename, mode="a"):
- self.stream = open(filename, mode)
- self.baseFilename = filename
- self.mode = mode
- def reopen(self):
- self.close()
- self.stream = open(self.baseFilename, self.mode)
- def remove(self):
- try:
- os.remove(self.baseFilename)
- except OSError, why:
- if why[0] != errno.ENOENT:
- raise
- class StreamHandler(Handler):
- def __init__(self, strm=None):
- self.stream = strm
-
- def remove(self):
- if hasattr(self.stream, 'clear'):
- self.stream.clear()
- def reopen(self):
- pass
- class BoundIO:
- def __init__(self, maxbytes, buf=''):
- self.maxbytes = maxbytes
- self.buf = buf
- def flush(self):
- pass
- def close(self):
- self.clear()
- def write(self, s):
- slen = len(s)
- if len(self.buf) + slen > self.maxbytes:
- self.buf = self.buf[slen:]
- self.buf += s
- def getvalue(self):
- return self.buf
- def clear(self):
- self.buf = ''
-
- class RotatingFileHandler(FileHandler):
- def __init__(self, filename, mode='a', maxBytes=512*1024*1024,
- backupCount=10):
- """
- Open the specified file and use it as the stream for logging.
- By default, the file grows indefinitely. You can specify particular
- values of maxBytes and backupCount to allow the file to rollover at
- a predetermined size.
- Rollover occurs whenever the current log file is nearly maxBytes in
- length. If backupCount is >= 1, the system will successively create
- new files with the same pathname as the base file, but with extensions
- ".1", ".2" etc. appended to it. For example, with a backupCount of 5
- and a base file name of "app.log", you would get "app.log",
- "app.log.1", "app.log.2", ... through to "app.log.5". The file being
- written to is always "app.log" - when it gets filled up, it is closed
- and renamed to "app.log.1", and if files "app.log.1", "app.log.2" etc.
- exist, then they are renamed to "app.log.2", "app.log.3" etc.
- respectively.
- If maxBytes is zero, rollover never occurs.
- """
- if maxBytes > 0:
- mode = 'a' # doesn't make sense otherwise!
- FileHandler.__init__(self, filename, mode)
- self.maxBytes = maxBytes
- self.backupCount = backupCount
- self.counter = 0
- self.every = 10
- def emit(self, record):
- """
- Emit a record.
- Output the record to the file, catering for rollover as described
- in doRollover().
- """
- FileHandler.emit(self, record)
- self.doRollover()
- def doRollover(self):
- """
- Do a rollover, as described in __init__().
- """
- if self.maxBytes <= 0:
- return
- try:
- pos = self.stream.tell()
- except IOError, why:
- # Attempt to trap IOError: [Errno 29] Illegal seek
- print self.baseFilename, self.maxBytes, self.stream
- raise
-
- if not (self.stream.tell() >= self.maxBytes):
- return
- self.stream.close()
- if self.backupCount > 0:
- for i in range(self.backupCount - 1, 0, -1):
- sfn = "%s.%d" % (self.baseFilename, i)
- dfn = "%s.%d" % (self.baseFilename, i + 1)
- if os.path.exists(sfn):
- if os.path.exists(dfn):
- os.remove(dfn)
- os.rename(sfn, dfn)
- dfn = self.baseFilename + ".1"
- if os.path.exists(dfn):
- os.remove(dfn)
- os.rename(self.baseFilename, dfn)
- self.stream = open(self.baseFilename, 'w')
- class LogRecord:
- def __init__(self, level, msg, **kw):
- self.level = level
- self.msg = msg
- self.kw = kw
- self.dictrepr = None
- def asdict(self):
- if self.dictrepr is None:
- now = time.time()
- msecs = (now - long(now)) * 1000
- part1 = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(now))
- asctime = '%s,%03d' % (part1, msecs)
- levelname = LOG_LEVELS_BY_NUM[self.level]
- if self.kw:
- msg = self.msg % self.kw
- else:
- msg = self.msg
- self.dictrepr = {'message':msg, 'levelname':levelname,
- 'asctime':asctime}
- return self.dictrepr
- class Logger:
- def __init__(self, level=None, handlers=None):
- if level is None:
- level = LevelsByName.INFO
- self.level = level
- if handlers is None:
- handlers = []
- self.handlers = handlers
- def close(self):
- for handler in self.handlers:
- handler.close()
- def blather(self, msg, **kw):
- if LevelsByName.BLAT >= self.level:
- self.log(LevelsByName.BLAT, msg, **kw)
- def trace(self, msg, **kw):
- if LevelsByName.TRAC >= self.level:
- self.log(LevelsByName.TRAC, msg, **kw)
-
- def debug(self, msg, **kw):
- if LevelsByName.DEBG >= self.level:
- self.log(LevelsByName.DEBG, msg, **kw)
-
- def info(self, msg, **kw):
- if LevelsByName.INFO >= self.level:
- self.log(LevelsByName.INFO, msg, **kw)
- def warn(self, msg, **kw):
- if LevelsByName.WARN >= self.level:
- self.log(LevelsByName.WARN, msg, **kw)
- def error(self, msg, **kw):
- if LevelsByName.ERRO >= self.level:
- self.log(LevelsByName.ERRO, msg, **kw)
- def critical(self, msg, **kw):
- if LevelsByName.CRIT >= self.level:
- self.log(LevelsByName.CRIT, msg, **kw)
- def log(self, level, msg, **kw):
- record = LogRecord(level, msg, **kw)
- for handler in self.handlers:
- if level >= handler.level:
- handler.emit(record)
- def addHandler(self, hdlr):
- self.handlers.append(hdlr)
- def getvalue(self):
- raise NotImplementedError
- def getLogger(filename, level, fmt, rotating=False, maxbytes=0, backups=0,
- stdout=False):
- handlers = []
- logger = Logger(level)
-
- if filename is None:
- if not maxbytes:
- maxbytes = 1<<21 #2MB
- io = BoundIO(maxbytes)
- handlers.append(StreamHandler(io))
- logger.getvalue = io.getvalue
- else:
- if rotating is False:
- handlers.append(FileHandler(filename))
- else:
- handlers.append(RotatingFileHandler(filename,'a',maxbytes,backups))
- if stdout:
- handlers.append(StreamHandler(sys.stdout))
- for handler in handlers:
- handler.setFormat(fmt)
- handler.setLevel(level)
- logger.addHandler(handler)
- return logger
|