Python日志庫logging總結(jié)

在部署項目時豆拨,不可能直接將所有的信息都輸出到控制臺中摔认,我們可以將這些信息記錄到日志文件中,這樣不僅方便我們查看程序運行時的情況无畔,也可以在項目出現(xiàn)故障時根據(jù)運行時產(chǎn)生的日志快速定位問題出現(xiàn)的位置啊楚。

1、日志級別

Python 標準庫 logging 用作記錄日志浑彰,默認分為六種日志級別(括號為級別對應(yīng)的數(shù)值)恭理,NOTSET(0)、DEBUG(10)郭变、INFO(20)颜价、WARNING(30)、ERROR(40)诉濒、CRITICAL(50)周伦。我們自定義日志級別時注意不要和默認的日志級別數(shù)值相同,logging 執(zhí)行時輸出大于等于設(shè)置的日志級別的日志信息未荒,如設(shè)置日志級別是 INFO专挪,則 INFO、WARNING片排、ERROR寨腔、CRITICAL 級別的日志都會輸出。

2率寡、logging 流程

官方的 logging 模塊工作流程圖如下:

從下圖中我們可以看出看到這幾種 Python 類型迫卢,LoggerLogRecord冶共、Filter乾蛤、HandlerFormatter捅僵。

類型說明:

Logger:日志家卖,暴露函數(shù)給應(yīng)用程序,基于日志記錄器和過濾器級別決定哪些日志有效命咐。

LogRecord :日志記錄器篡九,將日志傳到相應(yīng)的處理器處理。

Handler :處理器, 將(日志記錄器產(chǎn)生的)日志記錄發(fā)送至合適的目的地醋奠。

Filter :過濾器, 提供了更好的粒度控制,它可以決定輸出哪些日志記錄。

Formatter:格式化器, 指明了最終輸出中日志記錄的布局伊佃。

  1. 判斷 Logger 對象對于設(shè)置的級別是否可用窜司,如果可用,則往下執(zhí)行航揉,否則塞祈,流程結(jié)束。

  2. 創(chuàng)建 LogRecord 對象帅涂,如果注冊到 Logger 對象中的 Filter 對象過濾后返回 False议薪,則不記錄日志尤蛮,流程結(jié)束,否則斯议,則向下執(zhí)行产捞。

  3. LogRecord 對象將 Handler 對象傳入當前的 Logger 對象,(圖中的子流程)如果 Handler 對象的日志級別大于設(shè)置的日志級別哼御,再判斷注冊到 Handler 對象中的 Filter 對象過濾后是否返回 True 而放行輸出日志信息坯临,否則不放行,流程結(jié)束恋昼。

  4. 如果傳入的 Handler 大于 Logger 中設(shè)置的級別看靠,也即 Handler 有效,則往下執(zhí)行液肌,否則挟炬,流程結(jié)束。

  5. 判斷這個 Logger 對象是否還有父 Logger 對象嗦哆,如果沒有(代表當前 Logger 對象是最頂層的 Logger 對象 root Logger)辟宗,流程結(jié)束。否則將 Logger 對象設(shè)置為它的父 Logger 對象吝秕,重復(fù)上面的
    3泊脐、4 兩步,輸出父類 Logger 對象中的日志輸出烁峭,直到是 root Logger 為止容客。

3、日志輸出格式

日志的輸出格式可以認為設(shè)置约郁,默認格式為下圖所示缩挑。

4、基本使用

logging 使用非常簡單鬓梅,使用 basicConfig() 方法就能滿足基本的使用需要供置,如果方法沒有傳入?yún)?shù),會根據(jù)默認的配置創(chuàng)建Logger 對象绽快,默認的日志級別被設(shè)置為 WARNING芥丧,默認的日志輸出格式如上圖,該函數(shù)可選的參數(shù)如下表所示坊罢。

參數(shù)名稱 參數(shù)描述
filename 日志輸出到文件的文件名
filemode 文件模式续担,r[+]、w[+]活孩、a[+]
format 日志輸出的格式
datefat 日志附帶日期時間的格式
style 格式占位符物遇,默認為 "%" 和 “{}”
level 設(shè)置日志輸出級別
stream 定義輸出流,用來初始化 StreamHandler 對象,不能 filename 參數(shù)一起使用询兴,否則會ValueError 異常
handles 定義處理器乃沙,用來創(chuàng)建 Handler 對象,不能和 filename 诗舰、stream 參數(shù)一起使用警儒,否則也會拋出 ValueError 異常

示例代碼如下:

import logging

