最方便最好看最好用的python日志。

优惠码发放 2019-03-10

python 日志 logger stream class handler

最方便最好看最好用的python日志。

这个日志没有依赖自己的其他包,复制即可运行,也可以从pypi网站上下载或者pip来安装这个日志。

1、日志内置了7种模板,其中模版4和模板5,可以实现点击日志跳转到指定文件指定行数的功能,史无前例的实现这种方式。

2、使用了ColorHandler作为默认的控制台显示日志,而不是使用官方的StramHandler,实现五颜六色的日志,在茫茫大海的日志中一眼就能看出哪些是调试日志,哪些是错误日志哪些是警告日志和严重日志。绿色代表debug,天蓝色代表info,黄色代表warning,粉红色代表错误,血红色代表严重错误,颜色符合正常逻辑,具体的颜色显示业余自己设置的pycharm主题和配色有关,建议使用黑色主题,具体的颜色显示与pycahrm版本也有一些关系。

3、实现了进程安全的日志切片,引用的是第三方的Handler

4、添加了对国内邮箱 qq 163等支持的mailhandler,并且支持邮件发送控频。

5、添加了MongoHanler,可以自动拆分日志字段插入mongo

6.1、以上这些handler都不需要去手动调用添加各种handler,都是通过传参的方式,如,设置了文件名那么自动生成文件日志,添加了mongo的url登录链接,则添加mongohandler,以此类推。

6.2、要搞清楚为啥logger和各种handler,要弄清楚日志命名空间,各种handler的关系和logger的关系必须弄清楚23种设计模式的观察者模式,搞清楚这个模式了,就可以自己扩展各种各样的handler来满足自己的需求。

 

为什么要使用日志呢,

之前同事全部print,十分蛋疼,项目有几十万行,一运行起来,各种地方嵌套import各种模块,到处去print,十分操蛋,完全不知道哪里冒出来的日志,不好禁止,扩展不了各种handler,总之比日志差太多了。。

拿print当做日志用是属于py很初级的表现。

 

复制代码
  1 # coding=utf8
  2 """
  3 日志管理,支持日志打印到控制台或写入切片文件或mongodb或email
  4 使用方式为  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)
  5 或者 logger = LogManager('logger_name').get_without_handlers(),此种没有handlers不立即记录日志,之后可以在单独统一的总闸处对所有日志根据loggerame进行get_and_add_handlers添加相关的各种handlers
  6 创建一个邮件日志的用法为 logger = LogManager.bulid_a_logger_with_mail_handler('mail_logger_name', mail_time_interval=10, toaddrs=('909686xxx@qq.com', 'yangxx4508@dingtalk.com',subject='你的主题)),使用了独立的创建方式
  7 concurrent_log_handler的ConcurrentRotatingFileHandler解决了logging模块自带的RotatingFileHandler多进程切片错误,此ConcurrentRotatingFileHandler在win和linux多进程场景下log文件切片都ok.
  8 1、根据日志级别,使用coolorhanlder代替straemhandler打印5种颜色的日志,一目了然哪里是严重的日志。
  9 2、带有多种handler,邮件 mongo stream file的。
 10 3、支持pycharm点击日志跳转到对应代码文件的对应行。
 11 4、对相同命名空间的logger可以无限添加同种类型的handlers,不会重复使用同种handler记录日志。不需要用户自己去判断。
 12 
 13 """
 14 import sys
 15 import os
 16 from threading import Lock
 17 import unittest
 18 import time
 19 from collections import OrderedDict
 20 import pymongo
 21 import logging
 22 from logging import handlers
 23 from concurrent_log_handler import ConcurrentRotatingFileHandler  # 需要安装。ConcurrentLogHandler==0.9.1
 24 
 25 os_name = os.name
 26 formatter_dict = {
 27     1: logging.Formatter(
 28         '日志时间【%(asctime)s】 - 日志名称【%(name)s】 - 文件【%(filename)s】 - 第【%(lineno)d】行 - 日志等级【%(levelname)s】 - 日志信息【%(message)s】',
 29         "%Y-%m-%d %H:%M:%S"),
 30     2: logging.Formatter(
 31         '%(asctime)s - %(name)s - %(filename)s - %(funcName)s - %(lineno)d - %(levelname)s - %(message)s',
 32         "%Y-%m-%d %H:%M:%S"),
 33     3: logging.Formatter(
 34         '%(asctime)s - %(name)s - 【 File "%(pathname)s", line %(lineno)d, in %(funcName)s 】 - %(levelname)s - %(message)s',
 35         "%Y-%m-%d %H:%M:%S"),  # 一个模仿traceback异常的可跳转到打印日志地方的模板
 36     4: logging.Formatter(
 37         '%(asctime)s - %(name)s - "%(filename)s" - %(funcName)s - %(lineno)d - %(levelname)s - %(message)s -               File "%(pathname)s", line %(lineno)d ',
 38         "%Y-%m-%d %H:%M:%S"),  # 这个也支持日志跳转
 39     5: logging.Formatter(
 40         '%(asctime)s - %(name)s - "%(pathname)s:%(lineno)d" - %(funcName)s - %(levelname)s - %(message)s',
 41         "%Y-%m-%d %H:%M:%S"),  # 我认为的最好的模板,推荐
 42     6: logging.Formatter('%(name)s - %(asctime)-15s - %(filename)s - %(lineno)d - %(levelname)s: %(message)s',
 43                          "%Y-%m-%d %H:%M:%S"),
 44     7: logging.Formatter('%(levelname)s - %(filename)s - %(lineno)d - %(message)s'),  # 一个只显示简短文件名和所处行数的日志模板
 45 }
 46 
 47 
 48 # noinspection PyMissingOrEmptyDocstring
 49 class LogLevelException(Exception):
 50     def __init__(self, log_level):
 51         err = '设置的日志级别是 {0}, 设置错误,请设置为1 2 3 4 5 范围的数字'.format(log_level)
 52         Exception.__init__(self, err)
 53 
 54 
 55 # noinspection PyMissingOrEmptyDocstring
 56 class MongoHandler(logging.Handler):
 57     """
 58     一个mongodb的log handler,支持日志按loggername创建不同的集合写入mongodb中
 59     """
 60 
 61     # msg_pattern = re.compile('(\d+-\d+-\d+ \d+:\d+:\d+) - (\S*?) - (\S*?) - (\d+) - (\S*?) - ([\s\S]*)')
 62 
 63     def __init__(self, mongo_url, mongo_database='logs'):
 64         """
 65         :param mongo_url:  mongo连接
 66         :param mongo_database: 保存日志的数据库,默认使用logs数据库
 67         """
 68         logging.Handler.__init__(self)
 69         mongo_client = pymongo.MongoClient(mongo_url)
 70         self.mongo_db = mongo_client.get_database(mongo_database)
 71 
 72     def emit(self, record):
 73         # noinspection PyBroadException,PyPep8
 74         try:
 75             """以下使用解析日志模板的方式提取出字段"""
 76             # msg = self.format(record)
 77             # logging.LogRecord
 78             # msg_match = self.msg_pattern.search(msg)
 79             # log_info_dict = {'time': msg_match.group(1),
 80             #                  'name': msg_match.group(2),
 81             #                  'file_name': msg_match.group(3),
 82             #                  'line_no': msg_match.group(4),
 83             #                  'log_level': msg_match.group(5),
 84             #                  'detail_msg': msg_match.group(6),
 85             #                  }
 86             level_str = None
 87             if record.levelno == 10:
 88                 level_str = 'DEBUG'
 89             elif record.levelno == 20:
 90                 level_str = 'INFO'
 91             elif record.levelno == 30:
 92                 level_str = 'WARNING'
 93             elif record.levelno == 40:
 94                 level_str = 'ERROR'
 95             elif record.levelno == 50:
 96                 level_str = 'CRITICAL'
 97             log_info_dict = OrderedDict()
 98             log_info_dict['time'] = time.strftime('%Y-%m-%d %H:%M:%S')
 99             log_info_dict['name'] = record.name
