python內置模塊之Log日志模塊


Log日志模塊

前言

 logging 是python中的一個包,封裝所有日志功能。

例如獲取日志器 logging.getLogger 是logging包的 __init__ 文件中定義的函數(包的 __init__文件中的函數直接可以使用包名.函數名調用),如下 getLogger 代碼:

def getLogger(name=None):
    """
    Return a logger with the specified name, creating it if necessary.

    If no name is specified, return the root logger.
    """
    if not name or isinstance(name, str) and name == root.name:
        return root
    return Logger.manager.getLogger(name)
root = RootLogger(WARNING)
class RootLogger(Logger):
    """
    A root logger is not that different to any other logger, except that
    it must have a logging level and there is only one instance of it in
    the hierarchy.
    """
    def __init__(self, level):
        """
        Initialize the logger with the name "root".
        """
        Logger.__init__(self, "root", level)

    def __reduce__(self):
        return getLogger, ()

即 getLogger 函數返回值為Logger類的實例對象。

例如為日志器設置默認的日志級別 logger.setLevel() 為logging包的 __init__ 文件 Logger 類中的 setLevel 函數,代碼如下:

    def setLevel(self, level):
        """
        Set the logging level of this logger.  level must be an int or a str.
        """
        self.level = _checkLevel(level)
        self.manager._clear_cache()
def _checkLevel(level):
    if isinstance(level, int):
        rv = level
    elif str(level) == level:
        if level not in _nameToLevel:
            raise ValueError("Unknown level: %r" % level)
        rv = _nameToLevel[level]
    else:
        raise TypeError("Level not an integer or a valid string: %r" % level)
    return rv
_nameToLevel = {
    'CRITICAL': CRITICAL,
    'FATAL': FATAL,
    'ERROR': ERROR,
    'WARN': WARNING,
    'WARNING': WARNING,
    'INFO': INFO,
    'DEBUG': DEBUG,
    'NOTSET': NOTSET,
}
CRITICAL = 50
FATAL = CRITICAL
ERROR = 40
WARNING = 30
WARN = WARNING
INFO = 20
DEBUG = 10
NOTSET = 0

一、logging函數根據它們用來跟蹤的事件的級別或嚴重程度來命名。標准級別及其適用性描述如下(以嚴重程度遞增排序):

 

默認等級是WARNING,只有高於默認等級以上的級別才會打印到控制台。

①將日志直接輸出到屏幕

"""
由於默認設置的等級是warning,所有只有warning的信息會輸出到控制台。
"""

import logging

logging.debug('debug 信息')
logging.warning('只有這個會輸出。。。')
logging.info('info 信息')

輸出結果:

 

②通過logging.basicConfig函數對日志的輸出格式及方式做相關配置 

import logging

logging.basicConfig(level=logging.DEBUG,
                format='%(asctime)s %(filename)s[line:%(lineno)d] %(levelname)s %(message)s',
                datefmt='%a, %d %b %Y %H:%M:%S',
                filename='myapp.log',
                filemode='w')
    
logging.debug('This is debug message')
logging.info('This is info message')
logging.warning('This is warning message')


#./myapp.log文件中內容為:
#Sun, 24 May 2009 21:48:54 demo2.py[line:11] DEBUG This is debug message
#Sun, 24 May 2009 21:48:54 demo2.py[line:12] INFO This is info message
#Sun, 24 May 2009 21:48:54 demo2.py[line:13] WARNING This is warning message

logging.basicConfig參數:

#logging.basicConfig函數各參數:
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.WARNING【高於該日志級別才會打印到控制台上】 stream: 指定將日志的輸出流,可以指定輸出到sys.stderr,sys.stdout或者文件,默認輸出到sys.stderr,當stream和filename同時指定時,stream被忽略

 

③將日志同時輸出到多個Handler

定義handler,並使用addHander()添加到日志器,實現日志輸出到多個handler。

a、同時輸出到文件和屏幕

