Forked from
DM / dm-docs
261 commits behind, 90 commits ahead of the upstream repository.
Code owners
Assign users and groups as approvers for specific file changes. Learn more.
loggingManager.py 12.75 KiB
#!/usr/bin/env python
#
# Logging manager singleton.
#
#######################################################################
import re
import sys
import os.path
import logging
import ConfigParser
from dm.common.utility import configurationManager
from dm.common.exceptions.configurationError import ConfigurationError
#######################################################################
# Get singleton instance.
def getInstance():
from dm.common.utility.loggingManager import LoggingManager
try:
lm = LoggingManager()
except LoggingManager, ex:
lm = ex
return lm
def getLogger(name='defaultLogger'):
""" Convenience function to obtain logger. """
return getInstance().getLogger(name)
def setConsoleLogLevel(level):
""" Convenience function to set the console log level. """
return getInstance().setConsoleLogLevel(level)
def setFileLogLevel(level):
""" Convenience function to set the file log level. """
return getInstance().setFileLogLevel(level)
class LoggingManager:
"""
Configuration:
The log manager class is initialized via a configuration file
that may have the following sections:
ConsoleLogging # Used for output on the screen
FileLogging # Used for logging into a file
Each section in the configuration file should have the following
keys:
handler # Indicates which handler class to use
level # Indicates logging level
format # Indicates format for log messages
dateformat # Indicates date format used for log messages
Given below is an example of a valid configuration file:
[ConsoleLogging]
handler=ConsoleLoggingHandler(sys.stdout,)
level=info
format=[%(levelname)s] %(message)s
dateformat=%m/%d/%y %H:%M:%S
[FileLogging]
handler=TimedRotatingFileLoggingHandler('/tmp/dm.log')
level=debug
format=%(asctime)s,%(msecs)d [%(levelname)s] %(module)s:%(lineno)d %(user)s@%(host)s %(name)s (%(process)d): %(message)s
dateformat=%m/%d/%y %H:%M:%S
"""
# Singleton.
__instance = None
def __init__(self):
if LoggingManager.__instance:
raise LoggingManager.__instance
LoggingManager.__instance = self
self._consoleHandler = None
self._fileHandlerList = []
self._maxIntLevel = logging.CRITICAL
self._minIntLevel = logging.NOTSET
self._levelRegExList = []
self._logger = logging.getLogger(self.__class__.__name__)
self._initFlag = False
def setMinLogLevel(self, minLogLevel=logging.INFO):
self._minIntLevel = minLogLevel
def parseLevelRegEx(self, levelRegExList):
""" Parse a list of expressions of the form <regex>=<log-level>. """
lines = levelRegExList.split('\n')
for line in lines:
try:
# Use the right split so we can have '='s in the regex
(regex, level) = line.rsplit('=', 1)
pattern = re.compile(regex)
tuple = (pattern, logging.getLevelName(level.upper()))
self._levelRegExList.append(tuple)
except Exception, ex:
# Do not fail, but log an error.
self._logger.error('Parser error in log configuration file: %s' % line)
self._logger.exception(ex)
# Get Log Level based on a string representation
def getIntLogLevel(self, levelStr):
level = logging.getLevelName(levelStr)
# Level should be an integer
try:
return int(level)
except ValueError, ex:
raise ConfigurationError('"%s" is not valid log level' % levelStr)
# Configure log handlers.
def configureHandlers(self):
""" Configure log handlers from the config file. """
cm = configurationManager.getInstance()
configFile = cm.getLogConfigFile()
configSections = self.__getConfigSections(configFile)
# Console handler.
defaults = {
'level' : cm.getConsoleLogLevel(),
'format' : cm.getLogRecordFormat(),
'dateformat' : cm.getLogDateFormat(),
'handler' : 'ConsoleLoggingHandler(sys.stdout,)'
}
consoleHandler = self.__configureHandler(configFile, 'ConsoleLogging', defaults)
if consoleHandler != None:
self._consoleHandler = consoleHandler
# File logging. # Do not configure if log directory does
# not exist.
defaults['handler'] = None
defaults['level'] = cm.getFileLogLevel()
if not os.path.exists(configFile):
# No config file, we'll configure default.
defaultLogFile = cm.getLogFile()
defaultLogDir = os.path.dirname(defaultLogFile)
if os.path.exists(defaultLogDir):
handler = 'TimedRotatingFileLoggingHandler("%s")' % defaultLogFile
defaults['handler'] = handler
fileHandler = self.__configureHandler(configFile, 'FileLogging', defaults)
if fileHandler != None:
self._fileHandlerList.append(fileHandler)
else:
# Parse all file loggers present in the config file
for configSection in configSections:
if configSection.startswith('FileLogging'):
fileHandler = self.__configureHandler(configFile, configSection, defaults)
if fileHandler != None:
self._fileHandlerList.append(fileHandler)
# Add handlers to the root logger. Use logging class here
# to make sure we can have a logger when we parse the
# logger expressions
rootLogger = logging.getLogger('')
for handler in [self._consoleHandler] + self._fileHandlerList:
rootLogger.addHandler(handler)
# Get a logger factory based on our current config
self.configureLoggers(configFile, defaultLevel=cm.getFileLogLevel())
def configureLoggers(self, configFile, defaultLevel='error'):
configParser = ConfigParser.ConfigParser()
configParser.read(configFile)
rootLogLevel = 'error'
levelRegEx = '^.*$=%s' % (defaultLevel)
if configParser.has_section('LoggerLevels'):
rootLogLevel = configParser.get('LoggerLevels', 'root', rootLogLevel)
levelRegEx = configParser.get('LoggerLevels', 'levelregex', levelRegEx)
rootLevelInt = logging.getLevelName(rootLogLevel.upper())
logging.getLogger('').root.setLevel(rootLevelInt)
logging.getLogger('').debug('Set root logger to %s' % rootLevelInt)
if not levelRegEx:
return
# Parse expressions of the form <regex>=<log-level>. """
lines = levelRegEx.split('\n')
for line in lines:
try:
# Use the right split so we can have '='s in the regex
(regex, level) = line.rsplit('=', 1)
pattern = re.compile(regex)
tuple = (pattern, logging.getLevelName(level.upper()))
self._levelRegExList.append(tuple)
except Exception, ex:
# Do not fail
self._logger.error('Parser error in log configuration file: %s' % line)
self._logger.exception(ex)
def __getOptionFromConfigFile(self, configParser, configSection, key, defaultValue=None):
""" Get specified option from the configuration file. """
if configParser.has_section(configSection):
return configParser.get(configSection, key, True)
else:
return defaultValue
# Get the sections in the config file
def __getConfigSections(self, configFile):
""" Return a list of the sections in the given config file """
configParser = ConfigParser.RawConfigParser()
configParser.read(configFile)
return configParser.sections()
# Configure particular handler with given defaults.
def __configureHandler(self, configFile, configSection, defaults):
""" Configure specified handler with a given defaults. """
configParser = ConfigParser.ConfigParser(defaults)
configParser.read(configFile)
handlerOption = defaults['handler']
try:
handlerOption = configParser.get(configSection, 'handler', True)
except Exception, ex:
pass
# If handlerOption is empty, handler cannot be instantiated.
handler = None
if handlerOption != None:
# Handler argument format: MyHandler(arg1, arg2, ...)
# Module will be in lowercase letters, but the class
# should be capitalized.
handlerName = re.sub('\(.*', '', handlerOption)
moduleName = handlerName[0].lower() + handlerName[1:]
try:
exec 'from dm.common.utility import %s' % (moduleName)
exec '_handler = %s.%s' % (moduleName, handlerOption)
except IOError, ex:
errno, _emsg = ex
import errno
# If the exception raised is an I/O permissions error, ignore
# it and disable this log handler. This allows non-root users
# to use the (system-wide) default log configuration
if _errno != errno.EACCES:
raise
_handler = None
except Exception, ex:
raise ConfigurationError(exception=ex)
# Only request setting from the config file if it was
# not set via environment variable, or programmatically.
if _handler != None:
try:
_level = self.__getOptionFromConfigFile(configParser,
configSection, 'level', defaults['level'])
intLevel = self.getIntLogLevel(_level.upper())
_handler.setLevel(intLevel)
_format = self.__getOptionFromConfigFile(configParser, configSection, 'format', defaults['format'])
_dateformat = self.__getOptionFromConfigFile(configParser, configSection, 'dateformat', defaults['dateformat'])
_handler.setFormatter(logging.Formatter(_format, _dateformat))
except Exception, ex:
raise ConfigurationError(exception=ex)
# Look to see if there is a filter to apply to the handler
filter = None
try:
filter = configParser.get(configSection, 'filter')
except Exception, ex:
pass
if filter:
_handler.addFilter(logging.Filter(filter))
return _handler
def getLogger(self, name='defaultLogger'):
if not self._initFlag:
self._initFlag = True
self.configureHandlers()
logger = logging.getLogger(name)
logger.setLevel(self.getLevel(name))
return logger
def getLevel(self, name):
# Match from the known regex list.
level = logging.NOTSET
# The last regex is most important.
for e in reversed(self._levelRegExList):
(pattern, level) = e
# If we return not None it is a match
if not None == pattern.match(name):
break
if level > self._maxIntLevel:
level = self._maxIntLevel
if level < self._minIntLevel:
level = self._minIntLevel
return level
def setConsoleLogLevel(self, level):
try:
# We need to override the logger levels and the handler
intLevel = self.getIntLogLevel(level.upper())
self._consoleHandler.setLevel(intLevel)
self._maxIntLevel = intLevel
self._logger.setLevel(intLevel)
except Exception, ex:
raise ConfigurationError(exception=ex)
def setFileLogLevel(self, level):
try:
# We need to override the logger levels and the handler
intLevel = self.getIntLogLevel(level.upper())
for handler in self._fileHandlerList:
handler.setLevel(intLevel)
self._maxIntLevel = intLevel
self._logger.setLevel(intLevel)
except Exception, ex:
raise ConfigurationError(exception=ex)
#######################################################################
# Testing.
if __name__ == '__main__':
lm = getInstance()
logger = lm.getLogger('Main')
logger.info('Info In Main')
logger = lm.getLogger('Main')
logger.info('Info In Main 2')
logger = lm.getLogger('')
logger.info('Info using root logger')
logger = lm.getLogger('Main.2')
logger.info('Info in Main.2')
logger.debug('You should not see this message')
lm.setConsoleLogLevel('debug')
logger.debug('Debug in Main.2')