Python/logging
Python 的 logging 模塊提供了標準的日誌接口,通過它可存儲各種格式的日誌。使用Logging模塊的主要好處是所有Python模塊都可以參與日誌記錄。
日誌級別等級排序:critical > error > warning > info > debug( notset 等同於 debug )
Logging 模塊提供了兩種日誌記錄方式:
- 一種方式是使用 Logging 提供的模塊級別的函數
- 另一種方式是使用 Logging 日誌系統的四大組件記錄
Logging 定義的模塊級別函數
編輯import logging
# 打印日志级别
def test_logging():
logging.basicConfig(filename='F:/example.log', format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', datefmt='%m/%d/%Y %I:%M:%S %p',level=logging.DEBUG) #需要在开头就设置,在中间设置并无作用 可以设置 root 的日志级别和日志输出格式。
logging.debug('Python debug')
logging.info('Python info')
logging.warning('Python warning')
logging.error('Python Error')
logging.critical('Python critical')
test_logging()
logging.basicConfig函數各參數:
*encoding:如'utf-8'
- 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.WARNNING;
- stream:指定將日誌的輸出流,可以指定輸出到sys.stderr,sys.stdout或者文件,默認輸出到sys.stderr,當stream和filename同時指定時,stream被忽略;
logging 模塊四大組件
編輯- 日誌器(logger)需要通過處理器(handler)將日誌信息輸出到目標位置,不同的處理器(handler)可以將日誌輸出到不同的位置;
- 日誌器(logger)可以設置多個處理器(handler)將同一條日誌記錄輸出到不同的位置;在當前 Logger 對象中查找 Handlers,如果找不到任何 Handler,則往上到該 Logger 對象的父 Logger 中查找;如果找到一個或多個 Handler,則依次用 Handler 來處理日誌信息。但在每個 Handler 處理日誌信息過程中,會首先判斷日誌信息的等級是否大於該 Handler 的等級,如果大於,則往下執行(由 Logger 對象進入 Handler 對象中),否則,處理流程結束。
- 每個處理器(handler)都可以設置自己的過濾器(filter)實現日誌過濾,從而只保留感興趣的日誌;只要有一個過濾器返回假,則過濾結束,且該日誌信息將丟棄,不再處理,而處理流程也至此結束。
- 每個處理器(handler)都可以設置自己的格式器(formatter)實現同一條日誌以不同的格式輸出到不同的地方。
第一次導入 logging 模塊或使用 reload 函數重新導入 logging 模塊,logging 模塊中的代碼將被執行,這個過程中將產生 logging 日誌系統的默認配置。
可選的自定義配置logging標準模塊方式:
- dictConfig 是通過一個字典進行配置 Logger,Handler,Filter,Formatter;
- fileConfig 則是通過一個文件進行配置;
- listen 則監聽一個網絡端口,通過接收網絡數據來進行配置。
- 也可以直接調用 Logger,Handler 等對象中的方法在代碼中來顯式配置。
一個簡單示例:
import logging
logger = logging.getLogger(__name__)
logger.setLevel(level = logging.INFO)
handler = logging.FileHandler("log.txt")
handler.setLevel(logging.INFO)
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
handler.setFormatter(formatter)
console = logging.StreamHandler()#将日志同时输出到屏幕和日志文件
console.setLevel(logging.INFO)
logger.addHandler(handler)
logger.addHandler(console)
logger.info("Start print log")
logger.debug("Do something")
logger.warning("Something maybe fail.")
logger.info("Finish")
日誌器- Logger
編輯Logger 持有日誌記錄器的方法,日誌記錄器不直接實例化,而是通過模塊級函數 logger.getlogger (name) 來實例化,使用相同的名稱多次調用 getLogger() 總是會返回對相同 Logger 對象的引用。
getLogger() 方法後面最好加上所要日誌記錄的模塊名字,配置文件和打印日誌格式中的 %(name)s 對應的是這裏的模塊名字,如果不指定name則返回root對象。
logger.setLevel(logging.DEBUG),Logging 中有 NOTSET < DEBUG < INFO < WARNING < ERROR < CRITICAL這幾種級別,日誌會記錄設置級別以上的日誌
logger.addHandler(handler_name) # 為 Logger 實例增加一個處理器
logger.removeHandler(handler_name) # 為 Logger 實例刪除一個處理器
使用 Logger 對象中的 debug,info,error,warn,critical 等方法記錄日誌信息。
首先在主模塊定義了logger叫做'mainModule',並對它進行了配置。在解釋器進程裏面的其他地方通過getLogger('mainModule')得到的logger對象都是一樣的,不需要重新配置,可以直接使用。定義的該logger的子logger,都可以共享父logger的定義和配置。所謂的父子logger是通過命名來識別,任意以'mainModule'開頭的logger都是它的子logger,例如'mainModule.sub'。
處理器- Handler
編輯Handler 處理器類型有很多種,比較常用的有三個,StreamHandler,FileHandler,NullHandler
創建方法:sh = logging.StreamHandler(stream=None)
創建 StreamHandler 之後,可以通過使用以下方法設置日誌級別,設置格式化器 Formatter,增加或刪除過濾器 Filter:
ch.setLevel(logging.WARN) # 指定日志级别,低于WARN级别的日志将被忽略 ch.setFormatter(formatter_name) # 设置一个格式化器formatter ch.addFilter(filter_name) # 增加一个过滤器,可以增加多个 ch.removeFilter(filter_name) # 删除一个过滤器
handler名稱 | 位置 | 作用 |
---|---|---|
StreamHandler | logging.StreamHandler | 日誌輸出到流,可以是sys.stderr,sys.stdout或者文件 |
FileHandler | logging.FileHandler | 日誌輸出到文件 |
BaseRotatingHandler | logging.handlers.BaseRotatingHandler | 基本的日誌回滾方式 |
RotatingHandler | logging.handlers.RotatingHandler | 日誌回滾方式,支持日誌文件最大數量和日誌文件回滾 |
TimeRotatingHandler | logging.handlers.TimeRotatingHandler | 日誌回滾方式,在一定時間區域內回滾日誌文件 |
SocketHandler | logging.handlers.SocketHandler | 遠程輸出日誌到TCP/IP sockets |
DatagramHandler | logging.handlers.DatagramHandler | 遠程輸出日誌到UDP sockets |
SMTPHandler | logging.handlers.SMTPHandler | 遠程輸出日誌到郵件地址 |
SysLogHandler | logging.handlers.SysLogHandler | 日誌輸出到syslog |
NTEventLogHandler | logging.handlers.NTEventLogHandler | 遠程輸出日誌到Windows NT/2000/XP的事件日誌 |
MemoryHandler | logging.handlers.MemoryHandler | 日誌輸出到內存中的指定buffer |
HTTPHandler | logging.handlers.HTTPHandler | 通過"GET"或者"POST"遠程輸出到HTTP伺服器 |
過濾器- Filter
編輯Handlers 和 Loggers 可以使用 Filters 來完成比級別更複雜的過濾。 Filter 基類只允許特定 Logger 層次以下的事件。 例如用 『A.B』 初始化的 Filter 允許Logger 『A.B』, 『A.B.C』, 『A.B.C.D』, 『A.B.D』 等記錄的事件,logger『A.BB』, 『B.A.B』 等就不行。 如果用空字符串來初始化,所有的事件都接受。
創建方法: filter = logging.Filter(name=)
格式器- Formatter
編輯使用Formatter對象設置日誌信息最後的規則、結構和內容,默認的時間格式為%Y-%m-%d %H:%M:%S。
創建方法: formatter = logging.Formatter(fmt=None, datefmt=None)
其中,fmt 是消息的格式化字符串,datefmt 是日期字符串。如果不指明 fmt,將使用 '%(message)s' 。如果不指明 datefmt,將使用 ISO8601 日期格式。
例子1
編輯使用RotatingFileHandler,可以實現日誌回滾,
import logging
from logging.handlers import RotatingFileHandler
logger = logging.getLogger(__name__)
logger.setLevel(level = logging.INFO)
#定义一个RotatingFileHandler,最多备份3个日志文件,每个日志文件最大1K
rHandler = RotatingFileHandler("log.txt",maxBytes = 1*1024,backupCount = 3)
rHandler.setLevel(logging.INFO)
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
rHandler.setFormatter(formatter)
console = logging.StreamHandler()
console.setLevel(logging.INFO)
console.setFormatter(formatter)
logger.addHandler(rHandler)
logger.addHandler(console)
logger.info("Start print log")
logger.debug("Do something")
logger.warning("Something maybe fail.")
logger.info("Finish")
可以在工程目錄中看到,備份的日誌文件:
2016/10/09 19:36 732 log.txt 2016/10/09 19:36 967 log.txt.1 2016/10/09 19:36 985 log.txt.2 2016/10/09 19:36 976 log.txt.3
例子2
編輯Python中的traceback模塊被用於跟蹤異常返回信息,可以在logging中記錄下traceback,
import logging
logger = logging.getLogger(__name__)
logger.setLevel(level = logging.INFO)
handler = logging.FileHandler("log.txt")
handler.setLevel(logging.INFO)
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
handler.setFormatter(formatter)
console = logging.StreamHandler()
console.setLevel(logging.INFO)
logger.addHandler(handler)
logger.addHandler(console)
logger.info("Start print log")
logger.debug("Do something")
logger.warning("Something maybe fail.")
try:
open("sklearn.txt","rb")
except (SystemExit,KeyboardInterrupt):
raise
except Exception:
logger.error("Faild to open sklearn.txt from logger.error",exc_info = True)
#logger.exception("Failed to open sklearn.txt from logger.exception")#等效于上一行
logger.info("Finish")
控制台和日誌文件log.txt中輸出:
Start print log Something maybe fail. Faild to open sklearn.txt from logger.error Traceback (most recent call last): File "G:\zhb7627\Code\Eclipse WorkSpace\PythonTest\test.py", line 23, in <module> open("sklearn.txt","rb") IOError: [Errno 2] No such file or directory: 'sklearn.txt' Finish
例子3
編輯下面使用 Python 代碼配置一個非常簡單的記錄器,一個控制台處理程序和一個簡單的格式化程序:
logging.conf 配置文件:
[loggers]
keys=root,simpleExample
[handlers]
keys=consoleHandler
[formatters]
keys=simpleFormatter
[logger_root]
level=DEBUG
handlers=consoleHandler
[logger_simpleExample]
level=DEBUG
handlers=consoleHandler
qualname=simpleExample
propagate=0
[handler_consoleHandler]
class=StreamHandler
level=DEBUG
formatter=simpleFormatter
args=(sys.stdout,)
[formatter_simpleFormatter]
format=%(asctime)s - %(name)s - %(levelname)s - %(message)s
datefmt=
config_logging.py 配置器:
import logging
# create logger
logger = logging.getLogger('simple_example')
logger.setLevel(logging.DEBUG)
# create console handler and set level to debug
ch = logging.StreamHandler()
ch.setLevel(logging.DEBUG)
# create formatter
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
# add formatter to ch
ch.setFormatter(formatter)
# add ch to logger
logger.addHandler(ch)
# 'application' code
logger.debug('debug message')
logger.info('info message')
logger.warning('warn message')
logger.error('error message')
logger.critical('critical message')
recorder 記錄器:
import logging
import logging.config
logging.config.fileConfig('logging.conf')
# create logger
logger = logging.getLogger('simpleExample')
# 'application' code
logger.debug('debug message')
logger.info('info message')
logger.warning('warn message')
logger.error('error message')
logger.critical('critical message')
運行結果:
2019-10-16 19:45:34,440 - simple_example - DEBUG - debug message 2019-10-16 19:45:34,440 - simple_example - INFO - info message 2019-10-16 19:45:34,440 - simple_example - WARNING - warn message 2019-10-16 19:45:34,440 - simple_example - ERROR - error message 2019-10-16 19:45:34,441 - simple_example - CRITICAL - critical message
例子:通過JSON文件配置logger
編輯{
"version":1,
"disable_existing_loggers":false,
"formatters":{
"simple":{
"format":"%(asctime)s - %(name)s - %(levelname)s - %(message)s"
}
},
"handlers":{
"console":{
"class":"logging.StreamHandler",
"level":"DEBUG",
"formatter":"simple",
"stream":"ext://sys.stdout"
},
"info_file_handler":{
"class":"logging.handlers.RotatingFileHandler",
"level":"INFO",
"formatter":"simple",
"filename":"info.log",
"maxBytes":"10485760",
"backupCount":20,
"encoding":"utf8"
},
"error_file_handler":{
"class":"logging.handlers.RotatingFileHandler",
"level":"ERROR",
"formatter":"simple",
"filename":"errors.log",
"maxBytes":10485760,
"backupCount":20,
"encoding":"utf8"
}
},
"loggers":{
"my_module":{
"level":"ERROR",
"handlers":["info_file_handler"],
"propagate":"no"
}
},
"root":{
"level":"INFO",
"handlers":["console","info_file_handler","error_file_handler"]
}
}
import json
import logging.config
import os
def setup_logging(default_path = "logging.json",default_level = logging.INFO,env_key = "LOG_CFG"):
path = default_path
value = os.getenv(env_key,None)
if value:
path = value
if os.path.exists(path):
with open(path,"r") as f:
config = json.load(f)
logging.config.dictConfig(config)
else:
logging.basicConfig(level = default_level)
def func():
logging.info("start func")
logging.info("exec func")
logging.info("end func")
if __name__ == "__main__":
setup_logging(default_path = "logging.json")
func()
例子:通過YAML文件配置logger
編輯version: 1
disable_existing_loggers: False
formatters:
simple:
format: "%(asctime)s - %(name)s - %(levelname)s - %(message)s"
handlers:
console:
class: logging.StreamHandler
level: DEBUG
formatter: simple
stream: ext://sys.stdout
info_file_handler:
class: logging.handlers.RotatingFileHandler
level: INFO
formatter: simple
filename: info.log
maxBytes: 10485760
backupCount: 20
encoding: utf8
error_file_handler:
class: logging.handlers.RotatingFileHandler
level: ERROR
formatter: simple
filename: errors.log
maxBytes: 10485760
backupCount: 20
encoding: utf8
loggers:
my_module:
level: ERROR
handlers: [info_file_handler]
propagate: no
root:
level: INFO
handlers: [console,info_file_handler,error_file_handler]
import yaml
import logging.config
import os
def setup_logging(default_path = "logging.yaml",default_level = logging.INFO,env_key = "LOG_CFG"):
path = default_path
value = os.getenv(env_key,None)
if value:
path = value
if os.path.exists(path):
with open(path,"r") as f:
config = yaml.load(f)
logging.config.dictConfig(config)
else:
logging.basicConfig(level = default_level)
def func():
logging.info("start func")
logging.info("exec func")
logging.info("end func")
if __name__ == "__main__":
setup_logging(default_path = "logging.yaml")
func()