轉(zhuǎn)自 關(guān)于 logging 的一些瑣事
1. 為什么logging.info()默認(rèn)不輸出
因?yàn)槟J(rèn)生成的 root logger 的 level 是 logging.WARNING覆旭,低于該級別的就不輸出了型将〖雠埃可以進(jìn)行如下設(shè)置來輸出:
>>> import logging
>>> logging.info('test')
>>> root_logger = logging.getLogger() # 或使用未公開的 logging.root
>>> root_logger.level
30
>>> logging.getLevelName(30)
'WARNING'
>>> root_logger.level = logging.NOTSET
>>> logging.info('test')
INFO:root:test
如果還沒配置 handler 的話缚俏,可以用 logging.basicConfig() 來配置:
>>> root_logger.handlers
[]
>>> logging.basicConfig(level=logging.NOTSET)
>>> root_logger.handlers
[<logging.StreamHandler object at 0x108becd10>]
>>> logging.info('test')
INFO:root:test
2. 如何指定輸出格式
給 logger 的 handler 設(shè)置一個 logging.Formatter 對象:
>>> root_logger.handlers[0].formatter.format
<bound method Formatter.format of <logging.Formatter object at 0x10c062d90>>
>>> root_logger.handlers[0].formatter.datefmt
>>> root_logger.handlers[0].formatter._fmt
'%(levelname)s:%(name)s:%(message)s'
>>> LOGGING_FORMAT = '[%(levelname)1.1s %(asctime)s %(module)s:%(lineno)d] %(message)s'
>>> DATE_FORMAT = '%y%m%d %H:%M:%S'
>>> formatter = logging.Formatter(LOGGING_FORMAT, DATE_FORMAT)
>>> root_logger.handlers[0].formatter = formatter
>>> logging.info('test')
[I 130221 01:58:28 <stdin>:1] test
如果還沒配置 handler 的話,可以用 logging.basicConfig() 來配置:
logging.basicConfig(
level=logging.NOTSET,
format=LOGGING_FORMAT,
datefmt=DATE_FORMAT
)
詳細(xì)的格式介紹就查看文檔吧向拆。
3. 為什么我重定向了stdout卻看不到輸出
因?yàn)槟J(rèn)生成的 root logger 的 handler 的 stream 是 stderr酪耳,不是 stdout:
>>> root_logger.handlers[0].stream
<open file '<stderr>', mode 'w' at 0x1089cb270>
可以如下分別配置:
stdout_handler = logging.StreamHandler(sys.__stdout__)
stdout_handler.level = logging.DEBUG
stdout_handler.formatter = formatter
root_logger.addHandler(stdout_handler)
stderr_handler = logging.StreamHandler(sys.__stderr__)
stderr_handler.level = logging.WARNING
stderr_handler.formatter = formatter
root_logger.addHandler(stderr_handler)
4. 如何將日志輸出到文件
使用 logging.FileHandler():
handler = logging.FileHandler('log/test.log')
root_logger.addHandler(handler)
其中文件名可以使用相對路徑碗暗,但要保證文件夾存在。默認(rèn)的文件打開方式是 append晴圾。 如果還沒配置 handler 的話噪奄,可以用 logging.basicConfig() 來配置:
logging.basicConfig(
level=logging.NOTSET,
format=LOGGING_FORMAT,
datefmt=DATE_FORMAT,
filename='log/test.log',
filemode='a'
)
5. 捕捉了一個異常,如何輸出執(zhí)行堆棧都毒?
使用 logging.exception()碰缔,或在調(diào)用 logging.debug() 等方法時加上 exc_info=True 參數(shù)。
>>> try:
... 0 / 0
... except:
... logging.exception('Catch an exception.')
... print '-' * 10
... logging.warning('Catch an exception.', exc_info=True)
...
ERROR:root:Catch an exception.
Traceback (most recent call last):
File "<stdin>", line 2, in <module>
ZeroDivisionError: integer division or modulo by zero
----------
WARNING:root:Catch an exception.
Traceback (most recent call last):
File "<stdin>", line 2, in <module>
ZeroDivisionError: integer division or modulo by zero
6. 如何針對不同的模塊瀑焦,輸出到不同的日志
可以創(chuàng)建多個 logger:
console_handler = logging.StreamHandler(sys. __stdout__)
console_handler.level = logging.DEBUG
console_logger = logging.getLogger('test')
console_logger.addHandler(console_handler)
file_handler = logging.FileHandler('log/test.log')
file_handler.level = logging.WARNING
file_logger = logging.getLogger('test.file')
file_logger.addHandler(file_handler)
console_logger.error('test')
file_logger.error('test')
console_logger.parent is root_logger
file_logger.parent is console_logger
console_logger.getChild('file') is file_logger
每個 logger 都有個名字蝠猬,以 ‘.’ 來劃分繼承關(guān)系统捶。名字為空的就是 root_logger柄粹,console_logger 的名字是 ‘test’,因此 root_logger 是 console_logger 的 parent驻右;而 file_logger 的名字是 ‘test.file’,因此 console_logger 是 file_logger 的 parent愕把。 如果 logger 的 propagate 屬性為 True(默認(rèn)值),則它的記錄也會傳到父 logger嚣镜。因此橘蜜,file_logger 在記錄到文件的同時,也會在 stdout 輸出日志跌捆。 建議每個模塊都用自己的 logger象颖。
7. 如何指定某些日志不輸出
使用 logging.Filter 來過濾記錄:
mport logging
import random
class OddFilter(logging.Filter):
def __init__(self):
self.count = 0
def filter(self, record):
self.count += 1
if record.args[0] & 1:
record.count = self.count # 給 record 增加了 count 屬性
return True # 為 True 的記錄才輸出
return False
root_logger = logging.getLogger()
logging.basicConfig(level=logging.NOTSET, format='%(message)s (total: %(count)d)') # 可以使用 record.count 來格式化
root_logger.level = logging.NOTSET
root_logger.addFilter(OddFilter())
for i in xrange(100):
logging.error('number: %d', random.randint(0, 1000))
8. 大文件分割
可以使用 logging.handlers.RotatingFileHandler 和 logging.handlers.TimedRotatingFileHandler说订。前者按文件大小來分割,后者按時間來分割闺鲸。 它們會在達(dá)到分割條件時(文件達(dá)到指定大小或達(dá)到指定時間)埃叭,把當(dāng)前的日志重命名為備份文件,然后再打開新文件來記錄立镶。 值得一提的是类早,如果備份文件名已存在,就會被刪除缭召。所以在多進(jìn)程時不建議使用逆日。我是將日志輸出到 stdout 和 stderr,再用 supervisor 來分割日志搪哪。 此外還有一些沒考慮到的特殊情況,建議使用前讀讀源碼晓折,然后自行實(shí)現(xiàn)。