最友善最好看最好用的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=('[email protected]', '[email protected]',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 = '[email protected]',
459 toaddrs: tuple = ('[email protected]', 460 '[email protected]', '[email protected]',
461 '[email protected]', '[email protected]', '[email protected]'),
462 subject: str = '日志報警測試',
463 credentials: tuple = ('[email protected]', '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 '[email protected]', '[email protected]', ))
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