Python/logging

維基教科書,自由的教學讀本

Python 的 logging 模塊提供了標準的日誌接口,通過它可存儲各種格式的日誌。使用Logging模塊的主要好處是所有Python模塊都可以參與日誌記錄。

日誌級別等級排序:critical > error > warning > info > debug( notset 等同於 debug )

Logging 模塊提供了兩種日誌記錄方式:

  • 一種方式是使用 Logging 提供的模塊級別的函數
  • 另一種方式是使用 Logging 日誌系統的四大組件記錄

Logging 定義的模塊級別函數[編輯]

import logging

# 打印日志级别
def test_logging():
    logging.basicConfig(filename='F:/example.log', format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', datefmt='%m/%d/%Y %I:%M:%S %p',level=logging.DEBUG) #需要在开头就设置,在中间设置并无作用 可以设置 root 的日志级别和日志输出格式。
    logging.debug('Python debug')
    logging.info('Python info')
    logging.warning('Python warning')
    logging.error('Python Error')
    logging.critical('Python critical')

test_logging()

logging.basicConfig函數各參數:

*encoding:如'utf-8'
  • filename:指定日誌文件名;
  • filemode:和file函數意義相同,指定日誌文件的打開模式,'w'或者'a';
  • format:指定輸出的格式和內容,format可以輸出很多有用的信息,
    • %(levelno)s:打印日誌級別的數值
    • %(levelname)s:打印日誌級別的名稱
    • %(pathname)s:打印當前執行程序的路徑,其實就是sys.argv[0]
    • %(filename)s:打印當前執行程序名
    • %(funcName)s:打印日誌的當前函數
    • %(lineno)d:打印日誌的當前行號
    • %(asctime)s:打印日誌的時間
    • %(thread)d:打印線程ID
    • %(threadName)s:打印線程名稱
    • %(process)d:打印進程ID
    • %(message)s:打印日誌信息
  • datefmt:指定時間格式,同time.strftime();
  • level:設置日誌級別,默認為logging.WARNNING;
  • stream:指定將日誌的輸出流,可以指定輸出到sys.stderr,sys.stdout或者文件,默認輸出到sys.stderr,當stream和filename同時指定時,stream被忽略;

logging 模塊四大組件[編輯]

  • 日誌器(logger)需要通過處理器(handler)將日誌信息輸出到目標位置,不同的處理器(handler)可以將日誌輸出到不同的位置;
  • 日誌器(logger)可以設置多個處理器(handler)將同一條日誌記錄輸出到不同的位置;在當前 Logger 對象中查找 Handlers,如果找不到任何 Handler,則往上到該 Logger 對象的父 Logger 中查找;如果找到一個或多個 Handler,則依次用 Handler 來處理日誌信息。但在每個 Handler 處理日誌信息過程中,會首先判斷日誌信息的等級是否大於該 Handler 的等級,如果大於,則往下執行(由 Logger 對象進入 Handler 對象中),否則,處理流程結束。
  • 每個處理器(handler)都可以設置自己的過濾器(filter)實現日誌過濾,從而只保留感興趣的日誌;只要有一個過濾器返回假,則過濾結束,且該日誌信息將丟棄,不再處理,而處理流程也至此結束。
  • 每個處理器(handler)都可以設置自己的格式器(formatter)實現同一條日誌以不同的格式輸出到不同的地方。

第一次導入 logging 模塊或使用 reload 函數重新導入 logging 模塊,logging 模塊中的代碼將被執行,這個過程中將產生 logging 日誌系統的默認配置。

可選的自定義配置logging標準模塊方式:

  • dictConfig 是通過一個字典進行配置 Logger,Handler,Filter,Formatter;
  • fileConfig 則是通過一個文件進行配置;
  • listen 則監聽一個網絡端口,通過接收網絡數據來進行配置。
  • 也可以直接調用 Logger,Handler 等對象中的方法在代碼中來顯式配置。

一個簡單示例:

import logging
logger = logging.getLogger(__name__)
logger.setLevel(level = logging.INFO)
handler = logging.FileHandler("log.txt")
handler.setLevel(logging.INFO)
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
handler.setFormatter(formatter)

console = logging.StreamHandler()#将日志同时输出到屏幕和日志文件
console.setLevel(logging.INFO)
 
logger.addHandler(handler)
logger.addHandler(console)
 
logger.info("Start print log")
logger.debug("Do something")
logger.warning("Something maybe fail.")
logger.info("Finish")

日誌器- Logger[編輯]

Logger 持有日誌記錄器的方法,日誌記錄器不直接實例化,而是通過模塊級函數 logger.getlogger (name) 來實例化,使用相同的名稱多次調用 getLogger() 總是會返回對相同 Logger 對象的引用。

getLogger() 方法後面最好加上所要日誌記錄的模塊名字,配置文件和打印日誌格式中的 %(name)s 對應的是這裡的模塊名字,如果不指定name則返回root對象。

logger.setLevel(logging.DEBUG),Logging 中有 NOTSET < DEBUG < INFO < WARNING < ERROR < CRITICAL這幾種級別,日誌會記錄設置級別以上的日誌

logger.addHandler(handler_name) # 為 Logger 實例增加一個處理器

logger.removeHandler(handler_name) # 為 Logger 實例刪除一個處理器

使用 Logger 對象中的 debug,info,error,warn,critical 等方法記錄日誌信息。

首先在主模塊定義了logger叫做'mainModule',並對它進行了配置。在解釋器進程裡面的其他地方通過getLogger('mainModule')得到的logger對象都是一樣的,不需要重新配置,可以直接使用。定義的該logger的子logger,都可以共享父logger的定義和配置。所謂的父子logger是通過命名來識別,任意以'mainModule'開頭的logger都是它的子logger,例如'mainModule.sub'。

處理器- Handler[編輯]

Handler 處理器類型有很多種,比較常用的有三個,StreamHandler,FileHandler,NullHandler

創建方法:sh = logging.StreamHandler(stream=None)

創建 StreamHandler 之後,可以通過使用以下方法設置日誌級別,設置格式化器 Formatter,增加或刪除過濾器 Filter:

ch.setLevel(logging.WARN) # 指定日志级别,低于WARN级别的日志将被忽略
ch.setFormatter(formatter_name) # 设置一个格式化器formatter
ch.addFilter(filter_name) # 增加一个过滤器,可以增加多个
ch.removeFilter(filter_name) # 删除一个过滤器
logging中包含的handler
handler名稱 位置 作用
StreamHandler logging.StreamHandler 日誌輸出到流,可以是sys.stderr,sys.stdout或者文件
FileHandler logging.FileHandler 日誌輸出到文件
BaseRotatingHandler logging.handlers.BaseRotatingHandler 基本的日誌回滾方式
RotatingHandler logging.handlers.RotatingHandler 日誌回滾方式,支持日誌文件最大數量和日誌文件回滾
TimeRotatingHandler logging.handlers.TimeRotatingHandler 日誌回滾方式,在一定時間區域內回滾日誌文件
SocketHandler logging.handlers.SocketHandler 遠程輸出日誌到TCP/IP sockets
DatagramHandler logging.handlers.DatagramHandler 遠程輸出日誌到UDP sockets
SMTPHandler logging.handlers.SMTPHandler 遠程輸出日誌到郵件地址
SysLogHandler logging.handlers.SysLogHandler 日誌輸出到syslog
NTEventLogHandler logging.handlers.NTEventLogHandler 遠程輸出日誌到Windows NT/2000/XP的事件日誌
MemoryHandler logging.handlers.MemoryHandler 日誌輸出到內存中的指定buffer
HTTPHandler logging.handlers.HTTPHandler 通過"GET"或者"POST"遠程輸出到HTTP服務器

過濾器- Filter[編輯]

Handlers 和 Loggers 可以使用 Filters 來完成比級別更複雜的過濾。 Filter 基類只允許特定 Logger 層次以下的事件。 例如用 『A.B』 初始化的 Filter 允許Logger 『A.B』, 『A.B.C』, 『A.B.C.D』, 『A.B.D』 等記錄的事件,logger『A.BB』, 『B.A.B』 等就不行。 如果用空字符串來初始化,所有的事件都接受。

創建方法: filter = logging.Filter(name=)

格式器- Formatter[編輯]

使用Formatter對象設置日誌信息最後的規則、結構和內容,默認的時間格式為%Y-%m-%d %H:%M:%S。

創建方法: formatter = logging.Formatter(fmt=None, datefmt=None)

其中,fmt 是消息的格式化字符串,datefmt 是日期字符串。如果不指明 fmt,將使用 '%(message)s' 。如果不指明 datefmt,將使用 ISO8601 日期格式。

例子1[編輯]

使用RotatingFileHandler,可以實現日誌回滾,

    import logging
    from logging.handlers import RotatingFileHandler
    logger = logging.getLogger(__name__)
    logger.setLevel(level = logging.INFO)
    #定义一个RotatingFileHandler,最多备份3个日志文件,每个日志文件最大1K
    rHandler = RotatingFileHandler("log.txt",maxBytes = 1*1024,backupCount = 3)
    rHandler.setLevel(logging.INFO)
    formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
    rHandler.setFormatter(formatter)
     
    console = logging.StreamHandler()
    console.setLevel(logging.INFO)
    console.setFormatter(formatter)
     
    logger.addHandler(rHandler)
    logger.addHandler(console)
     
    logger.info("Start print log")
    logger.debug("Do something")
    logger.warning("Something maybe fail.")
    logger.info("Finish")

可以在工程目錄中看到,備份的日誌文件:

   2016/10/09  19:36               732 log.txt
   2016/10/09  19:36               967 log.txt.1
   2016/10/09  19:36               985 log.txt.2
   2016/10/09  19:36               976 log.txt.3

例子2[編輯]

Python中的traceback模塊被用於跟蹤異常返回信息,可以在logging中記錄下traceback,

    import logging
    logger = logging.getLogger(__name__)
    logger.setLevel(level = logging.INFO)
    handler = logging.FileHandler("log.txt")
    handler.setLevel(logging.INFO)
    formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
    handler.setFormatter(formatter)
     
    console = logging.StreamHandler()
    console.setLevel(logging.INFO)
     
    logger.addHandler(handler)
    logger.addHandler(console)
     
    logger.info("Start print log")
    logger.debug("Do something")
    logger.warning("Something maybe fail.")
    try:
        open("sklearn.txt","rb")
    except (SystemExit,KeyboardInterrupt):
        raise
    except Exception:
        logger.error("Faild to open sklearn.txt from logger.error",exc_info = True)
        #logger.exception("Failed to open sklearn.txt from logger.exception")#等效于上一行
    logger.info("Finish")

控制台和日誌文件log.txt中輸出:

   Start print log
   Something maybe fail.
   Faild to open sklearn.txt from logger.error
   Traceback (most recent call last):
     File "G:\zhb7627\Code\Eclipse WorkSpace\PythonTest\test.py", line 23, in <module>
       open("sklearn.txt","rb")
   IOError: [Errno 2] No such file or directory: 'sklearn.txt'
   Finish

例子3[編輯]

下面使用 Python 代碼配置一個非常簡單的記錄器,一個控制台處理程序和一個簡單的格式化程序:

logging.conf 配置文件:

[loggers]
keys=root,simpleExample

[handlers]
keys=consoleHandler

[formatters]
keys=simpleFormatter

[logger_root]
level=DEBUG
handlers=consoleHandler

[logger_simpleExample]
level=DEBUG
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
datefmt=

config_logging.py 配置器:

import logging
# create logger
logger = logging.getLogger('simple_example')
logger.setLevel(logging.DEBUG)

# create console handler and set level to debug
ch = logging.StreamHandler()
ch.setLevel(logging.DEBUG)

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

# add formatter to ch
ch.setFormatter(formatter)

# add ch to logger
logger.addHandler(ch)

# 'application' code
logger.debug('debug message')
logger.info('info message')
logger.warning('warn message')
logger.error('error message')
logger.critical('critical message')

recorder 記錄器:

import logging
import logging.config

logging.config.fileConfig('logging.conf')

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

# 'application' code
logger.debug('debug message')
logger.info('info message')
logger.warning('warn message')
logger.error('error message')
logger.critical('critical message')

運行結果:

2019-10-16 19:45:34,440 - simple_example - DEBUG - debug message
2019-10-16 19:45:34,440 - simple_example - INFO - info message
2019-10-16 19:45:34,440 - simple_example - WARNING - warn message
2019-10-16 19:45:34,440 - simple_example - ERROR - error message
2019-10-16 19:45:34,441 - simple_example - CRITICAL - critical message

例子:通過JSON文件配置logger[編輯]

{
    "version":1,
    "disable_existing_loggers":false,
    "formatters":{
        "simple":{
            "format":"%(asctime)s - %(name)s - %(levelname)s - %(message)s"
        }
    },
    "handlers":{
        "console":{
            "class":"logging.StreamHandler",
            "level":"DEBUG",
            "formatter":"simple",
            "stream":"ext://sys.stdout"
        },
        "info_file_handler":{
            "class":"logging.handlers.RotatingFileHandler",
            "level":"INFO",
            "formatter":"simple",
            "filename":"info.log",
            "maxBytes":"10485760",
            "backupCount":20,
            "encoding":"utf8"
        },
        "error_file_handler":{
            "class":"logging.handlers.RotatingFileHandler",
            "level":"ERROR",
            "formatter":"simple",
            "filename":"errors.log",
            "maxBytes":10485760,
            "backupCount":20,
            "encoding":"utf8"
        }
    },
    "loggers":{
        "my_module":{
            "level":"ERROR",
            "handlers":["info_file_handler"],
            "propagate":"no"
        }
    },
    "root":{
        "level":"INFO",
        "handlers":["console","info_file_handler","error_file_handler"]
    }
}
import json
import logging.config
import os
 
def setup_logging(default_path = "logging.json",default_level = logging.INFO,env_key = "LOG_CFG"):
    path = default_path
    value = os.getenv(env_key,None)
    if value:
        path = value
    if os.path.exists(path):
        with open(path,"r") as f:
            config = json.load(f)
            logging.config.dictConfig(config)
    else:
        logging.basicConfig(level = default_level)
 
def func():
    logging.info("start func")
 
    logging.info("exec func")
 
    logging.info("end func")
 
if __name__ == "__main__":
    setup_logging(default_path = "logging.json")
    func()

例子:通過YAML文件配置logger[編輯]

version: 1
disable_existing_loggers: False
formatters:
        simple:
            format: "%(asctime)s - %(name)s - %(levelname)s - %(message)s"
handlers:
    console:
            class: logging.StreamHandler
            level: DEBUG
            formatter: simple
            stream: ext://sys.stdout
    info_file_handler:
            class: logging.handlers.RotatingFileHandler
            level: INFO
            formatter: simple
            filename: info.log
            maxBytes: 10485760
            backupCount: 20
            encoding: utf8
    error_file_handler:
            class: logging.handlers.RotatingFileHandler
            level: ERROR
            formatter: simple
            filename: errors.log
            maxBytes: 10485760
            backupCount: 20
            encoding: utf8
loggers:
    my_module:
            level: ERROR
            handlers: [info_file_handler]
            propagate: no
root:
    level: INFO
    handlers: [console,info_file_handler,error_file_handler]
import yaml
import logging.config
import os
 
def setup_logging(default_path = "logging.yaml",default_level = logging.INFO,env_key = "LOG_CFG"):
    path = default_path
    value = os.getenv(env_key,None)
    if value:
        path = value
    if os.path.exists(path):
        with open(path,"r") as f:
            config = yaml.load(f)
            logging.config.dictConfig(config)
    else:
        logging.basicConfig(level = default_level)
 
def func():
    logging.info("start func")
 
    logging.info("exec func")
 
    logging.info("end func")
 
if __name__ == "__main__":
    setup_logging(default_path = "logging.yaml")
    func()