系列文章目录
第一章 初始 Python
第二章 认识 Python 变量、类型、运算符
第三章 认识 条件分支、循环结构
第四章 认识 Python的五种数据结构
第五章 认识 Python 函数、模块
第六章 认识面向对象三大特性
第七章 初探标准库之os库
第八章 初探标准库之pathlib库
第九章 初探标准库之Shutil库
第十章 初探标准库之re库
第十一章 初探标准库之time和datetime库
第十二章 初探标准库之configparser和第三方pyyaml库
第十三章 初探标准库之logging库
文章目录
- 系列文章目录
- 文章开篇
- 日志的作用
- 日志的等级
- 日志的消息格式
- logging四大组件
- Logger类
- Handler类
- Formatter类
- Filter类
- 真实代码封装
- 总结
文章开篇
Python的魅力,犹如星河璀璨,无尽无边;人生苦短、我用Python!
日志的作用
日志捕捉是软件运行时追踪内部事件的重要工具,对于错误排查和系统运维都至关重要;
通过在代码中嵌入日志记录方法,开发人员可以捕获并记录难以察觉的事件,并提供事件的背景信息;
此外,日志还标记事件的重要性或严重性级别,帮助开发人员快速识别关键事件,从而提高软件的健壮性和用户体验;
日志的等级
级别 | 应用场景 |
---|---|
DEBUG | 调试信息,通常在诊断问题的时候用 |
INFO | 普通信息,确认程序在按照预期运行 |
WARNING | 警告信息,程序运行时出现了意外情况或预示可能出现的问题,但程序仍会继续运行 |
ERROR | 错误信息,程序运行中出现了异常错误,程序某些功能无法执行 |
CRITICAL | 危险信息,程序运行中出现了严重的错误,导致程序无法继续运行 |
使用不同的日志级别进行记录日志信息
import logging
# 默认打印到控制台
logging.basicConfig(level=logging.DEBUG)
# 指定记录到日志文件(后续详解)
# logging.basicConfig(filename="test.log", level=logging.DEBUG)
logging.debug("调试信息") # DEBUG:root:调试信息
logging.info("普通信息") # INFO:root:普通信息
logging.warning("警告信息") # WARNING:root:警告信息
logging.error("错误信息") # ERROR:root:错误信息
logging.critical("危险信息") # CRITICAL:root:危险信息
日志的消息格式
格式 | 说明 |
---|---|
%(message)s | 日志消息内容 |
%(asctime)s | 创建日志记录可读时间 |
%(funcName)s | 日志调用所在函数名 |
%(levelname)s | 日志级别 |
%(levelno)s | 消息级别数字 |
%(lineno)s | 日志调用所在源码行号 |
%(module)s | 模块(filename名字部分) |
%(process) | 进程ID |
%(thread)s | 线程ID |
%(processName)s | 进程名 |
%(threadName)s | 线程名 |
logging四大组件
组件名称 | 对应类名 | 功能描述 |
---|---|---|
日志器 | Logger | 提供了应用程序可一直调用的接口 |
处理器 | Handler | 将logger创建的日志记录发送到合适的目的地输出 |
过滤器 | Filter | 提供了更细颗粒度的控制工具来决定输出哪条日志记录,丢弃哪条日志记录 |
格式器 | Formatter | 决定日志记录的最终输出格式 |
Logger类
Logger主要做三件事情:
- 提供日志记录方法给应用程序;
- 根据日志的严重等级或过滤器决定处理哪些日志;
- 将日志消息发送给所有处理器对象;
常用的配置方法 | 说明 |
---|---|
Logger.setLevel() | 为logger对象设置日志器处理的最低日志级别 |
Logger.addHandler() | 为logger对象添加一个Handler处理器对象 |
Logger.removeHandler() | 为logger对象移除一个Handler处理器对象 |
Logger.addFilter() | 为logger对象添加一个Filter过滤器对象 |
Logger.removeFilter() | 为logger对象移除一个Filter过滤器对象 |
关于Logger.setLevel()方法的说明:
在内置的级别中,DEBUG最低,CRITICAL最高;
如setLevel(logging.INFO)只会处理INFO及以上级别的日志,而忽略DEBUG级别的消息;
创建日志记录的方法 | 说明 |
---|---|
Logger.debug() | 创建一个调试级别的日志记录 |
Logger.info() | 创建一个普通级别的日志记录 |
Logger.warning() | 创建一个警告级别的日志记录 |
Logger.error() | 创建一个错误级别的日志记录 |
Logger.critical() | 创建一个危险级别的日志记录 |
Logger.exception() | 创建一个异常级别的日志记录 |
Logger.log() | 需要获取一个明确的日志级别参数来创建一个日志记录 |
获取Logger对象的方式:
- 通过Logger类的实例化方法创建一个Logger类的实例;
- 通过logging模块的getLogger()方法获取;
通常我们都是使用第二种方式,logging.getLogger()方法有一个可选参数name;
该参数表示将要返回的日志器的名称标识,如果不提供该参数,则其值为root;
相同的name参数多次调用getLogger()方法,返回同一个logger对象的引用;
import logging
# 默认打印到控制台
logging.basicConfig(level=logging.DEBUG)
log = logging.getLogger("zhangsan")
log.debug("调试信息") # DEBUG:root:调试信息
log.info("普通信息") # INFO:root:普通信息
log.warning("警告信息") # WARNING:root:警告信息
log.error("错误信息") # ERROR:root:错误信息
log.critical("危险信息") # CRITICAL:root:危险信息
Handler类
Handler对象的作用是根据日志消息的级别将其分发到指定位置,如文件、网络或邮件等。
Logger对象可通过addHandler()方法添加多个handler以满足不同的日志需求;
将所有严重级别的日志记录到文件;
将严重级别为error及以上的日志输出到标准输出;
将严重级别为critical的日志发送到邮件地址;
上述场景需要多个handlers,每个负责处理特定级别的日志并发送到特定位置;
开发人员通常只需关注handler中的少数方法;
对于使用内置handler对象的应用开发人员,最关键的是配置方法,如设置日志级别、格式和输出目标等;
其他方法对于大多数应用场景来说可能并不重要;
Handler常用配置方法 | 说明 |
---|---|
Handler.setLevel() | 为handler对象设置最低严重级别的日志消息 |
Handler.setFormatter() | 为handler对象设置一个Formatter格式器对象 |
Handler.addFilter() | 为handler对象添加一个Filter过滤器对象 |
Handler.removeFilter() | 为handler对象移除一个Filter过滤器对象 |
因为Handler是基类,仅定义了通用接口和默认行为;
应用程序代码应避免直接实例化和使用Handler实例;
开发人员应使用其子类或覆盖默认行为来满足具体需求;
常用的Handler对象 | 说明 |
---|---|
logging.StramHandler | 将日志消息输出到Stream,如std.out、std.err或任何file-like对象 |
logging.FileHandler | 将日志消息发送到磁盘文件,默认情况下文件会无限增长 |
logging.handlers.RotaingFileHandler | 将日志消息发送到磁盘文件,并支持日志文件按大小切割 |
logging.hanlders.TimedRotatingFileHandler | 将日志消息发送到磁盘文件,并支持日志文件按时间切割 |
logging.handlers.HTTPHandler | 将日志消息以GET或POST的方式发送给HTTP服务器 |
logging.handlers.SMTPHandler | 将日志消息发送给指定的Email地址 |
logging.NullHandler | 该Handler实例会忽略error messages,通常被想使用logging的library开发者使用来避免’No handlers could be found for logger XXX’信息的出现。 |
import logging
# 创建一个logger
logger = logging.getLogger('my_logger')
logger.setLevel(logging.DEBUG) # 设置日志级别
# 创建一个handler,用于将日志信息打印到控制台
handler = logging.StreamHandler()
# 给logger添加handler
logger.addHandler(handler)
# 记录一些日志
logger.debug('这是一个调试信息...') # 这是一个调试信息...
logger.info('这是一个普通信息...') # 这是一个普通信息...
logger.warning('这是一个警告信息...') # 这是一个警告信息...
logger.error('这是一个错误信息...') # 这是一个错误信息...
logger.critical('这是一个危险信息...') # 这是一个危险信息...
Formatter类
Formater对象用于配置日志信息的最终顺序、结构和内容;
与logging.Handler基类不同的是,Formatter类可以直接实例化;
对于特殊的输出需求,可以根据需求创建Formatter的子类来实现;
Formatter类的构造方法定义如下:
logging.Formatter.__init__(fmt=None, datefmt=None, style='%')
该构造方法有三个可选参数:
- fmt用于定义消息格式,默认为原始消息;
- datefmt设置日期格式,默认是"%Y-%m-%d %H:%M:%S";
- style在Python 3.2引入,可选’%', ‘{‘或’$’,默认为 ‘%’;
import logging
# 创建一个logger
logger = logging.getLogger('my_logger')
logger.setLevel(logging.DEBUG) # 设置日志级别
# 创建一个handler,用于将日志信息打印到控制台
handler = logging.StreamHandler()
# 定义handler的输出格式
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
handler.setFormatter(formatter)
# 给logger添加handler
logger.addHandler(handler)
# 记录一些日志
logger.debug('这是一个调试信息...') # 2024-02-27 14:11:17,513 - my_logger - DEBUG - 这是一个调试信息...
logger.info('这是一个普通信息...') # 2024-02-27 14:11:17,513 - my_logger - INFO - 这是一个普通信息...
logger.warning('这是一个警告信息...') # 2024-02-27 14:11:17,513 - my_logger - WARNING - 这是一个警告信息...
logger.error('这是一个错误信息...') # 2024-02-27 14:11:17,513 - my_logger - ERROR - 这是一个错误信息...
logger.critical('这是一个危险信息...') # 2024-02-27 14:11:17,513 - my_logger - CRITICAL - 这是一个危险信息...
Filter类
Filter是logging模块中一个基类;
用于Handler和Logger实现比日志级别更细粒度和复杂的过滤功能,它允许根据logger的层级来筛选日志事件;
filter方法用于具体控制传递的record记录是否能通过过滤,如果该方法返回值为0表示不能通过过滤,返回值为非0表示可以通过过滤;
class logging.Filter(name='')
filter(record)
Filter类根据传入的name参数值来过滤日志记录;
当name设置为’A.B’时,它只允许logger名称以’A.B’为前缀(包括’A.B’本身和它的任何子logger)的日志记录通过过滤;
对于不符合这一前缀规则的logger名称,如’A.BB’和’B.A.B’,它们产生的日志记录将被过滤掉;
如果name参数值为空字符串,则该过滤器允许所有日志事件通过,不进行任何名称相关的过滤;
import logging
class MyFilter(logging.Filter):
def __init__(self, name='', key_word='测试'):
super().__init__(name)
# 设置filter的名字,虽然这里使用了,但实际上filter的名字通常不用于过滤决策
self.name = name
# 设置过滤条件
self.key_word = key_word
self.levelno = logging.INFO
self.logger_name = 'my_app'
def filter(self, record):
# 如果日志信息中以“测试”开头,则忽略
if record.getMessage().startswith(self.key_word):
return False
# 如果record的levelno和logger_name与设置的条件匹配,则返回True,否则返回False
return record.levelno == self.levelno and record.name == self.logger_name
# 创建一个logger
logger = logging.getLogger('my_app')
logger.setLevel(logging.INFO)
# 创建一个handler,用于写入日志文件
handler = logging.StreamHandler()
# 创建一个自定义的filter
filter_instance = MyFilter()
# 添加filter到handler
handler.addFilter(filter_instance)
# 定义handler的输出格式
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
handler.setFormatter(formatter)
# 给logger添加handler
logger.addHandler(handler)
# 上述过滤器对象,设置仅记录INFO等级
logger.info('这是一个普通信息...') # 2024-02-27 14:11:17,513 - my_logger - INFO - 这是一个普通信息...
logger.info('测试是否能够捕捉info信息...') # 不会被记录
真实代码封装
真实应用案例封装,可直接拿去运用到实际开发项目中;
Log类是通过Python的内置模块logging模块封装的工具类;
代码释义如下:
- 单例模式,保证其他模块导入时不会重新实例化
- 按天生成,每天生成一个日志文件,不会让日志过大或过小
- 格式美化,展示日期时间、模块名称、行号、日志等级
- 输出打印
- 文件记录
import logging
import datetime
import os
import time
import traceback # 回溯模块
# 通过装饰器完成单例模式
def singleton(cls):
# 使用字典存储类对象的实例
instances = {}
def _singleton(*args, **kwargs):
if cls not in instances:
# 如果类没有被创建过,那就new个新对象并存储到字典中
instances[cls] = cls(*args, **kwargs)
return instances[cls]
return _singleton
class Log(object):
def __init__(self):
'''创建日志器'''
self.log = logging.getLogger()
# 创建handlers之前,将已经存在的移除,再重新创建,可以防止日志重复
while self.log.hasHandlers():
for handler in self.log.handlers:
self.log.removeHandler(handler)
# 设置日志信息等级
self.log.setLevel(level='INFO')
def console_handle(self):
'''创建控制台处理器'''
self.console_handler = logging.StreamHandler()
# 设置格式器
self.console_handler.setFormatter(self.get_formatter()[0])
# 返回作用给日志器使用
return self.console_handler
def file_handle(self):
'''创建文件处理器'''
# 当前文件的绝对路径去除当前文件所在目录加上日志文件存放目录
logfile_path = os.path.dirname(os.path.dirname(__file__)) + "/logs"
current_date = datetime.datetime.now().strftime("%Y-%m-%d")
logfile_name = logfile_path + '/执行日志 ' + current_date + '.log'
try:
# 判断是否已经生成日志文件存放目录
if not os.path.exists(logfile_path):
os.makedirs(logfile_path)
# 判断是否已经生成今天的日志文件
if not os.path.exists(logfile_name):
open(logfile_name, mode='a')
except OSError:
pass
self.file_handler = logging.FileHandler(filename=logfile_name, mode='a', encoding='utf-8')
# 设置格式器
self.file_handler.setFormatter(self.get_formatter()[1])
# 返回作用给日志器使用
return self.file_handler
def get_formatter(self):
'''格式器'''
self.console_fmt = logging.Formatter(
fmt='%(asctime)s --> %(filename)-30s [line:%(lineno)-3d] --> %(levelname)-5s --> %(message)s')
self.file_fmt = logging.Formatter(
fmt='%(asctime)s --> %(filename)-30s [line:%(lineno)-3d] --> %(levelname)-5s --> %(message)s')
# 返回作用给控制台处理器、文件处理器使用
return self.console_fmt, self.file_fmt
@singleton
def get_log(self):
'''日志器添加处理器'''
self.log.addHandler(self.console_handle())
self.log.addHandler(self.file_handle())
return self.log
log = Log().get_log()
if __name__ == '__main__':
log = Log().get_log()
log.info('提示信息')
log.error('错误信息')
try:
int('hello world')
except ValueError as e:
print('在此处进行异常的处理')
# 将错误信息写入日志文件
log.error(traceback.format_exc())
time.sleep(3)
print('测试是否被异常中断')
# 日志文件内容如下:
# 2024-02-27 14:40:10,923 --> LoggerCfg.py [line:84 ] --> INFO --> 提示信息
# 2024-02-27 14:40:10,923 --> LoggerCfg.py [line:85 ] --> ERROR --> 错误信息
# 2024-02-27 14:40:10,923 --> LoggerCfg.py [line:91 ] --> ERROR --> Traceback (most recent call last):
# File "/Users/yangkai/PythonWorkspace/第49课:第三方库/CASE11:Logging/LoggerCfg.py", line 87, in <module>
# int('hello world')
# ValueError: invalid literal for int() with base 10: 'hello world'
总结
Python的logging模块是Python标准库中的一个强大的日志记录工具;
它支持多种日志级别、灵活的配置选项和丰富的输出目标,如文件、控制台等;
日志记录是记录应用运行时状态和事件的关键方法,对于问题诊断、流程追踪、错误发现及性能优化具有重要意义;