您好,登錄后才能下訂單哦!
這篇文章主要講解了“Python日志模塊logging如何使用”,文中的講解內容簡單清晰,易于學習與理解,下面請大家跟著小編的思路慢慢深入,一起來研究和學習“Python日志模塊logging如何使用”吧!
對于開發日志,很多程序員誤區可能就是停留在直接print打印到后臺日志中,好的地方方便快捷,但是壞的地方就是日志輸出的內容十分混亂,不方便排查。面對不同級別的事件,以及需要執行的任務時,采取的日志操作動作是不一樣的。
對此結合Python官方文檔總結以下執行任務對應的工具:
需要執行的任務 | 任務對應的工具 |
直接打印程序結果 | |
記錄程序普通操作(比如請求記錄,狀態監控) | logging.info() |
程序發生特殊事件引發的警告信息 | logging.warning() |
程序發生特殊事件引發錯誤 | 直接拋出異常(raise Exception) |
報告錯誤而不引發異常 | logging.error()、logging.exception()、logging.critical() 分別使用特定錯誤 |
日志功能事件級別對應應用場景(以嚴重性遞增)
級別 | 應用場景 |
DEBUG | 細節信息,僅當診斷問題適用 |
INFO | 確認程序預期運行,記錄程序正常運行狀態 |
WARNING | 表明有已經或即將發生的意外 |
ERROR | 由于嚴重的問題,程序某些功能不能使用 |
CRTICAL | 嚴重的錯誤,程序已不能繼續執行 |
logging模塊默認級別是WARNING,意味著只會追蹤該級別以上的事件,除非更改日志配置;
日志記錄保存到文件
import logging logging.basicConfig(filename="example.log", level=logging.INFO, datefmt="%Y-%m-%d %H:%M:%S", encoding='utf-8') # 記錄日志信息 logging.debug("test DEBUG") logging.info("test Info") logging.warning("test Warning") logging.warning('%s before you %s', 'Look', 'leap!') logging.error("test Error")
代碼注解:
3.9版本中才更新了encoding,encoding參數在更早的Python版本中沒有指定時,編碼會使用open()的默認值;
level是設置默認日志追蹤級別的閾值,默認級別是WARNING
filename是日志文件的存放路徑;
(上述腳本如果連續多次運行,連續運行的消息將追繳到指定的example.log日志文件,如果想每次都是重新開始,即example.log日志不保存之前的日志信息,則修改filemode參數為'w';)
結合Python官方文檔,日志庫采用模塊化的方法,并提供幾類組件:記錄器、處理器、過濾器和格式器。
記錄器:暴露了應用程序代碼直接使用的接口。
處理器:將日志記錄(由記錄器創建)發送到適當的目標。
過濾器:提供了更細粒度的功能,用于確定要輸出的日志記錄。
格式器:指定最終輸出中日志記錄的樣式。
官方文檔中記錄器和處理在日志信息記錄流程:
解析:
首先是判斷Logger對象執行的方法是否大于設置的最低嚴重性,大于則創建LogRecord對象,小于則終止;
注冊的Filter對象進行過濾,如果為False不記錄日志;
將LogRecord對象傳遞到當前注冊到Logger對象中的Handler對象;判斷Handler對象設置的級別大于Logger對象則證明有效,以及注冊到Handler對象中Filter過濾后是否返回True;
最后判斷當前是否還有父Logger對象,如果是重復第三步,知道當前Logger設置為root Looger;
關于記錄器,主要的任務總結有三個:
暴露接口給應用程序記錄消息;
根據嚴重性(默認嚴重級別)或者過濾器決定要處理的日志信息;
將日志信息發送傳遞給對應日志處理器;
關于記錄器方法總結為兩類,配置和消息發送.
記錄器配置方法:
Logger.setLevel():設置記錄器處理的最低嚴重性日志信息(這就如果后續日志處理器設置的日志級別比記錄器低是無效的);
Logger.addHandler()和Logger.removeHandler():從記錄器對象中增加和刪除日志處理器對象;
Logger.addFilter()和Logger.removeFilter():從記錄器對象中增加和刪除過濾器;
記錄器常用創建信息方法:
Logger.debug() 、 Logger.info() 、 Logger.warning() 、 Logger.error() 和 Logger.critical() ;
Logger.exception()和以上的方法有點不同,只在異常處理程序中調用此方法,同時還記錄當前堆棧跟蹤信息;
關于處理器,簡單的可以理解為將特定嚴重級別的日志信息發送到特定的位置,常用的處理類型主要有兩個:
FileHandler
StreamHandler
由于內置處理對象常用的配置方法:
setLevel()方法,設置處理器中的最低嚴重性,即決定處理器該發送哪些級別的日志信息;
addFormatter,選擇該處理器使用的Formatter對象;
addFilter和removeFilter,在處理器上增加和刪除過濾器對象;
格式器配置日志消息的最終順序、結構和內容,格式器類的構造函數有三個可選參數:
消息格式字符串
日期格式字符串
樣式指示符
logging.Formatter.__init__(fmt=None, datefmt=None, style='%')
備注:
fmt消息格式字符串一般不為空,為空默認就只打印message信息;
datefmt默認日期格式為:%Y-%m-%d %H:%M:%S;
style參數可選的范圍為:%、{、$這三個,主要用于fmt消息中字符串替換;
關于style:
fm = Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s",
"%Y-%m-%d %H:%M:%S", style='{')
fm = Formatter("{asctime} - {name} - {levelname} - {message}",
"%Y-%m-%d %H:%M:%S", style='{')
fm = Formatter("$asctime - $name - $levelname - $message",
"%Y-%m-%d %H:%M:%S", style='$')
(這三種style使用方式,效果都一樣)
開發人員可以通過三種方式配置日志記錄:
使用提供的接口,顯示創建記錄器,處理器,格式器等直接配置;
通過fileConfig()函數讀取已經創建好的配置文件;
創建好配置函數字典傳遞到dictConfig()函數;
關于fileConfig()讀取的配置文件(官方示例):
[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
(關于讀取的配置文件格式類似ini格式)
關于logging模塊,這里介紹一下我目前最常用的業務場景:調用方請求一個后端的rest api接口,我需要記錄調用方請求的時間,地址,請求參數,處理請求后的結果,以及我需要將報錯的信息保存到指定的文件里,方便排查。
為了后期使用方便,在不更改原有處理函數的基礎下增加日志記錄的功能,我會選擇將日志記錄操作封裝在一個裝飾器函數。
所以我只需將這部分功能分成兩部分:生成記錄器、請求處理的裝飾器函數
# -*- coding: utf-8 -*- from logging import handlers from datetime import date import logging def init_logger(): """ 生成記錄器 :return: """ app_logger = logging.getLogger(APP_NAME) app_logger.setLevel(logging.INFO) fmt = logging.Formatter("%(asctime)s %(levelname)s: %(message)s", "%Y-%m-%d %H:%M:%S") # 正常日志打印到控制臺 console = logging.StreamHandler() console.setFormatter(fmt) console.setLevel(logging.INFO) # 異常日志記錄到log文件 today = date.today() file_name = "logs/exceptions_" + str(today) + ".log" fh = handlers.TimedRotatingFileHandler(filename=file_name, when='D', backupCount=30, encoding='utf-8') fh.setLevel("ERROR") fh.setFormatter(fmt) app_logger.addHandler(console) app_logger.addHandler(fh) return app_logger
代碼解析:
APP_NAME是預設好的項目名稱,可根據實際業務進行調整;
關于普通的StreamHandler前面已經提到了使用的方法,我這里之所選擇,是由于這個項目時Flask框架,后期部署通過uWSGI部署后端服務,我希望正常請求直接就打印在uwsgi的日志文件中,所以普通請求的處理器就選擇了StreamHandler
關于異常日志處理器,這里用到特殊的TimeRotatingFileHandler,這個內置的處理器可以根據不同的時間跨度進行保存日志,就可以將異常日志信息按照一天的時間進行保存,注意設置最低嚴重性是ERROR
from functools import wraps from flask import request app_logger = init_logger() def rest_log(return_type="dict"): def decorator(func): @wraps(func) def inner(*args, **kwargs): # 組裝打印的Message消息日志格式(請求URL,目標主機,請求方法,請求參數,響應內容) log_params = { "request": request.base_url, "host": request.host, "method": request.method } req_data = {} if request.method == "POST": req_data = dict(request.json) elif request.method == "GET": req_data = dict(request.args) log_params.update({"params": req_data}) # 請求處理函數 try: result = func(*args, **kwargs) except Exception as e: # 異常信息處理 err_msg = str(e) result = {"ret_code": 500, "ret_info": err_msg} app_logger.error(log_params, exc_info=True) if return_type == "tuple": result = (result, 500) if return_type == "tuple": log_params['result'] = result[0].data else: log_params['result'] = result app_logger.info(log_params) return result return inner return decorator
代碼解析:
主要分為三部分:HTTP請求request解析、異常請求信息處理、請求結果處理;
app_logger.error(log_params, exc_info=True)中的exc_info可以將異常信息添加到日志信息中,即app_logger.exception()的效果;
關于return_type參數是考慮到flask支持返回元組,即返回響應對象,響應狀態碼。考慮到日常使用場景會出現這種情況;
簡單使用示例:
# -*- coding: utf-8 -*- from flask import request, Blueprint from common.LogUtils import rest_log test_api = Blueprint("TestApi", __name__) @test_api.route("/log/test", methods=["GET"]) @rest_log() def test_log(): name = request.args.get("name", "") number = request.args.get('number', "") if not name or not number: raise Exception("number和name參數都不能為空") response = { "data": { "name": f"Hello, {name}", "number": number }, "ret_code": 200, "ret_info": "success" } return response
備注:
rest_log裝飾器不能放在test_api.route的前面,因為只有當路由注冊函數執行后,才能從request中獲取到對應的信息(base_url,host,method)
控制臺日志打印效果:
2022-05-22 12:01:01 INFO: {'request': 'http://127.0.0.1:23102/log/test', 'host': '127.0.0.1:23102', 'method': 'GET', 'params': {'name': 'zhangsn', 'number': '22'}, 'result': {'data': {'name': 'Hello, zhangsn', 'number': '22'}, 'ret_code': 200, 'ret_info': 'success'}}
異常日志打印:
2022-05-22 11:47:38 ERROR: {'request': 'http://127.0.0.1:23102/log/test', 'host': '127.0.0.1:23102', 'method': 'GET', 'params': {}}
Traceback (most recent call last):
File "C:\Users\admin\TestLogging\common\LogUtils.py", line 63, in inner
result = func(*args, **kwargs)
File "C:\Users\admin\TestLogging\controller\TestLogging.py", line 18, in test_log
raise Exception("number和name參數都不能為空")
Exception: number和name參數都不能為空
感謝各位的閱讀,以上就是“Python日志模塊logging如何使用”的內容了,經過本文的學習后,相信大家對Python日志模塊logging如何使用這一問題有了更深刻的體會,具體使用情況還需要大家實踐驗證。這里是億速云,小編將為大家推送更多相關知識點的文章,歡迎關注!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。