import logging
#設置一個basicConfig只能輸出到一個Handler
logging.basicConfig(level=logging.DEBUG,
                format='%(asctime)s %(filename)s[line:%(lineno)d] %(levelname)s %(message)s',
                datefmt='%a, %d %b %Y %H:%M:%S',
                filename='myapp.log',
                filemode='w')

#定義一個StreamHandler,將INFO級別或更高的日志信息打印到標准錯誤,並將其添加到當前的日志處理對象#
console = logging.StreamHandler()
console.setLevel(logging.INFO)
formatter = logging.Formatter('%(name)-12s: %(levelname)-8s %(message)s')
console.setFormatter(formatter)
logging.getLogger('').addHandler(console)

#輸出到文件的log級別為debug,輸出到stream的log級別為info
logging.debug('This is debug message')
logging.info('This is info message')
logging.warning('This is warning message')

b、添加一個handler:輸出到文件,並根據文件大小滾動存儲

在a的基礎上添加一個handler

from logging.handlers import RotatingFileHandler
#定義一個RotatingFileHandler,最多備份5個日志文件,每個日志文件最大10M
Rthandler = RotatingFileHandler('myapp.log', maxBytes=10*1024*1024,backupCount=5)
Rthandler.setLevel(logging.INFO)
formatter = logging.Formatter('%(name)-12s: %(levelname)-8s %(message)s')
Rthandler.setFormatter(formatter)
logging.getLogger('').addHandler(Rthandler)

logging幾種Handler類型:

logging.StreamHandler(默認):     日志輸出到流,可以是sys.stderr、sys.stdout或者文件
logging.FileHandler:             日志輸出到文件
logging.handlers.RotatingFileHandler    日志輸出到文件,基於文件大小滾動存儲日志
logging.handlers.TimedRotatingFileHandler    日志輸出到文件,基於時間周期滾動存儲日志
logging.handlers.SocketHandler:     遠程輸出日志到TCP/IP sockets
logging.handlers.DatagramHandler:      遠程輸出日志到UDP sockets
logging.handlers.SMTPHandler:          遠程輸出日志到郵件地址
logging.handlers.SysLogHandler:     日志輸出到syslog
logging.handlers.NTEventLogHandler: 遠程輸出日志到Windows NT/2000/XP的事件日志
logging.handlers.MemoryHandler:     日志輸出到內存中的制定buffer
logging.handlers.HTTPHandler:         通過"GET""POST"遠程輸出到HTTP服務器

 

通過配置文件配置logger

a、定義配置文件 logger.conf 

#logger.conf
###############################################
[loggers]
keys=root,example01,example02
[logger_root]
level=DEBUG
handlers=hand01,hand02
[logger_example01]
handlers=hand01,hand02
qualname=example01
propagate=0
[logger_example02]
handlers=hand01,hand03
qualname=example02
propagate=0
###############################################

[handlers]
keys=hand01,hand02,hand03
[handler_hand01]
class=StreamHandler
level=INFO
formatter=form02
args=(sys.stderr,)
[handler_hand02]
class=FileHandler
level=DEBUG
formatter=form01
args=('myapp.log', 'a')
[handler_hand03]
class=handlers.RotatingFileHandler
level=INFO
formatter=form02
args=('myapp.log', 'a', 10*1024*1024, 5)
###############################################
[formatters]
keys=form01,form02
[formatter_form01]
format=%(asctime)s %(filename)s[line:%(lineno)d] %(levelname)s %(message)s
datefmt=%a, %d %b %Y %H:%M:%S
[formatter_form02]
format=%(name)-12s: %(levelname)-8s %(message)s
datefmt=

b、 logging.config 獲取配置

import logging
import logging.config

logging.config.fileConfig("logger.conf")
logger = logging.getLogger("example01")

logger.debug('This is debug message')
logger.info('This is info message')
logger.warning('This is warning message')
import logging
import logging.config

logging.config.fileConfig("logger.conf")
logger = logging.getLogger("example02")

logger.debug('This is debug message')
logger.info('This is info message')
logger.warning('This is warning message')

二、logging庫【python中已封裝好的功能模塊】采取了模塊化的設計,

提供了許多組件:記錄器(日志器logger)、處理器(handles)、過濾器和格式化器(formatters)。

