python的logging模块

日志在工程调试以及后面生产部署过程中的问题排查中充当重要作用。在调试阶段,需要尽量详细的打印出信息,方便知道程序运行节点及出错信息;在部署阶段不容易出问题的模块可以减少打印信息,只打印重要信息即可,如何灵活的控制打印输出内容及输出保存方式,python自带的logging模块很方便。



来自python官方的logging模块参考文档:https://docs.python.org/3/library/logging.html#logger-objects

1、Logging Levels

logging模块是python内置的标准模块,主要用于输出运行日志,可以设置输出日志的等级、日志保存路径、日志文件回滚等操作。简单使用logging打印日志的示例如下:

import logging
logging.debug('Debugging information')
logging.info('Informational message')
logging.warning('Warning:config file %s not found', 'server.conf')
logging.error('Error occurred')
logging.critical('Critical error -- shutting down')

运行上面代码,会在终端看到如下打印结果:
在这里插入图片描述
可见默认情况下,日志是打印在终端,而且只打印级别大于等于 WARNING 的信息。日志级别等于如下:
CRITICAL > ERROR > WARNING > INFO > DEBUG > NOTSET,它们详细使用介绍如下表所示:
在这里插入图片描述

要深入使用logging模块,则需要理解下面四个对象,即Logger,Handler,Formatter和Filter,下面逐一介绍。


2、Logger

我们通过 logging.getLogger(name) 实例化一个Logger对象。多次调用具有相同名称的 getLogger() 将始终返回对相同Logger对象的引用。如下面代码所示:

import logging
logger1 = logging.getLogger('aaa')
logger2 = logging.getLogger('aaa')
logger3 = logging.getLogger('bbb')
print('logger1 id: ', id(logger1), 'logger2 id: ', id(logger2), 'logger3 id: ', id(logger3))

运行下面代码,logger1和logger2会打印出相同的id,表示logger2和logger1其实是同一个,因为它们具有相同的 name
在这里插入图片描述
通过不同的Logger对象,我们可以灵活控制不同模块的显示内容,简单的工程目录如下:
在这里插入图片描述
主程序在mytest.py中,同级目录包含lib模块和utils模块,uitl模块里指定了日志写入路径及写入格式
mytest.py

import logging
from lib import mylib
from utils.log_utils import get_logset

logger = logging.getLogger("mytest_logger")
logger.setLevel(logging.INFO)

def main():
    fh, ch = get_logset()
    logger.addHandler(fh)
    logger.addHandler(ch)
    logger.info("Start.")
    mylib.do_something()
    logger.warning("Warning.")
    logger.info("Finish.")


if __name__ == "__main__":
    main()

mylib.py

import logging
from utils.log_utils import get_logset

logger = logging.getLogger("mylib_logger")
logger.setLevel(logging.WARNING)

def do_something():
    fh, ch = get_logset()
    logger.addHandler(fh)
    logger.addHandler(ch)
    logger.info("info in mylib file.")
    logger.warning("warning in mylib file.")

log_utils.py

import logging

def get_logset():
    # 创建一个handler,用于写入日志文件
    fh = logging.FileHandler('mylog.log')
    # 再创建一个handler,用于输出到控制台
    ch = logging.StreamHandler()
    # 定义handler的输出格式formatter
    formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
    fh.setFormatter(formatter)
    ch.setFormatter(formatter)
    return fh, ch

可以看到,文件中保存的日志会和控制台输入的日志格式一样。
在这里插入图片描述
主程序mytest设置的日志级别是INFO,所以会打印大于等于INFO的日志信息,而lib模块中的mylib中设置日志级别是Warning,所以只会打印大于等于warning的日志信息,不会打印info。 这就达到了模块间独立控制日志输出的目的。

Logger是一个树形层级结构,输出信息之前都要获得一个Logger,name 中若存在 . 则代表存在继承关系,子、孙子以及后面的logger均会继承父logger的日志级别、Handler和Formatter设置。继承的logger可以通过Logger.setLevel(level)指定最低的日志级别,但若使用addHandler添加新的handler后,无法替换去除掉其父logger的输出,也无法用removeHandler去除掉父logger的handler。示例如下:

import logging

# 创建一个父logger
logger = logging.getLogger('mylogger')
logger.setLevel(logging.INFO)

logger1 = logging.getLogger('mylogger.child1')
logger1.setLevel(logging.WARNING)   # 子logger设置日志输出最低级别,生效

# 再创建两个个handler,用于输出到控制台,每个输出的格式不一样,用于区分父logger和子logger
ch = logging.StreamHandler()
ch1 = logging.StreamHandler()

# 定义handler的输出格式formatter
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
formatter1 = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(levelno)s - %(message)s')

ch.setFormatter(formatter)
ch1.setFormatter(formatter1)

# 给logger添加handler
logger.addHandler(ch)
logger1.addHandler(ch1)
logger1.removeHandler(ch)   # removeHandler不能移除ch Handler,控制台依然会打印

# 记录一条日志
logger.debug('logger debug message')
logger.info('logger info message')
logger.warning('logger warning message')
logger.error('logger error message')
logger.critical('logger critical message')

