程序都有記錄日志的需求,並且日志中包含的信息既有正常的程序訪問日志,還可能有錯誤、警告等信息輸出。
python的logging模塊提供了標准的日志接口,可以通過它存儲各種格式的日志,logging的日志分為debug(), info(), warning(), error() and critical()5個級別。
python默認只打印warning級別以上的日志
日志基礎:
兩種記錄日志的方式:
其實模塊級別的日志記錄函數也是對logging日志系統相關類的封裝。
模塊級別的常用函數
logging.debug(msg, *args, **kwargs)創建一條嚴重級別為DEBUG的日志記錄logging.info(msg, *args, **kwargs)創建一條嚴重級別為INFO的日志記錄logging.warning(msg, *args, **kwargs)創建一條嚴重級別為WARNING的日志記錄logging.error(msg, *args, **kwargs)創建一條嚴重級別為ERROR的日志記錄logging.critical(msg, *args, **kwargs)創建一條嚴重級別為CRITICAL的日志記錄logging.log(level, *args, **kwargs)創建一條嚴重級別為level的日志記錄logging.basicConfig(**kwargs)對root logger進行一次性配置默認只有warning級別以上的日志會打印
basicConfig所有參數信息
filename指定日志輸出目標文件的文件名filemode指定日志文件的打開模式,默認’a’format指定日志格式字符串datefmt指定日期/時間格式,需format中包含%(asctime)s字段level指定日志器的日志級別stream指定日志輸出目標,如sys.stdout、sys.stderr以及網絡stream。stream和filename不能共存style指定format格式字符串的風格,可取值為’%‘、’{‘和’$‘,默認為’%’handlers該選項如果應該是一個創建了多個Handler的可迭代對象,這些handler將會被添加到root loggerformat所有參數列表
asctime%(asctime)s字符串形式的當前時間。默認格式是 “2003-07-08 16:49:45,896”created%(created)f時間戳,就是調用time.time()函數返回的值msecs%(msecs)d日志發生時間的毫秒部分levelname%(levelname)s文字形式的日志級別levelno%(levelno)s數字形式的日志級別(10, 20, 30, 40, 50)name%(name)s使用的日志器名稱,默認是’root’message%(message)s日志記錄的文本內容,msg % args計算得到的pathname%(pathname)s調用日志記錄函數的源碼文件的全路徑filename%(filename)spathname的文件名部分,含文件後綴module%(module)sfilename的名稱部分,不包含後綴lineno%(lineno)d調用日志記錄函數的源代碼所在的行號funcName%(funcName)s調用日志記錄函數的函數名process%(process)d進程IDprocessName%(processName)s進程名稱thread%(thread)d線程IDthreadName%(thread)s線程名稱示例:
#!/usr/bin/python3
# -*- coding: UTF-8 -*-
__author__ = "A.L.Kun"
__file__ = "demo01.py"
__time__ = "2022/8/4 10:38"
import logging
logging.basicConfig(level=logging.DEBUG, format='%(asctime)s | %(levelname)s | %(message)s', datefmt='%Y-%m-%d %I:%M:%S %p')
# level 配置最低打印的日志級別
# format 格式化輸出子讓孩子信息
logging.error("error")
logging.log(logging.DEBUG, "%s is %s old", 'Tom', '12') # 如果信息裡面有變量,可以通過增加參數數量的方法來輸出完整內容
注意:
logging.debug()等方法的定義中,除了msg和args參數外,還有一個**kwargs參數。它們支持3個關鍵字參數:exc_info, stack_info, extra,下面對這幾個關鍵字參數作個說明。
exc_info: 其值為布爾值,如果該參數的值設置為True,則會將異常信息添加到日志消息中。如果沒有異常信息則添加None到日志信息中stack_info:其值也為布爾值,默認值為False。如果該參數的值設置為True,棧信息將會被添加到日志信息中extra:這是一個字典(dict)參數,它可以用來自定義消息格式中所包含的字段,但是它的key不能與logging模塊定義的字段沖突
組件之間的關系
日志器(logger)是入口,真正干活兒的是處理器(handler),處理器(handler)還可以通過過濾器(filter)和格式器(formatter)對要輸出的日志內容做過濾和格式化等處理操作

Logger對象有三個任務要做:
Logger對象最常用的方法分為兩類:配置方法 和 發送方法
Logger.setLevel()設置日志器將會處理的日志消息的最低嚴重級別Logger.addHandler() 和 Logger.removeHandler()為該logger對象添加 和 移除一個handler對象Logger.addFilter() 和 Logger.removeFilter()為該logger對象添加 和 移除一個filter對象Logger.debug(), Logger.info(), Logger.warning(), Logger.error(), Logger.critical()創建一個與它們的方法名對應等級的日志記錄Logger.exception()創建一個類似於Logger.error()的日志消息Logger.log()需要獲取一個明確的日志level參數來創建一個日志記錄
Logger.exception()與Logger.error()的區別在於:Logger.exception()將會輸出堆棧追蹤信息,另外一個通常只是在一個exception handler中調用該方法
一種方式是通過Logger類的實例化方法創建一個Logger類的實例,更通常的方法是用logging.getLogger()方法logging.getLogger()方法有一個可選參數name,該參數表示將要返回的日志器的名稱標識,如果不提供該參數,則其值為'root'。若以相同的name參數值多次調用getLogger()方法,將會返回指向同一個logger對象的引用。
# 聊天工具的圖形界面模塊可以這樣獲得它的Logger:
LOG = logging.getLogger(”chat.gui”)
# 核心模塊可以這樣:
LOG = logging.getLogger(”chat.kernel”)
logger的層級結構與有效等級:
logger的名稱是一個以’.‘分割的層級結構,每個’.‘後面的logger都是’.'前面的logger的childrenlogger上沒有被明確設置一個level,那麼該logger就是使用它parent的level,直到找到個一個明確設置了level的祖先為止。root logger總是會有一個明確的level設置(默認為 WARNING)。當決定是否去處理一個已發生的事件時,logger的有效等級將會被用來決定是否將該事件傳遞給該logger的handlers進行處理child loggers在完成對日志消息的處理後,默認會將日志消息傳遞給與它們的祖先loggers相關的handlers。因此不必所有loggers定義和配置handlers,只需要為一個頂層的logger配置handlers,然後按照需要創建child loggers就可足夠了。可以通過將一個logger的propagate屬性設置為False來關閉這種傳遞機制,默認為TrueHandler對象的作用是(基於日志消息的level)將消息分發到handler指定的位置(文件、網絡、郵件等)。Logger對象可以通過addHandler()方法為自己添加0個或者更多個handler對象。比如,一個應用程序可能想要實現以下幾個日志需求:
這種場景就需要3個不同的handlers,每個handler負責發送一個特定級別的日志到一個特定的位置
Handler.setLevel()設置handler將會處理的日志消息的最低嚴重級別Handler.setFormatter()為handler設置一個格式器對象Handler.addFilter() 和 Handler.removeFilter()為handler添加 和 刪除一個過濾器對象應用程序代碼不應該直接實例化和使用
Handler實例。因為Handler是一個基類,它只定義了所有handlers都應該有的接口
常用的Handler
logging.StreamHandler將日志消息發送到輸出到Stream,如std.out, std.err或任何file-like對象。logging.FileHandler將日志消息發送到磁盤文件,默認情況下文件大小會無限增長logging.handlers.RotatingFileHandler將日志消息發送到磁盤文件,並支持日志文件按大小切割logging.hanlders.TimedRotatingFileHandler將日志消息發送到磁盤文件,並支持日志文件按時間切割logging.handlers.HTTPHandler將日志消息以GET或POST的方式發送給一個HTTP服務器logging.handlers.SMTPHandler將日志消息發送給一個指定的email地址logging.NullHandler該Handler實例會忽略error messages具體使用可以參照官方文檔:https://docs.python.org/zh-cn/3/library/logging.handlers.html
日志的formatter是個獨立的組件,可以跟handler組合。Formater對象用於配置日志信息的最終順序、結構和內容。
Formatter類的構造方法定義如下:
logging.Formatter.__init__(fmt=None, datefmt=None, style='%')
參數:
fmt:指定消息格式化字符串,如果不指定該參數則默認使用message的原始值datefmt:指定日期格式字符串,如果不指定該參數則默認使用"%Y-%m-%d %H:%M:%S"style:可取值為 ‘%’, ‘{‘和 ‘$’,如果不指定該參數則默認使用’%’
#!/usr/bin/python3
# -*- coding: UTF-8 -*-
__author__ = "A.L.Kun"
__file__ = "demo01.py"
__time__ = "2022/8/4 10:38"
import logging
LOG = logging.getLogger()
fh = logging.FileHandler("access.log")
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
fh.setFormatter(formatter) # 把formater綁定到fh上
LOG.addHandler(fh)
LOG.warning("test")
Filter可以被Handler和Logger用來做比level更細粒度的、更復雜的過濾功能。Filter是一個過濾器基類,它只允許某個logger層級下的日志事件通過過濾。該類定義如下:
class logging.Filter(name='A.B')
filter(record)
一個filter實例化時傳遞的name參數值為’A.B’,那麼該
filter將只允許名稱為類似'A.B','A.B,C','A.B.C.D','A.B.D'的loggers產生的日志記錄通過過濾。如果name為空字符串,則允許所有的日志事件通過過濾。
filter方法用於具體控制傳遞的record記錄是否能通過過濾,如果該方法返回值為0表示不能通過過濾,返回值為非0表示可以通過過濾。
若需要,可以在
filter(record)方法內部改變該record,比如添加、刪除或修改一些屬性
還可以通過filter做一些統計工作,如計算一個特殊的logger或handler所處理的record數量等
使用示例:
class IgnoreBackupLogFilter(logging.Filter):
"""忽略帶db backup 的日志"""
def filter(self, record): # 固定寫法
return "db backup" not in record.getMessage()
logger.addFilter(IgnoreBackupLogFilter()) # 自定義過濾器
logger.warning("start to run db backup job ....")
logger.error("test error ....")
fileConfig()功能讀取它dictConfig()函數[loggers] # 設置兩個日志記錄器,root和core,用來區分運行的文件
keys=root, core
[handlers] # 設置handles
keys=consoleHandler,fileHandler
[formatters] # 設置格式化處理
keys=simpleFormatter
[logger_root] # 配置root日志輸出
level=DEBUG
handlers=fileHandler
[logger_core] # 配置core的日志輸出
level=DEBUG
# 輸出最低級別為debug
handlers=consoleHandler,fileHandler
# 添加控制台輸出和文件輸出
qualname=core
# 配置輸出名字,一定要和日志輸出的同名,相當於實例化中的參數name,root可以不用配置
propagate=0
# 是否要傳遞給祖先處理器,如果為1,則會輸出多遍,父類也會輸出
[handler_consoleHandler] # 配置控制台輸出consoleHandler
class=StreamHandler
level=WARNING
formatter=simpleFormatter
args=(sys.stdout,)
[handler_fileHandler] # 配置文件輸出fileHandler
class=FileHandler
level=DEBUG
formatter=simpleFormatter
args=('test.log','a+')
[formatter_simpleFormatter] # 配置輸出格式化simpleFormatter
format=%(asctime)s - %(name)s - %(levelname)s - %(message)s
datefmt=%Y-%m-%d %I:%M:%S %p
使用方法:
#!/usr/bin/python3
# -*- coding: UTF-8 -*-
__author__ = "A.L.Kun"
__file__ = "demo.py"
__time__ = "2022/8/4 11:42"
import logging
from logging.config import fileConfig, dictConfig
fileConfig("config.conf")
LOG = logging.getLogger("core")
LOG.error("這個是core對象的輸出哦")
LOG = logging.getLogger()
LOG.error("這個是root對象輸出哦")
環境配置:pip install PyYAML
我們先把上面的conf文件轉換為yaml文件
version: 1.0
formatters:
simpleFormatter:
format: '%(asctime)s - %(name)s - %(levelname)s - %(message)s'
datefmt: '%Y-%m-%d %I:%M:%S %p'
handlers:
fileHandler:
class: logging.FileHandler
level: DEBUG
formatter: simpleFormatter
filename: test.log
mode: a
encoding: utf8
consoleHandler:
class: logging.StreamHandler
level: WARNING
formatter: simpleFormatter
stream: ext://sys.stdout # 注意這個哦
loggers:
root:
level: DEBUG
handlers: [fileHandler]
core:
level: DEBUG
handlers: [consoleHandler,fileHandler]
qualname: core
propagate: 0
使用方法:
#!/usr/bin/python3
# -*- coding: UTF-8 -*-
__author__ = "A.L.Kun"
__file__ = "demo.py"
__time__ = "2022/8/4 11:42"
import yaml
import logging
from logging.config import fileConfig, dictConfig
# fileConfig("config.conf")
with open("config.yaml", 'r', encoding='utf-8') as f:
config = yaml.load(f.read(), yaml.FullLoader)
dictConfig(config)
LOG = logging.getLogger("core")
LOG.error("這個是core對象的輸出哦")