import json import logging import logging.config from functools import partial, partialmethod import cherrypy from cherrypy import _cperror from cmapi_server.constants import CMAPI_LOG_CONF_PATH class AddIpFilter(logging.Filter): """Filter to add IP address to logging record.""" def filter(self, record): record.ip = cherrypy.request.remote.name or cherrypy.request.remote.ip return True def custom_cherrypy_error( self, msg='', context='', severity=logging.INFO, traceback=False ): """Write the given ``msg`` to the error log. [now without hardcoded time] This is not just for errors! [looks awful, but cherrypy realisation as is] Applications may call this at any time to log application-specific information. If ``traceback`` is True, the traceback of the current exception (if any) will be appended to ``msg``. ..Note: All informatio """ exc_info = None if traceback: exc_info = _cperror._exc_info() self.error_log.log(severity, ' '.join((context, msg)), exc_info=exc_info) def dict_config(config_filepath: str): with open(config_filepath, 'r', encoding='utf-8') as json_config: config_dict = json.load(json_config) logging.config.dictConfig(config_dict) def add_logging_level(level_name, level_num, method_name=None): """ Comprehensively adds a new logging level to the `logging` module and the currently configured logging class. `level_name` becomes an attribute of the `logging` module with the value `level_num`. `methodName` becomes a convenience method for both `logging` itself and the class returned by `logging.getLoggerClass()` (usually just `logging.Logger`). If `methodName` is not specified, `levelName.lower()` is used. To avoid accidental clobberings of existing attributes, this method will raise an `AttributeError` if the level name is already an attribute of the `logging` module or if the method name is already present Example ------- >>> add_logging_level('TRACE', logging.DEBUG - 5) >>> logging.getLogger(__name__).setLevel('TRACE') >>> logging.getLogger(__name__).trace('that worked') >>> logging.trace('so did this') >>> logging.TRACE 5 """ if not method_name: method_name = level_name.lower() if hasattr(logging, level_name): raise AttributeError(f'{level_name} already defined in logging module') if hasattr(logging, method_name): raise AttributeError( f'{method_name} already defined in logging module' ) if hasattr(logging.getLoggerClass(), method_name): raise AttributeError(f'{method_name} already defined in logger class') # This method was inspired by the answers to Stack Overflow post # http://stackoverflow.com/q/2183233/2988730, especially # https://stackoverflow.com/a/35804945 # https://stackoverflow.com/a/55276759 logging.addLevelName(level_num, level_name) setattr(logging, level_name, level_num) setattr( logging.getLoggerClass(), method_name, partialmethod(logging.getLoggerClass().log, level_num) ) setattr(logging, method_name, partial(logging.log, level_num)) def config_cmapi_server_logging(): # add custom level TRACE only for develop purposes # could be activated using API endpoints or cli tool without relaunching add_logging_level('TRACE', 5) cherrypy._cplogging.LogManager.error = custom_cherrypy_error # reconfigure cherrypy.access log message format # Default access_log_format '{h} {l} {u} {t} "{r}" {s} {b} "{f}" "{a}"' # h - remote.name or remote.ip, l - "-", # u - getattr(request, 'login', None) or '-', t - self.time(), # r - request.request_line, s - status, # b - dict.get(outheaders, 'Content-Length', '') or '-', # f - dict.get(inheaders, 'Referer', ''), # a - dict.get(inheaders, 'User-Agent', ''), # o - dict.get(inheaders, 'Host', '-'), # i - request.unique_id, z - LazyRfc3339UtcTime() cherrypy._cplogging.LogManager.access_log_format = ( '{h} ACCESS "{r}" code {s}, bytes {b}, user-agent "{a}"' ) dict_config(CMAPI_LOG_CONF_PATH) def change_loggers_level(level: str): """Set level for each custom logger except cherrypy library. :param level: logging level to set :type level: str """ loggers = [ logging.getLogger(name) for name in logging.root.manager.loggerDict if 'cherrypy' not in name ] loggers.append(logging.getLogger()) # add RootLogger for logger in loggers: logger.setLevel(level)