logging.basicConfig()
logging.debug('This is a debug message')
logging.info('This is an info message')
logging.warning('This is a warning message')
logging.error('This is an error message')
logging.critical('This is a critical message')
復(fù)制代碼

輸出結(jié)果如下:

WARNING:root:This is a warning message
ERROR:root:This is an error message
CRITICAL:root:This is a critical message
復(fù)制代碼

傳入常用的參數(shù),示例代碼如下(這里日志格式占位符中的變量放到后面介紹):

import logging

logging.basicConfig(filename="test.log", filemode="w", format="%(asctime)s %(name)s:%(levelname)s:%(message)s", datefmt="%d-%m-%Y %H:%M:%S", level=logging.DEBUG)
logging.debug('This is a debug message')
logging.info('This is an info message')
logging.warning('This is a warning message')
logging.error('This is an error message')
logging.critical('This is a critical message')
復(fù)制代碼

生成的日志文件 test.log 始衅,內(nèi)容如下:

13-10-18 21:10:32 root:DEBUG:This is a debug message
13-10-18 21:10:32 root:INFO:This is an info message
13-10-18 21:10:32 root:WARNING:This is a warning message
13-10-18 21:10:32 root:ERROR:This is an error message
13-10-18 21:10:32 root:CRITICAL:This is a critical message
復(fù)制代碼

但是當發(fā)生異常時冷蚂,直接使用無參數(shù)的 debug()、info()汛闸、warning()蝙茶、error()、critical() 方法并不能記錄異常信息诸老,需要設(shè)置 exc_info 參數(shù)為 True 才可以隆夯,或者使用 exception() 方法,還可以使用 log() 方法别伏,但還要設(shè)置日志級別和 exc_info 參數(shù)蹄衷。

import logging

logging.basicConfig(filename="test.log", filemode="w", format="%(asctime)s %(name)s:%(levelname)s:%(message)s", datefmt="%d-%M-%Y %H:%M:%S", level=logging.DEBUG)
a = 5
b = 0
try:
    c = a / b
except Exception as e:
    # 下面三種方式三選一,推薦使用第一種
    logging.exception("Exception occurred")
    logging.error("Exception occurred", exc_info=True)
    logging.log(level=logging.DEBUG, msg="Exception occurred", exc_info=True)
復(fù)制代碼

5厘肮、自定義 Logger

上面的基本使用可以讓我們快速上手 logging 模塊愧口,但一般并不能滿足實際使用,我們還需要自定義 Logger类茂。

一個系統(tǒng)只有一個 Logger 對象耍属,并且該對象不能被直接實例化,沒錯巩检,這里用到了單例模式厚骗,獲取 Logger 對象的方法為 getLogger

注意:這里的單例模式并不是說只有一個 Logger 對象兢哭,而是指整個系統(tǒng)只有一個根 Logger 對象领舰,Logger 對象在執(zhí)行 info()、error() 等方法時實際上調(diào)用都是根 Logger 對象對應(yīng)的 info()迟螺、error() 等方法冲秽。

我們可以創(chuàng)造多個 Logger 對象,但是真正輸出日志的是根 Logger 對象煮仇。每個 Logger 對象都可以設(shè)置一個名字劳跃,如果設(shè)置logger = logging.getLogger(__name__)name 是 Python 中的一個特殊內(nèi)置變量浙垫,他代表當前模塊的名稱(默認為 main)。則 Logger 對象的 name 為建議使用使用以點號作為分隔符的命名空間等級制度。

Logger 對象可以設(shè)置多個 Handler 對象和 Filter 對象夹姥,Handler 對象又可以設(shè)置 Formatter 對象杉武。Formatter 對象用來設(shè)置具體的輸出格式,常用變量格式如下表所示辙售,所有參數(shù)見 Python(3.7)官方文檔

變量 格式 變量描述
asctime %(asctime)s 將日志的時間構(gòu)造成可讀的形式轻抱,默認情況下是精確到毫秒,如 2018-10-13 23:24:57,832旦部,可以額外指定 datefmt 參數(shù)來指定該變量的格式
name %(name) 日志對象的名稱
filename %(filename)s 不包含路徑的文件名
pathname %(pathname)s 包含路徑的文件名
funcName %(funcName)s 日志記錄所在的函數(shù)名
levelname %(levelname)s 日志的級別名稱
message %(message)s 具體的日志信息
lineno %(lineno)d 日志記錄所在的行號
pathname %(pathname)s 完整路徑
process %(process)d 當前進程ID
processName %(processName)s 當前進程名稱
thread %(thread)d 當前線程ID
threadName %threadName)s 當前線程名稱

