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()

 

posted @ 2021-05-17 13:12  习久性成  阅读(1085)  评论(0编辑  收藏  举报