Python 日誌功能詳解

Gevin發表於2016-10-26

本文首發於Gevin的部落格

原文連結:Python 日誌功能詳解

未經 Gevin 授權,禁止轉載

Python 日誌功能詳解

軟體開發中通過日誌記錄程式的執行情況是一個開發的好習慣,對於錯誤排查和系統運維都有很大幫助。Python標準庫自帶日誌模組,已經足夠強大,大部分情況下,python程式的日誌功能直接呼叫標準庫的日誌模組即可。《The Hitchhiker’s Guide to Python》已對“日誌”進行了詳細闡述,python的官方文件也對日誌做了說明,但Gevin依然感覺,通過這些英文資料,還不能讓初學者在短時間迅速掌握python日誌模組的使用,因此按照自己的思路,把相關內容做了如下整理,並附加一些Gevin認為文件中沒有說明清楚的內容。

1. 基本用法

如果開發輕量級的應用,對日誌的需求也比較簡單,直接參考如下示例,在相關程式碼邏輯中加入日誌功能即可:

#!/usr/local/bin/python
# -*- coding: utf-8 -*-

import logging

logging.debug('debug message')
logging.info('info message')
logging.warn('warn message')
logging.error('error message')
logging.critical('critical message')複製程式碼

output:

WARNING:root:warn message
ERROR:root:error message
CRITICAL:root:critical message複製程式碼

預設情況下,logging模組將日誌列印到螢幕上,日誌級別為WARNING(即只有日誌級別等於或高於WARNING的日誌資訊才會輸出),日誌格式為 warning level:instance name:warning message

1.1 將日誌記錄到檔案中

將日誌記錄到檔案中,只需在呼叫logging模組記錄日誌前,做個簡單的配置即可:

#!/usr/local/bin/python
# -*- coding: utf-8 -*-

import logging

# 配置日誌檔案和日誌級別
logging.basicConfig(filename='logger.log', level=logging.INFO)

logging.debug('debug message')
logging.info('info message')
logging.warn('warn message')
logging.error('error message')
logging.critical('critical message')複製程式碼

本樣例在記錄日誌前,通過logging.basicConfig做簡單配置,將日誌記錄到日誌檔案logger.log中,也修改了預設的日誌等級,高於或等於INFO級別的日誌資訊,都會記錄到日誌檔案中。

2. 更加完善的日誌功能

2.1 幾個關鍵概念

如果要更加靈活的使用日誌模組,首先要了解日誌模組是怎樣工作的。 LoggerHandlerFormatterFilter是日誌模組的幾個基本概念,日誌模組的工作原理要從這四個基本概念說起。

  • Logger 即記錄器,Logger提供了日誌相關功能的呼叫介面。
  • Handler 即處理器,將(記錄器產生的)日誌記錄傳送至合適的目的地。
  • Filter 即過濾器,提供了更好的粒度控制,它可以決定輸出哪些日誌記錄。
  • Formatter 即格式化器,指明瞭最終輸出中日誌記錄的格式。

2.1.1 Logger

Logger 即“記錄器”,Logger物件例項是日誌記錄功能的載體,如:

#!/usr/local/bin/python
# -*- coding: utf-8 -*-

import logging

logger = logging.getLogger('simple_example')

logger.debug('debug message')
logger.info('info message')
logger.warn('warn message')
logger.error('error message')
logger.critical('critical message')複製程式碼

值得一提的是,Logger物件從不直接例項化,而是通過模組級的功能logging.getLogger(name)建立Logger例項。呼叫 logging.getLogger(name) 功能時,如果傳入的name引數值相同,則總是返回同一個Logger物件例項的引用。

如果沒有顯式的進行建立,則預設建立一個root logger,並應用預設的日誌級別(WARN)、預設的處理器Handler(StreamHandler,即將日誌資訊列印輸出在標準輸出上),和預設的格式化器Formatter(預設的格式即為第一個簡單使用程式中輸出的格式)。

Logger類包含的成員和方法可以檢視官方文件

2.1.2 Handler