Logger 對象和 Handler 對象都可以設(shè)置級別祈搜,而默認 Logger 對象級別為 30 ,也即 WARNING士八,默認 Handler 對象級別為 0容燕,也即 NOTSET。logging 模塊這樣設(shè)計是為了更好的靈活性婚度,比如有時候我們既想在控制臺中輸出DEBUG 級別的日志蘸秘,又想在文件中輸出WARNING級別的日志』茸拢可以只設(shè)置一個最低級別的 Logger 對象醋虏,兩個不同級別的 Handler 對象,示例代碼如下:

import logging
import logging.handlers

logger = logging.getLogger("logger")

handler1 = logging.StreamHandler()
handler2 = logging.FileHandler(filename="test.log")

logger.setLevel(logging.DEBUG)
handler1.setLevel(logging.WARNING)
handler2.setLevel(logging.DEBUG)

formatter = logging.Formatter("%(asctime)s %(name)s %(levelname)s %(message)s")
handler1.setFormatter(formatter)
handler2.setFormatter(formatter)

logger.addHandler(handler1)
logger.addHandler(handler2)

# 分別為 10哮翘、30颈嚼、30
# print(handler1.level)
# print(handler2.level)
# print(logger.level)

logger.debug('This is a customer debug message')
logger.info('This is an customer info message')
logger.warning('This is a customer warning message')
logger.error('This is an customer error message')
logger.critical('This is a customer critical message')
復(fù)制代碼

控制臺輸出結(jié)果為:

2018-10-13 23:24:57,832 logger WARNING This is a customer warning message
2018-10-13 23:24:57,832 logger ERROR This is an customer error message
2018-10-13 23:24:57,832 logger CRITICAL This is a customer critical message
復(fù)制代碼

文件中輸出內(nèi)容為:

2018-10-13 23:44:59,817 logger DEBUG This is a customer debug message
2018-10-13 23:44:59,817 logger INFO This is an customer info message
2018-10-13 23:44:59,817 logger WARNING This is a customer warning message
2018-10-13 23:44:59,817 logger ERROR This is an customer error message
2018-10-13 23:44:59,817 logger CRITICAL This is a customer critical message
復(fù)制代碼

創(chuàng)建了自定義的 Logger 對象,就不要在用 logging 中的日志輸出方法了饭寺,這些方法使用的是默認配置的 Logger 對象阻课,否則會輸出的日志信息會重復(fù)。

import logging
import logging.handlers

logger = logging.getLogger("logger")
handler = logging.StreamHandler()
handler.setLevel(logging.DEBUG)
formatter = logging.Formatter("%(asctime)s %(name)s %(levelname)s %(message)s")
handler.setFormatter(formatter)
logger.addHandler(handler)
logger.debug('This is a customer debug message')
logging.info('This is an customer info message')
logger.warning('This is a customer warning message')
logger.error('This is an customer error message')
logger.critical('This is a customer critical message')
復(fù)制代碼

輸出結(jié)果如下(可以看到日志信息被輸出了兩遍):

2018-10-13 22:21:35,873 logger WARNING This is a customer warning message
WARNING:logger:This is a customer warning message
2018-10-13 22:21:35,873 logger ERROR This is an customer error message
ERROR:logger:This is an customer error message
2018-10-13 22:21:35,873 logger CRITICAL This is a customer critical message
CRITICAL:logger:This is a customer critical message
復(fù)制代碼

說明:在引入有日志輸出的 python 文件時佩研,如 import test.py柑肴,在滿足大于當前設(shè)置的日志級別后就會輸出導(dǎo)入文件中的日志。

6旬薯、Logger 配置

通過上面的例子晰骑,我們知道創(chuàng)建一個 Logger 對象所需的配置了,上面直接硬編碼在程序中配置對象绊序,配置還可以從字典類型的對象和配置文件獲取硕舆。打開 logging.config Python 文件,可以看到其中的配置解析轉(zhuǎn)換函數(shù)骤公。

從字典中獲取配置信息:

import logging.config

config = {
    'version': 1,
    'formatters': {
        'simple': {
            'format': '%(asctime)s - %(name)s - %(levelname)s - %(message)s',
        },
        # 其他的 formatter
    },
    'handlers': {
        'console': {
            'class': 'logging.StreamHandler',
            'level': 'DEBUG',
            'formatter': 'simple'
        },
        'file': {
            'class': 'logging.FileHandler',
            'filename': 'logging.log',
            'level': 'DEBUG',
            'formatter': 'simple'
        },
        # 其他的 handler
    },
    'loggers':{
        'StreamLogger': {
            'handlers': ['console'],
            'level': 'DEBUG',
        },
        'FileLogger': {
            # 既有 console Handler抚官,還有 file Handler
            'handlers': ['console', 'file'],
            'level': 'DEBUG',
        },
        # 其他的 Logger
    }
}