1、Logger 暴露了應用程序代碼能直接使用的接口。

2、Handler將(記錄器產生的)日志記錄發送至合適的目的地。

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

4、Formatter 指明了最終輸出中日志記錄的布局。

三、Loggers

Logger 對象要做三件事情。

首先,它們向應用代碼暴露了許多方法,這樣應用可以在運行時記錄消息。

其次,記錄器對象通過嚴重程度(默認的過濾設施)或者過濾器對象來決定哪些日志消息需要記錄下來。

第三,記錄器對象將相關的日志消息傳遞給所有感興趣的日志處理器。

常用的記錄器對象的方法分為兩類:配置和發送消息。

這些是最常用的配置方法:
①Logger.setLevel()指定logger將會處理的最低的安全等級日志信息:
debug是最低的內置安全等級;
critical是最高的內置安全等級。
例如,如果嚴重程度為INFO,記錄器將只處理INFO,WARNING,ERROR和CRITICAL消息,DEBUG消息被忽略。

②Logger.addHandler()和Logger.removeHandler()從記錄器對象中添加和刪除處理程序對象。處理器詳見Handlers。

③Logger.addFilter()和Logger.removeFilter()從記錄器對象添加和刪除過濾器對象。

四、Handlers

處理程序對象負責將適當的日志消息(基於日志消息的嚴重性)分派到處理程序的指定目標。【根據分配到的Handlers不同,又將不同的日志信息以不同的方式輸出】

Logger 對象可以通過addHandler()方法增加零個或多個handler對象。

舉個例子:①一個應用可以將所有的日志消息發送至日志文件;②所有的錯誤級別(error)及以上級別的日志消息發送至標准輸出;③所有的嚴重級別(critical)日志消息發送至某個電子郵箱。
在這個例子中需要三個獨立的處理器,每一個負責將特定級別的日志消息發送至特定的位置。

常用的有4種:

1、 logging.StreamHandler : 控制台輸出

向類似與 sys.stdout 或者 sys.stderr 的任何文件對象(file object)輸出信息。

它的構造函數是: StreamHandler([strm]) 

參數介紹:strm參數是一個文件對象。默認是sys.stderr(不傳strm參數時,默認將日志信息輸出至控制台)。

2、 logging.FileHandler  : 文件輸出

用於向一個文件輸出日志信息。

構造函數是: FileHandler(filename[,mode]) 

參數介紹:①filename文件名:必須指定一個文件名;②mode是文件的打開方式。默認是’a',即添加到文件末尾。

3、logging.handlers.RotatingFileHandler : 按照大小自動分割日志文件,一旦達到指定的大小重新生成文件

這個Handler類似於上面的FileHandler,但是它可以管理文件大小。當文件達到一定大小之后,它會自動將當前日志文件改名,然后創建一個新的同名日志文件繼續輸出。
例子:比如日志文件是chat.log。
當chat.log達到指定的大小之后,RotatingFileHandler自動把文件改名為chat.log.
1。
不過,如果chat.log.1已經存在,會先把chat.log.1重命名為chat.log.2。。。最后重新創建 chat.log,繼續輸出日志信息。

構造函數: RotatingFileHandler( filename[, mode[, maxBytes[, backupCount]]]) 

參數介紹:①filename文件名:必須指定一個文件名。②mode是文件的打開方式。默認是’a',即添加到文件末尾。③maxBytes用於指定日志文件的最大文件大小。如果maxBytes為0,意味着日志文件可以無限大,這時上面描述的重命名過程就不會發生。】④backupCount用於指定保留的備份文件的個數。【如果指定為2,當上面描述的重命名過程發生時,原有的chat.log.2並不會被更名,而是被刪除。】

4、 logging.handlers.TimedRotatingFileHandler :按照時間自動分割日志文件

這個Handler和RotatingFileHandler類似;
不過,它沒有通過判斷文件大小來決定何時重新創建日志文件,而是間隔一定時間就 自動創建新的日志文件。
重命名的過程與RotatingFileHandler類似:不過新的文件不是附加數字,而是當前時間。

