Log日志模塊
前言
logging 是python中的一個包,封裝所有日志功能。
例如獲取日志器 logging.getLogger 是logging包的 __init__ 文件中定義的函數(包的 __init__文件中的函數直接可以使用包名.函數名調用),如下 getLogger 代碼:
def getLogger(name=None): """ Return a logger with the specified name, creating it if necessary. If no name is specified, return the root logger. """ if not name or isinstance(name, str) and name == root.name: return root return Logger.manager.getLogger(name)
root = RootLogger(WARNING)
class RootLogger(Logger): """ A root logger is not that different to any other logger, except that it must have a logging level and there is only one instance of it in the hierarchy. """ def __init__(self, level): """ Initialize the logger with the name "root". """ Logger.__init__(self, "root", level) def __reduce__(self): return getLogger, ()
即 getLogger 函數返回值為Logger類的實例對象。
例如為日志器設置默認的日志級別 logger.setLevel() 為logging包的 __init__ 文件 Logger 類中的 setLevel 函數,代碼如下:
def setLevel(self, level): """ Set the logging level of this logger. level must be an int or a str. """ self.level = _checkLevel(level) self.manager._clear_cache()
def _checkLevel(level): if isinstance(level, int): rv = level elif str(level) == level: if level not in _nameToLevel: raise ValueError("Unknown level: %r" % level) rv = _nameToLevel[level] else: raise TypeError("Level not an integer or a valid string: %r" % level) return rv
_nameToLevel = { 'CRITICAL': CRITICAL, 'FATAL': FATAL, 'ERROR': ERROR, 'WARN': WARNING, 'WARNING': WARNING, 'INFO': INFO, 'DEBUG': DEBUG, 'NOTSET': NOTSET, }
CRITICAL = 50 FATAL = CRITICAL ERROR = 40 WARNING = 30 WARN = WARNING INFO = 20 DEBUG = 10 NOTSET = 0
一、logging函數根據它們用來跟蹤的事件的級別或嚴重程度來命名。標准級別及其適用性描述如下(以嚴重程度遞增排序):


默認等級是WARNING,只有高於默認等級以上的級別才會打印到控制台。
①將日志直接輸出到屏幕
""" 由於默認設置的等級是warning,所有只有warning的信息會輸出到控制台。 """ import logging logging.debug('debug 信息') logging.warning('只有這個會輸出。。。') logging.info('info 信息')
輸出結果:

