循環日誌分爲按大小切分和按時間切分,對應實現類以下。python
常規文件回滾,須要指定文件名,encoding,maxByteswindows
若是maxbytes=0 或backupcount=0狀況下不回滾,也就是隻寫到一個文件中。安全
新的日誌永遠寫入filename.log,當它滿的時候會將filename.log更名爲filename.log.1或其它附加值;socket
def _set_file_handler(self, level=None):
file_name = os.path.join(LOG_PATH, '{}.log'.format(self.name))
file_handler = RotatingFileHandler(file_name,
maxBytes=5000,
backupCount=5,
encoding='utf-8')
if not level:
file_handler.setLevel(self.level)
else:
file_handler.setLevel(level)
formatter = logging.Formatter('%(asctime)s %(filename)s [line:%(lineno)d] %(levelname)s %(message)s')
file_handler.setFormatter(formatter)
self.file_handler = file_handler
self.addHandler(file_handler)函數
def _set_time_rotating_handler(self, level=None):
file_name = os.path.join(LOG_PATH, '{}.log'.format(self.name))
time_handler = logging.handlers.TimedRotatingFileHandler(file_name,
when='h',
interval=1,
backupCount=5)
if not level:
time_handler.setLevel(self.level)
else:
time_handler.setLevel(level)
formatter = logging.Formatter('%(asctime)s %(filename)s [line:%(lineno)d] %(levelname)s %(message)s')
time_handler.setFormatter(formatter)
time_handler.suffix = "%Y-%m-%d_%H-%M-%S.log"
self.addHandler(time_handler)測試
須要注意的就是suffix的書寫ui
有幾個坑要注意:線程
logging 是線程安全的,handler 內部使用了 threading.RLock() 來保證同一時間只有一個線程可以輸出。日誌
可是,在使用 logging.FileHandler 時,多進程同時寫一個日誌文件是不支持的。orm
測試時發現多個進程寫同一個文件是能夠的,但部分文檔說若是寫長字符串時會出問題,測試寫5000個字符的日誌沒問題,更長未確認。
在日誌回滾時必定會出問題。
多進程寫日誌回滾的代碼以下:
t = self.rolloverAt - self.interval
if self.utc:
timeTuple = time.gmtime(t)
else:
timeTuple = time.localtime(t)
dfn = self.baseFilename + "." + time.strftime(self.suffix, timeTuple)
if os.path.exists(dfn):
os.remove(dfn)
os.rename(self.baseFilename, dfn)
關鍵是在每一個進程在過了rotate時間點以後寫第一條日誌時,都會執行這個doRollover,判斷文件是否存在,若是存在則刪除,更名,往新的.log中寫入,等多個進程都作一遍後,前一週期的日誌徹底刪除,本週期的日誌也會有部分被刪除。
術語一點的講,就是在對文件操做時,沒有對多進程進行一些約束。
解決辦法:
既然問題是文件重名,那麼就不讓文件有重名,實際就是每一個進程各寫一個文件;有兩種方法:
1)在文件名中加入pid,問題是老的pid不會被自動刪除,沒法自維護,不過能夠經過定時任務檢查並刪除。
2)另外一種方法是使用進程name作爲logger名,在建立進程時給出不一樣的name,這樣不會出現上面的問題;
不讓文件重名的好處是能夠單獨追蹤每一個進程的日誌,壞處是日誌不集中。
建議使用這種方式,寫日誌有兩種狀況:
1-通常狀況下每一個進程是負責不一樣任務的,分開記錄便於查找;
2-若是確實須要多個進程執行同樣的任務,這時在查找日誌記錄時是比較麻煩的,不肯定在哪一個日誌文件中,但考慮到工程效率,本方法也是可行的。
報錯:The "freeze_support()" line can be omitted if the program is not going to be
緣由及解決方法:
應該是在其它的子進程 裏又開了進程,
把建立進程部分放到 if __name__ == ‘__main__’下便可
例:
if __name__ == '__main__':
p = Process(target=func, name='my_process')
p.daemon = True
p.start()
p.join()
print('process execute complete.')
getLogger是模塊級的函數,實際是調用manager.getLogger()
def getLogger(name=None):
"""
Return a logger with the specified name, creating it if necessary.
If no name is specified, return the root logger.
"""
if name:
return Logger.manager.getLogger(name)
else:
return root
繼續:manager.getLogger()
def getLogger(self, name):
"""
Get a logger with the specified name (channel name), creating it
if it doesn't yet exist. This name is a dot-separated hierarchical
name, such as "a", "a.b", "a.b.c" or similar.
If a PlaceHolder existed for the specified name [i.e. the logger
didn't exist but a child of it did], replace it with the created
logger and fix up the parent/child references which pointed to the
placeholder to now point to the logger.
"""
rv = None
if not isinstance(name, str):
raise TypeError('A logger name must be a string')
_acquireLock()
try:
if name in self.loggerDict:
rv = self.loggerDict[name]
if isinstance(rv, PlaceHolder):
ph = rv
rv = (self.loggerClass or _loggerClass)(name)
rv.manager = self
self.loggerDict[name] = rv
self._fixupChildren(ph, rv)
self._fixupParents(rv)
else:
rv = (self.loggerClass or _loggerClass)(name)
rv.manager = self
self.loggerDict[name] = rv
self._fixupParents(rv)
finally:
_releaseLock()
return rv
logging經過manager.loggerDict維護了一個logger列表,實現了相同名稱返回同一個loogger。
若是logger名不存在,則建立一個新的:
rv = (self.loggerClass or _loggerClass)(name)
基本等同於logger(name)
另一點是logger的繼承關係,是經過下面兩個方法實現的
self._fixupChildren(ph, rv)
self._fixupParents(rv)