python自動了logging模塊,可以方便的記錄和列印日誌
大致的思路
import logging
# 定義日誌輸出格式
formattler = '%(levelname)s - %(name)s - %(asctime)s - %(message)s'
fmt = logging.Formatter(formattler)
# 獲得logger,默認獲得root logger對象
# 設置logger級別 debug
# root logger默認的級別是warning級別。
# 不設置的話 只能發送 >= warning級別的日誌
logger = logging.getLogger()
logger.setLevel(logging.DEBUG)
# 設置handleer日誌處理器,日誌具體怎麼處理都在日誌處理器裡面定義
# SteamHandler 流處理器,輸出到控制臺,輸出方式為stdout
# StreamHandler默認輸出到sys.stderr
# 設置handler所處理的日誌級別。
# 只能處理 >= 所設置handler級別的日誌
# 設置日誌輸出格式
stream_handler = logging.StreamHandler(sys.stdout)
stream_handler.setLevel(logging.DEBUG)
stream_handler.setFormatter(fmt)
file_handler.setLevel(logging.DEBUG)
file_handler.setFormatter(fmt)
# 對logger增加handler日誌處理器
logger.addHandler(stream_handler)其中logger的日誌格式參考
%(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: 列印日誌信息在程序模塊直接使用就可以可以了
def test(num):
time.sleep(3)
logger.debug('日誌測試' + str(num))
多進程下logger使用自帶的logger不支持多進程,因為涉及到進程間通信等問題。所以需要進行特殊處理,這裡是別人造好的輪子, 拿過來直接用了,請參考。
定義一個多進程日誌處理的handler
import multiprocessing
import logging
import os
import datetime
import re
try:
import codecs
except ImportError:
codecs = None
class MultiprocessHandler(logging.FileHandler):
"""支持多進程的TimedRotatingFileHandler"""
def __init__(self,filename,when='D',backupCount=0,encoding=None,delay=False):
"""filename 日誌文件名,when 時間間隔的單位,backupCount 保留文件個數
delay 是否開啟 OutSteam緩存
True 表示開啟緩存,OutStream輸出到緩存,待緩存區滿後,刷新緩存區,並輸出緩存數據到文件。
False表示不緩存,OutStrea直接輸出到文件"""
self.prefix = filename
self.backupCount = backupCount
self.when = when.upper()
# 正則匹配 年-月-日
self.extMath = r"^\d{4}-\d{2}-\d{2}"
# S 每秒建立一個新文件
# M 每分鐘建立一個新文件
# H 每天建立一個新文件
# D 每天建立一個新文件
self.when_dict = {
'S':"%Y-%m-%d-%H-%M-%S",
'M':"%Y-%m-%d-%H-%M",
'H':"%Y-%m-%d-%H",
'D':"%Y-%m-%d"
}
#日誌文件日期後綴
self.suffix = self.when_dict.get(when)
if not self.suffix:
raise ValueError(u"指定的日期間隔單位無效: %s" % self.when)
#拼接文件路徑 格式化字符串
self.filefmt = os.path.join("logs","%s.%s" % (self.prefix,self.suffix))
#使用當前時間,格式化文件格式化字符串
self.filePath = datetime.datetime.now().strftime(self.filefmt)
#獲得文件夾路徑
_dir = os.path.dirname(self.filefmt)
try:
#如果日誌文件夾不存在,則創建文件夾
if not os.path.exists(_dir):
os.makedirs(_dir)
except Exception:
print(u"創建文件夾失敗")
print(u"文件夾路徑:" + self.filePath)
pass
if codecs is None:
encoding = None
logging.FileHandler.__init__(self,self.filePath,'a+',encoding,delay)
def shouldChangeFileToWrite(self):
"""更改日誌寫入目的寫入文件
:return True 表示已更改,False 表示未更改"""
#以當前時間獲得新日誌文件路徑
_filePath = datetime.datetime.now().strftime(self.filefmt)
#新日誌文件日期 不等於 舊日誌文件日期,則表示 已經到了日誌切分的時候
# 更換日誌寫入目的為新日誌文件。
#例如 按 天 (D)來切分日誌
# 當前新日誌日期等於舊日誌日期,則表示在同一天內,還不到日誌切分的時候
# 當前新日誌日期不等於舊日誌日期,則表示不在
#同一天內,進行日誌切分,將日誌內容寫入新日誌內。
if _filePath != self.filePath:
self.filePath = _filePath
return True
return False
def doChangeFile(self):
"""輸出信息到日誌文件,並刪除多於保留個數的所有日誌文件"""
#日誌文件的絕對路徑
self.baseFilename = os.path.abspath(self.filePath)
#stream == OutStream
#stream is not None 表示 OutStream中還有未輸出完的緩存數據
if self.stream:
#flush close 都會刷新緩衝區,flush不會關閉stream,close則關閉stream
#self.stream.flush()
self.stream.close()
#關閉stream後必須重新設置stream為None,否則會造成對已關閉文件進行IO操作。
self.stream = None
#delay 為False 表示 不OutStream不緩存數據 直接輸出
# 所有,只需要關閉OutStream即可
if not self.delay:
#這個地方如果關閉colse那麼就會造成進程往已關閉的文件中寫數據,從而造成IO錯誤
#delay == False 表示的就是 不緩存直接寫入磁碟
#我們需要重新在打開一次stream
#self.stream.close()
self.stream = self._open()
#刪除多於保留個數的所有日誌文件
if self.backupCount > 0:
print('刪除日誌')
for s in self.getFilesToDelete():
print(s)
os.remove(s)
def getFilesToDelete(self):
"""獲得過期需要刪除的日誌文件"""
#分離出日誌文件夾絕對路徑
#split返回一個元組(absFilePath,fileName)
#例如:split('I:\ScripPython\char4\mybook\util\logs\mylog.2017-03-19)
#返回(I:\ScripPython\char4\mybook\util\logs, mylog.2017-03-19)
# _ 表示佔位符,沒什麼實際意義,
dirName,_ = os.path.split(self.baseFilename)
fileNames = os.listdir(dirName)
result = []
#self.prefix 為日誌文件名 列如:mylog.2017-03-19 中的 mylog
#加上 點號 . 方便獲取點號後面的日期
prefix = self.prefix + '.'
plen = len(prefix)
for fileName in fileNames:
if fileName[:plen] == prefix:
#日期後綴 mylog.2017-03-19 中的 2017-03-19
suffix = fileName[plen:]
#匹配符合規則的日誌文件,添加到result列表中
if re.compile(self.extMath).match(suffix):
result.append(os.path.join(dirName,fileName))
result.sort()
#返回 待刪除的日誌文件
# 多於 保留文件個數 backupCount的所有前面的日誌文件。
if len(result) < self.backupCount:
result = []
else:
result = result[:len(result) - self.backupCount]
return result
def emit(self, record):
"""發送一個日誌記錄
覆蓋FileHandler中的emit方法,logging會自動調用此方法"""
try:
if self.shouldChangeFileToWrite():
self.doChangeFile()
logging.FileHandler.emit(self,record)
except (KeyboardInterrupt,SystemExit):
raise
except:
self.handleError(record)主程序部分
from MultiprocessLogFileHander import MultiprocessHandler #引入該handler
# 使用我們寫的多進程版Handler理器,定義日誌輸出到mylog.log文件內
# 文件打開方式默認為 a, append的方式寫入
# 按天(D)
file_handler = MultiprocessHandler('mylog', when='D')
file_handler.setLevel(logging.DEBUG)
file_handler.setFormatter(fmt)
# 對logger增加handler日誌處理器
logger.addHandler(file_handler)
# 發送debug級別日誌消息
def test(num):
time.sleep(3)
logger.debug('日誌測試' + str(num))
if __name__ == '__main__':
pool = multiprocessing.Pool(processes=3)
for i in range(10):
pool.apply_async(func=test, args=(i,))
pool.close()
pool.join()
print('完畢')運行之後結果如下
DEBUG - root - 2020-11-06 18:39:52,659 - 日誌測試0
DEBUG - root - 2020-11-06 18:39:52,682 - 日誌測試1
DEBUG - root - 2020-11-06 18:39:52,690 - 日誌測試2
DEBUG - root - 2020-11-06 18:39:55,659 - 日誌測試3
DEBUG - root - 2020-11-06 18:39:55,682 - 日誌測試4
DEBUG - root - 2020-11-06 18:39:55,690 - 日誌測試5
DEBUG - root - 2020-11-06 18:39:58,660 - 日誌測試6
DEBUG - root - 2020-11-06 18:39:58,683 - 日誌測試7
DEBUG - root - 2020-11-06 18:39:58,691 - 日誌測試8
DEBUG - root - 2020-11-06 18:40:01,661 - 日誌測試9
完畢