②通過logging.basicConfig函數對日志的輸出格式及方式做相關配置
import logging logging.basicConfig(level=logging.DEBUG, format='%(asctime)s %(filename)s[line:%(lineno)d] %(levelname)s %(message)s', datefmt='%a, %d %b %Y %H:%M:%S', filename='myapp.log', filemode='w') logging.debug('This is debug message') logging.info('This is info message') logging.warning('This is warning message') #./myapp.log文件中內容為: #Sun, 24 May 2009 21:48:54 demo2.py[line:11] DEBUG This is debug message #Sun, 24 May 2009 21:48:54 demo2.py[line:12] INFO This is info message #Sun, 24 May 2009 21:48:54 demo2.py[line:13] WARNING This is warning message
logging.basicConfig參數:
#logging.basicConfig函數各參數:
filename: 指定日志文件名 filemode: 和file函數意義相同,指定日志文件的打開模式,'w'或'a' format: 指定輸出的格式和內容,format可以輸出很多有用信息,如上例所示: %(levelno)s: 打印日志級別的數值 %(levelname)s: 打印日志級別名稱 %(pathname)s: 打印當前執行程序的路徑,其實就是sys.argv[0] %(filename)s: 打印當前執行程序名 %(funcName)s: 打印日志的當前函數 %(lineno)d: 打印日志的當前行號 %(asctime)s: 打印日志的時間 %(thread)d: 打印線程ID %(threadName)s: 打印線程名稱 %(process)d: 打印進程ID %(message)s: 打印日志信息 datefmt: 指定時間格式,同time.strftime() level: 設置日志級別,默認為logging.WARNING【高於該日志級別才會打印到控制台上】 stream: 指定將日志的輸出流,可以指定輸出到sys.stderr,sys.stdout或者文件,默認輸出到sys.stderr,當stream和filename同時指定時,stream被忽略
③將日志同時輸出到多個Handler
定義handler,並使用addHander()添加到日志器,實現日志輸出到多個handler。
a、同時輸出到文件和屏幕
import logging #設置一個basicConfig只能輸出到一個Handler logging.basicConfig(level=logging.DEBUG, format='%(asctime)s %(filename)s[line:%(lineno)d] %(levelname)s %(message)s', datefmt='%a, %d %b %Y %H:%M:%S', filename='myapp.log', filemode='w') #定義一個StreamHandler,將INFO級別或更高的日志信息打印到標准錯誤,並將其添加到當前的日志處理對象# console = logging.StreamHandler() console.setLevel(logging.INFO) formatter = logging.Formatter('%(name)-12s: %(levelname)-8s %(message)s') console.setFormatter(formatter) logging.getLogger('').addHandler(console) #輸出到文件的log級別為debug,輸出到stream的log級別為info logging.debug('This is debug message') logging.info('This is info message') logging.warning('This is warning message')
b、添加一個handler:輸出到文件,並根據文件大小滾動存儲
在a的基礎上添加一個handler
from logging.handlers import RotatingFileHandler #定義一個RotatingFileHandler,最多備份5個日志文件,每個日志文件最大10M Rthandler = RotatingFileHandler('myapp.log', maxBytes=10*1024*1024,backupCount=5) Rthandler.setLevel(logging.INFO) formatter = logging.Formatter('%(name)-12s: %(levelname)-8s %(message)s') Rthandler.setFormatter(formatter) logging.getLogger('').addHandler(Rthandler)
logging幾種Handler類型:
logging.StreamHandler(默認): 日志輸出到流,可以是sys.stderr、sys.stdout或者文件 logging.FileHandler: 日志輸出到文件 logging.handlers.RotatingFileHandler 日志輸出到文件,基於文件大小滾動存儲日志 logging.handlers.TimedRotatingFileHandler 日志輸出到文件,基於時間周期滾動存儲日志 logging.handlers.SocketHandler: 遠程輸出日志到TCP/IP sockets logging.handlers.DatagramHandler: 遠程輸出日志到UDP sockets logging.handlers.SMTPHandler: 遠程輸出日志到郵件地址 logging.handlers.SysLogHandler: 日志輸出到syslog logging.handlers.NTEventLogHandler: 遠程輸出日志到Windows NT/2000/XP的事件日志 logging.handlers.MemoryHandler: 日志輸出到內存中的制定buffer logging.handlers.HTTPHandler: 通過"GET"或"POST"遠程輸出到HTTP服務器
④通過配置文件配置logger
a、定義配置文件 logger.conf
#logger.conf ############################################### [loggers] keys=root,example01,example02 [logger_root] level=DEBUG handlers=hand01,hand02 [logger_example01] handlers=hand01,hand02 qualname=example01 propagate=0 [logger_example02] handlers=hand01,hand03 qualname=example02 propagate=0 ############################################### [handlers] keys=hand01,hand02,hand03 [handler_hand01] class=StreamHandler level=INFO formatter=form02 args=(sys.stderr,) [handler_hand02] class=FileHandler level=DEBUG formatter=form01 args=('myapp.log', 'a') [handler_hand03] class=handlers.RotatingFileHandler level=INFO formatter=form02 args=('myapp.log', 'a', 10*1024*1024, 5) ############################################### [formatters] keys=form01,form02 [formatter_form01] format=%(asctime)s %(filename)s[line:%(lineno)d] %(levelname)s %(message)s datefmt=%a, %d %b %Y %H:%M:%S [formatter_form02] format=%(name)-12s: %(levelname)-8s %(message)s datefmt=
b、 logging.config 獲取配置
import logging import logging.config logging.config.fileConfig("logger.conf") logger = logging.getLogger("example01") logger.debug('This is debug message') logger.info('This is info message') logger.warning('This is warning message')
import logging import logging.config logging.config.fileConfig("logger.conf") logger = logging.getLogger("example02") logger.debug('This is debug message') logger.info('This is info message') logger.warning('This is warning message')
二、logging庫【python中已封裝好的功能模塊】采取了模塊化的設計,
提供了許多組件:記錄器(日志器logger)、處理器(handles)、過濾器和格式化器(formatters)。
1、Logger 暴露了應用程序代碼能直接使用的接口。
2、Handler將(記錄器產生的)日志記錄發送至合適的目的地。
3、Filter提供了更好的粒度控制,它可以決定輸出哪些日志記錄。
4、Formatter 指明了最終輸出中日志記錄的布局。
三、Loggers
Logger 對象要做三件事情。
首先,它們向應用代碼暴露了許多方法,這樣應用可以在運行時記錄消息。
其次,記錄器對象通過嚴重程度(默認的過濾設施)或者過濾器對象來決定哪些日志消息需要記錄下來。
第三,記錄器對象將相關的日志消息傳遞給所有感興趣的日志處理器。
常用的記錄器對象的方法分為兩類:配置和發送消息。
這些是最常用的配置方法:
①Logger.setLevel()指定logger將會處理的最低的安全等級日志信息:
debug是最低的內置安全等級;
critical是最高的內置安全等級。
例如,如果嚴重程度為INFO,記錄器將只處理INFO,WARNING,ERROR和CRITICAL消息,DEBUG消息被忽略。
②Logger.addHandler()和Logger.removeHandler()從記錄器對象中添加和刪除處理程序對象。處理器詳見Handlers。
③Logger.addFilter()和Logger.removeFilter()從記錄器對象添加和刪除過濾器對象。
四、Handlers
處理程序對象負責將適當的日志消息(基於日志消息的嚴重性)分派到處理程序的指定目標。【根據分配到的Handlers不同,又將不同的日志信息以不同的方式輸出】
Logger 對象可以通過addHandler()方法增加零個或多個handler對象。
舉個例子:①一個應用可以將所有的日志消息發送至日志文件;②所有的錯誤級別(error)及以上級別的日志消息發送至標准輸出;③所有的嚴重級別(critical)日志消息發送至某個電子郵箱。
在這個例子中需要三個獨立的處理器,每一個負責將特定級別的日志消息發送至特定的位置。
常用的有4種:
1、 logging.StreamHandler : 控制台輸出
向類似與 sys.stdout 或者 sys.stderr 的任何文件對象(file object)輸出信息。
它的構造函數是: StreamHandler([strm])
參數介紹:strm參數是一個文件對象。默認是sys.stderr(不傳strm參數時,默認將日志信息輸出至控制台)。
2、 logging.FileHandler : 文件輸出
用於向一個文件輸出日志信息。
構造函數是: FileHandler(filename[,mode])
參數介紹:①filename文件名:必須指定一個文件名;②mode是文件的打開方式。默認是’a',即添加到文件末尾。
3、logging.handlers.RotatingFileHandler : 按照大小自動分割日志文件,一旦達到指定的大小重新生成文件
這個Handler類似於上面的FileHandler,但是它可以管理文件大小。當文件達到一定大小之后,它會自動將當前日志文件改名,然后創建一個新的同名日志文件繼續輸出。
例子:比如日志文件是chat.log。
當chat.log達到指定的大小之后,RotatingFileHandler自動把文件改名為chat.log.1。
不過,如果chat.log.1已經存在,會先把chat.log.1重命名為chat.log.2。。。最后重新創建 chat.log,繼續輸出日志信息。
構造函數: RotatingFileHandler( filename[, mode[, maxBytes[, backupCount]]])
參數介紹:①filename文件名:必須指定一個文件名。②mode是文件的打開方式。默認是’a',即添加到文件末尾。③maxBytes用於指定日志文件的最大文件大小。【如果maxBytes為0,意味着日志文件可以無限大,這時上面描述的重命名過程就不會發生。】。④backupCount用於指定保留的備份文件的個數。【如果指定為2,當上面描述的重命名過程發生時,原有的chat.log.2並不會被更名,而是被刪除。】
4、 logging.handlers.TimedRotatingFileHandler :按照時間自動分割日志文件
這個Handler和RotatingFileHandler類似;
不過,它沒有通過判斷文件大小來決定何時重新創建日志文件,而是間隔一定時間就 自動創建新的日志文件。
重命名的過程與RotatingFileHandler類似:不過新的文件不是附加數字,而是當前時間。
構造函數: TimedRotatingFileHandler( filename [,when [,interval [,backupCount]]])
參數介紹:
①filename文件名:必須指定一個文件名。
②mode是文件的打開方式。默認是’a',即添加到文件末尾。③backupCount用於指定保留的備份文件的個數。
④interval是時間間隔。
⑤when參數是一個字符串。表示時間間隔的單位,不區分大小寫。它有以下取值:
S 秒 M 分 H 小時 D 天 W 每星期(interval==0時代表星期一) midnight 每天凌晨
配置方法:
① handlers.setLevel() :記錄器的級別決定了消息是否要傳遞給處理器。每個處理器的級別決定了消息是否要分發。【 logger.setLevel() 為記錄器設置日志級別(根據設置的日志級別決定日志信息是否傳遞給處理器), handlers.setLevel 為處理器設置日志級別(根據每個處理器設置的日志級別決定日志信息是否輸出至指定地方)。】
② handlers.setFormatter() :為該處理器選擇一個格式化器。
③ handlers.addFilter() 和 handlers.removeFilter():分別配置和取消配置處理器上的過濾器對象。
五、Formatters
Formatter對象設置日志信息最后的規則、結構和內容;【日志生成時的樣式】
默認的時間格式為%Y-%m-%d %H:%M:%S。
如下圖為Formatter常用的一些信息:

