Python中logging模块学习
什么是logging模块?
logging模块是python提供的用于记录程序日志的模块
为什么需要logging模块?
我们完全可以自己打开文件然后将日志写进去,但是这种方法需要重复操作并且没有任何技术含量,所以python帮我们将日志封装成了logging模块,这样我们在记录日志时,只需要简单的调用接口即可!
日志级别
在开始记录日志前,还需要明确日志的级别。
随着程序的使用和时间的推移,会产生大量的日志,那么如何在大量日志中快速找到需要的信息成为了问题,那么解决方法就是将日志划分级别。
logging模块将日志划分为了5个级别,从低到高分别是:
- logging.debug—调试信息,细粒度,比较重要的方法需要查看变量的详细信息或者详细运行情况时开启。
- logging.info—常规信息,粗粒度,比如了解某个函数是否运行可以使用INFO。
- logging.warning—
- logging.error—
- logging.critical—
本质上他们是使用数字来表示级别的,从低到高分别是10,20,30,40,50.
logging模块的基础使用
一、最简单的使用方式(默认配置)
# 导入模块
import logging
# 输出日志
logging.debug("debug message")
logging.info("info messange")
logging.warning("warning message")
logging.error("error message")
logging.critical("critical message")
# 控制台输出为:
# WARNING:warning
# ERROR:error
# CRITICAL:critical
我们会发现debug和info的信息都没有输出,这是因为它们的级别不够,默认情况下logging的最低显示级别为warning,对应的数值为30.
以上是最简单的日志输出方式,但并不是最有用的,大多数情况下我们需要自己配置logging的行为。
二、基础使用方式(简单地自定义配置)
1、使用方法:
import logging
logging.basicConfig()
"""
可用参数:
filename: 设置日志存储的文件名
filemode: 设置日志文件的打开方式,默认为追加"a",可以修改为重写"w"
format: 指定日志显示格式
datefmt: 指定日期时间格式
level: 设置日志的级别
"""
logging.basicConfig(
filename = "output.log",
filemode = "a",
datefmt = "%Y-%m-%d %H:%M:%S %p",
format = "line:%(lineno)d-%(asctime)s-[%(levelname)s]-funtion:%(funcName)s: %(message)s",
level = logging.DEBUG
)
说明:logging.basicConfig()是一个一次性的简单配置工具,也就是说旨在第一次调用时该函数会起作用,后续调用不会起作用,作用更不会累加。
2、格式化全部可用名称:
%(name)s: Logger的名字,并非用户名,详细查看
%(levelno)s: 数字形式的日志级别
%(levelname)s: 文本形式的日志级别
%(pathname)s: 调用日志输出函数的模块的完整路径名,可能没有
%(filename)s: 调用日志输出函数的模块的文件名
%(module)s: 调用日志输出函数的模块名
%(funcName)s: 调用日志处处函数的函数名
%(lineno)s: 调用日志输出函数的语句所在的代码行
%(created)f: 当前时间,用UNIX标准的表示时间的浮点数表示
%(relativeCreated)d: 输出日志信息时的,自Logger创建以来的毫秒数
%(asctime)s: 字符串形式的当前时间,默认格式是“2003-07-08 16:49:45,896”,逗号后面的是毫秒
%(thread)d: 线程ID,可能没有
%(threadName)s: 线程名,可能没有
%(process)s: 进程ID,可能没有
%(message)s: 用户输出的信息
至此我们已经可以自己来配置以及写基础信息了,但是当我们想要同一个日志输出到不同位置时,这些基础配置就无法实现了。
例如:
1、有一个登陆注册的功能,需要记录日志,同时需要生成两份日志,一份给程序员看,一份给老板看,作为程序员应该查看较为详细的日志,老板看的日志应该简单一些,因为他不需要关心程序的具体细节。
2、在开发过程中,我们可能需要打印信息,我们此时的重点在信息的打印上,比如输出一个概率分布等,但同时我们也需要对该函数进行详细日志记录。
要实现上面的需求我们需要系统地学习logging模块。
logging模块进阶
一、logging模块的四个核心角色
- Logger — 提供了应用程序一直可以使用的接口
- Filter — 日志过滤器,过滤日志(提供了更细粒度的控制工具来决定输出哪条日志,丢弃哪条日志)
- Handler — 日志处理器,用于对日志进行格式化,输出到指定的位置(控制台或者文件)
- Formatter — 处理日志的格式(决定日志记录的最终输出格式)
二、这些组件之间的关系描述:
- Logger(日志器)需要通过Handler(处理器)将日志信息输出到目标位置,如控制台或者文件中等。
- 不同的Handler(处理器)可以将日志输出到不同的位置。
- Logger(日志器)可以设置多个处理器将同一条日志记录输出到不同的位置。
- 每个Handler(处理器)都可以设置自己的Filter(过滤器)来实现日志过滤,从而只保留感兴趣的日志。
- 每个Handler(处理器)都可以设置自己的Formatter(格式器)实现同一条日志以不同的格式输出到不同的地方。
总结来说就是Logger是入口,真正干活的是Handler,Handler还可以通过Filter和Formatter对要输出的日志内容做过滤和格式化等处理操作。
三、logging日志模块相关类以及常用方法介绍
1、Logger
Logger对象有三个任务要做:
1)向应用程序代码提供几个方法,使得应用程序可以在运行时记录日志的消息。
2)基于日志严重等级(默认的过滤设施)或者Filter对象(更细粒度的过滤)来决定要对哪些日志进行后续处理。
3)将日志消息产送给感兴趣的日志Handler。
Logger对象最常用的方法分为两类:配置方法和消息发送方法。
1)配置方法
- Logger.setLevel() — 设置日志器将会处理的消息的最低级别
- Logger.addHandler() & Logger.removeHandler() — 为该logger对象添加和移除一个Handler对象
- Logger.addFilter() & Logger.removeFilter() — 为该logger对象添加和移除一个Filter对象
logger对象配置完成后,可以通过以下方法来创建日志记录
2)日志记录创建方法
- Logger.debug() & Logger.info() & Logger.warning() & Logger.error() $ Logger.critical() — 创建一个对应等级的日志记录
- Logger.exception() — 创建一个类似于Logger.error()的日志消息
- Logger.log() — 需要获取一个明确的日志level参数来创建一个日志记录
一个logger对象怎么创建?
一种方法是使用将Logger类实例化的方法来创建,但是通常使用第二种方法:logging.getLogger().
logging.getLogger()方法有一个可选参数name,该参数表示要返回的Logger(日志器)的名称标识,如果不提供该参数,那么默认值为"root",若以相同的方法多次调用getLogger()方法,将会返回只想同一个logger对象的引用。
2、Handler
Handler类的作用是(基于日志消息的level)将消息分发到handler指定的位置(文件、控制台等)。Logger对象可以通过addHandler()方法为自己添加1个或者更多个handler对象,比如,一个应用程序可能想要实现以下几个日志需求:
- 把所有日志都发送到一个日志文件中
- 把所有严重级别大于等于error的日志控制台输出
- 把所有严重级别为critical的日志单独写入一个日志文件中
实现以上三个需求就需要三个handler。
需要说明的是,应用程序代码不应该直接实例化和使用Handler实例,因为Handler是一个基类,它只定义了所有Handler子类都应该有的接口,同时提供了一些子类可以直接使用或者覆盖的默认行为,下面是一些常用的Handler:
- logging.StreamHandler — 将日志消息发送到输出Stream,例如控制台输出或者任何file-like对象
- logging.FileHandler — 将日志消息发送到磁盘文件,默认情况下,文件大小会无限增长
- logging.handlers.RotatingHandler — 将日志消息发送到磁盘文件,并支持日志文件按照大小切割
- logging.handlers.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"信息的出现
3、Filter(暂时了解)
Filter可以被Handler进而Logger用来做比level更细粒度的、更复杂的过滤功能。Filter是一个过滤器基类,它只允许某个logger层级下的日志时间通过过滤,该类定义如下:
class logging.Filter(name="")
filter(record)
比如,一个filter实例化时传递的name参数值为"A, B",那么该filter实例将只允许名称为类似如下规则的loggers产生的日志通过过滤:“A.B”,“A.B.C”,“A.B.C.D”,“A.B.D”,而名称为"A.BB","B.A.B"的loggers产生的日志则会被过滤,如果name的值为空字符串,那么允许所有的日志事件通过过滤。
filter方法用于具体控制传递的record记录是否能通过过滤,如果该方法返回值为0表示不能通过过滤,返回值非0表示可以通过过滤。
4、Formatter
Formater会先用于配置日志信息的最终顺序、结构和内容。与logging.Handler基类不同的是,应用程序代码可以直接实例化Formatter类,另外,如果你的应用程序需要一些特殊的处理行为,也可以实现一个Formatter的子类来完成。
Formatter类的构造方法如下:
logging.Formatter.__init__(fmt=None,datefmt=None,style="%")
可见,该构造方法接收三个可选参数:
- fmt:指定消息格式化字符串,如果不指定则使用message的原始值
- datefmt:指定日期格式字符串,如果不指定该参数则默认使用"%Y-%m-%d %H:%M:%S"
- style:Python3.2新增加的参数,可取值为”%“,和"{","$",如不指定该蚕食则默认使用"%"
一般直接用logging.Formatter(fmt, datefmt)
四、日志处理的简要流程
- 创建一个logger,由logger产生日志
- 设置logger日志的等级
- 创建合适的Handler(FileHandler要有路径)
- 设置每个Handler的日志等级
- 创建日志的格式
- 向Handler中添加上面创建的Handler格式
- 将上面的Handler添加到logger中
- 打印日志
代码如下:
import logging
# 创建logger
logger = logging.getLogger("logger1")
# 设置logger日志等级
logger.setlevel(logging.DEBUG)
# 创建Handler
# 文件句柄
fh = logging.FileHandler("test,log", encoding="utf-8")
# 控制台句柄
ch = logging.StreamHandler()
# 设置日志输出格式
formatter = logging.Formatter(
fmt = "%(asctime)s %(name)s %(filename)s %(message)s",
datefmt = "%Y/%m/%d %X"
)
# 注意logger.Formatter的大小写
fh.setFormatter(formatter)
ch.setFormatter(formatter)
# 为logger添加日志处理器
logger.addHandler(fh)
logger.addHandler(ch)
# 输出不同等级的log
logger.warning("warning")
logger.info("info")
logger.error("error")
至此我们可以实现上面老板的需求了,但是这并不是我们最终的实现方式,因为每次都需要编写这样的代码是非常麻烦的。
logging终极解决方案
一、logging的继承(了解)
可以将一个日志指定为另一个日志的子日志或者孙日志,当存在继承关系时,子孙级日志收到日志时会将该日志向上传递指定继承关系:
import logging
log1 = logging.getLogger("father")
log2 = logging.getLogger("father.son")
log3 = logging.getLogger("father.son.grandson")
fh = logging.FileHandler(filename="cc.log", encoding=”utf-8“)
fm = logging.Formatter("%(asctime)s - %(name)s -%(filename)s - %(message)s")
log1.addHandler(fh)
log2.addHandler(fh)
log3.addHandler(fh)
fh,setFormatter(fm)
# 测试
# log1.error("test")
# log2.error("test")
log3.error("test")
# 取消传递
log3.propagate = False
# 再次测试
log3.error("test")
二、通过字典配置日志(重点)
每次都想要编写代码来配置非常麻烦,我们可以写一个完整的配置保存起来,以便后续直接使用。
import logging,config
logging.config.dictConfig(LOGGING_DIC)
logging.getLogger("aa"),debug("debugb message")
以下为LOGGING_DIC模板:
standard_format = "[%(asctime)s][%(threadName)s:%(thread)d][task_id:%(name)s][%(filename)s:%(lineno)d]"\
"[%(levelname)s][%(messages)s]" #其中name为getLogger指定的名字
id_simple_format = "[%(levelname)s][%(asctime)s] %(message)s"
logfile_path = "配置文件路径"
LOGGING_DIC = {
"version":1,
"disable_existing_loggers":False,
"formatters":{
"standard":{
"format":standard_format,
},
"simple":{
"format":simple_format
},
},
"filters":{},
"handlers":{
# 打印到终端的日志
”console":{
"level": "DEBUG",
"class": "logging,StreamHandler",
"formatter": "simple",
},
# 打印到文件的日志,收集info及以上的日志
"default": {
"level": "DEBUG",
"class": "logging.handlers.RotatingFileHandler",
"formatter": "standrd",
"filename": logfile_path,
"maxBytes": 1024*1024*5,
"backupCount": 5,
"encoding": "utf-8",
}
}
"loggers": {
# logging.getLogger(__name__)拿到的logger配置
"aa": {
"handlers": ["default", "console"],
"level": "DEBUG",
"propagate": True,
},
},
}
声明:本文是在以下两篇文章中整理合并而来,可读性更高,中间加入了一些作者自己的见解,感谢原作者!
https://blog.csdn.net/yatere/article/details/6655445
https://blog.csdn.net/linwow/article/details/89213411