程序都有記錄日志的需求,並且日志中包含的信息既有正常的程序訪問日志,還可能有錯誤、警告等信息輸出。
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 logger
format
所有參數列表
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)s
pathname的文件名部分,含文件後綴module
%(module)s
filename的名稱部分,不包含後綴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
的children
logger
上沒有被明確設置一個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對象的輸出哦")