Python學習筆記:logging(日誌處理)

在一個軟件中,日誌是能夠說必不可少的一個組成部分,一般會在定位客戶問題或者記錄軟件使用狀況等場景中會用到。logging模板塊是Python的一個內置標準庫,用於實現對日誌的控制輸出,對於日常的日誌輸出,甚至是系統級的日誌輸出,也均可以使用logging模塊來進行實現。html

1、使用basicConfig進行簡單的一次性配置服務器

basicConfig一次性配置,簡單示例:函數

# -*- coding:utf-8 -*-
import logging
import datetime


# filename:設置日誌輸出文件,以天爲單位輸出到不一樣的日誌文件,以避免單個日誌文件日誌信息過多,
# 日誌文件若是不存在則會自動建立,但前面的路徑如log文件夾必須存在,不然會報錯
log_file = 'log/sys_%s.log' % datetime.datetime.strftime(datetime.datetime.now(), '%Y-%m-%d')
# level:設置日誌輸出的最低級別,即低於此級別的日誌都不會輸出
# 在平時開發測試的時候能夠設置成logging.debug以便定位問題,但正式上線後建議設置爲logging.WARNING,既能夠下降系統I/O的負荷,也能夠避免輸出過多的無用日誌信息
log_level = logging.WARNING
# format:設置日誌的字符串輸出格式
log_format = '%(asctime)s[%(levelname)s]: %(message)s'
logging.basicConfig(filename=log_file, level=logging.WARNING, format=log_format)
logger = logging.getLogger()

# 如下日誌輸出因爲level被設置爲了logging.WARNING,因此debug和info的日誌不會被輸出
logger.debug('This is a debug message!')
logger.info('This is a info message!')
logger.warning('This is a warning message!')
logger.error('This is a error message!')
logger.critical('This is a critical message!')

運行後,日誌文件sys_2019-04-14.log的內容以下:測試

2019-04-14 23:34:42,444[WARNING]: This is a warning message!
2019-04-14 23:34:42,444[ERROR]: This is a error message!
2019-04-14 23:34:42,444[CRITICAL]: This is a critical message!

 

logging.basicConfig:用於對logging模塊整個日誌輸出的一次性配置,也就是說屢次配置時以第一次配置的爲準,以後再使用basicConfig進行配置則無效。spa

logging.basicConfig的參數:線程

filename:設置日誌輸出的文件,默認輸出到控制檯。
filemode:設置打開日誌文件的方式,默認爲「a」,即追加。
format:設置日誌輸出的字符串格式,具體的格式有以下幾種:debug

  • %(name)s:日誌記錄器的名稱
  • %(levelno)s:日誌級別數值
  • %(levelname)s:日誌級別名稱
  • %(pathname)s:輸出日誌時當前文件的絕對路徑
  • %(filename)s:輸出日誌時當前文件名(包含後綴)
  • %(module)s:輸出日誌時的模塊名(即%(filename)s不包含後綴名)
  • %(funcName)s:輸出日誌時所在函數名
  • %(lineno)d:輸出日誌時在文件中的行號
  • %(asctime)s:輸出日誌時的時間
  • %(thread)d:輸出日誌的當前線程ID
  • %(threadName)s:輸出日誌的當前線程名稱
  • %(process)s:輸出日誌的當前進程ID
  • %(processName)s:輸出日誌的當前進程名稱
  • %(message)s:輸出日誌的內容

