mirror of
https://git.zavage.net/Zavage-Software/wikicrawl.git
synced 2024-11-25 01:19:19 -07:00
166 lines
5.6 KiB
Python
166 lines
5.6 KiB
Python
import appdirs
|
|
import colorlog
|
|
import logging
|
|
import logging.config
|
|
import os
|
|
|
|
from ._bootstrap import _bootstrap_logger
|
|
from . import _util
|
|
|
|
DEFAULT_LOG_SETTINGS = {
|
|
'formatters': {
|
|
'colored': {
|
|
'class': 'colorlog.ColoredFormatter',
|
|
# 'format': '%(log_color)s%(levelname)-8s%(reset)s:%(log_color)s%(name)-5s%(reset)s:%(white)s%(message)s'
|
|
'format': '%(white)s%(name)7s%(reset)s|%(log_color)s%(message)s',
|
|
}
|
|
},
|
|
|
|
'handlers': {
|
|
'stderr': {
|
|
'class': 'logging.StreamHandler',
|
|
'level': 'debug',
|
|
'formatter': 'colored'
|
|
}
|
|
|
|
},
|
|
|
|
'loggers': {
|
|
'root': {
|
|
'handlers': ['stderr',],
|
|
'level': 'debug'
|
|
},
|
|
'app_skellington': {
|
|
# 'handlers': ['stderr',],
|
|
'level': 'critical',
|
|
'propagate': 'false'
|
|
}
|
|
}
|
|
}
|
|
|
|
class LoggingLayer:
|
|
def __init__(self, appname, appauthor, config=None):
|
|
self.appname = appname
|
|
self.appauthor = appauthor
|
|
self.loggers = {}
|
|
|
|
def __getitem__(self, k):
|
|
"""
|
|
Returns Logger object named <k>.
|
|
|
|
Example:
|
|
log = LoggingLayer(...)
|
|
log['db'].info('loaded database module')
|
|
|
|
Args:
|
|
k: the name of the logger to retrieve (k, i.e. key)
|
|
"""
|
|
logger = self.loggers.get(k)
|
|
if not logger:
|
|
logger = logging.getLogger(k)
|
|
self.loggers[k] = logger
|
|
return logger
|
|
|
|
def configure_logging(self, config_dict=None):
|
|
"""
|
|
Set the logging level for the process. Verbosity is controlled by a
|
|
parameter in the config.
|
|
|
|
Advice: While DEBUG verbosity is useful to debug, it can produce too much
|
|
noise for typical operation.
|
|
"""
|
|
if config_dict is None:
|
|
_bootstrap_logger.debug('No application logging configuration provided. Using default')
|
|
config_dict = DEFAULT_LOG_SETTINGS
|
|
|
|
self.transform_config(config_dict)
|
|
|
|
try:
|
|
# TODO(MG) switch to pretty-print, as it'd be more human readable
|
|
_bootstrap_logger.debug('Log configuration: %s', config_dict)
|
|
logging.config.dictConfig(config_dict)
|
|
except Exception as ex:
|
|
print('unable to configure logging:', ex, type(ex))
|
|
|
|
def transform_config(self, config_dict):
|
|
"""
|
|
Fix some incompatibilities and differences between the config-file logging
|
|
parameters and the final config dictionary passed into the logging module.
|
|
"""
|
|
# Version should be hard-coded 1, per Python docs
|
|
if 'version' in config_dict:
|
|
if config_dict['version'] != 1:
|
|
_bootstrap_logger.warn("logging['version'] must be '1' per Python docs")
|
|
config_dict['version'] = 1
|
|
|
|
self._add_own_logconfig(config_dict)
|
|
|
|
# Replace logger level strings with value integers from module
|
|
for handler in config_dict['handlers']:
|
|
d = config_dict['handlers'][handler]
|
|
self._convert_str_to_loglevel(d, 'level')
|
|
|
|
# Replace logger level strings with value integers from module
|
|
for logger in config_dict['loggers']:
|
|
d = config_dict['loggers'][logger]
|
|
self._convert_str_to_loglevel(d, 'level')
|
|
|
|
# Replace 'root' logger with '', logging module convention for root handler
|
|
# Note: '' is disallowed in ConfigObj (hence the reason for this replacement)
|
|
config_dict['loggers'][''] = config_dict['loggers']['root']
|
|
del config_dict['loggers']['root']
|
|
|
|
|
|
# Evaluate the full filepath of the file handler
|
|
if 'file' not in config_dict['handlers']:
|
|
return
|
|
|
|
if os.path.abspath(config_dict['handlers']['file']['filename']) ==\
|
|
config_dict['handlers']['file']['filename']:
|
|
# Path is already absolute
|
|
pass
|
|
else:
|
|
dirname = appdirs.user_log_dir(self.appname, self.appauthor)
|
|
_util.ensure_dir_exists(dirname)
|
|
log_filepath = os.path.join(dirname, config_dict['handlers']['file']['filename'])
|
|
config_dict['handlers']['file']['filename'] = log_filepath
|
|
|
|
def _add_own_logconfig(self, config_dict):
|
|
if os.environ.get('APPSKELLINGTON_ENABLE_LOGGING', None):
|
|
if 'app_skellington' not in config_dict['loggers']:
|
|
config_dict['loggers']['app_skellington'] = {
|
|
'level': 'debug', 'propagate': 'false'
|
|
}
|
|
else:
|
|
config_dict['loggers']['app_skellington']['level'] = 'debug'
|
|
|
|
def _convert_str_to_loglevel(self, dict_, key):
|
|
"""
|
|
Convert a dictionary value from a string representation of a log level
|
|
into the numeric value of that log level. The value is modified in-place
|
|
and is passed in by a dictionary reference and a key name.
|
|
|
|
For example,
|
|
d = {'loggers': {'cas': {'level': 'critical'}}}
|
|
convert_str_to_loglevel(d['loggers']['cas'], 'level')
|
|
=>
|
|
d is now {'loggers': {'cas': {'level': logging.CRITICAL}}}
|
|
"""
|
|
try:
|
|
s = dict_[key]
|
|
except KeyError as ex:
|
|
raise
|
|
if s == 'critical':
|
|
dict_[key] = logging.CRITICAL
|
|
elif s == 'error':
|
|
dict_[key] = logging.ERROR
|
|
elif s == 'warning':
|
|
dict_[key] = logging.WARNING
|
|
elif s == 'info':
|
|
dict_[key] = logging.INFO
|
|
elif s == 'debug':
|
|
dict_[key] = logging.DEBUG
|
|
elif s == 'all':
|
|
dict_[key] = logging.NOTSET
|
|
|