""" 实现对日志文件的配置封装,详细参考 getLogger 方法。 输出日志使用 echo_log 方法。 输出到日志文件使用 logToFile 方法。 """ import logging import sys import traceback from logging import handlers from typing import Any, Union, Optional from paste.core import config logger_config_name = 'logger.default' """ 默认配置字段名称,当在 getLogger 方法中设置了不同名称后,该变量会被修改。 """ paste_logger: Optional[logging.Logger] = None """ 全局日志对象,获取日志对象时初始化。 """ def set_logger_config(config_name: str): """ 设置新的日志配置名称。 :param config_name: 日志配置名称 """ global logger_config_name if config_name != logger_config_name: logger_config_name = config_name def get_logger(): """ 取得日志对象。先根据配置,更新系统日志配置。若配置了额外的日志文件、格式、层级,则增加响应的日志输出。 注意:除非额外配置,否则都使用与系统日志相同的配置参数。 配置结构参考:: { "logger": { "basic": { "filename": "sys_log.log", "format": "%(asctime)s - %(name)s - %(levelname)s - %(message)s", "level": 20 }, "filename": "zb_data_log.log", "name": "ZbData" } } """ global paste_logger if paste_logger is None: # 系统日志基本配置 _log_base_cfg: dict = config.get_config(f'{logger_config_name}.basic', {}) # 系统日志文件 _base_log_file = _log_base_cfg.get('filename', '') _base_log_format = _log_base_cfg.get('format', '') _base_formatter = logging.Formatter(_base_log_format) _base_level = _log_base_cfg.get('level', logging.INFO) # 日志名称,若不配置,则使用名称:SQL_ALEX _log_name = config.get_config(f'{logger_config_name}.name', 'SQL_ALEX') # 日志文件最大值 _log_max_bytes = config.get_config(f'{logger_config_name}.max_bytes', 0) # 日志文件备份数量 _log_backup_count = config.get_config(f'{logger_config_name}.backup_count', 0) # 日志格式,若不配置,则使用 base 中的配置 _log_format = config.get_config(f'{logger_config_name}.format', _base_log_format) _formatter = logging.Formatter(_log_format) # 日志层级,若不配置,则使用 base 中的配置 _log_level = config.get_config(f'{logger_config_name}.level', _base_level) # 日志文件 _log_file = config.get_config(f'{logger_config_name}.filename', '') # 更新系统日志基本配置 logging.basicConfig(**_log_base_cfg) # 重新绑定系统日志文件句柄 _base_log_file_handler: Optional[handlers.RotatingFileHandler] = None if _base_log_file not in (None, ''): _base_log_file_handler = handlers.RotatingFileHandler( _base_log_file, maxBytes=_log_max_bytes, backupCount=_log_backup_count ) _base_log_file_handler.setFormatter(_base_formatter) logging.root.handlers = [_base_log_file_handler] # 创建日志对象 paste_logger = logging.Logger(name=_log_name, level=_log_level) # 绑定日志文件句柄 if _log_file not in ('', None): # 若配置了日志文件,则创建文件句柄 _file_handler = handlers.RotatingFileHandler( _log_file, maxBytes=_log_max_bytes, backupCount=_log_backup_count ) _file_handler.setFormatter(_formatter) paste_logger.addHandler(_file_handler) else: # 若未配置,则使用系统日志文件 if _base_log_file_handler is not None: paste_logger.addHandler(_base_log_file_handler) # 绑定控制台输出 _console_handler = logging.StreamHandler() _console_handler.setFormatter(_formatter) paste_logger.addHandler(_console_handler) return paste_logger def echo_log(msg: Union[str, Exception], level: int = logging.INFO, is_log_exc: bool = False): """ 输出日志文本。默认输出到日志文件,但是可能不便于查询,这里应该考虑支持输出到日志数据库。 :param msg: 消息内容,当是 Exception 对象时,从 args 中取出第一项作为消息 :param level: 消息等级 :param is_log_exc: 是否输出异常的 Traceback 信息到日志文件 """ _root = logging.root _logging = get_logger() _log_level = level if isinstance(msg, Exception): _log_level = logging.ERROR if len(msg.args) > 0 and isinstance(msg.args[0], str): msg = msg.args[0] else: msg = str(msg) _logging.log(level=_log_level, msg=msg) if is_log_exc: exception_to_file() def exception_to_file(): """ 自动检测异常,并输出异常的 Traceback 信息到日志文件。 """ _, _, tb = sys.exc_info() if tb is not None: _msg_list = ['Traceback: \n\n'] + traceback.format_tb(tb) log_to_file(msg=''.join(_msg_list), level=logging.ERROR) def log_to_file(msg: Any, level: int = logging.INFO): """ 输出消息到日志文件。 :param msg: 消息 :param level: 消息等级 """ _logger = get_logger() _record = _logger.makeRecord(name=_logger.name, level=level, fn=__file__, lno=0, args=(), exc_info=None, msg=msg) for hdl in _logger.handlers: if isinstance(hdl, logging.FileHandler): hdl.handle(_record)