python多個文件logging
A. python - 日誌記錄模塊(logging)的二次封裝
上篇文章 對logging做了基本介紹,我們可以使用logging來做日誌的簡單記錄。但實際項目應用時,我們一般會根據自身需要對其做二次封裝(loggingV2),然後在其他python文件中, 先import申明後直接調用。
廢話不多說,下面給幾個二次封裝的簡單示例:
示例一:
loggingV2.py - 封裝
logMain.py - 應用
示例二:
對上述示例進行 模塊化封局凳裝 ,如下log.py
則任何聲明了log模塊的python文件都可以調用logging日誌系統,如下logMain.py
示例三:
對上述示例進行 定製化封裝 ,如下myLog.py
需求:
1)同時實現終端顯示與日誌文件保存
2)日誌文件名除日期外,增加顯示時間,精確到秒
3)日誌輸出級別可配置
4)日誌保存路徑與文件名可配置
5)日誌跨天(或者小時/分鍾),另生成新文件保存
改寫logMain.py,如下:
示例四:
對上述示例進行 非同步線程封裝 ,如下myThreadLog.py
需求:
1)獨立線程處理日誌,不影響主程序性能
2)使用隊列緩嫌非同步處理日誌記錄
繼續改寫logMain.py,如下:
注意 - 線程相關操作函數(如下):
1.threading.Thread() — 創建線程並初始化線程,可以為線程傳遞參數
2.threading.enumerate() — 返回一個包含正在運行的線程的list
3.threading.activeCount(): 返回正桐哪旅在運行的線程數量,與len(threading.enumerate())有相同的結果
4.Thread.start() — 啟動線程
5.Thread.join() — 阻塞函數,一直等到線程結束
6.Thread.isAlive() — 返回線程活動狀態
7.Thread.setName() — 設置線程名
8.Thread.getName() — 獲取線程名
9.Thread.setDaemon() — 設置為後台線程,這里默認是False,設置為True之後則主線程不會再等待子線程結束才結束,而是主線程結束意味程序退出,子線程也立即結束,注意調用時必須設置在start()之前;
10.除了以上常用函數,線程還經常與互斥鎖Lock/事件Event/信號量Condition/隊列Queue等函數配合使用
B. 如何動態修改python logging配置文件
配置文件:
#Configuration for log output
#Naiveloafer
#2012-06-04
[loggers]
keys=root,xzs
[handlers]
keys=consoleHandler,fileHandler,rotatingFileHandler
[formatters]
keys=simpleFmt
[logger_root]
level=DEBUG
#handlers=consoleHandler
#handlers=fileHandler
handlers=rotatingFileHandler
[logger_xzs]
level=DEBUG
handlers=rotatingFileHandler
qualname=xzs
propagate=0
[handler_consoleHandler]
class=StreamHandler
level=DEBUG
formatter=simpleFmt
args=(sys.stdout,)
[handler_fileHandler]
class=FileHandler
level=DEBUG
formatter=simpleFmt
args=("../log/p2pplayer.log", "a")
[handler_rotatingFileHandler]
class=handlers.RotatingFileHandler
level=DEBUG
formatter=simpleFmt
args=("../log/p2pplayer.log", "a", 20*1024*1024, 10)
[formatter_simpleFmt]
format=%(asctime)s - %(name)s - %(levelname)s - %(message)s - [%(filename)s:%(lineno)s]
datefmt=
測試代碼:
def log_test02():
import logging
import logging.config
CONF_LOG = "../conf/p2pplayer_logging.conf"
logging.config.fileConfig(CONF_LOG); # 採用配置文件
logger = logging.getLogger("xzs")
logger.debug("Hello xzs")
logger = logging.getLogger()
logger.info("Hello root")
if __name__ == "__main__":
log_test02()
輸出:
2012-06-04 15:28:05,751 - xzs - DEBUG - Hello xzs - [xlog.py:29]
2012-06-04 15:28:05,751 - root - INFO - Hello root - [xlog.py:32]
具體就不詳細說明了,總之是能夠運行的,這個文件配置搞了我兩天時間。
特別是class=XXXX要注意!!!
C. python 多線程logger問題
因為logging是threadsafe的,但不是process-safe(應該沒有這個詞兒,只是為了便於理解)的。這段代碼就是多個進程共同操作一個日誌文件。這種情況下,logging的行為就很難說了。
我測試了一下,日誌中大概幾百行。而且,可以看到一些順序錯亂現象:
Fri, 08 Aug 2014 01:19:38 logging_in_multithread.py[line:40] theadWorking ERROR 2
FFri, 08 Aug 2014 01:19:36 logging_in_multithread.py[line:40] theadWorking ERROR 11(注意這里的FFri)
把代碼這樣改:
fornuminrange(processNum):
p=Process(target=processWorking,args=('2',))
processs.append(p)
p.start()
p.join()
還有其他方法,比如:為logging實現一個FileHandler,以使logging在multiple process的環境下也能正常工作。這是我從網上了解到的做法,自己還沒實踐過。
Python Manual中logging Cookbook中有這么一段話:
Logging to a single file from multiple processes
Although logging is thread-safe, and logging to a single file from multiple threads in a single process is supported, logging to a single file from multiple processes is not supported, because there is no standard way to serialize access to a single file across multiple processes in Python. If you need to log to a single file from multiple processes, one way of doing this is to have all the processes log to a SocketHandler, and have a separate process which implements a socket server which reads from the socket and logs to file. (If you prefer, you can dedicate one thread in one of the existing processes to perform this function.)
這段話中也提出了另外一種解決方案。
D. python里的logging怎麼寫多個文件
an example:
#coding:utf-8
#filename:cfg/logger.yml
version:1
formatters:
simple:
format:'%(asctime)s-%(name)s-%(levelname)s-%(message)s'
consolefmt:
format:'%(name)s-%(levelname)s-%(message)s'
handlers:
console:
class:logging.StreamHandler
formatter:consolefmt
level:WARNING
stream:ext://sys.stdout
ownerloggerfile:
class:logging.handlers.RotatingFileHandler
formatter:simple
level:INFO
filename:log/billingcodeowner.log
maxBytes:1048576
backupCount:3
phnloggerfile:
class:logging.handlers.RotatingFileHandler
formatter:simple
level:INFO
filename:log/phnparser.log
maxBytes:1048576
backupCount:3
loggers:
billingcodeowner:
level:DEBUG
handlers:[ownerloggerfile]
propagate:no
phoneparser:
level:DEBUG
handlers:[console,phnloggerfile]
propagate:no
root:
level:DEBUG
handlers:[console,phnloggerfile]
usage in python application:
importlogging
importlogging.config
importcodecs
importyaml
logging.config.dictConfig(codecs.open("cfg/logger.yml",'r','utf-8').read())
logger=logging.getLogger("billingcodeowner")
E. python logging 問題
請參考我下面的代碼以及對應的 log,看上去沒有問題,我懷疑是 log config 的問題
importlogging
logging.basicConfig(level=logging.DEBUG,
format='%(asctime)s%(filename)s[line:%(lineno)d]%(message)s',
datefmt='%a,%d%b%Y%H:%M:%S',
filename='log.log',
filemode='w')
classA:
def__init__(self):
logging.info('A')
__c=C()
__d=D()
classB:
def__init__(self):
logging.info('B')
classC:
def__init__(self):
logging.info('C')
__e=E()
__f=F()
classD:
def__init__(self):
logging.info('D')
classE:
def__init__(self):
logging.info('E')
classF:
def__init__(self):
logging.info('F')
if__name__=='__main__':
a=A()
b=B()
F. 多進程環境python logging列印日誌混亂問題
解決辦法如下:
多麼痛的領悟,困擾了這么久的問題其實就是一個參數配置錯了。
fileMode:表示日誌文件的打開方式。w-直接寫,使用這個配置當系統重啟的時候日誌會清空,一個進程打開後其他進程是無法使用的;a-尾部追加,大家都可以打開往文件結尾進行追加寫入。
本人主語言是java,轉到python後日誌這塊踩了幾個坑。再說說另外一個坑,就是異常堆棧的列印問題,在java中logger是可以使用error直接列印出來的。在python中error跟其他日誌記錄方法沒太大差別,是無法列印異常堆棧的,列印堆棧請使用 logger.exception("異常說明", e) 。
G. python程序中logging怎麼用
簡單將日誌列印到屏幕:
[python] view plain
import logging
logging.debug('debug message')
logging.info('info message')
logging.warning('warning message')
logging.error('error message')
logging.critical('critical message')
輸出:
WARNING:root:warning message
ERROR:root:error message
CRITICAL:root:critical message
可見,默認情況下Python的
logging模塊將日誌列印到了標准輸出中,且只顯示了大於等於WARNING級別的日誌,這說明默認的日誌級別設置為WARNING(日誌級別等級
CRITICAL > ERROR > WARNING > INFO > DEBUG >
NOTSET),默認的日誌格式為日誌級別:Logger名稱:用戶輸出消息。
靈活配置日誌級別,日誌格式,輸出位置
[python] view plain
import logging
logging.basicConfig(level=logging.DEBUG,
format='%(asctime)s %(filename)s[line:%(lineno)d] %(levelname)s %(message)s',
datefmt='%a, %d %b %Y %H:%M:%S',
filename='/tmp/test.log',
filemode='w')
logging.debug('debug message')
logging.info('info message')
logging.warning('warning message')
logging.error('error message')
logging.critical('critical message')
查看輸出:
cat /tmp/test.log
Mon, 05 May 2014 16:29:53 test_logging.py[line:9] DEBUG debug message
Mon, 05 May 2014 16:29:53 test_logging.py[line:10] INFO info message
Mon, 05 May 2014 16:29:53 test_logging.py[line:11] WARNING warning message
Mon, 05 May 2014 16:29:53 test_logging.py[line:12] ERROR error message
Mon, 05 May 2014 16:29:53 test_logging.py[line:13] CRITICAL critical message
可見在logging.basicConfig()函數中可通過具體參數來更改logging模塊默認行為,可用參數有
filename:用指定的文件名創建FiledHandler(後邊會具體講解handler的概念),這樣日誌會被存儲在指定的文件中。
filemode:文件打開方式,在指定了filename時使用這個參數,默認值為「a」還可指定為「w」。
format:指定handler使用的日誌顯示格式。
datefmt:指定日期時間格式。
level:設置rootlogger(後邊會講解具體概念)的日誌級別
stream:用指定的stream創建StreamHandler。可以指定輸出到sys.stderr,sys.stdout或者文件,默認為sys.stderr。若同時列出了filename和stream兩個參數,則stream參數會被忽略。
format參數中可能用到的格式化串:
%(name)s Logger的名字
%(levelno)s 數字形式的日誌級別
%(levelname)s 文本形式的日誌級別
%(pathname)s 調用日誌輸出函數的模塊的完整路徑名,可能沒有
%(filename)s 調用日誌輸出函數的模塊的文件名
%(mole)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用戶輸出的消息
H. logging:多線程調試時用來代替print和單步調試
當你要寫多線程項目時,不免要調試錯誤,要debug。
一般debug的工具就是列印函數print, 調試工具gdb進行單步調試,但是多線程時,單步調試就很雞肋了,這時就需要列印日誌了
沒錯,列印日誌無疑是調試多線程工程的高效工具了。
在python中開發,就要用到logging日誌庫了
logging庫已經封裝好日誌需要的基本功能,能夠實現在文件里,在命令行等寫日誌
還能輸出日誌信息的類型,如debug,warning,error等
細節在這里有所介紹:
https://docs.python.org/3/howto/logging.html
文本介紹一下,第一次使用logging時,要熟悉logging時,需要用一個非常簡單的例子
先看一個最簡單的例子:
運行以上代碼:
printed out on the console. The INFO message doesn』t appear because the default level is WARNING . The printed message includes the indication of the level and the description of the event provided in the logging call, i.e. 『Watch out!』. Don』t worry about the 『root』 part for now: it will be explained later. The actual output can be formatted quite flexibly if you need that; formatting options will also be explained later.
輸出結果為:
如果你想每次都在一個新的日誌文件中寫日誌,那麼使用filemode='w'參數:
分別在main 函數里,在mylib.py里寫日誌
運行後的輸出:
以上代碼會顯示:
該 format參數的值
以上代碼會顯示:
還是該format:
以上代碼會輸出
如果想自己設定時間的格式:
會這樣顯示:
logging庫中用了模塊化的思路,把日誌的整體功能用了4個基本的模塊來完成:
Loggers,Handlers,Filters,Formatters
其中,Handlers,主要配置將信息寫到命令行,還是寫到文件里。
Filters,是對信息本身的過濾,決定那些信息不寫,那些信息寫。
Formatters決定信息輸出的格式。比如是否輸出時間,是否輸出logger本身的名字等,決定那些信息在前,那些信息在後等。
Logger就是Handler,Filter,Formatter配置的一個日誌對象了。
下面我們逐個說一下這4個類:
它有三個功能,1 提供分級日誌的輸出,比如 WARNING,ERROR,INFO等不同等級。2. 它可以決定哪些信息輸出,哪些信息不輸出。3 它可以將一條信息發給命令行和文件,可以把一條信息發給多個handler去處理。
logger 最常用的成員方法大概分兩類:配置和信息發送
以下是常用的配置函數:
Logger.setLevel() 設定信息記錄的等級。如果一條信息的等級比我們設定的低,那麼就不對此條信息進行處理。信息一共分為:DEBUG,INFO,WARNING,ERROR,CRITICAL 五個級別,其中DEBUG是最低的等級,CRITICAL是最高的等級。
Logger.addHandler() 和 Logger.removeHandler(),添加或者刪除信息處理器Handler。這個信息處理器就是定義了將日誌寫入命令行,還是寫入文件,或者寫入郵件等。
Logger.addFilter() 和 Logger.removeFilter() .添加或者刪除信息過濾器Filter。這個信息過濾器,決定了哪些信息不顯示。
可以看到,信息過濾器,信息處理器,信息等級共同配置了logger.
並不是每個logger你都需要去配置一遍,你可以利用logger的繼承機制,只配置父logger.
一旦配置好logger之後,就可以用以下函數來在你自己代碼的任意位置來記錄日誌了。
Logger.debug(),Logger.info(),Logger.warning(),Logger.error()和Logger.critical.這些函數的功能都是建立了日誌記錄信息,不同的是,函數名字就代表了其建立的日誌信息的等級。
Logger.exeception()建立一個與Logger.error()比較相似的信息。但是Logger.exception()是放入一個追蹤盞裡面的。所以,只有在exception handler處理器中,才能使用它。
Logger.log()發送一個LOG 等級的信息,使用LOG等級的信息稍微繁瑣些,因為使用LOG等級可以自定義等級。
getLogger() 返回一個logger的引用,如果指定了名字,那麼返回特定名字對應的logger,如果沒有指定名字,那就返回一個名字為root的loger的引用名字root,或者你指定的名字是一種級連結構。用同樣的名字去調用getLogger(),會得到同樣的值。logger的名字也決定了logger在樹結構中的層級。例如:這里有一個logger的名字為foo,那麼foo.bar,foo.bar.baz,還有foo.bam都是foo的子孫,logger還有一個有效等級level的概念。這個有效等級其實就是決定debug,info,warning等不同類型的消息是否進行記錄。因為logger本身有了父子那樣的繼承關系,所以有效等級level也是可以繼承的。如果子logger沒有設定了自身的level,那麼就把父logger的level繼承過來使用。如果子logger本身設定了level,就用自身這個level.如果父logger仍然沒有設定level,那就看父logger的父logger,一直這么追述下去,就會追述到root上,所以,我們必須給root設定一個level,或者默認一個level,方便root的子logger去繼承。我們給root設定的默認levle為WARNING.如果有些日誌的level,相比我們設定的WARNING低,那麼它就不會被傳遞給Handler去處理,就不會被列印出來,或者記錄進日誌文件。另外logger的屬性也是可繼承的,所以就只配置一下root logger即可,沒有root logger時,只用配置一個相對的那個根logger就行了
handler 信息處理器是負責信息分發給不同的目的地的,這個目的地可能是命令行,也可能是文件,或者郵件。分發時,同樣要檢查信息本身的等級severity.一個logger可以用addHandler()函數添加0個或者多個handlers。比如有這樣一個場景,希望發送所有的log等級的信息到一個log文件內。所有的等級為錯誤的信息到stdout 標准輸出上,發送所有的critical信息到郵件上。這樣的場景需要3個不同的處理器,每個處理器負責發送相應等級的信息到相應的目的地。
標准庫里包含一些處理器類型,本教程主要使用StreamHandler 和 FileHandler兩種信息處理器。
處理器中的成員函數非常少,我們用來配置處理器的成員函數大概有這個幾個:
setLevel(),用來設置處理器處理的信息等級。注意到logger中有setLevel(),而處理器中也有setLevel(),也就是說,logger把信息通過信息等級過濾一遍後,logger內的處理器需要根據處理器自身的功能設定,再根據信息等級來過濾一遍。
setFormatter() 為處理器設定一種信心記錄的格式Formatter
addFilter() removeFilter()函數添加信息過濾器或者刪除信息過濾器的函數。
到這里,我們發現,logger類中,將信息發送到不同的目的地就依賴Handlers實現的,
所有Handler就需要用Formatter和Filter來配置一下。接下來,我們看看Formatter和Filter.
它決定了信息顯示的順序,結構和內容。可以直接操作formatter的相關類,也可以自己繼承 formatter類,去完成自己的設定。formatter的構造函數需要3個可選的參數:字元串格式的信息,日期,一個符號。
logging.Formatter.__init__(fmt=None,datafmt=None,style='%')
如果這里沒有設定參數,就使用默認參數。對於fmt來說,就顯示原來信息,對於datafmt來說,就以年-月-日 時:分:秒的合適顯示。
下面的fmt就定義了一個按照 時間-信息等級-信息本身 來記錄日誌的方法:
這里有三種方法:
寫代碼配置logging的例子:
運行結果為:
寫配置文件配置logging的例子:
代碼中所需要的配置文件ogging.conf的具體內容為 :
以上代碼的輸出為:
顯然,配置文件修改起來相對容易很多。
還有更方便的配置方式,使用字典來配置:
運行結果與上面一樣。
到這里,基本完成了logging庫的比較常用的使用方式。
比如在什麼情況下,需要使用一個什麼都不做的處理器NullHandler(),
還有關於信息等級的更詳細的解釋:
以及如何自定義信息等級。
還有關於異常的處理
還有關於信息的,信息都是字元串的。不過你也可以直接把某個類作為信息拋出來,因為類會自動調用其__str__()函數,返回一個類的字元串回來。
還有就是優化性能的一些小技巧
這里就編寫expensive_func1 和 expensive_func2 來完成設定數據格式。
以下表格也顯示了如何收集運行log的代碼文件信息,線程信息,進程信息,處理器信息
以及如何操作才能收集代碼文件信息,線程信息,進程信息,以及處理器信息。
我們一般會使用收集代碼文件,線程,進程等信息,因為這樣大大方便了多線程多進程工程的調試。
I. python logging 意圖:根據運行的不同時間來創建log文件,而不是固定命名,如:2013-06-13.log
原生loggging類+TimedRotatingFileHandler類實現按dayhoursecond切分
importlogging
fromlogging.
log=logging.getLogger(loggerName)
formatter=logging.Formatter('%(name)-12s%(asctime)slevel-%(levelname)-8sthread-%(thread)-8d%(message)s')#每行日誌的前綴設置
fileTimeHandler=TimedRotatingFileHandler(BASIC_LOG_PATH+filename,"S",1,10)
fileTimeHandler.suffix="%Y%m%d.log"#設置切分後日誌文件名的時間格式默認filename+"."+suffix如果需要更改需要改logging源碼
fileTimeHandler.setFormatter(formatter)
logging.basicConfig(level=logging.INFO)
fileTimeHandler.setFormatter(formatter)
log.addHandler(fileTimeHandler)
try:
log.error(msg)
exceptException,e:
print"writeLogerror"
finally:
log.removeHandler(fileTimeHandler)
值 interval的類型
S 秒
M 分鍾
H 小時
D 天
W 周
midnight 在午夜