Handler 將日誌資訊傳送到設定的位置,可以通過Logger物件的addHandler()方法為Logger物件新增0個或多個handler。一種日誌的典型應用場景為,系統希望將所有的日誌資訊儲存到log檔案中,其中日誌等級等於或高於ERROR的訊息還要在螢幕標準輸出上顯示,日誌等級為CRITICAL的還需要傳送郵件通知;這種場景就需要3個獨立的handler來實現需求,這三個handler分別與指定的日誌等級或日誌位置做響應

需要一提的是,為Logger配置的handler不能是Handler基類物件,而是Handler的子類物件,常用的Handler為StreamHandler, FileHandler, 和NullHandler,Handler的全部子類及詳細介紹可以檢視官方文件相應頁面如果需要了解更多關於Handler的資訊,直接檢視官方文件即可。

2.1.3 Formatter

Formatter 用於設定日誌輸出的格式,與前兩個基本概念不同的是,該類可以直接初始化物件,即 formatter=logging.Formatter(fmt=None, datefmt=None),建立formatter時,傳入分別fmtdatefmt引數來修改日誌格式和時間格式,預設的日誌格式為%(asctime)s - %(levelname)s - %(message)s,預設的時間格式為%Y-%m-%d %H:%M:%S

2.1.4 Filter

Filter 可用於Logger物件或Handler物件,用於提供比日誌等級更加複雜的日誌過濾方式。預設的filter只允許在指定logger層級下的日誌訊息通過過濾。例如,如果把filter設定為filter=logging.Filter('A.B'),則logger ‘A.B’, ‘A.B.C’, ‘A.B.C.D’, ‘A.B.D’ 產生的日誌資訊可以通過過濾,但'A.BB', 'B.A.B'均不行。如果以空字串初始化filter,則所有的日誌訊息都可以通過過濾。

Filter在日誌功能配置中是非必須的,只在對日誌訊息過濾需求比較複雜時配置使用即可。

2.2 日誌產生流程

日誌產生的流程邏輯參考下圖即可:

Python 日誌功能詳解

2.3 日誌模組的使用

日誌模組使用的關鍵是“日誌的配置”,日誌配置好後,只要呼叫logger.INFO(), logger.ERROR()等方法即可建立日誌內容。

開發者可以通過三種方法配置日誌模組:

  1. 在Python程式碼中顯示建立loggers, handlers, formatters甚至filters,並呼叫這幾個物件中的各個配置函式來完成日誌配置
  2. 將配置資訊寫到配置檔案中,然後讀取配置檔案資訊來完成日誌配置
  3. 將配置資訊寫到一個Dict中,然後讀取這個配置字典來完成日誌配置

2.3.1 通過程式碼配置並使用日誌模組

通過程式碼配置日誌模組簡單方便,但如果需要修改配置時,需要改程式碼,因此不建議在大型專案中使用這種方法。

通過程式碼配置日誌模組可以很好的理解日誌模組的工作原理,用於學習,是一個很好的案例,因此Gevin也在下文中對此詳細介紹。

1. 建立Logger

import logging

# create logger
logger = logging.getLogger('simple_example')

# Set default log level
logger.setLevel(logging.DEBUG)複製程式碼

2. 建立Handler


# create console handler and set level to warn

ch = logging.StreamHandler()
ch.setLevel(logging.WARN)複製程式碼

3. 建立Fomatter

# create formatter
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')複製程式碼

4. 配置Logger

# add formatter to ch
ch.setFormatter(formatter)

# add ch to logger
# The final log level is the higher one between the default and the one in handler
logger.addHandler(ch)複製程式碼

5. 使用日誌模組

logger.debug('debug message')
logger.info('info message')
logger.warn('warn message')
logger.error('error message')
logger.critical('critical message')複製程式碼

6. 完整的例子


#!/usr/local/bin/python
# -*- coding: utf-8 -*-

import logging

# create logger
logger = logging.getLogger('simple_example')

# Set default log level
logger.setLevel(logging.DEBUG)


ch = logging.StreamHandler()
ch.setLevel(logging.WARN)

ch2 = logging.FileHandler('logging.log')
ch2.setLevel(logging.INFO)

# create formatter
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')

# add formatter to ch
ch.setFormatter(formatter)
ch2.setFormatter(formatter)

