python日志,支持彩色打印和文件大小切片写入和写入mongodb
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 RotatingFileHandlerif os.name == 'posix':from cloghandler import ConcurrentRotatingFileHandlerformatter_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*?) - ([\s\S]*)')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 = Noneif 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.namelog_info_dict['file_path'] = record.pathnamelog_info_dict['file_name'] = record.filenamelog_info_dict['func_name'] = record.funcNamelog_info_dict['line_no'] = record.linenolog_info_dict['log_level'] = level_strlog_info_dict['detail_msg'] = record.msgcol = self.mongo_db.get_collection(record.name)col.insert_one(log_info_dict)except (KeyboardInterrupt, SystemExit):raiseexcept: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.levelnomsg = self.format(record)if record.levelno == 10:print('\033[0;32m%s\033[0m' % msg) # 綠色elif record.levelno == 20:print('\033[0;36m%s\033[0m' % msg) # 青藍色elif record.levelno == 30:print('\033[0;34m%s\033[0m' % msg) # 藍色elif record.levelno == 40:print('\033[0;35m%s\033[0m' % msg) # 紫紅色elif record.levelno == 50:print('\033[0;31m%s\033[0m' % msg) # 血紅色except (KeyboardInterrupt, SystemExit):raiseexcept:self.handleError(record)class LogManager(object):"""一個日志類,用于創建和捕獲日志,支持將日志打印到控制臺打印和寫入日志文件。"""def __init__(self, logger_name=None):""":param logger_name: 日志名稱,當為None時候打印所有日志"""self.logger = logging.getLogger(logger_name)self._logger_level = Noneself._is_add_stream_handler = Noneself._log_path = Noneself._log_filename = Noneself._log_file_size = Noneself._mongo_url = Noneself._formatter = Nonedef get_logger_and_add_handlers(self, 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):""":param log_level_int: 日志輸出級別,設置為 1 2 3 4 5,分別對應輸出DEBUG,INFO,WARNING,ERROR,CRITICAL日志:param is_add_stream_handler: 是否打印日志到控制臺:param log_path: 設置存放日志的文件夾路徑:param log_filename: 日志的名字,僅當log_path和log_filename都不為None時候才寫入到日志文件。:param log_file_size :日志大小,單位M,默認10M:param mongo_url : mongodb的連接,為None時候不添加mongohandler:param formatter_template :日志模板,1為formatter_dict的詳細模板,2為簡要模板:type log_level_int :int:type is_add_stream_handler :bool:type log_path :str:type log_filename :str:type mongo_url :str:type log_file_size :int"""self.__check_log_level(log_level_int)self._logger_level = self.__transform_logger_level(log_level_int)self._is_add_stream_handler = is_add_stream_handlerself._log_path = log_pathself._log_filename = log_filenameself._log_file_size = log_file_sizeself._mongo_url = mongo_urlself._formatter = formatter_dict[formatter_template]self.__set_logger_level()self.__add_handlers()return self.loggerdef get_logger_without_handlers(self):"""返回一個不帶hanlers的logger"""return self.loggerdef __set_logger_level(self):self.logger.setLevel(self._logger_level)@staticmethoddef __check_log_level(log_level_int):if log_level_int not in [1, 2, 3, 4, 5]:raise LogLevelException(log_level_int)@staticmethoddef __transform_logger_level(log_level_int):logger_level = Noneif log_level_int == 1:logger_level = logging.DEBUGelif log_level_int == 2:logger_level = logging.INFOelif log_level_int == 3:logger_level = logging.WARNINGelif log_level_int == 4:logger_level = logging.ERRORelif log_level_int == 5:logger_level = logging.CRITICALreturn logger_leveldef __add_handlers(self):if self._is_add_stream_handler:for h in self.logger.handlers:if isinstance(h, logging.StreamHandler):breakelse:self.__add_stream_handler()if all([self._log_path, self._log_filename]):for h in self.logger.handlers:if os.name == 'nt':if isinstance(h, RotatingFileHandler):breakif os.name == 'posix':if isinstance(h, (RotatingFileHandler, ConcurrentRotatingFileHandler)):breakelse:self.__add_file_handler()if self._mongo_url:for h in self.logger.handlers:if isinstance(h, MongoHandler):breakelse:self.__add_mongo_handler()def __add_mongo_handler(self):"""寫入日志到mongodb"""mongo_handler = MongoHandler(self._mongo_url, 'logs')mongo_handler.setLevel(logging.DEBUG)mongo_handler.setFormatter(self._logger_level)self.logger.addHandler(mongo_handler)def __add_stream_handler(self):"""日志顯示到控制臺"""# stream_handler = logging.StreamHandler()stream_handler = ColorHandler() # 不使用streamhandler,使用自定義的彩色日志 stream_handler.setLevel(self._logger_level)stream_handler.setFormatter(self._formatter)self.logger.addHandler(stream_handler)def __add_file_handler(self):"""日志寫入日志文件"""if not os.path.exists(self._log_path):os.makedirs(self._log_path)log_file = os.path.join(self._log_path, self._log_filename)os_name = os.namerotate_file_handler = Noneif os_name == 'nt':# windows下用這個,非進程安全rotate_file_handler = RotatingFileHandler(log_file, mode="a", maxBytes=self._log_file_size * 1024 * 1024, backupCount=10,encoding="utf-8")if os_name == 'posix':# linux下可以使用ConcurrentRotatingFileHandler,進程安全的日志方式rotate_file_handler = ConcurrentRotatingFileHandler(log_file, mode="a", maxBytes=self._log_file_size * 1024 * 1024,backupCount=10, encoding="utf-8")rotate_file_handler.setLevel(self._logger_level)rotate_file_handler.setFormatter(self._formatter)self.logger.addHandler(rotate_file_handler)class _Test(unittest.TestCase):@unittest.skipdef test_repeat_add_handlers_(self):"""測試重復添加handlers"""LogManager('test').get_logger_and_add_handlers(1, log_path='../logs', log_filename='test.log')LogManager('test').get_logger_and_add_handlers(1, log_path='../logs', log_filename='test.log')LogManager('test').get_logger_and_add_handlers(1, log_path='../logs', log_filename='test.log')test_log = LogManager('test').get_logger_and_add_handlers(1, log_path='../logs', log_filename='test.log')print('下面這一句不會重復打印四次和寫入日志四次')time.sleep(1)test_log.debug('這一句不會重復打印四次和寫入日志四次')@unittest.skipdef test_get_logger_without_hanlders(self):"""測試沒有handlers的日志"""log = LogManager('test2').get_logger_without_handlers()print('下面這一句不會被打印')time.sleep(1)log.info('這一句不會被打印')@unittest.skipdef test_add_handlers(self):"""這樣可以在具體的地方任意寫debug和info級別日志,只需要在總閘處規定級別就能過濾,很方便"""LogManager('test3').get_logger_and_add_handlers(2)log1 = LogManager('test3').get_logger_without_handlers()print('下面這一句是info級別,可以被打印出來')time.sleep(1)log1.info('這一句是info級別,可以被打印出來')print('下面這一句是debug級別,不能被打印出來')time.sleep(1)log1.debug('這一句是debug級別,不能被打印出來')@unittest.skipdef test_only_write_log_to_file(self):"""只寫入日志文件"""log5 = LogManager('test5').get_logger_and_add_handlers(is_add_stream_handler=False, log_path='../logs', log_filename='test5.log')print('下面這句話只寫入文件')log5.debug('這句話只寫入文件')def test_color_and_mongo_hanlder(self):"""測試彩色日志和日志寫入mongodb"""from app import configlogger = LogManager('helloMongo').get_logger_and_add_handlers(mongo_url=config.connect_url)logger.debug('一個debug級別的日志')logger.info('一個info級別的日志')logger.warning('一個warning級別的日志')logger.error('一個error級別的日志')logger.critical('一個critical級別的日志')if __name__ == "__main__":unittest.main()?
test_color_and_mongo_hanlder的效果如下。3、因為給logger添加了自定義的ColorHandler,所以控制臺記錄的日志是彩色的。
?
4、mongodb里面
?
使用日志logger的name屬性作為collection的名字,不同的loggername創建不同的collection。主要保存的字段的有日志name,是在什么文件的什么函數/方法的哪一行打印了什么級別的日志和日志詳情。
?
轉載于:https://www.cnblogs.com/ydf0509/p/9153226.html
總結
以上是生活随笔為你收集整理的python日志,支持彩色打印和文件大小切片写入和写入mongodb的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: python序列中是否包含某个元素
- 下一篇: Go语言的数组