簡介:
我們在寫python程序的時候,很多時候都有bug,都是自己寫的,自己造的孽,又的時候報錯又是一堆,不知道是那部分出錯了。
我這初學者水平,就是打print,看哪部分執行了,哪部分沒執行,由此來看問題大概在什么地方。
其實python有更好的處理方案,logging模塊。
從Python2.3起,Python的標准庫加入了logging模塊.logging模塊給運行中的應用提供了一個標准的信息輸出接口.典型的logging機制實現是把要輸出的數據簡單地寫到一個txt文件中去.寫log文件的方式是一種常見的打log的方式,而logging模塊提供的更多,它可以把輸出信息輸出到所有類文件的對象中去,甚至TCP和UDP的sockets,email服務器,Unix的syslog系統,NT系列的事件log系統,內存的buffer和HTTP服務器,當然還有”真正的”文件中去.
Logging庫被設計成模塊的方式,它提供了以下幾個子模塊:loggers,handlers,filters和formatters.Loggers把應用需要直接調用的接口暴露出來.Handlers把log記錄發到相應的目的地.Filters決定哪些記錄需要發給handler.Formatters定義了log記錄的輸出格式.
Logger對象扮演了三重角色.首先,它暴露給應用幾個方法以便應用可以在運行時寫log.其次,Logger對象按照log信息的嚴重程度或者根據filter對象來決定如何處理log信息(默認的過濾功能).最后,logger還負責把log信息傳送給相關的loghandlers.
Logger中最長使用的方法分成兩部分中:configuration和message sending.
用於Configuration的方法:
setLevel(level)
addFilter(filter)
removeFilter(filter)
addHandler(handler)
removeHandler(handler)
setLevel()方法定義了一個logger處理的最底嚴重程度(比如說中/高/底三種,我定義為中,那么只有嚴重程度為中或者高的log才會被處理).debug級別是內置的最低級別,critical是最高級別.舉例來說,如果嚴重級別設為info級,logger僅僅處理info,warning,error和critical級的log,而debug級別的則忽略掉.
根據logger對象的設置,以下的方法被用來寫log:
debug(log_message, [*args[, **kwargs]])
info(log_message, [*args[, **kwargs]])
warning(log_message, [*args[, **kwargs]])
error(log_message, [*args[, **kwargs]])
critical(log_message, [*args[, **kwargs]])
exception(message[, *args])
log(log_level, log_message, [*args[, **kwargs]])
Handler對象負責分配合適的log信息(基於log信息的嚴重程度)到handler指定的目的地.Logger對象可以用addHandler()方法添加零個或多個handler對象到它自身.一個常見的場景是,一個應用可能希望把所有的log信息都發送到一個log文件中去,所有的error級別以上的log信息都發送到stdout,所有critical的log信息通過email發送.這個場景里要求三個不同handler處理,每個handler負責把特定的log信息發送到特定的地方.
標准庫里面包括以下的handlers:
StreamHandler (流式,控制台模式?)
FileHandler (文件式)
RotatingFileHandler (自動覆蓋文件)
TimedRotatingFileHandler (按時間自動覆蓋文件)
SocketHandler
DatagramHandler
SysLogHandler
NTEventLogHandler
SMTPHandler (SMTP郵件處理)
MemoryHandler
HTTPHandler
一:基本使用
1.無腦測試
import logging import sys # 獲取logger實例,如果參數為空則返回root logger logger = logging.getLogger("AppName") # 指定logger輸出格式 formatter = logging.Formatter('%(asctime)s %(levelname)-8s: (%(name)s)%(pathname)s %(message)s') # 文件日志 file_handler = logging.FileHandler("test.log") file_handler.setFormatter(formatter) # 可以通過setFormatter指定輸出格式 # 控制台日志 console_handler = logging.StreamHandler(sys.stdout) console_handler.formatter = formatter # 也可以直接給formatter賦值 # 為logger添加的日志處理器 logger.addHandler(file_handler) logger.addHandler(console_handler) # 指定日志的最低輸出級別,默認為WARN級別 logger.setLevel(logging.DEBUG) # 輸出不同級別的log logger.debug('this is debug info') logger.info('this is information') logger.warning('this is warning message') logger.error('this is error message') logger.fatal('this is fatal message, it is same as logger.critical') logger.critical('this is critical message') # 記錄異常信息 try: 1 / 0 except: logger.exception('except:') # 移除文件日志處理器,那么log文件就不記錄這些日志了 logger.removeHandler(file_handler) logger.debug('this is debug info----2') #修改日志輸出級別 logger.setLevel(logging.ERROR) logger.info('this is information----2') logger.warning('this is warning message----2') logger.error('this is error message----2') logger.fatal('this is fatal message, it is same as logger.critical----2') logger.critical('this is critical message----2')
2.結果
控制台有輸出,文件有記錄,在改變了日志等級以后,會有部分信息被隱藏。
這不正式我們需要的么?
以前自己打print,調試完了還要再去注釋掉或者刪除。
用這個就好了。
寫代碼調試的時候,用logging.debug
調試完了,把logging配置為warning,或者error。debug就不輸出了。
二:配置
1.logging自身的方法設置
上面的無腦測試,就是通過logging自身的方法進行的設置,只是不方便統一調用
2.通過文件加載配置(fileConfig)
通過配置文件,加載配置,據說版本比較老,部分配置參數不支持,推薦用字典加載。
3.通過字典加載配置(dictConfig)
推薦用這種方式加載配置信息。
logging的字典配置信息主要分5個部分:基本設置、日志內容格式、過濾器、處理器和管理器。
三:字典加載配置
1.無腦測試
# !/usr/bin/python3 # -*- coding: utf-8 -*- # @Time : 2018-06-26 9:10 # @Author : Jackadam # @Email :jackadam@sina.com # @File : logging_conf.py # @Software: PyCharm import logging.config, logging, os BASE_DIR = os.path.dirname(os.path.abspath(__file__)) DEBUG = True # 標記是否在開發環境 # 給過濾器使用的判斷 class RequireDebugTrue(logging.Filter): # 實現filter方法 def filter(self, record): return DEBUG LOGGING = { # 基本設置 'version': 1, # 日志級別 'disable_existing_loggers': False, # 是否禁用現有的記錄器 # 日志格式集合 'formatters': { # 標准輸出格式 'standard': { # [具體時間][線程名:線程ID][日志名字:日志級別名稱(日志級別ID)] [輸出的模塊:輸出的函數]:日志內容 'format': '[%(asctime)s][%(threadName)s:%(thread)d][%(name)s:%(levelname)s(%(lineno)d)][%(module)s:%(funcName)s]:%(message)s' } }, # 過濾器 'filters': { 'require_debug_true': { '()': RequireDebugTrue, } }, # 處理器集合 'handlers': { # 輸出到控制台 'console': { 'level': 'DEBUG', # 輸出信息的最低級別 'class': 'logging.StreamHandler', 'formatter': 'standard', # 使用standard格式 'filters': ['require_debug_true', ], # 僅當 DEBUG = True 該處理器才生效 }, # 輸出到文件 'log': { 'level': 'DEBUG', 'class': 'logging.handlers.RotatingFileHandler', 'formatter': 'standard', 'filename': os.path.join(BASE_DIR, 'debug.log'), # 輸出位置 'maxBytes': 1024 * 1024 * 5, # 文件大小 5M 'backupCount': 5, # 備份份數 'encoding': 'utf8', # 文件編碼 }, }, # 日志管理器集合 'loggers': { # 管理器 'default': { 'handlers': ['console', 'log'], 'level': 'DEBUG', 'propagate': True, # 是否傳遞給父記錄器 }, # 管理器 'shoujitiku': { 'handlers': ['console', 'log'], 'level': 'DEBUG', 'propagate': True, # 是否傳遞給父記錄器 }, } } def log_main(): # 加載前面的標准配置 logging.config.dictConfig(LOGGING) # 獲取loggers其中的一個日志管理器 logger = logging.getLogger("shoujitiku") return logger loger = log_main() loger.debug('hello')
2.調用方式
函數log_main():
代碼中定義了一個字典LOGGING,
加載配置就用
logging.config.dictConfig(LOGGING)
然后使用其中一個日志管理器叫做shoujitiku
logger=logging.getLogger('shoujitiku')
然后返回logger
3.配置文件中的調用
logging.getLogger('shoujitiku'),首先獲取日志管理器集合當中的一個管理器shoujitiku,其中handlers,包括了兩個處理器,一個是控制台輸出console,用方法'class': 'logging.StreamHandler',,一個是日志文件循環輸出log,用方法'class': 'logging.handlers.RotatingFileHandler',
每個處理器當中都有'level': 'DEBUG', # 輸出信息的最低級別 'class': 'logging.StreamHandler', # 使用什么方法 'formatter': 'standard', # 使用standard格式
4.class
- StreamHandler instances send error messages to streams (file-like objects). 控制台輸出
- FileHandler instances send error messages to disk files. 文件記錄
- RotatingFileHandler instances send error messages to disk files, with support for maximum log file sizes and log file rotation. 滾動文件記錄
- TimedRotatingFileHandler instances send error messages to disk files, rotating the log file at certain timed intervals. 滾動時間記錄
- SocketHandler instances send error messages to TCP/IP sockets. TCP端口?不懂,沒用。
- DatagramHandler instances send error messages to UDP sockets. UDP端口?不懂,沒用。
- SMTPHandler instances send error messages to a designated email address. SMTP發郵件
5.formatter
Formatter對象定義了log信息的結構和內容,構造時需要帶兩個參數:
- 一個是格式化的模板
fmt,默認會包含最基本的level和message信息 - 一個是格式化的時間樣式
datefmt,默認為2003-07-08 16:49:45,896 (%Y-%m-%d %H:%M:%S)
fmt中允許使用的變量可以參考下表。
- %(name)s Logger的名字
- %(levelno)s 數字形式的日志級別
- %(levelname)s 文本形式的日志級別
- %(pathname)s 調用日志輸出函數的模塊的完整路徑名,可能沒有
- %(filename)s 調用日志輸出函數的模塊的文件名
- %(module)s 調用日志輸出函數的模塊名|
- %(funcName)s 調用日志輸出函數的函數名|
- %(lineno)d 調用日志輸出函數的語句所在的代碼行
- %(created)f 當前時間,用UNIX標准的表示時間的浮點數表示|
- %(relativeCreated)d 輸出日志信息時的,自Logger創建以來的毫秒數|
- %(asctime)s 字符串形式的當前時間。默認格式是“2003-07-08 16:49:45,896”。逗號后面的是毫秒
- %(thread)d 線程ID。可能沒有
- %(threadName)s 線程名。可能沒有
- %(process)d 進程ID。可能沒有
- %(message)s 用戶輸出的消息
6.過濾器
一個日志管理器可以包含多個處理器。有時在某些環境或場景下,希望不使用某些處理器。
以console那個處理器為例。若用py2exe等打包Python腳本,不要添加console處理器。
打包之后的程序會把控制台的消息也輸出一個log日志(而且還彈窗提示)。此時我要通過過濾器判斷是否需要輸出到控制台該處理器。
過濾器同樣放在一個過濾器集合中,如下代碼:
- #過濾器
- 'filters':{
- 'require_debug_true': {
- '()': RequireDebugTrue,
- }
- },
其中鍵名是過濾器的名稱,鍵值是過濾器的內容。
過濾器內容只需要設置一個屬性,該屬性值是繼承了logging.Filter的類。如下代碼:
- DEBUG = True #標記是否在開發環境
- #給過濾器使用的判斷
- class RequireDebugTrue(logging.Filter):
- #實現filter方法
- def filter(self, record):
- return DEBUG
繼承該類需要實現filter方法,返回一個布爾值。
在開發環境,我設置DEBUG為True;在客戶端,我設置DEBUG為False。從而控制是否需要使用某些處理器。
當然,該值你也可以想辦法通過一下判斷動態設置。
貌似是設置一個類,里面有默認方法filter,返回一個布爾值。再過濾器中引用這個類就可以了。
四:SMTPHandler
1.處理器
# 發個郵件 'email':{ 'level':'DEBUG', 'class':'logging.handlers.SMTPHandler', 'formatter': 'standard', 'mailhost':'smtp.163.com', #SMTP地址 'fromaddr':'jackadam@163.com', #發件人郵箱 'toaddrs':'jackadam@sina.com', #收件人郵箱,可以['a@163.com','a@sina.com','a@google.com'] 'subject':'log info', #郵件標題 'credentials':['jackadam','**********'] #郵箱登陸信息,用戶名 ***是密碼 },
五:TimedRotatingFileHandler
1.處理器
'log_debug': { 'level': 'DEBUG', 'class': 'logging.handlers.TimedRotatingFileHandler', 'formatter': 'standard', 'filename': os.path.join(BASE_DIR, 'TESTdebug.log'), # 輸出位置 'when': 'S', #分割單位 'interval': 1,#單位長度 'backupCount': 10, # 備份份數 'encoding': 'utf8', # 文件編碼 },
2.常用參數
- when:是一個字符串,用於描述滾動周期的基本單位,字符串的值及意義如下:
“S”: Seconds 秒
“M”: Minutes 分鍾
“H”: Hours 小時
“D”: Days 天
“W”: Week day (0=Monday) 周幾
“midnight”: Roll over at midnight 午夜 - interval: 滾動周期,單位有when指定,比如:when=’D’,interval=1,表示每天產生一個日志文件;
- backupCount: 表示日志文件的保留個數;
3.注意事項
除了上述參數之外,TimedRotatingFileHandler還有兩個比較重要的成員變量,它們分別是suffix和extMatch。
suffix是指日志文件名的后綴,suffix中通常帶有格式化的時間字符串,filename和suffix由“.”連接構成文件名(例如:filename=“runtime”, suffix=“%Y-%m-%d.log”,生成的文件名為runtime.2015-07-06.log)。
extMatch是一個編譯好的正則表達式,用於匹配日志文件名的后綴,它必須和suffix是匹配的,如果suffix和extMatch匹配不上的話,過期的日志是不會被刪除的。
比如,suffix=“%Y-%m-%d.log”, extMatch的只應該是re.compile(r”^\d{4}-\d{2}-\d{2}.log$”)。
默認情況下,在TimedRotatingFileHandler對象初始化時,suffxi和extMatch會根據when的值進行初始化:
‘S’: suffix=”%Y-%m-%d_%H-%M-%S”, extMatch=r”\^d{4}-\d{2}-\d{2}_\d{2}-\d{2}-\d{2}”;
‘M’:suffix=”%Y-%m-%d_%H-%M”,extMatch=r”^\d{4}-\d{2}-\d{2}_\d{2}-\d{2}”;
‘H’:suffix=”%Y-%m-%d_%H”,extMatch=r”^\d{4}-\d{2}-\d{2}_\d{2}”;
‘D’:suffxi=”%Y-%m-%d”,extMatch=r”^\d{4}-\d{2}-\d{2}”;
‘MIDNIGHT’:”%Y-%m-%d”,extMatch=r”^\d{4}-\d{2}-\d{2}”;
‘W’:”%Y-%m-%d”,extMatch=r”^\d{4}-\d{2}-\d{2}”;
如果對日志文件名沒有特殊要求的話,可以不用設置suffix和extMatch,如果需要,一定要讓它們匹配上。
貌似這是老版本的信息,我在python3.6中沒能實現。
六:RotatingFileHandler
1.處理器
'log': { 'level': 'DEBUG', 'class': 'logging.handlers.RotatingFileHandler', 'formatter': 'standard', 'filename': os.path.join(BASE_DIR, 'TESTdebug.log'), # 輸出位置 'maxBytes': 1024 * 1024 * 5, # 文件大小 5M 'backupCount': 5, # 備份份數 'encoding': 'utf8', # 文件編碼 },
這個是根據文件大小進行滾動記錄的。
會自動添加數字后綴。
七:優先級
我們使用getLogger方法時,讀取的是日志管理器集合,由日志管理器集合來決定使用哪個或哪幾個日志管理器。
而我們在日志管理器集合和處理器都定義了level,那么以級別高的為准,級別低的就不輸出了。
八:總結
1.定義日志格式集合,或單個
2.定義處理器,並確定使用什么格式
3.定義日志管理器集合,決定使用什么處理器,控制台 文件 郵件,任意組合。
4.再任意文件中引入
loger = log_main() loger.debug('hello')
九:新用法--定時循環的任務,發送一個循環的記錄到指定郵箱。
1.定義一個文件處理器:
'log_email': { 'level': 'INFO', 'class': 'logging.FileHandler', 'formatter': 'email', 'filename': os.path.join(BASE_DIR, 'email.log'), # 輸出位置 'encoding': 'utf8', # 文件編碼 },
2.加入日志管理器集合
3.定義一個函數來進行讀取email.log文件,並把內容返回回去,然后刪除這個email.log
import os def Smtp_log(): file=os.getcwd() + '/log/email.log' fp=open(file,'r+',encoding='utf-8') text = fp.readlines() resoult='' for i in text: print(i) resoult=resoult+i os.remove(file) return resoult
4.在任意文件中引入
loger.warning(SMTP_log.Smtp_log())
4.一個問題
由於原來發郵件的日志等級高,所以warning的內容也記錄在info debug級的日志中。
現在warning發送的是一段時間的日志,造成了info debug日志的重復,雖然有時間記錄,可以用來區分,還是不方便,且經常造成軟件崩潰。
目前的做法時,另外實例化一個logging
專門用來處理發郵件。
相關代碼如下:
def log_main(): # 加載前面的標准配置 logging.config.dictConfig(LOGGING) # 獲取loggers其中的一個日志管理器 logger = logging.getLogger("default") return logger def loger_email(): # 加載前面的標准配置 logging.config.dictConfig(LOGGING) # 獲取loggers其中的一個日志管理器 logger = logging.getLogger("email") return logger def Smtp_log(): file=os.getcwd() + '/log/email.log' fp=open(file,'r+',encoding='utf-8') text = fp.readlines() resoult='' for i in text: print(i) resoult=resoult+i os.remove(file) return resoult
LOGGING = { # 基本設置 'version': 1, # 日志級別 'disable_existing_loggers': False, # 是否禁用現有的記錄器 'addLevelName': '5, "EMAIL"', # 日志格式集合 'formatters': { # 標准輸出格式 'standard': { # [具體時間][線程名:線程ID][日志名字:日志級別名稱(日志級別ID)] [輸出的模塊:輸出的函數]:日志內容 'format': '[%(asctime)s]:%(levelname)s(%(lineno)d)][%(module)s:%(funcName)s]:%(message)s' }, 'email': { # [具體時間][線程名:線程ID][日志名字:日志級別名稱(日志級別ID)] [輸出的模塊:輸出的函數]:日志內容 'format': '[%(asctime)s]:%(message)s' } }, # 過濾器 'filters': { 'require_debug_true': { '()': RequireDebugTrue, } }, # 處理器集合 'handlers': { # 輸出到控制台 'console': { 'level': 'INFO', # 輸出信息的最低級別 'class': 'logging.StreamHandler', 'formatter': 'standard', # 使用standard格式 'filters': ['require_debug_true', ], # 僅當 DEBUG = True 該處理器才生效 }, # 輸出到文件 'log_debug': { 'level': 'INFO', 'class': 'logging.handlers.TimedRotatingFileHandler', 'formatter': 'standard', 'filename': os.path.join(BASE_DIR, 'debug.log'), # 輸出位置 'when': 'midnight', 'interval': 1, 'backupCount': 31, # 備份份數 'encoding': 'utf8', # 文件編碼 }, 'log_info': { 'level': 'WARNING', 'class': 'logging.handlers.TimedRotatingFileHandler', 'formatter': 'standard', 'filename': os.path.join(BASE_DIR, 'info.log'), # 輸出位置 'when': 'midnight', 'interval': 1, 'backupCount': 31, # 備份份數 'encoding': 'utf8', # 文件編碼 }, 'log_email': { 'level': 'INFO', 'class': 'logging.FileHandler', 'formatter': 'email', 'filename': os.path.join(BASE_DIR, 'email.log'), # 輸出位置 'encoding': 'utf8', # 文件編碼 }, 'email': { 'level': 'DEBUG', 'class': 'logging.handlers.SMTPHandler', 'formatter': 'email', 'mailhost': 'smtp.163.com', # SMTP地址 'fromaddr': 'jackadam@163.com', # 發件人郵箱 'toaddrs': 'jackadam@sina.com', # 收件人郵箱,可以['a@163.com','a@sina.com','a@google.com'] 'subject': '答題機報告', # 郵件標題 'credentials': ['jackadam', '********'] # 郵箱登陸信息,用戶名 ***是密碼 }, }, # 日志管理器集合 'loggers': { # 管理器 'default': { 'handlers': ['console', 'log_debug', 'log_info', 'log_email'], 'level': 'DEBUG', 'propagate': True, # 是否傳遞給父記錄器 }, 'email': { 'handlers': ['email'], 'level': 'DEBUG', 'propagate': True, # 是否傳遞給父記錄器 }, } }