logging.config.dictConfig(config)
StreamLogger = logging.getLogger("StreamLogger")
FileLogger = logging.getLogger("FileLogger")
# 省略日志輸出
復(fù)制代碼

從配置文件中獲取配置信息:

常見的配置文件有 ini 格式、yaml 格式阶捆、JSON 格式凌节,或者從網(wǎng)絡(luò)中獲取都是可以的钦听,只要有相應(yīng)的文件解析器解析配置即可,下面只展示了 ini 格式和 yaml 格式的配置倍奢。

test.ini 文件

[loggers]
keys=root,sampleLogger

[handlers]
keys=consoleHandler

[formatters]
keys=sampleFormatter

[logger_root]
level=DEBUG
handlers=consoleHandler

[logger_sampleLogger]
level=DEBUG
handlers=consoleHandler
qualname=sampleLogger
propagate=0

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

[formatter_sampleFormatter]
format=%(asctime)s - %(name)s - %(levelname)s - %(message)s

復(fù)制代碼

testinit.py 文件

import logging.config

logging.config.fileConfig(fname='test.ini', disable_existing_loggers=False)
logger = logging.getLogger("sampleLogger")
# 省略日志輸出
復(fù)制代碼

test.yaml 文件

version: 1
formatters:
  simple:
    format: '%(asctime)s - %(name)s - %(levelname)s - %(message)s'
handlers:
  console:
    class: logging.StreamHandler
    level: DEBUG
    formatter: simple

loggers:
  simpleExample:
    level: DEBUG
    handlers: [console]
    propagate: no
root:
  level: DEBUG
  handlers: [console]
復(fù)制代碼

testyaml.py 文件

import logging.config
# 需要安裝 pyymal 庫
import yaml

with open('test.yaml', 'r') as f:
    config = yaml.safe_load(f.read())
    logging.config.dictConfig(config)

logger = logging.getLogger("sampleLogger")
# 省略日志輸出
復(fù)制代碼

7朴上、實戰(zhàn)中的問題

1、中文亂碼

上面的例子中日志輸出都是英文內(nèi)容卒煞,發(fā)現(xiàn)不了將日志輸出到文件中會有中文亂碼的問題痪宰,如何解決到這個問題呢?FileHandler 創(chuàng)建對象時可以設(shè)置文件編碼畔裕,如果將文件編碼設(shè)置為 “utf-8”(utf-8 和 utf8 等價)衣撬,就可以解決中文亂碼問題啦。一種方法是自定義 Logger 對象扮饶,需要寫很多配置具练,另一種方法是使用默認配置方法 basicConfig(),傳入 handlers 處理器列表對象贴届,在其中的 handler 設(shè)置文件的編碼靠粪。網(wǎng)上很多都是無效的方法,關(guān)鍵參考代碼如下:

# 自定義 Logger 配置
handler = logging.FileHandler(filename="test.log", encoding="utf-8")
復(fù)制代碼
# 使用默認的 Logger 配置
logging.basicConfig(handlers=[logging.FileHandler("test.log", encoding="utf-8")], level=logging.DEBUG)
復(fù)制代碼

2毫蚓、臨時禁用日志輸出

有時候我們又不想讓日志輸出占键,但在這后又想輸出日志。如果我們打印信息用的是 print() 方法元潘,那么就需要把所有的 print() 方法都注釋掉畔乙,而使用了 logging 后,我們就有了一鍵開關(guān)閉日志的 "魔法"翩概。一種方法是在使用默認配置時牲距,給 logging.disabled() 方法傳入禁用的日志級別,就可以禁止設(shè)置級別以下的日志輸出了钥庇,另一種方法時在自定義 Logger 時牍鞠,Logger 對象的 disable 屬性設(shè)為 True,默認值是 False评姨,也即不禁用难述。

logging.disable(logging.INFO)
復(fù)制代碼
logger.disabled = True
復(fù)制代碼

3、日志文件按照時間劃分或者按照大小劃分

如果將日志保存在一個文件中吐句,那么時間一長胁后,或者日志一多,單個日志文件就會很大嗦枢,既不利于備份攀芯,也不利于查看。我們會想到能不能按照時間或者大小對日志文件進行劃分呢文虏?答案肯定是可以的侣诺,并且還很簡單殖演,logging 考慮到了我們這個需求。logging.handlers 文件中提供了 TimedRotatingFileHandlerRotatingFileHandler 類分別可以實現(xiàn)按時間和大小劃分紧武。打開這個 handles 文件剃氧,可以看到還有其他功能的 Handler 類敏储,它們都繼承自基類 BaseRotatingHandler阻星。