# add ch to logger
# The final log level is the higher one between the default and the one in handler
logger.addHandler(ch)
logger.addHandler(ch2)

# 'application' code
logger.debug('debug message')
logger.info('info message')
logger.warn('warn message')
logger.error('error message')
logger.critical('critical message')複製程式碼

2.3.2 通過配置檔案配置並使用日誌模組

通過配置檔案配置日誌模組時,配置檔案通常使用.ini格式,日誌模組需要呼叫fileConfig,即logging.config.fileConfig('logging_config.ini'),然後logger的使用方法與上面相同:


#!/usr/local/bin/python
# -*- coding: utf-8 -*-

import logging
import logging.config

logging.config.fileConfig('logging_config.ini')

# create logger
logger = logging.getLogger('root')

# 'application' code
logger.debug('debug message')
logger.info('info message')
logger.warn('warn message')
logger.error('error message')
logger.critical('critical message')複製程式碼

其中,logging_config.ini檔案內容如下:

[loggers]
keys=root,simpleExample

[handlers]
keys=consoleHandler

[formatters]
keys=simpleFormatter

[logger_root]
level=DEBUG
handlers=consoleHandler

[logger_simpleExample]
level=INFO
handlers=consoleHandler
qualname=simpleExample
propagate=0

[handler_consoleHandler]
class=StreamHandler
level=DEBUG
formatter=simpleFormatter
args=(sys.stdout,)

[formatter_simpleFormatter]
format=%(asctime)s - %(name)s - %(levelname)s - %(message)s複製程式碼

通過配置檔案配置日誌模組,邏輯與程式碼中配置一樣,也是把logger, handlerformatter定義好,然後組裝到一起即可,無非ini配置和程式碼配置時的語法不通而已,開發者在基於ini檔案配置日誌模組時,只要參考上面例子做相應修改即可。

2.3.3 通過Dict物件配置並使用日誌模組

基於Dict物件配置日誌模組在python中應用廣泛,很多Django或Flask專案都採用這種方式,但很多官方文件對這種方法介紹並不多,因此,本文提供一個使用樣例,以後開發中參考該樣例修改一下即可。


#!/usr/local/bin/python
# -*- coding: utf-8 -*-

import logging
import logging.config

config = {
    'version': 1,
    'formatters': {
        'simple': {
            'format': '%(asctime)s - %(name)s - %(levelname)s - %(message)s',
        },
    },
    'handlers': {
        'console': {
            'class': 'logging.StreamHandler',
            'level': 'DEBUG',
            'formatter': 'simple'
        },
        'file': {
            'class': 'logging.FileHandler',
            'filename': 'logging.log',
            'level': 'DEBUG',
            'formatter': 'simple'
        },
    },
    'loggers':{
        'root': {
            'handlers': ['console'],
            'level': 'DEBUG',
            # 'propagate': True,
        },
        'simple': {
            'handlers': ['console', 'file'],
            'level': 'WARN',
        }
    }
}

logging.config.dictConfig(config)


print 'logger:'
logger = logging.getLogger('root')

logger.debug('debug message')
logger.info('info message')
logger.warn('warn message')
logger.error('error message')
logger.critical('critical message')


print 'logger2:'
logger2 = logging.getLogger('simple')

logger2.debug('debug message')
logger2.info('info message')
logger2.warn('warn message')
logger2.error('error message')
logger2.critical('critical message')複製程式碼

注:

日誌的嚴重等級

Log Level如下,嚴重等級為NOTSET, DEBUG, INFO, WARNING, ERROR, CRITICAL, 嚴重程度依次遞增

CRITICAL: 50
ERROR: 40
WARNING: 30
INFO: 20
DEBUG: 10
NOTSET: 0複製程式碼

修改日誌訊息的格式

日誌的預設顯示格式為:%(asctime)s - %(name)s - %(levelname)s - %(message)s,如果只想顯示日誌等級和日誌資訊,可以把格式改為:%(levelname)s:%(message)s,想了解全部Formatter中的可用變數,請查閱LogRecord attributes

日期時間的預設格式是ISO8601,修改日期時間格式請參考 time.strftime()

相關文章