logger1.debug('logger1 debug message')
logger1.info('logger1 info message')
logger1.warning('logger1 warning message')
logger1.error('logger1 error message')
logger1.critical('logger1 critical message')

运行上面代码,会在控制台看到如下结果:
在这里插入图片描述
若在父logger没有添加FileHandler,而子logger添加的是FileHandler,则只会在日志文件中记录子logger打印的内容。


3、Handler

logging日志的处理模块,用来自定义日志对象的规则(比如:设置日志输出格式、等级等),官方文档:https://docs.python.org/3/library/logging.handlers.html#module-logging.handlers
常用的子类有:StreamHandler,FileHandler,RotatingFileHandler,TimedRotatingFileHandler,网络日志输出SocketHandler,HTTPHandler等等。

logging.StreamHandler 可以向类似与sys.stdout或者sys.stderr的任何文件对象(file object)输出信息
logging.FileHandler 用于向一个文件输出日志信息
logging.NullHandler 不执行任何格式化或输出。它本质上是一个供库开发人员使用的“无操作”处理程序。
logging.handlers.RotatingFileHandler 类似于上面的FileHandler,但是它可以管理文件大小。当文件达到一定大小之后,它会自动将当前日志文件改名,然后创建一个新的同名日志文件继续输出
logging.handlers.TimedRotatingFileHandler 和RotatingFileHandler类似,不过,它没有通过判断文件大小来决定何时重新创建日志文件,而是间隔一定时间就自动创建新的日志文件
logging.handlers.SocketHandler 使用TCP协议,将日志信息发送到网络。
logging.handlers.DatagramHandler 使用UDP协议,将日志信息发送到网络。
logging.handlers.SysLogHandler 日志输出到syslog
logging.handlers.NTEventLogHandler 远程输出日志到Windows NT/2000/XP的事件日志 
logging.handlers.SMTPHandler 远程输出日志到邮件地址
logging.handlers.MemoryHandler 日志输出到内存中的制定buffer
logging.handlers.HTTPHandler 通过"GET""POST"远程输出到HTTP服务器
logging.handlers.QueueHandler 支持向队列发送日志消息
logging.handlers.QueueListener 它与QueueHandler一起工作,支持从队列接收日志消息

定义好了Handler对象后,可以通过Handler对象的方法设置日志级别,格式,过滤等操作。
Handler.setLevel(level):指定日志级别,低于lel级别的日志将被忽略
Handler.setFormatter(formatter):给这个handler选择一个Formatter
Handler.addFilter(filter)、Handler.removeFilter(filter):新增或删除一个filter对象

下面以时间回滚Handler为例,展示如何使用:

#按时间回滚日志,并设置最多保存5个日志文件

import logging
from logging import handlers

#创建一个logger日志对象
logger = logging.getLogger('test_logger')
logger.setLevel(logging.DEBUG)    #设置默认的日志级别

#创建日志格式对象
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')

#创建TimedRotatingFileHandler对象,按1秒回滚一次,最多保存5个日志文件
trh = handlers.TimedRotatingFileHandler("test.log", when='S', interval=1, backupCount=5)
#TimedRotatingFileHandler对象自定义日志级别
trh.setLevel(logging.DEBUG)
#TimedRotatingFileHandler对象自定义日志格式
trh.setFormatter(formatter)

logger.addHandler(trh)    #logger日志对象加载TimedRotatingFileHandler对象
#日志输出
while True:
    logger.info('print info log.')

运行上面代码,控制台不会显示任何打印,只会在当前目录下保存5个日志文件:
在这里插入图片描述


4、Formatter

Formatter对象设置日志信息输出的规则、结构和内容,默认的时间格式为%Y-%m-%d %H:%M:%S。
format参数中可能用到的格式化串:
%(name)s Logger的名字
%(levelno)s 数字形式的日志级别
%(levelname)s 文本形式的日志级别
%(pathname)s 调用日志输出函数的模块的完整路径名,可能没有
%(filename)s 调用日志输出函数的模块的文件名
%(module)s 调用日志输出函数的模块名
%(funcName)s 调用日志输出函数的函数名
%(lineno)d 调用日志输出函数的语句所在的代码行
%(created)f 当前时间,用UNIX标准的表示时间的浮 点数表示
%(relativeCreated)d 输出日志信息时的,自Logger创建以 来的毫秒数
%(asctime)s 字符串形式的当前时间。默认格式是 “2003-07-08 16:49:45,896”。逗号后面的是毫秒
%(thread)d 线程ID。可能没有
%(threadName)s 线程名。可能没有
%(process)d 进程ID。可能没有
%(message)s用户输出的消息


5、Filter

给日志输出规则添加过滤限制,只输出符合Filter对象name的打印信息。根据Filter初始化函数注释可以知道,是根据Logger的name指定的过滤对象,会影响以name命名的logger以及其子logger对象,若没有指定name,则不会进行过滤。

Initialize with the name of the logger which, together with its 
children, will have its events allowed through the filter. If no 
name is specified, allow every event.

若为Handler加Filter则所有使用了该Handler的Logger都会受到影响。而为Logger添加Filter只会影响到自身。


介绍一个更好用的日志库,nb_log,基于logging开发的,没有第三方库,更快使用更方便,直接命令 pip install nb_log ,官方介绍文档:nb-log-doc

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值