1
0
mirror of https://github.com/rzzzwilson/pymlac.git synced 2025-06-10 09:32:41 +00:00
2015-10-15 12:37:14 +07:00

213 lines
5.9 KiB
Python

#!/usr/bin/env python
"""
A simple logger.
Simple usage:
import log
log = log.Log('my_log.log', log.Log.DEBUG)
log('A line in the log at the default level (DEBUG)')
log('A log line at WARN level', Log.WARN)
log.info('log line issued at INFO level')
Based on the 'borg' recipe from [http://code.activestate.com/recipes/66531/].
Log levels styled on the Python 'logging' module.
Log output includes the module and line # of the log() call.
"""
import os
import sys
import datetime
import traceback
################################################################################
# A simple (?) logger.
################################################################################
class Log(object):
__shared_state = {} # this __dict__ shared by ALL instances
# the predefined logging levels
CRITICAL = 50
ERROR = 40
WARN = 30
INFO = 20
DEBUG = 10
NOTSET = 0
_level_num_to_name = {NOTSET: 'NOTSET',
DEBUG: 'DEBUG',
INFO: 'INFO',
WARN: 'WARN',
ERROR: 'ERROR',
CRITICAL: 'CRITICAL'}
# default maximum length of filename (enforced)
DefaultMaxFname = 15
def __init__(self, logfile=None, level=NOTSET, append=False,
max_fname=DefaultMaxFname):
"""Initialise the logging object.
logfile the path to the log file
level logging level - don't log below this level
append True if log file is appended to
"""
# make sure we have same state as all other log objects
self.__dict__ = Log.__shared_state
self.max_fname = max_fname
self.sym_level = 'NOTSET' # set in call to check_level()
self.level = self.check_level(level)
# don't allow logfile to change after initially set
if not hasattr(self, 'logfile'):
if logfile is None:
logfile = '%s.log' % __name__
try:
if append:
self.logfd = open(logfile, 'a')
else:
self.logfd = open(logfile, 'w')
except IOError:
# assume we have readonly filesystem
basefile = os.path.basename(logfile)
if sys.platform == 'win32':
logfile = os.path.join('C:\\', basefile)
else:
logfile = os.path.join('~', basefile)
# try to open logfile again
if append:
self.logfd = open(logfile, 'a')
else:
self.logfd = open(logfile, 'w')
self.logfile = logfile
self.critical('='*55)
self.critical('Log started on %s, log level=%s'
% (datetime.datetime.now().ctime(),
self._level_num_to_name[level]))
self.critical('-'*55)
def check_level(self, level):
"""Check the level value for legality.
If 'level' is invalid, raise Exception. If valid, return value.
"""
try:
level = int(level)
except ValueError:
msg = "Logging level invalid: '%s'" % str(level)
print(msg)
raise Exception(msg)
if not 0 <= level <= 50:
msg = "Logging level invalid: '%s'" % str(level)
print(msg)
raise Exception(msg)
return level
def set_level(self, level):
"""Set logging level."""
level = self.check_level(level)
# convert numeric level to symbolic
sym = self._level_num_to_name.get(level, None)
if sym is None:
# not recognized symbolic but it's legal, so interpret as 'XXXX+2'
sym_10 = 10 * (level/10)
sym_rem = level - sym_10
sym = '%s+%d' % (self._level_num_to_name[sym_10], sym_rem)
self.level = level
self.sym_level = sym
self.critical('Logging level set to %02d (%s)' % (level, sym))
def __call__(self, msg=None, level=None):
"""Call on the logging object.
msg message string to log
level level to log 'msg' at (if not given, assume self.level)
"""
# get level to log at
if level is None:
level = self.level
# are we going to log?
if level < self.level:
return
if msg is None:
msg = ''
# get time
to = datetime.datetime.now()
hr = to.hour
min = to.minute
sec = to.second
msec = to.microsecond
# caller information - look back for first module != <this module name>
frames = traceback.extract_stack()
frames.reverse()
try:
(_, mod_name) = __name__.rsplit('.', 1)
except ValueError:
mod_name = __name__
for (fpath, lnum, mname, _) in frames:
fname = os.path.basename(fpath).rsplit('.', 1)[0]
if fname != mod_name:
break
# get string for log level
loglevel = self._level_num_to_name[level]
fname = fname[:self.max_fname]
self.logfd.write('%02d:%02d:%02d.%06d|%8s|%*s:%-4d|%s\n'
% (hr, min, sec, msec, loglevel, self.max_fname,
fname, lnum, msg))
self.logfd.flush()
def critical(self, msg):
"""Log a message at CRITICAL level."""
self(msg, self.CRITICAL)
def error(self, msg):
"""Log a message at ERROR level."""
self(msg, self.ERROR)
def warn(self, msg):
"""Log a message at WARN level."""
self(msg, self.WARN)
def info(self, msg):
"""Log a message at INFO level."""
self(msg, self.INFO)
def debug(self, msg):
"""Log a message at DEBUG level."""
self(msg, self.DEBUG)
def __del__(self):
self.logfd.close()