六、代碼示例
代碼示例1:get_logger(獲取日志器):
# 導包 import logging.handlers import os import time # 新建 類 class Logs: root_path = os.path.abspath(os.path.dirname(__file__)).split('shippingSchedule')[0] # 新建一個日志器變量 __logger = None # 定義生成日志文件的時間 __log_time = time.strftime("%Y-%m-%d", time.localtime()) # 新建獲取日志器的方法 @classmethod def get_logger(cls): # 判斷日志器為空: if cls.__logger is None: # 獲取日志器【Logger 暴露了應用程序代碼能直接使用的接口】 cls.__logger = logging.getLogger() # 包名.方法名直接調用logging包下的初始化模塊中的getLogger()返回RootLogger # 修改默認級別 cls.__logger.setLevel(logging.DEBUG) log_path = cls.root_path + os.sep + "python--log" + os.sep + "info.python--log" + "_" + cls.__log_time # 獲取處理器【Handler將(記錄器產生的)日志記錄發送至合適的目的地】 th = logging.handlers.TimedRotatingFileHandler(filename=log_path, when="midnight", interval=1, backupCount=3, encoding="utf-8") # 獲取格式化器【Filter提供了更好的粒度控制,它可以決定輸出哪些日志記錄】 fmt = "%(asctime)s %(levelname)s [%(filename)s(%(funcName)s:%(lineno)d)] - %(message)s" fm = logging.Formatter(fmt) # Formatter 指明了最終輸出中日志記錄的布局。 # 將格式化器添加到處理器中 th.setFormatter(fm) # 將處理器添加到日志器中 cls.__logger.addHandler(th) # 返回日志器 return cls.__logger if __name__ == '__main__': log = Logs.get_logger() log.info("測試信息級別日志") log.error("測試錯誤級別")
代碼示例2:輸出log到控制台以及同時將log寫入log文件
# encoding:utf-8 import logging from logging import handlers class Logger(object): level_relations = { 'debug': logging.DEBUG, 'info': logging.INFO, 'warning': logging.WARNING, 'error': logging.ERROR, 'crit': logging.CRITICAL } # 日志級別關系映射 def __init__(self, filename, level='info', when='D', backCount=3, fmt='%(asctime)s -%(name)s- %(pathname)s[line:%(lineno)d] - %(levelname)s: %(message)s'): self.logger = logging.getLogger(filename) # 獲取日志器(filename是日志器的名稱,如果不填,默認日志器名稱為root) format_str = logging.Formatter(fmt) # 設置日志格式 self.logger.setLevel(self.level_relations.get(level)) # 設置日志級別 sh = logging.StreamHandler() # 輸出至控制台 sh.setFormatter(format_str) # 設置輸出至控制台上日志格式 th = handlers.TimedRotatingFileHandler(filename=filename, when=when, backupCount=backCount, encoding='utf-8') # 往文件里寫入#指定間隔時間自動生成文件的處理器 # 實例化TimedRotatingFileHandler # interval是時間間隔,backupCount是備份文件的個數,如果超過這個個數,就會自動刪除,when是間隔的時間單位,單位有以下幾種: # S 秒 # M 分 # H 小時、 # D 天、 # W 每星期(interval==0時代表星期一) # midnight 每天凌晨 th.setFormatter(format_str) # 設置文件里寫入的格式 self.logger.addHandler(sh) # 把對象加到logger里 self.logger.addHandler(th) if __name__ == '__main__': log = Logger('all.log', level='debug') log.logger.debug('debug') log.logger.info('info') log.logger.warning('警告') log.logger.error('報錯') log.logger.critical('嚴重') Logger('error.log', level='error').logger.error('error')
代碼示例3:輸出log日志到控制台以及同時將log日志寫入log文件+裝飾器函數代替測試用例腳本中函數調用。
# -*- encoding:utf-8 -*- import logging import os import time import traceback from functools import wraps """handlers是什么?""" # logging模塊中包含的類 # 用來自定義日志對象的規則(比如:設置日志輸出格式、等級等) # 常用子類:StreamHandler、FileHandler # StreamHandler 控制台輸出日志 # FileHandler 日志輸出到文件 # 日志文件路徑 LOG_PATH = os.path.join(os.path.dirname(os.path.dirname(os.path.realpath(__file__))), "log") if not os.path.exists(LOG_PATH): os.mkdir(LOG_PATH) class Logger: def __init__(self): # 創建日志路徑和時間 self.log_path = os.path.join(LOG_PATH, "{}.log".format(time.strftime("%Y%m%d"))) # 創建一個logger日志對象,並傳參為log日志器命名為log self.logger = logging.getLogger("log") # 為日志器設置默認的日志級別 self.logger.setLevel(logging.DEBUG) # 創建日志格式對象 self.formater = logging.Formatter('[%(asctime)s][%(filename)s %(lineno)d][%(levelname)s]: %(message)s') # 創建FileHandler對象(輸出log至文件) self.file_handlers = logging.FileHandler(self.log_path, mode='a', encoding="UTF-8") # 創建StreamHandler對象(輸出log至控制台) self.console_handlers = logging.StreamHandler() # FileHandler對象定義日志級別 self.file_handlers.setLevel(logging.DEBUG) # StreamHandler對象定義日志級別 self.console_handlers.setLevel(logging.DEBUG) # 設置FileHandler處理器的格式 self.file_handlers.setFormatter(self.formater) # 設置StreamHandler處理器的格式 self.console_handlers.setFormatter(self.formater) # logger日志對象加載FileHandler對象 self.logger.addHandler(self.file_handlers) # logger日志對象加載StreamHandler對象 self.logger.addHandler(self.console_handlers) Logger = Logger().logger # 定義一個裝飾器,為修飾測試方法提供附加操作(測試方法調用前,測試方法調用后) def decorate_log(func): @wraps(func) def log(*args, **kwargs): Logger.info(f'------開始執行{func.__name__}------') try: func(*args, **kwargs) except Exception as e: Logger.error(f'------{func.__name__}執行失敗,失敗原因:{e}------') Logger.error(f"{func.__name__} is error,here are details:{traceback.format_exc()}") raise e else: Logger.info(f'------{func.__name__}執行成功------') return log @decorate_log def hbq(): assert 1 == 1 if __name__ == '__main__': hbq() # Logger.info("---測試開始---") # Logger.error("---測試結束---") # Logger.debug("---測試結束---")
衍生:結合代碼示例3:
python裝飾器functools.wraps(func)詳解
1、先看一段代碼:
def is_login(func): def foo(*args, **kwargs): return func(*args, **kwargs) return foo def test(): print('我是:', test.__name__) @is_login def test1(): print('我是:', test1.__name__) @is_login def test2(): print('我是:', test2.__name__) if __name__ == '__main__': test() test1() test2()
運行結果:
我是: test
我是: foo
我是: foo
可以發現函數的函數名即 func.__name__已被裝飾器改變,變成了裝飾器內返回的函數的函數名
2、在裝飾器內返回的函數的函數名上新增 @wraps 裝飾器/ functools.wraps(func) 裝飾器
from functools import wraps def is_login(func): @wraps(func) def foo(*args, **kwargs): return func(*args, **kwargs) return foo def test(): print('我是:', test.__name__) @is_login def test1(): print('我是:', test1.__name__) @is_login def test2(): print('我是:', test2.__name__) if __name__ == '__main__': test() test1() test2()
運行結果:
我是: test
我是: test1
我是: test2
結論:
@wraps 可以保證被裝飾器修飾的函數的 func.__name__ 的值即函數名保持不變。
裝飾器的優化
以時間裝飾器為例,進行優化
- 裝飾器的統一模板
from functools import wraps # 對函數的裝飾器, 對類func最好為cls def decorate(func): @wraps(func) # 增添或修改功能的函數 def wrapper(*args,**kwargs): # 執行被裝飾的函數 result = func(*args,**kwargs) # 返回結果 return result # 返回內層函數 return wrapper
普通--時間裝飾器
from functools import wraps import time from random import randint def use_time(func): @wraps(func) def wrapper(*args,**kwargs): st_time = time.time() result = func(*args,**kwargs) end_time = time.time() print(f'{func.__name__}函數use_time:{end_time-st_time}s') return wrapper @use_time def foo(): time.sleep(randint(1,3)) for _ in range(3): foo()
運行結果:
foo函數use_time:3.0130558013916016s foo函數use_time:1.0116651058197021s foo函數use_time:1.01423978805542s
下面對改裝飾器進行優化(解耦)
- 可以發先上面時間裝飾器計算的結果,只能在控制台上打印
- 那我們怎樣才能將它輸出為日志呢???
- 我們需要將他的結果進行自定輸出
# 在增加一層函數 from functools import wraps import time from random import randint def record(output): def use_time(func): @wraps(func) def wrapper(*args, **kwargs): st_time = time.time() func(*args, **kwargs) end_time = time.time() # print(f'{func.__name__}函數use_time:{end_time-st_time}s') output(func.__name__, 'use', end_time - st_time) return wrapper return use_time # 改裝飾器的結果就可以自定義了,下面以print函數為例 @record(print) def foo(): time.sleep(randint(2, 5)) if __name__ == '__main__': foo()
結果輸出日志
# 在增加一層函數 from functools import wraps import time from random import randint def record(output): def use_time(func): @wraps(func) def wrapper(*args, **kwargs): st_time = time.time() func(*args, **kwargs) end_time = time.time() # print(f'{func.__name__}函數use_time:{end_time-st_time}s') output(func.__name__, end_time - st_time) return wrapper return use_time def write_log(name, content): with open('./time.log', 'a', encoding='utf-8')as f: f.write(f'{name}耗時:{content}\r\n') # \r\n 換行 # 只需要將裝飾器改為@record(write_log) @record(write_log) def foo(): time.sleep(randint(2, 5)) if __name__ == '__main__': foo()