# TimedRotatingFileHandler 類構(gòu)造函數(shù)
def __init__(self, filename, when='h', interval=1, backupCount=0, encoding=None, delay=False, utc=False, atTime=None):
# RotatingFileHandler 類的構(gòu)造函數(shù)
def __init__(self, filename, mode='a', maxBytes=0, backupCount=0, encoding=None, delay=False)
復(fù)制代碼

示例代碼如下:

# 每隔 1000 Byte 劃分一個日志文件,備份文件為 3 個
file_handler = logging.handlers.RotatingFileHandler("test.log", mode="w", maxBytes=1000, backupCount=3, encoding="utf-8")
復(fù)制代碼
# 每隔 1小時 劃分一個日志文件已添,interval 是時間間隔妥箕,備份文件為 10 個
handler2 = logging.handlers.TimedRotatingFileHandler("test.log", when="H", interval=1, backupCount=10)
復(fù)制代碼

Python 官網(wǎng)雖然說 logging 庫是線程安全的,但在多進程更舞、多線程畦幢、多進程多線程環(huán)境中仍然還有值得考慮的問題,比如缆蝉,如何將日志按照進程(或線程)劃分為不同的日志文件宇葱,也即一個進程(或線程)對應(yīng)一個文件。
總結(jié):Python logging 庫設(shè)計的真的非常靈活刊头,如果有特殊的需要還可以在這個基礎(chǔ)的 logging 庫上進行改進黍瞧,創(chuàng)建新的 Handler 類解決實際開發(fā)中的問題。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末原杂,一起剝皮案震驚了整個濱河市印颤,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌穿肄,老刑警劉巖年局,帶你破解...
    沈念sama閱讀 221,548評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異咸产,居然都是意外死亡矢否,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,497評論 3 399
  • 文/潘曉璐 我一進店門脑溢,熙熙樓的掌柜王于貴愁眉苦臉地迎上來僵朗,“玉大人,你說我怎么就攤上這事焚志∫旅裕” “怎么了?”我有些...
    開封第一講書人閱讀 167,990評論 0 360
  • 文/不壞的土叔 我叫張陵酱酬,是天一觀的道長壶谒。 經(jīng)常有香客問我,道長膳沽,這世上最難降的妖魔是什么汗菜? 我笑而不...
    開封第一講書人閱讀 59,618評論 1 296
  • 正文 為了忘掉前任让禀,我火速辦了婚禮,結(jié)果婚禮上陨界,老公的妹妹穿的比我還像新娘巡揍。我一直安慰自己,他們只是感情好菌瘪,可當我...
    茶點故事閱讀 68,618評論 6 397
  • 文/花漫 我一把揭開白布腮敌。 她就那樣靜靜地躺著,像睡著了一般俏扩。 火紅的嫁衣襯著肌膚如雪糜工。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,246評論 1 308
  • 那天录淡,我揣著相機與錄音捌木,去河邊找鬼。 笑死嫉戚,一個胖子當著我的面吹牛刨裆,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播彬檀,決...
    沈念sama閱讀 40,819評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼帆啃,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了凤覆?” 一聲冷哼從身側(cè)響起链瓦,我...
    開封第一講書人閱讀 39,725評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎盯桦,沒想到半個月后慈俯,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,268評論 1 320
  • 正文 獨居荒郊野嶺守林人離奇死亡拥峦,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,356評論 3 340
  • 正文 我和宋清朗相戀三年贴膘,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片略号。...
    茶點故事閱讀 40,488評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡刑峡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出玄柠,到底是詐尸還是另有隱情突梦,我是刑警寧澤,帶...
    沈念sama閱讀 36,181評論 5 350
  • 正文 年R本政府宣布羽利,位于F島的核電站宫患,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏这弧。R本人自食惡果不足惜娃闲,卻給世界環(huán)境...
    茶點故事閱讀 41,862評論 3 333
  • 文/蒙蒙 一贪惹、第九天 我趴在偏房一處隱蔽的房頂上張望贸桶。 院中可真熱鬧草慧,春花似錦监透、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,331評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至捌年,卻和暖如春瓢娜,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背礼预。 一陣腳步聲響...
    開封第一講書人閱讀 33,445評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留虏劲,地道東北人托酸。 一個月前我還...
    沈念sama閱讀 48,897評論 3 376
  • 正文 我出身青樓,卻偏偏與公主長得像柒巫,于是被迫代替她去往敵國和親励堡。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 45,500評論 2 359

推薦閱讀更多精彩內(nèi)容