datefmt:設置日誌時間字符串的輸出格式,默認時間字符串格式爲%Y-%m-%d %H:%M:%S。
style:設置format字符串格式化的風格,能夠是「%」,「{」或「$」,默認是「%」。
level:設置日誌的級別,具體有如下幾種(由高到低):調試

  • CRITICAL:致命錯誤
  • ERROR:嚴重錯誤
  • WARNING:須要給出的提示
  • INFO:通常的日誌信息
  • DEBUG:在debug過程當中的debug信息

stream:指定日誌的輸出Stream,能夠是sys.stderr,sys.stdout或者是文件(即便用open函數打開的文件流,可是這個文件流logging模塊不會主動關閉它),默認是sys.stderr,若是同時指定了filename參數和stream參數,那stream參數就會被忽略。日誌

 

2、使用Handler將日誌同時輸出到文件和控制檯code

添加Handler打印日誌,簡單示例:

# -*- coding:utf-8 -*-
import logging
import datetime


logger = logging.getLogger()
# 設置此logger的最低日誌級別,以後添加的Handler級別若是低於這個設置,則以這個設置爲最低限制
logger.setLevel(logging.INFO)

# 建立一個FileHandler,將日誌輸出到文件
log_file = 'log/sys_%s.log' % datetime.datetime.strftime(datetime.datetime.now(), '%Y-%m-%d')
file_handler = logging.FileHandler(log_file)
# 設置此Handler的最低日誌級別
file_handler.setLevel(logging.WARNING)
# 設置此Handler的日誌輸出字符串格式
log_formatter = logging.Formatter('%(asctime)s[%(levelname)s]: %(message)s')
file_handler.setFormatter(log_formatter)

# 建立一個StreamHandler,將日誌輸出到Stream,默認輸出到sys.stderr
stream_handler = logging.StreamHandler()
stream_handler.setLevel(logging.INFO)

# 將不一樣的Handler添加到logger中,日誌就會同時輸出到不一樣的Handler控制的輸出中
# 注意若是此logger在以前使用basicConfig進行基礎配置,由於basicConfig會自動建立一個Handler,因此此logger將會有3個Handler
# 會將日誌同時輸出到3個Handler控制的輸出中
logger.addHandler(file_handler)
logger.addHandler(stream_handler)

# 文件中將會輸出WARNING及以上級別的日誌
# 控制檯將會輸出INFO及以上級別的日誌
logger.debug('This is a debug message!')
logger.info('This is a info message!')
logger.warning('This is a warning message!')
logger.error('This is a error message!')
logger.critical('This is a critical message!')

運行後,日誌文件sys_2019-04-15.log的內容以下:

2019-04-15 21:50:40,292[WARNING]: This is a warning message!
2019-04-15 21:50:40,292[ERROR]: This is a error message!
2019-04-15 21:50:40,293[CRITICAL]: This is a critical message!

控制檯打印的內容以下:

This is a info message!
This is a warning message!
This is a error message!
This is a critical message!

 

經過給logger添加不一樣的Handler,能夠將日誌同時輸出到不一樣的地方,須要注意的是使用basicConfig進行一次性基礎配置時,會根據配置內容自動建立一個Handler,因此若是以後再添加了N個Handler,實際上總的Handler數量有N+1個。

通常在logging或者logging.handlers下就能夠你想要的Handler,不一樣的Handler會以不一樣的方式輸出到不一樣的地方,如下是幾種經常使用的Handler:

from logging import FileHandler: 以「a」(追加)的方式將日誌輸出到文件,若是文件不存在,則自動建立該文件。
from logging import StreamHandler: 將日誌輸出到Stream,好比sys.stderr、sys.stdour、文件流等。
from logging.handlers import RotatingFileHandler: 將日誌輸出到文件,能夠經過設置文件大小,文件達到上限後自動建立一個新的文件來繼續輸出文件。
from logging.handlers import TimedRotatingFileHandler: 將日誌輸出到文件,能夠經過設置時間,使日誌根據不一樣的時間自動建立並輸出到不一樣的文件中。
from logging.handlers import HTTPHandler: 將日誌發送到一個HTTP服務器。
from logging.handlers import SMTPHandler: 將日誌發送到一個指定的郵件地址。

 

3、使用不一樣的日誌級別輸出日誌

最低級別:logger.setLevel爲設置logger的最低日誌級別,若是handler中也設置了級別,則不能低於這個級別,低於這個級別的設置是無效的。

不一樣級別的意義:在開發或者部署應用程序時,須要儘量詳盡的信息來進行開發和調試,這時候用的比較多的是來自DEBUG和INFO級別的日誌信息,可是到了正式上線或者生產環境時,應該使用WARNING和CRITICAL級別的日誌信息以下降機器的I/O壓力和提升獲取到錯誤信息的效率。

日誌量:日誌的信息量應該是與日誌級別成反比的:DEBUG>INFO>WARNING>ERROR>CRITICAL。

不一樣級別的設置:只有日誌級別大於或等於指定日誌級別的日誌纔會被輸出,因此在程序中能夠設置一個比較高的日誌級別,好比WARNING,開發和調試的時候修改成DEBUG或INFO以便獲得更多的日誌信息,正式上線的時候這些日誌仍是保持WARNING的級別。

 

4、輸出traceback.format_exc異常信息

能夠在打印指定信息的同時在下一行打印出traceback.format_exc異常信息,如:logger.error(msg, exc_info=True)。若是指定了exc_info爲True來打印錯誤消息,可是沒有發生錯誤的話,就會在消息的下一行打印「NoneType: None」。

注:對於參數exc_info,其實info,debug等函數也有這個關鍵字參數,可是不推薦使用這些低級別的打印函數來打印錯誤消息。

 

5、配置日誌:配置文件和字典配置

參考文章:http://www.cnblogs.com/yyds/p/6885182.html

配置文件方式:將配置文件使用特定規則配置好對應section和option,而後經過logging.config.fileConfig函數加載並使用文件中的配置信息。

 配置文件配置logging,簡單示例:

以下爲配置文件log_cfg.ini的配置內容

[loggers]
keys=root, console

[handlers]
keys=consolehandler, filehandler

[formatters]
keys=consoleformatter, fileformatter

[logger_root]
level=DEBUG
handlers=filehandler

[logger_console]
level=WARNING
handlers=consolehandler
qualname=console
propagate=0

[handler_filehandler]
class=FileHandler
args=('log/sys.log', )
level=WARNING
formatter=fileformatter

[handler_consolehandler]
class=logging.StreamHandler
args=(sys.stdout, )
level=DEBUG
formatter=consoleformatter

[formatter_fileformatter]
format=%(asctime)s[%(name)s][%(levelname)s]: %(message)s

[formatter_consoleformatter]
format=%(asctime)s[%(levelname)s]: %(message)s

以下爲py代碼

# -*- coding:utf-8 -*-
import logging.config

# 加載配置文件
logging.config.fileConfig('log_cfg.ini')

# 獲取配置好的logger
logger = logging.getLogger('console')

logger.debug('This is a debug message!')
logger.info('This is a info message!')
logger.warning('This is a warning message!')
logger.error('This is a error message!')
logger.critical('This is a critical message!')

# 獲取沒有配置的logger時,會自動使用root中配置的Handler
any_logger = logging.getLogger('any')
any_logger.critical('This is a test message!')

運行結果:

以下爲sys.log文件內容

2019-04-21 21:39:43,029[any][CRITICAL]: This is a test message!

以下爲控制檯輸出

2019-04-21 21:39:43,029[WARNING]: This is a warning message!
2019-04-21 21:39:43,029[ERROR]: This is a error message!
2019-04-21 21:39:43,029[CRITICAL]: This is a critical message!

 

配置規則:

  • loggers:此項爲必須配置的section,並使用keys來指定須要配置的logger。keys中指定的logger必需要在文件中有對應的配置,且keys中必須包含root這個logger,但相反,已配置的logger能夠不用在loggers下指定,能夠在須要使用的使用再添加上去。
  • handlers:此項爲必須配置的section,並使用keys來指定須要配置的handler。keys中指定的handler必需要在文件中有對應的配置,但相反,已配置的handler能夠不用在handlers下指定,能夠在須要使用的使用再添加上去。
  • formatters:此項爲必須配置的section,並使用keys來指定須要配置的formatter。keys中指定的formatter必需要在文件中有對應的配置,但相反,已配置的formatter能夠不用在formatters下指定,能夠在須要使用的使用再添加上去。
  • logger:命名方式爲logger_xxx,xxx爲在loggers中指定的logger名稱。對於root這個logger,必須指定level和handlers這兩個option,而對於非root的其餘logger,除了level和handlers必須配置外,還須要配置qualname,代碼中使用logging.getLogger()時讀取的就是qualname這個option。另外對於非root的logger,還能夠指定propagate(可選項),其默認值爲1,表示會將消息傳遞給父logger的handler進行處理,也能夠設置爲0,表示不往上層傳遞消息。
  • handler:命名方式爲handler_xxx,xxx爲在handlers中指定的handler名稱,class和args爲必須配置的option,表示建立handler的類和對應的初始化參數,class能夠是相對於logging模塊的類,也能夠是直接使用import進行導入的類,args則必須是元組格式,哪怕只有一個參數也要是元組的格式。level和formatter爲可選的option,且指定的formatter也必定要是在formatters和formatter中已配置好的。
  • formatter:命名方式爲formatter_xxx,xxx爲在formatters中指定的formatter名稱,這個section下的option都是可選的,便可以不配置任何option項。可是通常會配置format,用於指定輸出字符串的格式,也能夠指定datefmt,用於指定輸出的時間字符串格式,默認爲%Y-%m-%d %H:%M:%S。
  • 獲取沒有配置的logger:若是代碼中獲取的是沒有配置的logger,則會默認以root的handler來進行處理。

 

字典配置方式:將字典或者類字典的配置文件(如JSON格式的配置文件或者YAML格式的配置文件)使用特定規則配置好對應key和value,經過logging.config.dictConfig函數加載並使用裏面的配置信息。這裏須要說明的是,dictConfig只須要一個能夠解析成字典的對象便可,能夠是Python的字典或者其餘配置文件,只要能構建出這個字典就行。

以YAML格式的文件來配置字典方式配置logging,簡單示例:

以下爲配置文件log_cfg.yml的配置內容

version: 1
root:
    level: DEBUG
    handlers: [filehandler, ]
loggers:
    console:
        level: WARNING
        handlers: [consolehandler, ]
        propagate: no
handlers:
    filehandler:
        class: logging.FileHandler
        filename: log/sys.log
        level: WARNING
        formatter: fileformatter
    consolehandler:
        class: logging.StreamHandler
        stream: ext://sys.stdout
        level: DEBUG
        formatter: consoleformatter
formatters:
    fileformatter:
        format: '%(asctime)s[%(name)s][%(levelname)s]: %(message)s'
    consoleformatter:
        format: '%(asctime)s[%(levelname)s]: %(message)s'

以下爲py代碼

# -*- coding:utf-8 -*-
import logging.config
import yaml

# 加載配置文件
with open('log_cfg.yml') as log_cfg_file:
    log_dictcfg = yaml.safe_load(log_cfg_file)

logging.config.dictConfig(log_dictcfg)

# 獲取配置好的logger
logger = logging.getLogger('console')

logger.debug('This is a debug message!')
logger.info('This is a info message!')
logger.warning('This is a warning message!')
logger.error('This is a error message!')
logger.critical('This is a critical message!')

# 獲取沒有配置的logger時,會自動使用root中配置的Handler,若是沒有配置root,則會使用默認的root
any_logger = logging.getLogger('any')
any_logger.critical('This is a test message!')

 

輸出結果:同上一個配置文件方式的示例的輸出是相同的。

配置規則:

  • version:必選項,表示配置規則的版本號,不過可用值只有1。
  • root:可選項,用於配置root,若是沒有配置,則會自動建立一個默認的root。
  • loggers:可選項,用於配置logger,這裏propagate能夠配置爲yes/no,也能夠配置爲1/0、True/False等,固然也能夠不配置,默認爲yes下的效果。
  • handlers:可選項,用於配置handler,配置handler時,除了class爲必選項外,其餘的都是可選項,且class的值必須是能import的,不能直接是FileHandler這種相對於logging模塊的路徑。另外,每一個handler的配置字典中,除了class、level、formatter和filters外,其餘的配置信息會傳給class對應類的初始化函數中,好比上面配置文件中的filename和stream。
  • formatters:可選項,用於配置formatter,能夠配置format和datefmt等信息。
  • 外部對象訪問:外部對象是指不能直接進行訪問,須要import導入的對象,這時候須要加一個ext://前綴以便識別,而後系統就會import這個前綴後面的內容。

 

6、日誌中輸出額外信息:日誌傳參、LoggerAdapter

參考文章:http://www.cnblogs.com/yyds/p/6897964.html

直接傳參,簡單示例:

ip = '127.0.0.1'
username = 'Jason'
# 第一個參數爲格式化消息字符串,第二個及以後的參數爲對應的變量
logger.warning('[%s][%s]This is a warning message!', ip, username)

 

使用extra參數,簡單示例:

logger = logging.getLogger()
logger.setLevel(logging.DEBUG)

console_handler = logging.StreamHandler()
console_handler.setLevel(logging.WARNING)
# 在formatter中定義好須要額外傳遞參數,且在以後的日誌輸出必須給出這些額外參數的字典值
console_formatter = logging.Formatter('%(asctime)s[%(ip)s][%(username)s]: %(message)s')
console_handler.setFormatter(console_formatter)

logger.addHandler(console_handler)

# 將對應的字典信息傳給extra
extra = {'ip': '127.0.0.1', 'username': 'Jason'}
logger.warning('This is a warning message!', extra=extra)
# 若是不傳入extra參數,則會報錯
# logger.warning('This is a warning message!')

 

使用LoggerAdapter設置默認的extra參數,簡單示例: 

# -*- coding:utf-8 -*-
import logging


class IpUserLoggerAdapter(logging.LoggerAdapter):
    # 原process方法就兩行代碼,即便用默認的extra值
    # 重寫process方法,若是沒有傳入extra,才使用默認值
    def process(self, msg, kwargs):
        if 'extra' not in kwargs:
            kwargs['extra'] = self.extra

        return msg, kwargs


def get_ipuser_logger():
    logger = logging.getLogger()
    logger.setLevel(logging.DEBUG)

    console_handler = logging.StreamHandler()
    console_handler.setLevel(logging.WARNING)
    console_formatter = logging.Formatter('%(asctime)s[%(ip)s][%(user)s][%(levelname)s]: %(message)s')
    console_handler.setFormatter(console_formatter)

    logger.addHandler(console_handler)

    # 設置默認ip和user,這裏的key要和formatter中的key對應
    local_extra = {
        'ip': '127.0.0.1',
        'user': 'Jason'
    }
    return IpUserLoggerAdapter(logger, local_extra)


if __name__ == '__main__':
    ipuser_logger = get_ipuser_logger()
    ipuser_logger.debug('This is a debug message!')
    ipuser_logger.info('This is a info message!')
    ipuser_logger.warning('This is a warning message!')
    # 打印一個臨時的ip和user
    ipuser_logger.error('This is a error message!', extra={'ip': '0.0.0.0', 'user': 'anyone'})
    ipuser_logger.critical('This is a critical message!')

 

運行後,控制檯輸出爲:

2019-04-24 21:46:44,433[127.0.0.1][Jason][WARNING]: This is a warning message!
2019-04-24 21:46:44,433[0.0.0.0][anyone][ERROR]: This is a error message!
2019-04-24 21:46:44,433[127.0.0.1][Jason][CRITICAL]: This is a critical message!

 

 

7、子logger和共享配置

子logger在logging模塊中,logger是相似於樹的層級結構,父logger和子logger之間使用點號「.」鏈接和識別,如:logging.getLogger('root'),logging.getLogger('root.child'),logging.getLogger('root.child.grand')……

配置共享:子logger擁有父logger相同的配置,若是在子logger中又另外增長了一些配置,那麼子logger不只擁有父logger的配置,也有本身新增的配置,不過須要注意的是就算子logger新增的配置信息與父logger相同(好比文件),也會執行爲它單獨執行一次,即父logger和子logger的配置互不影響,而且子logger輸出一條日誌時會先執行子logger的配置,再執行父logger的配置。

隔離父looger:logger中有一個屬性propagate,默認爲True,即子logger會共享父logger的配置,能夠手動設置爲False,這樣子logger執行完後就不會再去執行父logger的配置了。

全局使用:在程序運行過程當中,若是想要使用以前使用的logger,不須要每次都運行一次對應的配置,直接使用logging.getLogger(name)獲得對應名稱的logger便可。

注:在使用logging.getLogger(name=None)時若是沒有傳入logger的名稱name,那麼會自動返回名爲root的logger。

簡單示例:

# -*- coding:utf-8 -*-
import logging
import datetime

# 建立一個父logger
logger = logging.getLogger('main')
logger.setLevel(logging.INFO)

# 給父logger添加一個文件輸出的Handler
log_file = 'log/sys_%s.log' % datetime.datetime.strftime(datetime.datetime.now(), '%Y-%m-%d')
file_handler = logging.FileHandler(log_file)
file_handler.setLevel(logging.WARNING)
log_formatter = logging.Formatter('%(asctime)s[%(name)s][%(levelname)s]: %(message)s')
file_handler.setFormatter(log_formatter)
logger.addHandler(file_handler)

# 向文件輸出一條日誌
logger.error('This is a error message!')

# 建立一個子logger
child_logger = logging.getLogger('main.child')
# 給子logger添加一個輸出到控制檯的Handler
stream_handler = logging.StreamHandler()
stream_handler.setLevel(logging.WARNING)
child_logger.addHandler(stream_handler)
# 設置propagate屬性爲False能夠隔離父logger的配置
# child_logger.propagate = False
# 同時向文件和控制檯輸出一條日誌 child_logger.warning('This is a warning message!')

運行後,日誌文件sys_2019-04-16.log文件內容以下:

2019-04-16 00:09:53,154[main][ERROR]: This is a error message!
2019-04-16 00:09:53,154[main.child][WARNING]: This is a warning message!

控制檯打印的內容以下:

This is a warning message!
相關文章
相關標籤/搜索