構造函數: TimedRotatingFileHandler( filename [,when [,interval [,backupCount]]]) 

參數介紹:

①filename文件名:必須指定一個文件名。

②mode是文件的打開方式。默認是’a',即添加到文件末尾。③backupCount用於指定保留的備份文件的個數。

④interval是時間間隔。

⑤when參數是一個字符串。表示時間間隔的單位,不區分大小寫。它有以下取值:

S 秒

M 分

H 小時

D 天

W 每星期(interval==0時代表星期一)

midnight 每天凌晨

配置方法:

① handlers.setLevel() :記錄器的級別決定了消息是否要傳遞給處理器。每個處理器的級別決定了消息是否要分發。【 logger.setLevel() 為記錄器設置日志級別(根據設置的日志級別決定日志信息是否傳遞給處理器), handlers.setLevel 為處理器設置日志級別(根據每個處理器設置的日志級別決定日志信息是否輸出至指定地方)。】

② handlers.setFormatter() :為該處理器選擇一個格式化器。

③ handlers.addFilter() 和 handlers.removeFilter():分別配置和取消配置處理器上的過濾器對象。

五、Formatters

Formatter對象設置日志信息最后的規則、結構和內容;【日志生成時的樣式】

默認的時間格式為%Y-%m-%d %H:%M:%S。

如下圖為Formatter常用的一些信息:

 六、代碼示例

代碼示例1:get_logger(獲取日志器): 

# 導包
import logging.handlers
import os
import time


# 新建 類
class Logs:
    root_path = os.path.abspath(os.path.dirname(__file__)).split('shippingSchedule')[0]

    # 新建一個日志器變量
    __logger = None
    # 定義生成日志文件的時間
    __log_time = time.strftime("%Y-%m-%d", time.localtime())

    # 新建獲取日志器的方法
    @classmethod
    def get_logger(cls):
        # 判斷日志器為空:
        if cls.__logger is None:
            # 獲取日志器【Logger 暴露了應用程序代碼能直接使用的接口】
            cls.__logger = logging.getLogger()  # 包名.方法名直接調用logging包下的初始化模塊中的getLogger()返回RootLogger
            # 修改默認級別
            cls.__logger.setLevel(logging.DEBUG)
            log_path = cls.root_path + os.sep + "python--log" + os.sep + "info.python--log" + "_" + cls.__log_time
            # 獲取處理器【Handler將(記錄器產生的)日志記錄發送至合適的目的地】
            th = logging.handlers.TimedRotatingFileHandler(filename=log_path,
                                                           when="midnight",
                                                           interval=1,
                                                           backupCount=3,
                                                           encoding="utf-8")
            # 獲取格式化器【Filter提供了更好的粒度控制,它可以決定輸出哪些日志記錄】
            fmt = "%(asctime)s %(levelname)s [%(filename)s(%(funcName)s:%(lineno)d)] - %(message)s"
            fm = logging.Formatter(fmt)  # Formatter 指明了最終輸出中日志記錄的布局。
            # 將格式化器添加到處理器中
            th.setFormatter(fm)
            # 將處理器添加到日志器中
            cls.__logger.addHandler(th)
        # 返回日志器
        return cls.__logger


if __name__ == '__main__':
    log = Logs.get_logger()
    log.info("測試信息級別日志")
    log.error("測試錯誤級別")

代碼示例2:輸出log到控制台以及同時將log寫入log文件

# encoding:utf-8
import logging
from logging import handlers


