1、项目中使用了自定义的ColorHandler和MongoHandler,使用了内置的RotatingFileHandler和三方库的ConcurrentRotatingFileHandler。
支持不同logger name的日志写入不同的文件,不同logger name日志写入不同的mongodb 的collection。LogManager比较容易调用,因为里面的内部方法全都使用了下划线,使用了下划线的就是保护和私有方法不需要外界调用,也不需要看懂他,在调用时候pycharm不会自动补全提示这些带下划线的无关方法了,只暴露了get_and_add_handlers和get_without_handlers两个可能需要调用的方法,pyrcharm可以自动补全这两个方法。
2、主要思路和模式是:
logger添加handler后,每次写logger.debug/info就支持日志多种记录方式,是因为logger和handler是发布者和订阅者的关系,在设计模式里面叫做观察者模式。当进行logger.debug时候,logger会调用自己的_log方法,_log方法调用了handle方法,handle调用了call handler方法,callhandler找到所有订阅者,调用了订阅者(这里的订阅者就是各种已被addHandler添加到列表的handler对象)的handle方法,handler的handler方法会调用emit方法。所以在写自定义handler时候只需要继承logging.Handler类,重写emit方法就可以了。如果有特殊的需要传入其他参数,除了emit方法以外,还需要重写init方法。
所以一共有两个主要的设计模式在里面,logger和各种handler对象之间用的模式是观察者模式,各种花样的handler类和基本的Handler类是使用了模板模式(模板模式是把主要的方法步骤在基类写,具体的某些不同步骤在模板类被设置为了抽象方法,子类必须重写。)
# coding=utf8 """ 日志管理,支持日志打印到控制台或写入文件或mongodb 使用方式为 logger = LogManager('logger_name').get_and_add_handlers(log_level_int=1, is_add_stream_handler=True, log_path=None, log_filename=None, log_file_size=10,mongo_url=None,formatter_template=2) 或者 logger = LogManager('logger_name').get_without_handlers(),此种不立即记录日志,之后可以在单独统一的总闸处对所有日志根据loggerame进行get_and_add_handlers就能捕获到所有日志并记录了 """ import os import unittest import time import re from collections import OrderedDict import pymongo import logging from logging.handlers import RotatingFileHandler if os.name == 'posix': from cloghandler import ConcurrentRotatingFileHandler formatter_dict = { 1: logging.Formatter('日志时间【%(asctime)s】 - 日志名称【%(name)s】 - 文件【%(filename)s】 - 第【%(lineno)d】行 - 日志等级【%(levelname)s】 - 日志信息【%(message)s】', "%Y-%m-%d %H:%M:%S"), 2: logging.Formatter('%(asctime)s - %(name)s - %(filename)s - %(lineno)d - %(levelname)s - %(message)s', "%Y-%m-%d %H:%M:%S"), } class LogLevelException(Exception): def __init__(self, log_level): err = '设置的日志级别是 {0}, 设置错误,请设置为1 2 3 4 5 范围的数字'.format(log_level) Exception.__init__(self, err) class MongoHandler(logging.Handler): """ 一个mongodb的log handler,支持日志按loggername创建不同的集合写入mongodb中 """ msg_pattern = re.compile('(d+-d+-d+ d+:d+:d+) - (S*?) - (S*?) - (d+) - (S*?) - ([sS]*)') def __init__(self, mongo_url, mongo_database='logs'): """ :param mongo_url: mongo连接 :param mongo_database: 保存日志ide数据库,默认使用logs数据库 """ logging.Handler.__init__(self) mongo_client = pymongo.MongoClient(mongo_url) self.mongo_db = mongo_client.get_database(mongo_database) def emit(self, record): try: """以下使用解析日志模板的方式提取出字段""" # msg = self.format(record) # logging.LogRecord # msg_match = self.msg_pattern.search(msg) # log_info_dict = {'time': msg_match.group(1), # 'name': msg_match.group(2), # 'file_name': msg_match.group(3), # 'line_no': msg_match.group(4), # 'log_level': msg_match.group(5), # 'detail_msg': msg_match.group(6), # } level_str = None if record.levelno == 10: level_str = 'DEBUG' elif record.levelno == 20: level_str = 'INFO' elif record.levelno == 30: level_str = 'WARNING' elif record.levelno == 40: level_str = 'ERROR' elif record.levelno == 50: level_str = 'CRITICAL' log_info_dict = OrderedDict() log_info_dict['time'] = time.strftime('%Y-%m-%d %H:%M:%S') log_info_dict['name'] = record.name log_info_dict['file_path'] = record.pathname log_info_dict['file_name'] = record.filename log_info_dict['func_name'] = record.funcName log_info_dict['line_no'] = record.lineno log_info_dict['log_level'] = level_str log_info_dict['detail_msg'] = record.msg col = self.mongo_db.get_collection(record.name) col.insert_one(log_info_dict) except (KeyboardInterrupt, SystemExit): raise except: self.handleError(record) class ColorHandler(logging.StreamHandler): """彩色日志,根据不同级别的日志显示不同颜色""" def emit(self, record): """ 0 40 黑色 31 41 红色 32 42 绿色 33 43 黃色 34 44 蓝色 35 45 紫红色 36 46 青蓝色 37 47 白色 :param record: :return: """ try: # logging.LogRecord.levelno msg = self.format(record) if record.levelno == 10: print('