程序都有记录日志的需求,并且日志中包含的信息既有正常的程序访问日志,还可能有错误、警告等信息输出。
python的logging模块提供了标准的日志接口,可以通过它存储各种格式的日志,logging的日志分为debug(), info(), warning(), error() and critical()
5个级别。
python默认只打印warning级别以上的日志
日志基础:
两种记录日志的方式:
其实模块级别的日志记录函数也是对logging日志系统相关类的封装。
模块级别的常用函数
函数 | 说明 |
---|---|
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级别以上的日志会打印
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
模块定义的字段冲突
组件名称 | 对应类名 | 功能描述 |
---|---|---|
日志器 | Logger | 提供了应用程序可一直使用的接口 |
处理器 | Handler | 将logger创建的日志记录发送到合适的目的输出 |
过滤器 | Filter | 提供了更细粒度的控制工具来决定输出哪条日志记录,丢弃哪条日志记录 |
格式器 | Formatter | 决定日志记录的最终输出格式 |
组件之间的关系
日志器(logger)是入口,真正干活儿的是处理器(handler),处理器(handler)还可以通过过滤器(filter)和格式器(formatter)对要输出的日志内容做过滤和格式化等处理操作
Logger对象有三个任务要做:
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
中调用该方法
一种方式是通过Logger
类的实例化方法创建一个Logger
类的实例,更通常的方法是用logging.getLogger()
方法
logging.getLogger()
方法有一个可选参数name
,该参数表示将要返回的日志器的名称标识,如果不提供该参数,则其值为'root'
。若以相同的name
参数值多次调用getLogger()
方法,将会返回指向同一个logger
对象的引用。
# 聊天工具的图形界面模块可以这样获得它的Logger: LOG = logging.getLogger(”chat.gui”) # 核心模块可以这样: LOG = logging.getLogger(”chat.kernel”)
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来关闭这种传递机制,默认为TrueHandler对象的作用是(基于日志消息的level)将消息分发到handler指定的位置(文件、网络、邮件等)。Logger对象可以通过addHandler()方法为自己添加0个或者更多个handler对象。比如,一个应用程序可能想要实现以下几个日志需求:
这种场景就需要3个不同的handlers,每个handler负责发送一个特定级别的日志到一个特定的位置
方法 | 描述 |
---|---|
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
日志的formatter是个独立的组件,可以跟handler组合。Formater对象用于配置日志信息的最终顺序、结构和内容。
Formatter类的构造方法定义如下:
logging.Formatter.__init__(fmt=None, datefmt=None, style='%')
参数:
fmt
:指定消息格式化字符串,如果不指定该参数则默认使用message
的原始值datefmt
:指定日期格式字符串,如果不指定该参数则默认使用"%Y-%m-%d %H:%M:%S
"style
:可取值为 '%', '{'和 '$',如果不指定该参数则默认使用'%'
#!/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")
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 ....")
fileConfig()
功能读取它dictConfig()
函数[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对象输出哦")
环境配置: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对象的输出哦")