赞
踩
当入门一门语言时,最简单最直观的打印日志信息方式就是使用 print() 函数了,而这毕竟是自己练习和测试才会这样做。当参与项目时一定会去使用日志模块实现日志信息的打印和记录,而 Python 提供了内置的日志模块 logging,有必要深入了解一下哦。
logging 日志的级别一共有五种,且存在输出的优先级:critical > error > warning > info > debug,默认情况下是不会打印 info 和 debug 级别日志的,测试如下:
引入 logging 日志模块后,如果不进行任何选项的设置,只会输出 warning 级别及高于 warning 级别的日志,且输出的格式也比较单一,默认格式为【日志级别:root:日志输出内容】。
此时,我们可使用 basicConfig() 方法进行更多的设置,该方法参数选项有:
根据该 API 的参数选项,就可以自定义配置日志输出的风格了,举例说明:
- import logging
- from datetime import date, datetime
-
- filename = f'D:\\XXX\\{str(date.today())}.log'
- format_option = '%(asctime)s - %(filename)s[line:%(lineno)d] - %(threadName)s - %(levelname)s: %(message)s'
- logging.basicConfig(filename=filename, # 输出到指定log文件
- filemode='a+', # 以a+写入
- format=format_option,
- datefmt='%Y-%m-%d %H:%M:%S', # 日期时间格式:2022-11-25 21:59:59
- level=logging.INFO # 高于该级别的日志都可以输出显示)
- logging.info(f"current datetime {datetime.strftime(datetime.now(), '%Y-%m-%d %H:%M:%S')}")
- logging.warning(f"current datetime {datetime.strftime(datetime.now(), '%Y/%m/%d %H:%M:%S')}")
输出到 2022-11-25.log 文件的日志,如下:
当然,使用 basicConfig() 设置日志风格的方式也存在局限性,比如:切割日志、将日志记录到多个文件中。
Python 也是一门面向对象的语言,可以通过 Handler 对象进行日志选项的设置,有以下两种方式引入 Handler。
<1>、通过 logging 模块引入的 Handler 有:
这里,最常用的是 StreamHandler 和 FileHandler ,后面会进行详细的说明。
<2>、通过 logging.handlers 模块引入的 Handler 有:
logging 模块提供如此丰富的日志处理器,我们可以根据不同的使用场景选用相应的 Handler。比如,实现日志切割选用 RotatingFileHandler 就最为合适;解决多进程导致的日志记录阻塞问题,选用 QueueHandler 就比较合适;.....,后面也会进行详细的说明。
Stream 日志记录处理器,支持自定义日志风格,会以流的形式将日志内容输出,默认输出到 sys.stderr,也可以在构造器内指定具体的 steam 实例。演示如下:
- # 获取日志记录器
- logger = logging.getLogger()
- # 设置全局日志输出级别
- logger.setLevel(logging.INFO)
- # 创建stream日志记录处理器,默认使用sys.stderr
- streamHandler = logging.StreamHandler()
- # 定义日志输出风格(格式器)
- format_option = '%(asctime)s - %(filename)s[line:%(lineno)d] - %(threadName)s - %(levelname)s: %(message)s'
- streamHandler.setFormatter(logging.Formatter(format_option))
- # 将日志记录处理器加入日志对象
- logger.addHandler(streamHandler)
- logger.info(f"current datetime {datetime.strftime(datetime.now(), '%Y-%m-%d %H:%M:%S')}")
- logger.info(f"current datetime {datetime.strftime(datetime.now(), '%Y/%m/%d %H:%M:%S')}")
效果如下:
文件日志记录处理器,也支持自定义日志风格,并将日志的内容输出到磁盘文件保存。构造器支持日志输出文件名称、写入模式、编码方式、是否延迟记录 delay 等选项设置。演示如下:
- logger = logging.getLogger() # 获取日志记录器
- logger.setLevel(logging.INFO) # 设置全局日志输出级别
- # 创建文件日志记录处理器,并指定一些设置选项
- fileHandler = logging.FileHandler(filename=f'D:\\XXX\\{str(date.today())}_fileHandler.log',
- mode='a+', encoding='utf-8', delay=False)
- # 定义日志输出风格(格式器)
- format_option = '%(asctime)s - %(filename)s[line:%(lineno)d] - %(threadName)s - %(levelname)s: %(message)s'
- fileHandler.setFormatter(logging.Formatter(format_option))
- # 定义日志输出级别(这个不起作用,需要使用全局定义)
- # fileHandler.setLevel(logging.DEBUG)
- # 将日志记录处理器加入日志对象
- logger.addHandler(fileHandler)
- logger.info(f"current datetime {datetime.strftime(datetime.now(), '%Y-%m-%d %H:%M:%S')}")
- logger.info(f"current datetime {datetime.strftime(datetime.now(), '%Y/%m/%d %H:%M:%S')}")
效果如下:
请注意,如果 delay 参数为 True 时,则文件打开会被推迟至第一次调用 emit() 方法,而 emit() 方法需要引入 logging.LogRecord(),这里不再深入研究了。
RotatingFileHandler 的父类为 BaseRotatingHandler,而 BaseRotatingHandler 的父类是 logging.FileHandler,因此 RotatingFileHandler 本质上是通过 FileHandler 实现操作磁盘文件的。实现日志分割,演示如下:
- logger = logging.getLogger() # 获取日志记录器
- logger.setLevel(logging.DEBUG) # 设置全局日志级别
- # 基本设置:log文件路径、写入模式、切割后每个文件大小(5k)、文件数量(现分在4个)、编码方式、是否延迟记录日志
- rotatingFileHandler = RotatingFileHandler('D:\\XXX\\test_rotatingFileHandler.log',
- mode='a', maxBytes=5 * 1024,
- backupCount=4, encoding='utf-8', delay=False)
- # 定义日志输出风格(格式器)
- format_option = '%(asctime)s - %(filename)s[line:%(lineno)d] - %(threadName)s - %(levelname)s: %(message)s'
- rotatingFileHandler.setFormatter(logging.Formatter(format_option))
- # 将日志记录处理器加入日志对象
- logger.addHandler(rotatingFileHandler)
- for i in range(1, 221):
- logger.info(f"你可曾记得,这已经是我第{i}次说了,哎!")
切割效果如下:
在 Web 应用程序中,经常会存在多个进程阻塞日志记录的问题,我们可采用 QueueHandler + QueueListener 方案加以解决。
实际中,对性能有要求的一些关键线程,可以只为日志对象连接一个 QueueHandler。日志对象只需简单地写入队列即可,这里可为队列设置足够大的容量,也可以在初始化时不设置容量上限。
QueueListener 的优势在于,可以用同一个实例为多个 QueueHandlers 服务。QueueListener 要求传入一个队列和一个或多个 handler,并启动一个内部的线程,用于侦听 QueueHandlers(或其他 LogRecords 源)发送的 LogRecord 队列,LogRecords 会从队列中移除并传给 handler 处理。演示如下:
我们知道,在 Python 3.2 及以后的版本中,可以从字典中加载日志配置,也就意味着可以通过 JSON 或者 YAML 文件加载日志的配置,以 .yml 文件为例说明。
第一步,自定义 zdy_log.yml 文件,配置如下:
version: 1 disable_existing_loggers: False # 格式器配置 formatters: simple: format: '%(asctime)s - %(filename)s[line:%(lineno)d] - %(threadName)s - %(levelname)s: %(message)s' # 处理器配置 handlers: console: # 控制台 class: logging.StreamHandler level: DEBUG formatter: simple stream: ext://sys.stdout info_file_handler: # info级别的日志文件配置 class: logging.handlers.RotatingFileHandler level: INFO formatter: simple filename: info.log maxBytes: 10485760 backupCount: 20 encoding: utf8 error_file_handler: # errors级别的日志文件配置 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 logging.config, yaml
- from datetime import datetime
-
- # 加载日志文件
- with open('zdy_log.yml', 'r', encoding='utf-8') as f:
- logging.config.dictConfig(yaml.load(f))
- # 输出日志
- logging.info(f"current datetime {datetime.strftime(datetime.now(), '%Y-%m-%d %H:%M:%S')}")
- logging.info(f"current datetime {datetime.strftime(datetime.now(), '%Y/%m/%d %H:%M:%S')}")
第三步,运行并查看结果:
当然,也可以选择使用 .conf 文件格式去配置日志选项,不过,这里推荐使用字典形式配置。
logging 日志库采用模块化方法,并提供了几类重要的组件:记录器、处理器、过滤器和格式器,作用描述如下:
日志事件信息会在 LogRecord 实例中的记录器、处理器、格式器和过滤器之间传递。
通常使用 getLogger(name=None) 方式创建记录器对象,当 name 为 None 的时候默认是根日志记录器;当设置 name 为具体名称,则按这个名称去寻找根日志记录器。记录器最常见的配置方法有:setLevel()、addFilter()和removeFilter()、addHandler()和removeHandler(),以及不同级别的日志方法(critical()/error()/....../debug()),需要注意,这种日志级别的配置是全局性的。
当创建了记录器对象后,通过 addHandler() 向自己添加0个或多个处理器对象。根据不同处理场景的需求,处理器会将日志消息分派到具体的 Handler 上。另外,处理器也提供了一些配置方法:setLevel()、setFormatter()、addFilter()和removeFilter(),其中 setLevel() 只会在该处理器内部生效。
格式器 Formatter,可轻松实现对输出日志内容的自定义,重要的参数有日志内容格式化,日期时间格式化,格式化风格,默认验证 style 的正确性,构造器如下所示:
logging.Formatter.__init__(self, fmt=None, datefmt=None, style='%', validate=True)
过滤器则是更细化的控制日志输出内容,可被记录器和处理器用来实现比按层级提供更复杂的过滤操作,基本过滤器类只允许低于日志记录器层级结构中低于特定层级的事件。通常使用 Filter(name='') 方式创建过滤器对象,如果 name 用空字符串初始化,则所有日志事件都会通过;如果用 'a.b' 初始化的过滤器,则允许 'a.b'、'a.b.c'、'a.b.c.d' 的日志事件通过,像 'a.c.b'、'b.c' 等是不允许。过滤器有3个常用的方法:addFilter(filter)、removeFilter(filter)、filter(record),如下:
- class Filter(object):
- def __init__(self, name=''):
- .....
- def filter(self, record):
- .....
-
- class Filterer(object):
- def addFilter(self, filter):
- .....
- def removeFilter(self, filter):
- .....
记录日志的一个重要目的是,当程序出现问题时可以快速定位和解决,而 logging 模块提供的 error 级别日志,通过 exc_info 参数选项可以打印异常信息,如下:
- try:
- doSomeThings()
- except Exception as e:
- logging.error('error!!!!', exc_info=True)
当然,为了更方便把程序中的运行异常打印或者保存下来做异常分析,推荐使用跟踪异常信息的模块 traceback,常用 API 有:
将异常信息保存到文件,如下:
traceback.print_exc(file=open('traceback_ERROR.txt','w+'))
traceback 模块在跟踪异常信息方面相对灵活,建议使用。
【人生苦短,学习Python!Python模块系列将持续更新和记录......】
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。