文章目录
logging模块
一、 基础使用
1、 简介
程序都有记录日志的需求,并且日志中包含的信息既有正常的程序访问日志,还可能有错误、警告等信息输出。
python的logging模块提供了标准的日志接口,可以通过它存储各种格式的日志,logging的日志分为debug(), info(), warning(), error() and critical()
5个级别。
python默认只打印warning级别以上的日志
日志基础:
两种记录日志的方式:
- 第一种方式是使用logging提供的模块级别的函数
- 第二种方式是使用Logging日志系统的四大组件
其实模块级别的日志记录函数也是对logging日志系统相关类的封装。
2、 常用函数
模块级别的常用函数
函数 | 说明 |
---|---|
logging.debug(msg, *args, **kwargs) | 创建一条严重级别为DEBUG的日志记录 |
logging.info(msg, *args, **kwargs) | 创建一条严重级别为INFO的日志记录 |
logging.warning(msg, *args, **kwargs) | 创建一条严重级别为WARNING的日志记录 |
logging.error(msg, *args, **kwargs) | 创建一条严重级别为ERROR的日志记录 |
logging.critical(msg, *args, **kwargs) | 创建一条严重级别为CRITICAL的日志记录 |
logging.log(level, *args, **kwargs) | 创建一条严重级别为level的日志记录 |
logging.basicConfig(**kwargs) | 对root logger进行一次性配置 |
默认只有warning级别以上的日志会打印
3、 日志配置
basicConfig
所有参数信息
参数名称 | 描述 |
---|---|
filename | 指定日志输出目标文件的文件名 |
filemode | 指定日志文件的打开模式,默认’a’ |
format | 指定日志格式字符串 |
datefmt | 指定日期/时间格式,需format 中包含%(asctime)s 字段 |
level | 指定日志器的日志级别 |
stream | 指定日志输出目标,如sys.stdout 、sys.stderr 以及网络stream 。stream 和filename 不能共存 |
style | 指定format 格式字符串的风格,可取值为’%‘、’{‘和’$‘,默认为’%’ |
handlers | 该选项如果应该是一个创建了多个Handler 的可迭代对象,这些handler 将会被添加到root logger |
format
所有参数列表
字段/属性名称 | 使用格式 | 描述 |
---|---|---|
asctime | %(asctime)s | 字符串形式的当前时间。默认格式是 “2003-07-08 16:49:45,896” |
created | %(created)f | 时间戳,就是调用time.time()函数返回的值 |
msecs | %(msecs)d | 日志发生时间的毫秒部分 |
levelname | %(levelname)s | 文字形式的日志级别 |
levelno | %(levelno)s | 数字形式的日志级别(10, 20, 30, 40, 50) |
name | %(name)s | 使用的日志器名称,默认是’root’ |
message | %(message)s | 日志记录的文本内容,msg % args 计算得到的 |
pathname | %(pathname)s | 调用日志记录函数的源码文件的全路径 |
filename | %(filename)s | pathname的文件名部分,含文件后缀 |
module | %(module)s | filename的名称部分,不包含后缀 |
lineno | %(lineno)d | 调用日志记录函数的源代码所在的行号 |
funcName | %(funcName)s | 调用日志记录函数的函数名 |
process | %(process)d | 进程ID |
processName | %(processName)s | 进程名称 |
thread | %(thread)d | 线程ID |
threadName | %(thread)s | 线程名称 |
示例:
#!/usr/bin/python3
# -*- coding: UTF-8 -*-
__author__ = "A.L.Kun"
__file__ = "demo01.py"
__time__ = "2022/8/4 10:38"
import logging
logging.basicConfig(level=logging.DEBUG, format='%(asctime)s | %(levelname)s | %(message)s', datefmt='%Y-%m-%d %I:%M:%S %p')
# level 配置最低打印的日志级别
# format 格式化输出子让孩子信息
logging.error("error")
logging.log(logging.DEBUG, "%s is %s old", 'Tom', '12') # 如果信息里面有变量,可以通过增加参数数量的方法来输出完整内容
注意:
logging.debug()
等方法的定义中,除了msg
和args
参数外,还有一个**kwargs
参数。它们支持3个关键字参数:exc_info, stack_info, extra
,下面对这几个关键字参数作个说明。
exc_info
: 其值为布尔值,如果该参数的值设置为True
,则会将异常信息添加到日志消息中。如果没有异常信息则添加None
到日志信息中stack_info
:其值也为布尔值,默认值为False
。如果该参数的值设置为True
,栈信息将会被添加到日志信息中extra
:这是一个字典(dict)
参数,它可以用来自定义消息格式中所包含的字段,但是它的key
不能与logging
模块定义的字段冲突
二、 日志进阶
1、 四大组件
组件名称 | 对应类名 | 功能描述 |
---|---|---|
日志器 | Logger | 提供了应用程序可一直使用的接口 |
处理器 | Handler | 将logger创建的日志记录发送到合适的目的输出 |
过滤器 | Filter | 提供了更细粒度的控制工具来决定输出哪条日志记录,丢弃哪条日志记录 |
格式器 | Formatter | 决定日志记录的最终输出格式 |
组件之间的关系
- 日志器(logger)需要通过处理器(handler)将日志信息输出到目标位置,如:文件、sys.stdout、网络等
- 不同的处理器(handler)可以将日志输出到不同的位置
- 日志器(logger)可以设置多个处理器(handler)将同一条日志记录输出到不同的位置
- 每个处理器(handler)都可以设置自己的过滤器(filter)实现日志过滤,从而只保留感兴趣的日志
- 每个处理器(handler)都可以设置自己的格式器(formatter)实现同一条日志以不同的格式输出到不同的地方
日志器(logger)是入口,真正干活儿的是处理器(handler),处理器(handler)还可以通过过滤器(filter)和格式器(formatter)对要输出的日志内容做过滤和格式化等处理操作
![](https://fastly.jsdelivr.net/gh/liuzhongkun1/imgStore@master/img/b9g7jh4jx21659581845737.png)
2、 Logger组件
2.1 常用方法
Logger对象有三个任务要做:
- 向应用程序代码暴露几个方法,使应用程序可以在运行时记录日志消息
- 基于日志严重等级(默认的过滤设施)或filter对象来决定要对哪些日志进行后续处理
- 将日志消息传送给所有感兴趣的日志handlers
Logger对象最常用的方法分为两类:配置方法 和 发送方法
配置方法 | 描述 |
---|---|
Logger.setLevel() | 设置日志器将会处理的日志消息的最低严重级别 |
Logger.addHandler() 和 Logger.removeHandler() | 为该logger对象添加 和 移除一个handler对象 |
Logger.addFilter() 和 Logger.removeFilter() | 为该logger对象添加 和 移除一个filter对象 |
发送方法 | 描述 |
---|---|
Logger.debug() , Logger.info() , Logger.warning() , Logger.error() , Logger.critical() | 创建一个与它们的方法名对应等级的日志记录 |
Logger.exception() | 创建一个类似于Logger.error() 的日志消息 |
Logger.log() | 需要获取一个明确的日志level参数来创建一个日志记录 |
Logger.exception()
与Logger.error()
的区别在于:Logger.exception()
将会输出堆栈追踪信息,另外一个通常只是在一个exception handler
中调用该方法
2.2 获取对象
一种方式是通过Logger
类的实例化方法创建一个Logger
类的实例,更通常的方法是用logging.getLogger()
方法
logging.getLogger()
方法有一个可选参数name
,该参数表示将要返回的日志器的名称标识,如果不提供该参数,则其值为'root'
。若以相同的name
参数值多次调用getLogger()
方法,将会返回指向同一个logger
对象的引用。
# 聊天工具的图形界面模块可以这样获得它的Logger:
LOG = logging.getLogger(”chat.gui”)
# 核心模块可以这样:
LOG = logging.getLogger(”chat.kernel”)
2.3 层级等级
logger的层级结构与有效等级:
- 层级结构
logger
的名称是一个以’.‘分割的层级结构,每个’.‘后面的logger
都是’.'前面的logger
的children
- 有效等级
如果一个logger
上没有被明确设置一个level
,那么该logger
就是使用它parent
的level
,直到找到个一个明确设置了level
的祖先为止。root logger
总是会有一个明确的level
设置(默认为WARNING
)。当决定是否去处理一个已发生的事件时,logger
的有效等级将会被用来决定是否将该事件传递给该logger
的handlers
进行处理 - 继承关系
child loggers
在完成对日志消息的处理后,默认会将日志消息传递给与它们的祖先loggers
相关的handlers
。因此不必所有loggers
定义和配置handlers
,只需要为一个顶层的logger
配置handlers
,然后按照需要创建child loggers
就可足够了。可以通过将一个logger
的propagate
属性设置为False来关闭这种传递机制,默认为True
3、 Handler组件
3.1 组件用途
Handler对象的作用是(基于日志消息的level)将消息分发到handler指定的位置(文件、网络、邮件等)。Logger对象可以通过addHandler()方法为自己添加0个或者更多个handler对象。比如,一个应用程序可能想要实现以下几个日志需求:
- 把所有日志都发送到一个日志文件中
- 把所有严重级别大于等于error的日志发送到stdout(标准输出)
- 把所有严重级别为critical的日志发送到一个email邮件地址
这种场景就需要3个不同的handlers,每个handler负责发送一个特定级别的日志到一个特定的位置
3.2 组件方法
方法 | 描述 |
---|---|
Handler.setLevel() | 设置handler将会处理的日志消息的最低严重级别 |
Handler.setFormatter() | 为handler设置一个格式器对象 |
Handler.addFilter() 和 Handler.removeFilter() | 为handler添加 和 删除一个过滤器对象 |
应用程序代码不应该直接实例化和使用
Handler
实例。因为Handler
是一个基类,它只定义了所有handlers
都应该有的接口
常用的Handler
Handler | 描述 |
---|---|
logging.StreamHandler | 将日志消息发送到输出到Stream,如std.out, std.err或任何file-like对象。 |
logging.FileHandler | 将日志消息发送到磁盘文件,默认情况下文件大小会无限增长 |
logging.handlers.RotatingFileHandler | 将日志消息发送到磁盘文件,并支持日志文件按大小切割 |
logging.hanlders.TimedRotatingFileHandler | 将日志消息发送到磁盘文件,并支持日志文件按时间切割 |
logging.handlers.HTTPHandler | 将日志消息以GET或POST的方式发送给一个HTTP服务器 |
logging.handlers.SMTPHandler | 将日志消息发送给一个指定的email地址 |
logging.NullHandler | 该Handler实例会忽略error messages |
具体使用可以参照官方文档:https://docs.python.org/zh-cn/3/library/logging.handlers.html
4、 Formater组件
4.1 用途语法
日志的formatter是个独立的组件,可以跟handler组合。Formater对象用于配置日志信息的最终顺序、结构和内容。
Formatter类的构造方法定义如下:
logging.Formatter.__init__(fmt=None, datefmt=None, style='%')
参数:
fmt
:指定消息格式化字符串,如果不指定该参数则默认使用message
的原始值datefmt
:指定日期格式字符串,如果不指定该参数则默认使用"%Y-%m-%d %H:%M:%S
"style
:可取值为 ‘%’, ‘{‘和 ‘$’,如果不指定该参数则默认使用’%’
4.2 使用案例
#!/usr/bin/python3
# -*- coding: UTF-8 -*-
__author__ = "A.L.Kun"
__file__ = "demo01.py"
__time__ = "2022/8/4 10:38"
import logging
LOG = logging.getLogger()
fh = logging.FileHandler("access.log")
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
fh.setFormatter(formatter) # 把formater绑定到fh上
LOG.addHandler(fh)
LOG.warning("test")
5、 Filter组件
Filter可以被Handler和Logger用来做比level更细粒度的、更复杂的过滤功能。Filter是一个过滤器基类,它只允许某个logger层级下的日志事件通过过滤。该类定义如下:
class logging.Filter(name='A.B')
filter(record)
一个filter实例化时传递的name参数值为’A.B’,那么该
filter
将只允许名称为类似'A.B','A.B,C','A.B.C.D','A.B.D'
的loggers
产生的日志记录通过过滤。如果name
为空字符串,则允许所有的日志事件通过过滤。
filter
方法用于具体控制传递的record
记录是否能通过过滤,如果该方法返回值为0表示不能通过过滤,返回值为非0表示可以通过过滤。
若需要,可以在
filter(record)
方法内部改变该record
,比如添加、删除或修改一些属性
还可以通过filter
做一些统计工作,如计算一个特殊的logger
或handler
所处理的record
数量等
使用示例:
class IgnoreBackupLogFilter(logging.Filter):
"""忽略带db backup 的日志"""
def filter(self, record): # 固定写法
return "db backup" not in record.getMessage()
logger.addFilter(IgnoreBackupLogFilter()) # 自定义过滤器
logger.warning("start to run db backup job ....")
logger.error("test error ....")
三、 配置文件
- 使用配置方法的 Python 代码显式创建记录器,处理程序和格式化程序
- 创建日志记录配置文件并使用该
fileConfig()
功能读取它 - 创建配置信息字典并将其传递给
dictConfig()
函数
1、 conf文件
[loggers] # 设置两个日志记录器,root和core,用来区分运行的文件
keys=root, core
[handlers] # 设置handles
keys=consoleHandler,fileHandler
[formatters] # 设置格式化处理
keys=simpleFormatter
[logger_root] # 配置root日志输出
level=DEBUG
handlers=fileHandler
[logger_core] # 配置core的日志输出
level=DEBUG
# 输出最低级别为debug
handlers=consoleHandler,fileHandler
# 添加控制台输出和文件输出
qualname=core
# 配置输出名字,一定要和日志输出的同名,相当于实例化中的参数name,root可以不用配置
propagate=0
# 是否要传递给祖先处理器,如果为1,则会输出多遍,父类也会输出
[handler_consoleHandler] # 配置控制台输出consoleHandler
class=StreamHandler
level=WARNING
formatter=simpleFormatter
args=(sys.stdout,)
[handler_fileHandler] # 配置文件输出fileHandler
class=FileHandler
level=DEBUG
formatter=simpleFormatter
args=('test.log','a+')
[formatter_simpleFormatter] # 配置输出格式化simpleFormatter
format=%(asctime)s - %(name)s - %(levelname)s - %(message)s
datefmt=%Y-%m-%d %I:%M:%S %p
使用方法:
#!/usr/bin/python3
# -*- coding: UTF-8 -*-
__author__ = "A.L.Kun"
__file__ = "demo.py"
__time__ = "2022/8/4 11:42"
import logging
from logging.config import fileConfig, dictConfig
fileConfig("config.conf")
LOG = logging.getLogger("core")
LOG.error("这个是core对象的输出哦")
LOG = logging.getLogger()
LOG.error("这个是root对象输出哦")
2、 yaml文件
环境配置:pip install PyYAML
我们先把上面的conf文件转换为yaml文件
version: 1.0
formatters:
simpleFormatter:
format: '%(asctime)s - %(name)s - %(levelname)s - %(message)s'
datefmt: '%Y-%m-%d %I:%M:%S %p'
handlers:
fileHandler:
class: logging.FileHandler
level: DEBUG
formatter: simpleFormatter
filename: test.log
mode: a
encoding: utf8
consoleHandler:
class: logging.StreamHandler
level: WARNING
formatter: simpleFormatter
stream: ext://sys.stdout # 注意这个哦
loggers:
root:
level: DEBUG
handlers: [fileHandler]
core:
level: DEBUG
handlers: [consoleHandler,fileHandler]
qualname: core
propagate: 0
使用方法:
#!/usr/bin/python3
# -*- coding: UTF-8 -*-
__author__ = "A.L.Kun"
__file__ = "demo.py"
__time__ = "2022/8/4 11:42"
import yaml
import logging
from logging.config import fileConfig, dictConfig
# fileConfig("config.conf")
with open("config.yaml", 'r', encoding='utf-8') as f:
config = yaml.load(f.read(), yaml.FullLoader)
dictConfig(config)
LOG = logging.getLogger("core")
LOG.error("这个是core对象的输出哦")