class Logger(object):
    level_relations = {
        'debug': logging.DEBUG,
        'info': logging.INFO,
        'warning': logging.WARNING,
        'error': logging.ERROR,
        'crit': logging.CRITICAL
    }  # 日志級別關系映射

    def __init__(self, filename, level='info', when='D', backCount=3,
                 fmt='%(asctime)s -%(name)s- %(pathname)s[line:%(lineno)d] - %(levelname)s: %(message)s'):
        self.logger = logging.getLogger(filename)  # 獲取日志器(filename是日志器的名稱,如果不填,默認日志器名稱為root)
        format_str = logging.Formatter(fmt)  # 設置日志格式
        self.logger.setLevel(self.level_relations.get(level))  # 設置日志級別
        sh = logging.StreamHandler()  # 輸出至控制台
        sh.setFormatter(format_str)  # 設置輸出至控制台上日志格式
        th = handlers.TimedRotatingFileHandler(filename=filename, when=when, backupCount=backCount,
                                               encoding='utf-8')  # 往文件里寫入#指定間隔時間自動生成文件的處理器
        # 實例化TimedRotatingFileHandler
        # interval是時間間隔,backupCount是備份文件的個數,如果超過這個個數,就會自動刪除,when是間隔的時間單位,單位有以下幾種:
        # S 秒
        # M 分
        # H 小時、
        # D 天、
        # W 每星期(interval==0時代表星期一)
        # midnight 每天凌晨
        th.setFormatter(format_str)  # 設置文件里寫入的格式
        self.logger.addHandler(sh)  # 把對象加到logger里
        self.logger.addHandler(th)


if __name__ == '__main__':
    log = Logger('all.log', level='debug')
    log.logger.debug('debug')
    log.logger.info('info')
    log.logger.warning('警告')
    log.logger.error('報錯')
    log.logger.critical('嚴重')
    Logger('error.log', level='error').logger.error('error')

代碼示例3:輸出log日志到控制台以及同時將log日志寫入log文件+裝飾器函數代替測試用例腳本中函數調用。

# -*- encoding:utf-8 -*-
import logging
import os
import time
import traceback
from functools import wraps

"""handlers是什么?"""
# logging模塊中包含的類
# 用來自定義日志對象的規則(比如:設置日志輸出格式、等級等)
# 常用子類:StreamHandler、FileHandler
# StreamHandler 控制台輸出日志
# FileHandler 日志輸出到文件

# 日志文件路徑
LOG_PATH = os.path.join(os.path.dirname(os.path.dirname(os.path.realpath(__file__))), "log")
if not os.path.exists(LOG_PATH):
    os.mkdir(LOG_PATH)


class Logger:

    def __init__(self):
        # 創建日志路徑和時間
        self.log_path = os.path.join(LOG_PATH, "{}.log".format(time.strftime("%Y%m%d")))

        # 創建一個logger日志對象,並傳參為log日志器命名為log
        self.logger = logging.getLogger("log")

        # 為日志器設置默認的日志級別
        self.logger.setLevel(logging.DEBUG)

        # 創建日志格式對象
        self.formater = logging.Formatter('[%(asctime)s][%(filename)s %(lineno)d][%(levelname)s]: %(message)s')

        # 創建FileHandler對象(輸出log至文件)
        self.file_handlers = logging.FileHandler(self.log_path, mode='a', encoding="UTF-8")
        # 創建StreamHandler對象(輸出log至控制台)
        self.console_handlers = logging.StreamHandler()

        # FileHandler對象定義日志級別
        self.file_handlers.setLevel(logging.DEBUG)
        # StreamHandler對象定義日志級別
        self.console_handlers.setLevel(logging.DEBUG)

        # 設置FileHandler處理器的格式
        self.file_handlers.setFormatter(self.formater)
        # 設置StreamHandler處理器的格式
        self.console_handlers.setFormatter(self.formater)

        # logger日志對象加載FileHandler對象
        self.logger.addHandler(self.file_handlers)
        # logger日志對象加載StreamHandler對象
        self.logger.addHandler(self.console_handlers)


Logger = Logger().logger


# 定義一個裝飾器,為修飾測試方法提供附加操作(測試方法調用前,測試方法調用后)
def decorate_log(func):
    @wraps(func)
    def log(*args, **kwargs):
        Logger.info(f'------開始執行{func.__name__}------')
        try:
            func(*args, **kwargs)
        except Exception as e:
            Logger.error(f'------{func.__name__}執行失敗,失敗原因:{e}------')
            Logger.error(f"{func.__name__} is error,here are details:{traceback.format_exc()}")
            raise e
        else:
            Logger.info(f'------{func.__name__}執行成功------')

    return log