100             log_info_dict['file_path'] = record.pathname
101             log_info_dict['file_name'] = record.filename
102             log_info_dict['func_name'] = record.funcName
103             log_info_dict['line_no'] = record.lineno
104             log_info_dict['log_level'] = level_str
105             log_info_dict['detail_msg'] = record.msg
106             col = self.mongo_db.get_collection(record.name)
107             col.insert_one(log_info_dict)
108         except (KeyboardInterrupt, SystemExit):
109             raise
110         except Exception:
111             self.handleError(record)
112 
113 
114 class ColorHandler0(logging.Handler):
115     """彩色日志handler,根据不同级别的日志显示不同颜色"""
116     bule = 96 if os_name == 'nt' else 36
117     yellow = 93 if os_name == 'nt' else 33
118 
119     def __init__(self):
120         logging.Handler.__init__(self)
121         self.formatter_new = logging.Formatter(
122             '%(asctime)s - %(name)s - "%(filename)s" - %(funcName)s - %(lineno)d - %(levelname)s - %(message)s',
123             "%Y-%m-%d %H:%M:%S")
124         # 对控制台日志单独优化显示和跳转,单独对字符串某一部分使用特殊颜色,主要用于第四种模板,以免filehandler和mongohandler中带有\033
125 
126     @classmethod
127     def _my_align(cls, string, length):
128         if len(string) > length * 2:
129             return string
130         custom_length = 0
131         for w in string:
132             custom_length += 1 if cls._is_ascii_word(w) else 2
133         if custom_length < length:
134             place_length = length - custom_length
135             string += ' ' * place_length
136         return string
137 
138     @staticmethod
139     def _is_ascii_word(w):
140         if ord(w) < 128:
141             return True
142 
143     def emit(self, record):
144         """
145         30    40    黑色
146         31    41    红色
147         32    42    绿色
148         33    43    黃色
149         34    44    蓝色
150         35    45    紫红色
151         36    46    青蓝色
152         37    47    白色
153         :type record:logging.LogRecord
154         :return:
155         """
156 
157         if self.formatter is formatter_dict[4] or self.formatter is self.formatter_new:
158             self.formatter = self.formatter_new
159             if os.name == 'nt':
160                 self.__emit_for_fomatter4_pycahrm(record)  # 使用模板4并使用pycharm时候
161             else:
162                 self.__emit_for_fomatter4_linux(record)  # 使用模板4并使用linux时候
163         else:
164             self.__emit(record)  # 其他模板
165 
166     def __emit_for_fomatter4_linux(self, record):
167         """
168         当使用模板4针对linxu上的终端打印优化显示
169         :param record:
170         :return:
171         """
172         # noinspection PyBroadException,PyPep8
173         try:
174             msg = self.format(record)
175             file_formatter = ' ' * 10 + '\033[7mFile "%s", line %d\033[0m' % (record.pathname, record.lineno)
176             if record.levelno == 10:
177                 print('\033[0;32m%s' % self._my_align(msg, 150) + file_formatter)
178             elif record.levelno == 20:
179                 print('\033[0;34m%s' % self._my_align(msg, 150) + file_formatter)
180             elif record.levelno == 30:
181                 print('\033[0;33m%s' % self._my_align(msg, 150) + file_formatter)
182             elif record.levelno == 40:
183                 print('\033[0;35m%s' % self._my_align(msg, 150) + file_formatter)
184             elif record.levelno == 50:
185                 print('\033[0;31m%s' % self._my_align(msg, 150) + file_formatter)
186         except (KeyboardInterrupt, SystemExit):
187             raise
188         except Exception:
189             self.handleError(record)
190 
191     def __emit_for_fomatter4_pycahrm(self, record):
192         """
193         当使用模板4针对pycahrm的打印优化显示
194         :param record:
195         :return:
196         """
197         #              \033[0;93;107mFile "%(pathname)s", line %(lineno)d, in %(funcName)s\033[0m
198         # noinspection PyBroadException
199         try:
200             msg = self.format(record)
201             # for_linux_formatter = ' ' * 10 + '\033[7m;File "%s", line %d\033[0m' % (record.pathname, record.lineno)
202             file_formatter = ' ' * 10 + '\033[0;93;107mFile "%s", line %d\033[0m' % (record.pathname, record.lineno)
203             if record.levelno == 10:
204                 print('\033[0;32m%s\033[0m' % self._my_align(msg, 200) + file_formatter)  # 绿色
205             elif record.levelno == 20:
206                 print('\033[0;36m%s\033[0m' % self._my_align(msg, 200) + file_formatter)  # 青蓝色
207             elif record.levelno == 30:
208                 print('\033[0;92m%s\033[0m' % self._my_align(msg, 200) + file_formatter)  # 蓝色
209             elif record.levelno == 40:
210                 print('\033[0;35m%s\033[0m' % self._my_align(msg, 200) + file_formatter)  # 紫红色
211             elif record.levelno == 50:
212                 print('\033[0;31m%s\033[0m' % self._my_align(msg, 200) + file_formatter)  # 血红色
213         except (KeyboardInterrupt, SystemExit):
214             raise
215         except:  # NOQA
216             self.handleError(record)
217 
218     def __emit(self, record):
219         # noinspection PyBroadException
220         try:
221             msg = self.format(record)
222             if record.levelno == 10:
223                 print('\033[0;32m%s\033[0m' % msg)  # 绿色
224             elif record.levelno == 20:
225                 print('\033[0;%sm%s\033[0m' % (self.bule, msg))  # 青蓝色 36    96
226             elif record.levelno == 30:
227                 print('\033[0;%sm%s\033[0m' % (self.yellow, msg))
228             elif record.levelno == 40:
229                 print('\033[0;35m%s\033[0m' % msg)  # 紫红色
230             elif record.levelno == 50:
231                 print('\033[0;31m%s\033[0m' % msg)  # 血红色
232         except (KeyboardInterrupt, SystemExit):
233             raise
234         except:  # NOQA
235             self.handleError(record)
236 
237 
238 class ColorHandler(logging.Handler):
239     """
240     A handler class which writes logging records, appropriately formatted,
241     to a stream. Note that this class does not close the stream, as
242     sys.stdout or sys.stderr may be used.
243     """
244 
245     terminator = '\n'
246     bule = 96 if os_name == 'nt' else 36
247     yellow = 93 if os_name == 'nt' else 33
248 
249     def __init__(self, stream=None):
250         """
251         Initialize the handler.
252 
253         If stream is not specified, sys.stderr is used.
254         """
255         logging.Handler.__init__(self)
256         if stream is None:
257             stream = sys.stdout  # stderr无彩。
258         self.stream = stream
259 
260     def flush(self):
261         """
262         Flushes the stream.
263         """
264         self.acquire()
265         try:
266             if self.stream and hasattr(self.stream, "flush"):
267                 self.stream.flush()
268         finally:
269             self.release()
270 
271     def emit(self, record):
272         """
273         Emit a record.
274 
275         If a formatter is specified, it is used to format the record.
276         The record is then written to the stream with a trailing newline.  If
277         exception information is present, it is formatted using
278         traceback.print_exception and appended to the stream.  If the stream
279         has an 'encoding' attribute, it is used to determine how to do the
280         output to the stream.
281         """
282         # noinspection PyBroadException
283         try:
284             msg = self.format(record)
285             stream = self.stream
286             if record.levelno == 10:
287                 msg_color = ('\033[0;32m%s\033[0m' % msg)  # 绿色
288             elif record.levelno == 20:
289                 msg_color = ('\033[0;%sm%s\033[0m' % (self.bule, msg))  # 青蓝色 36    96
290             elif record.levelno == 30:
291                 msg_color = ('\033[0;%sm%s\033[0m' % (self.yellow, msg))
292             elif record.levelno == 40:
293                 msg_color = ('\033[0;35m%s\033[0m' % msg)  # 紫红色
294             elif record.levelno == 50:
295                 msg_color = ('\033[0;31m%s\033[0m' % msg)  # 血红色
296             else:
297                 msg_color = msg
298             # print(msg_color,'***************')
299             stream.write(msg_color)
300             stream.write(self.terminator)
301             self.flush()
302         except Exception:
303             self.handleError(record)
304 
305     def __repr__(self):
306         level = logging.getLevelName(self.level)
307         name = getattr(self.stream, 'name', '')
308         if name:
309             name += ' '
310         return '<%s %s(%s)>' % (self.__class__.__name__, name, level)
311 
312 
313 class CompatibleSMTPSSLHandler(handlers.SMTPHandler):
314     """
315     官方的SMTPHandler不支持SMTP_SSL的邮箱,这个可以两个都支持,并且支持邮件发送频率限制
316     """
317 
318     def __init__(self, mailhost, fromaddr, toaddrs: tuple, subject,
319                  credentials=None, secure=None, timeout=5.0, is_use_ssl=True, mail_time_interval=0):
320         """
321 
322         :param mailhost:
323         :param fromaddr:
324         :param toaddrs:
325         :param subject:
326         :param credentials:
327         :param secure:
328         :param timeout:
329         :param is_use_ssl:
330         :param mail_time_interval: 发邮件的时间间隔,可以控制日志邮件的发送频率,为0不进行频率限制控制,如果为60,代表1分钟内最多发送一次邮件
331         """
332         # noinspection PyCompatibility
333         super().__init__(mailhost, fromaddr, toaddrs, subject,
334                          credentials, secure, timeout)
335         self._is_use_ssl = is_use_ssl
336         self._current_time = 0
337         self._time_interval = mail_time_interval
338         self._msg_map = dict()  # 是一个内容为键时间为值得映射
339         self._lock = Lock()
340 
341     def emit0(self, record: logging.LogRecord):
342         """
343         不用这个判断内容
344         """
345         from threading import Thread
346         if sys.getsizeof(self._msg_map) > 10 * 1000 * 1000:
347             self._msg_map.clear()
348         if record.msg not in self._msg_map or time.time() - self._msg_map[record.msg] > self._time_interval:
349             self._msg_map[record.msg] = time.time()
350             # print('发送邮件成功')
351             Thread(target=self.__emit, args=(record,)).start()
352         else:
353             print(f'[log_manager.py]  邮件发送太频繁,此次不发送这个邮件内容: {record.msg}    ')
354 
355     def emit(self, record: logging.LogRecord):
356         """
357         Emit a record.
358 
359         Format the record and send it to the specified addressees.
360         """
361         from threading import Thread
362         with self._lock:
363             if time.time() - self._current_time > self._time_interval:
364                 self._current_time = time.time()
365                 Thread(target=self.__emit, args=(record,)).start()
366             else:
367                 print(f'[log_manager.py]  邮件发送太频繁,此次不发送这个邮件内容: {record.msg}    ')
368 
369     def __emit(self, record):
370         # noinspection PyBroadException
371         try:
372             import smtplib
373             from email.message import EmailMessage
374             import email.utils
375             t_start = time.time()
376             port = self.mailport
377             if not port:
378                 port = smtplib.SMTP_PORT
379             smtp = smtplib.SMTP_SSL(self.mailhost, port, timeout=self.timeout) if self._is_use_ssl else smtplib.SMTP(
380                 self.mailhost, port, timeout=self.timeout)
381             msg = EmailMessage()
382             msg['From'] = self.fromaddr
383             msg['To'] = ','.join(self.toaddrs)
384             msg['Subject'] = self.getSubject(record)
385             msg['Date'] = email.utils.localtime()
386             msg.set_content(self.format(record))
387             if self.username:
388                 if self.secure is not None:
389                     smtp.ehlo()
390                     smtp.starttls(*self.secure)
391                     smtp.ehlo()
392                 smtp.login(self.username, self.password)
393             smtp.send_message(msg)
394             smtp.quit()
395             # noinspection PyPep8
396             print(
397                 f'[log_manager.py]  {time.strftime("%H:%M:%S",time.localtime())} 发送邮件给 {self.toaddrs} 成功,'
398                 f'用时{round(time.time() - t_start,2)} ,发送的内容是--> {record.msg}                    \033[0;35m!!!请去邮箱检查,可能在垃圾邮件中\033[0m')
399         except Exception as e:
400             # self.handleError(record)
401             print(
402                 f'[log_manager.py]   {time.strftime("%H:%M:%S",time.localtime())}  \033[0;31m !!!!!! 邮件发送失败,原因是: {e} \033[0m')
403 
404 
405 # noinspection PyTypeChecker
406 def get_logs_dir_by_folder_name(folder_name='/app/'):
407     """获取app文件夹的路径,如得到这个路径
408     D:/coding/hotel_fares/app
409     如果没有app文件夹,就在当前文件夹新建
410     """
411     three_parts_str_tuple = (os.path.dirname(__file__).replace('\\', '/').partition(folder_name))
412     # print(three_parts_str_tuple)
413     if three_parts_str_tuple[1]:
414         return three_parts_str_tuple[0] + three_parts_str_tuple[1] + 'logs/'  # noqa
415     else:
416         return three_parts_str_tuple[0] + '/logs/'  # NOQA
417 
418 
419 def get_logs_dir_by_disk_root():
420     """
421     返回磁盘根路径下的pythonlogs文件夹,当使用文件日志时候自动创建这个文件夹。
422     :return:
423     """
424     from pathlib import Path
425     return str(Path(Path(__file__).absolute().root) / Path('pythonlogs'))
426 
427 
428 # noinspection PyMissingOrEmptyDocstring,PyPep8
429 class LogManager(object):
430     """
431     一个日志管理类,用于创建logger和添加handler,支持将日志打印到控制台打印和写入日志文件和mongodb和邮件。
432     """
433     logger_name_list = []
434     logger_list = []
435 
436     def __init__(self, logger_name=None):
437         """
438         :param logger_name: 日志名称,当为None时候创建root命名空间的日志,一般不要传None,除非你确定需要这么做
439         """
440         self._logger_name = logger_name
441         self.logger = logging.getLogger(logger_name)
442         self._logger_level = None
443         self._is_add_stream_handler = None
444         self._do_not_use_color_handler = None
445         self._log_path = None
446         self._log_filename = None
447         self._log_file_size = None
448         self._mongo_url = None
449         self._formatter = None
450 
451     # 此处可以使用*args ,**kwargs减少很多参数,但为了pycharm更好的自动智能补全提示放弃这么做
452     @classmethod
453     def bulid_a_logger_with_mail_handler(cls, logger_name, log_level_int=10, *, is_add_stream_handler=True,
454                                          do_not_use_color_handler=False, log_path=get_logs_dir_by_disk_root(),
455                                          log_filename=None,
456                                          log_file_size=100, mongo_url=None,
457                                          formatter_template=5, mailhost: tuple = ('smtp.mxhichina.com', 465),
458                                          fromaddr: str = 'xxx@oo.com',
459                                          toaddrs: tuple = ('yy@oo.com', 460                                                            'zzg@oo.com', 'xxxx@qq.com',
461                                                            'ttttt@qq.com', 'hhh@qq.com', 'mmmm@oo.com'),
462                                          subject: str = '日志报警测试',
463                                          credentials: tuple = ('xxx@oo.com', '123456789'),
464                                          secure=None, timeout=5.0, is_use_ssl=True, mail_time_interval=60):
465         """
466         创建一个附带邮件handler的日志
467         :param logger_name:
468         :param log_level_int: 可以用1 2  3  4 5 ,用可以用官方logging模块的正规的10 20 30 40 50,兼容。
469         :param is_add_stream_handler:
470         :param do_not_use_color_handler:
471         :param log_path:
472         :param log_filename:
473         :param log_file_size:
474         :param mongo_url:
475         :param formatter_template:
476         :param mailhost:
477         :param fromaddr:
478         :param toaddrs:
479         :param subject:
480         :param credentials:
481         :param secure:
482         :param timeout:
483         :param is_use_ssl:
484         :param mail_time_interval: 邮件的频率控制,为0不限制,如果为100,代表100秒内相同内容的邮件最多发送一次邮件
485         :return:
486         """
487         if log_filename is None:
488             log_filename = f'{logger_name}.log'
489         logger = cls(logger_name).get_logger_and_add_handlers(log_level_int=log_level_int,
490                                                               is_add_stream_handler=is_add_stream_handler,
491                                                               do_not_use_color_handler=do_not_use_color_handler,
492                                                               log_path=log_path, log_filename=log_filename,
493                                                               log_file_size=log_file_size, mongo_url=mongo_url,
494                                                               formatter_template=formatter_template, )
495         smtp_handler = CompatibleSMTPSSLHandler(mailhost, fromaddr,
496                                                 toaddrs,
497                                                 subject,
498                                                 credentials,
499                                                 secure,
500                                                 timeout,
501                                                 is_use_ssl,
502                                                 mail_time_interval,
503                                                 )
504         log_level_int = log_level_int * 10 if log_level_int < 10 else log_level_int
505         smtp_handler.setLevel(log_level_int)
506         smtp_handler.setFormatter(formatter_dict[formatter_template])
507         if not cls.__judge_logger_contain_handler_class(logger, CompatibleSMTPSSLHandler):
508             if logger.name == 'root':
509                 for logger_x in cls.logger_list:
510                     for hdlr in logger_x.handlers:
511                         if isinstance(hdlr, CompatibleSMTPSSLHandler):
512                             logger_x.removeHandler(hdlr)
513             logger.addHandler(smtp_handler)
514 
515         return logger
516 
517     # 加*是为了强制在调用此方法时候使用关键字传参,如果以位置传参强制报错,因为此方法后面的参数中间可能以后随时会增加更多参数,造成之前的使用位置传参的代码参数意义不匹配。
518     def get_logger_and_add_handlers(self, log_level_int: int = 10, *, is_add_stream_handler=True,
519                                     do_not_use_color_handler=False, log_path=get_logs_dir_by_disk_root(),
520                                     log_filename=None, log_file_size=100,
521                                     mongo_url=None,
522                                     formatter_template=5):
523         """
524        :param log_level_int: 日志输出级别,设置为 1 2 3 4 5,分别对应原生logging.DEBUG(10),logging.INFO(20),logging.WARNING(30),logging.ERROR(40),logging.CRITICAL(50)级别,现在可以直接用10 20 30 40 50了,兼容了。
525        :param is_add_stream_handler: 是否打印日志到控制台
526        :param do_not_use_color_handler :是否禁止使用color彩色日志
527        :param log_path: 设置存放日志的文件夹路径
528        :param log_filename: 日志的名字,仅当log_path和log_filename都不为None时候才写入到日志文件。
529        :param log_file_size :日志大小,单位M,默认10M
530        :param mongo_url : mongodb的连接,为None时候不添加mongohandler
531        :param formatter_template :日志模板,1为formatter_dict的详细模板,2为简要模板,5为最好模板
532        :type log_level_int :int
533        :type is_add_stream_handler :bool
534        :type log_path :str
535        :type log_filename :str
536        :type mongo_url :str
537        :type log_file_size :int
538        """
539         self._logger_level = log_level_int * 10 if log_level_int < 10 else log_level_int
540         self._is_add_stream_handler = is_add_stream_handler
541         self._do_not_use_color_handler = do_not_use_color_handler
542         self._log_path = log_path
543         self._log_filename = log_filename
544         self._log_file_size = log_file_size
545         self._mongo_url = mongo_url
546         self._formatter = formatter_dict[formatter_template]
547         self.__set_logger_level()
548         self.__add_handlers()
549         self.logger_name_list.append(self._logger_name)
550         self.logger_list.append(self.logger)
551         return self.logger
552 
553     def get_logger_without_handlers(self):
554         """返回一个不带hanlers的logger"""
555         return self.logger
556 
557     # noinspection PyMethodMayBeStatic,PyMissingOrEmptyDocstring
558     def look_over_all_handlers(self):
559         print(f'{self._logger_name}名字的日志的所有handlers是--> {self.logger.handlers}')
560 
561     def remove_all_handlers(self):
562         for hd in self.logger.handlers:
563             self.logger.removeHandler(hd)
564 
565     def remove_handler_by_handler_class(self, handler_class: type):
566         """
567         去掉指定类型的handler
568         :param handler_class:logging.StreamHandler,ColorHandler,MongoHandler,ConcurrentRotatingFileHandler,MongoHandler,CompatibleSMTPSSLHandler的一种
569         :return:
570         """
571         if handler_class not in (logging.StreamHandler, ColorHandler, MongoHandler, ConcurrentRotatingFileHandler, MongoHandler, CompatibleSMTPSSLHandler):
572             raise TypeError('设置的handler类型不正确')
573         for handler in self.logger.handlers:
574             if isinstance(handler, handler_class):
575                 self.logger.removeHandler(handler)
576 
577     def __set_logger_level(self):
578         self.logger.setLevel(self._logger_level)
579 
580     def __remove_handlers_from_other_logger_when_logger_name_is_none(self, handler_class):
581         """
582         当logger name为None时候需要移出其他logger的handler,否则重复记录日志
583         :param handler_class: handler类型
584         :return:
585         """
586         if self._logger_name is None:
587             for logger in self.logger_list:
588                 for hdlr in logger.handlers:
589                     if isinstance(hdlr, handler_class):
590                         logger.removeHandler(hdlr)
591 
592     @staticmethod
593     def __judge_logger_contain_handler_class(logger: logging.Logger, handler_class):
594         for h in logger.handlers + logging.getLogger().handlers:
595             if isinstance(h, (handler_class,)):
596                 return True
597 
598     def __add_handlers(self):
599         if self._is_add_stream_handler:
600             if not self.__judge_logger_contain_handler_class(self.logger,
601                                                              ColorHandler):  # 主要是阻止给logger反复添加同种类型的handler造成重复记录
602                 self.__remove_handlers_from_other_logger_when_logger_name_is_none(ColorHandler)
603                 self.__add_stream_handler()
604 
605         if all([self._log_path, self._log_filename]):
606             if not self.__judge_logger_contain_handler_class(self.logger, ConcurrentRotatingFileHandler):
607                 self.__remove_handlers_from_other_logger_when_logger_name_is_none(ConcurrentRotatingFileHandler)
608                 self.__add_file_handler()
609 
610         if self._mongo_url:
611             if not self.__judge_logger_contain_handler_class(self.logger, MongoHandler):
612                 self.__remove_handlers_from_other_logger_when_logger_name_is_none(MongoHandler)
613                 self.__add_mongo_handler()
614 
615     def __add_mongo_handler(self):
616         """写入日志到mongodb"""
617         mongo_handler = MongoHandler(self._mongo_url)
618         mongo_handler.setLevel(self._logger_level)
619         mongo_handler.setFormatter(self._formatter)
620         self.logger.addHandler(mongo_handler)
621 
622     def __add_stream_handler(self):
623         """
624         日志显示到控制台
625         """
626         # stream_handler = logging.StreamHandler()
627         stream_handler = ColorHandler() if not self._do_not_use_color_handler else logging.StreamHandler()  # 不使用streamhandler,使用自定义的彩色日志
628         stream_handler.setLevel(self._logger_level)
629         stream_handler.setFormatter(self._formatter)
630         self.logger.addHandler(stream_handler)
631 
632     def __add_file_handler(self):
633         """
634         日志写入日志文件
635         """
636         if not os.path.exists(self._log_path):
637             os.makedirs(self._log_path)
638         log_file = os.path.join(self._log_path, self._log_filename)
639         rotate_file_handler = None
640         if os_name == 'nt':
641             # windows下用这个,非进程安全
642             rotate_file_handler = ConcurrentRotatingFileHandler(log_file, maxBytes=self._log_file_size * 1024 * 1024,
643                                                                 backupCount=3,
644                                                                 encoding="utf-8")
645         if os_name == 'posix':
646             # linux下可以使用ConcurrentRotatingFileHandler,进程安全的日志方式
647             rotate_file_handler = ConcurrentRotatingFileHandler(log_file, maxBytes=self._log_file_size * 1024 * 1024,
648                                                                 backupCount=3, encoding="utf-8")
649         rotate_file_handler.setLevel(self._logger_level)
650         rotate_file_handler.setFormatter(self._formatter)
651         self.logger.addHandler(rotate_file_handler)
652 
653 
654 class LoggerMixin(object):
655     subclass_logger_dict = {}
656 
657     @property
658     def logger(self):
659         if self.__class__.__name__ + '1' not in self.subclass_logger_dict:
660             logger_var = LogManager(self.__class__.__name__).get_logger_and_add_handlers()
661             self.subclass_logger_dict[self.__class__.__name__ + '1'] = logger_var
662             return logger_var
663         else:
664             return self.subclass_logger_dict[self.__class__.__name__ + '1']
665 
666     @property
667     def logger_with_file(self):
668         if self.__class__.__name__ + '2' not in self.subclass_logger_dict:
669             logger_var = LogManager(type(self).__name__).get_logger_and_add_handlers(log_filename=type(self).__name__ + '.log', log_file_size=50)
670             self.subclass_logger_dict[self.__class__.__name__ + '2'] = logger_var
671             return logger_var
672         else:
673             return self.subclass_logger_dict[self.__class__.__name__ + '2']
674 
675     @property
676     def logger_with_file_mongo(self):
677         from app import config
678         if self.__class__.__name__ + '3' not in self.subclass_logger_dict:
679             logger_var = LogManager(type(self).__name__).get_logger_and_add_handlers(log_filename=type(self).__name__ + '.log', log_file_size=50, mongo_url=config.connect_url)
680             self.subclass_logger_dict[self.__class__.__name__ + '3'] = logger_var
681             return logger_var
682         else:
683             return self.subclass_logger_dict[self.__class__.__name__ + '3']
684 
685 
686 simple_logger = LogManager('simple').get_logger_and_add_handlers()
687 defaul_logger = LogManager('hotel').get_logger_and_add_handlers(do_not_use_color_handler=True, formatter_template=7)
688 file_logger = LogManager('hotelf').get_logger_and_add_handlers(do_not_use_color_handler=True,
689                                                                log_filename='hotel_' + time.strftime("%Y-%m-%d",
690                                                                                                      time.localtime()) + ".log",
691                                                                formatter_template=7)
692 
693 
694 # noinspection PyMethodMayBeStatic,PyNestedDecorators,PyArgumentEqualDefault
695 class _Test(unittest.TestCase):
696     # noinspection PyMissingOrEmptyDocstring
697     @classmethod
698     def tearDownClass(cls):
699         """
700 
701         """
702         time.sleep(1)
703 
704     @unittest.skip
705     def test_repeat_add_handlers_(self):
706         """测试重复添加handlers"""
707         LogManager('test').get_logger_and_add_handlers(log_path='../logs', log_filename='test.log')
708         LogManager('test').get_logger_and_add_handlers(log_path='../logs', log_filename='test.log')
709         LogManager('test').get_logger_and_add_handlers(log_path='../logs', log_filename='test.log')
710         test_log = LogManager('test').get_logger_and_add_handlers(log_path='../logs', log_filename='test.log')
711         print('下面这一句不会重复打印四次和写入日志四次')
712         time.sleep(1)
713         test_log.debug('这一句不会重复打印四次和写入日志四次')
714 
715     @unittest.skip
716     def test_get_logger_without_hanlders(self):
717         """测试没有handlers的日志"""
718         log = LogManager('test2').get_logger_without_handlers()
719         print('下面这一句不会被打印')
720         time.sleep(1)
721         log.info('这一句不会被打印')
722 
723     @unittest.skip
724     def test_add_handlers(self):
725         """这样可以在具体的地方任意写debug和info级别日志,只需要在总闸处规定级别就能过滤,很方便"""
726         LogManager('test3').get_logger_and_add_handlers(2)
727         log1 = LogManager('test3').get_logger_without_handlers()
728         print('下面这一句是info级别,可以被打印出来')
729         time.sleep(1)
730         log1.info('这一句是info级别,可以被打印出来')
731         print('下面这一句是debug级别,不能被打印出来')
732         time.sleep(1)
733         log1.debug('这一句是debug级别,不能被打印出来')
734 
735     @unittest.skip
736     def test_only_write_log_to_file(self):  # NOQA
737         """只写入日志文件"""
738         log5 = LogManager('test5').get_logger_and_add_handlers(20)
739         log6 = LogManager('test6').get_logger_and_add_handlers(is_add_stream_handler=False, log_filename='test6.log')
740         print('下面这句话只写入文件')
741         log5.debug('这句话只写入文件')
742         log6.debug('这句话只写入文件')
743 
744     # @unittest.skip
745     def test_color_and_mongo_hanlder(self):
746         """测试彩色日志和日志写入mongodb"""
747         from app import config
748         logger = LogManager('helloMongo').get_logger_and_add_handlers(mongo_url=config.connect_url)
749         logger.debug('一个debug级别的日志')
750         logger.info('一个info级别的日志')
751         logger.warning('一个warning级别的日志')
752         logger.error('一个error级别的日志')
753         logger.critical('一个critical级别的日志')
754 
755     @unittest.skip
756     def test_get_app_logs_dir(self):  # NOQA
757         print(get_logs_dir_by_folder_name())
758         print(get_logs_dir_by_disk_root())
759 
760     @unittest.skip
761     def test_none(self):
762         # noinspection PyUnusedLocal
763         log1 = LogManager('log1').get_logger_and_add_handlers()
764         LogManager().get_logger_and_add_handlers()
765 
766         LogManager().get_logger_and_add_handlers()
767         log1 = LogManager('log1').get_logger_and_add_handlers()
768         LogManager().get_logger_and_add_handlers()
769         LogManager('log1').get_logger_and_add_handlers(log_filename='test_none.log')
770         log1.debug('打印几次?')
771 
772     @unittest.skip
773     def test_formater(self):
774         logger2 = LogManager('test_formater2').get_logger_and_add_handlers(formatter_template=6)
775         logger2.debug('测试日志模板2')
776         logger5 = LogManager('test_formater5').get_logger_and_add_handlers(formatter_template=5)
777         logger5.error('测试日志模板5')
778         defaul_logger.debug('dddddd')
779         file_logger.info('ffffff')
780 
781     @unittest.skip
782     def test_bulid_a_logger_with_mail_handler(self):
783         """
784         测试日志发送到邮箱中
785         :return:
786         """
787         logger = LogManager.bulid_a_logger_with_mail_handler('mail_logger_name', mail_time_interval=10, toaddrs=(
788             'yyy@qq.com', 'zz@oo.com', ))
789         # LogManager.bulid_a_logger_with_mail_handler('mail_logger_name', mail_time_interval=10)
790         # LogManager.bulid_a_logger_with_mail_handler(None, log_filename='mail.log', mail_time_interval=10)
791         for _ in range(100):
792             logger.warning('啦啦啦啦啦')
793             logger.warning('测试邮件日志的内容。。。。')
794             time.sleep(2)
795 
796     @unittest.skip
797     def test_remove_handler(self):
798         logger = LogManager('test13').get_logger_and_add_handlers()
799         logger.debug('去掉coloerhandler前')
800         LogManager('test13').remove_handler_by_handler_class(ColorHandler)
801         logger.debug('去掉coloerhandler后,此记录不会被打印')
802 
803 
804 if __name__ == "__main__":
805     unittest.main()
复制代码

 

 

喜欢面向对象和设计模式的解读,喜欢对比极端面向过程编程和oop编程消耗代码代码行数的区别。致力于使用oop和36种设计模式写出最高可复用的框架级代码和使用最少的代码行数完成任务,致力于使用oop和设计模式来使大部分代码减少90%行,致力于使任何文件最低减少50%行。
原文地址https://www.cnblogs.com/ydf0509/p/10502683.html
登录 后评论
下一篇
corcosa
8725人浏览
2019-10-08
相关推荐
python爬虫入门基本知识
1806人浏览
2017-08-01 14:49:00
写在使用 Linux 工作一年后
1682人浏览
2018-10-25 22:47:18
传统应用的docker化迁移
847人浏览
2017-08-02 11:39:00
传统应用的docker化迁移
694人浏览
2017-07-03 10:35:00
Python快速教程 尾声
618人浏览
2014-03-16 10:09:00
0
0
0
597