Python中logging日志模块详解(精心整理版本)

Python中logging模块学习

什么是logging模块?

logging模块是python提供的用于记录程序日志的模块

为什么需要logging模块?

我们完全可以自己打开文件然后将日志写进去,但是这种方法需要重复操作并且没有任何技术含量,所以python帮我们将日志封装成了logging模块,这样我们在记录日志时,只需要简单的调用接口即可!

日志级别

在开始记录日志前,还需要明确日志的级别。

随着程序的使用和时间的推移,会产生大量的日志,那么如何在大量日志中快速找到需要的信息成为了问题,那么解决方法就是将日志划分级别。
logging模块将日志划分为了5个级别,从低到高分别是:

  1. logging.debug—调试信息,细粒度,比较重要的方法需要查看变量的详细信息或者详细运行情况时开启。
  2. logging.info—常规信息,粗粒度,比如了解某个函数是否运行可以使用INFO。
  3. logging.warning—
  4. logging.error—
  5. 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)

四、日志处理的简要流程

  1. 创建一个logger,由logger产生日志
  2. 设置logger日志的等级
  3. 创建合适的Handler(FileHandler要有路径)
  4. 设置每个Handler的日志等级
  5. 创建日志的格式
  6. 向Handler中添加上面创建的Handler格式
  7. 将上面的Handler添加到logger中
  8. 打印日志

代码如下:

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

  • 6
    点赞
  • 34
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值