@decorate_log
def hbq():
    assert 1 == 1


if __name__ == '__main__':
    hbq()

    # Logger.info("---測試開始---")
    # Logger.error("---測試結束---")
    # Logger.debug("---測試結束---")

衍生:結合代碼示例3:

python裝飾器functools.wraps(func)詳解

1、先看一段代碼:

def is_login(func):
    def foo(*args, **kwargs):
        return func(*args, **kwargs)

    return foo


def test():
    print('我是:', test.__name__)


@is_login
def test1():
    print('我是:', test1.__name__)


@is_login
def test2():
    print('我是:', test2.__name__)


if __name__ == '__main__':
    test()
    test1()
    test2()

運行結果:

我是: test
我是: foo
我是: foo

可以發現函數的函數名即 func.__name__已被裝飾器改變,變成了裝飾器內返回的函數的函數名

2、在裝飾器內返回的函數的函數名上新增 @wraps 裝飾器/ functools.wraps(func) 裝飾器

from functools import wraps


def is_login(func):
    @wraps(func)
    def foo(*args, **kwargs):
        return func(*args, **kwargs)

    return foo


def test():
    print('我是:', test.__name__)


@is_login
def test1():
    print('我是:', test1.__name__)


@is_login
def test2():
    print('我是:', test2.__name__)


if __name__ == '__main__':
    test()
    test1()
    test2()

運行結果:

我是: test
我是: test1
我是: test2

結論:

 @wraps 可以保證被裝飾器修飾的函數的 func.__name__ 的值即函數名保持不變。

裝飾器的優化

以時間裝飾器為例,進行優化

  • 裝飾器的統一模板
from functools import wraps
# 對函數的裝飾器, 對類func最好為cls
def decorate(func):
    @wraps(func)
    # 增添或修改功能的函數
    def wrapper(*args,**kwargs):
                # 執行被裝飾的函數
        result = func(*args,**kwargs) 
        # 返回結果
        return result
    # 返回內層函數
    return wrapper

普通--時間裝飾器

from functools import wraps
import time
from random import randint

def use_time(func):
    @wraps(func)
    def wrapper(*args,**kwargs):
        st_time = time.time()
        result = func(*args,**kwargs)
        end_time = time.time()
        print(f'{func.__name__}函數use_time:{end_time-st_time}s')
    return wrapper
 
    
@use_time
def foo():
    time.sleep(randint(1,3))

for _ in range(3):
    foo()

運行結果:

foo函數use_time:3.0130558013916016s
foo函數use_time:1.0116651058197021s
foo函數use_time:1.01423978805542s

下面對改裝飾器進行優化(解耦)

  • 可以發先上面時間裝飾器計算的結果,只能在控制台上打印
  • 那我們怎樣才能將它輸出為日志呢???
  • 我們需要將他的結果進行自定輸出
# 在增加一層函數
from functools import wraps
import time
from random import randint


def record(output):
    def use_time(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            st_time = time.time()
            func(*args, **kwargs)
            end_time = time.time()
            #             print(f'{func.__name__}函數use_time:{end_time-st_time}s')
            output(func.__name__, 'use', end_time - st_time)

        return wrapper

    return use_time


# 改裝飾器的結果就可以自定義了,下面以print函數為例
@record(print)
def foo():
    time.sleep(randint(2, 5))


if __name__ == '__main__':
    foo()

結果輸出日志

# 在增加一層函數
from functools import wraps
import time
from random import randint


def record(output):
    def use_time(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            st_time = time.time()
            func(*args, **kwargs)
            end_time = time.time()
            #             print(f'{func.__name__}函數use_time:{end_time-st_time}s')
            output(func.__name__, end_time - st_time)

        return wrapper

    return use_time


def write_log(name, content):
    with open('./time.log', 'a', encoding='utf-8')as f:
        f.write(f'{name}耗時:{content}\r\n')  # \r\n 換行


# 只需要將裝飾器改為@record(write_log)
@record(write_log)
def foo():
    time.sleep(randint(2, 5))


if __name__ == '__main__':
    foo()

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM