5312 字
27 分钟

Python日志模块

logging#

Python 日志模块#

日志是可以追踪某些软件允许时候发生的事件的方法,通过观察日志信息可以帮助开发人员发现并处理错误

日志等级 level描述对应方法
DEBUG最详细的日志信息,典型应用场景是 问题诊断logging.debug
INFO信息详细程度仅次于DEBUG,通常只记录关键节点信息,用于确认一切都是按照我们预期的那样进行工作logging.info
WARNING当某些不期望的事情发生时记录的信息(如,磁盘可用空间较低),但是此时应用程序还是正常运行的logging.warning
ERROR由于一个更严重的问题导致某些功能不能正常运行时记录的信息logging.error
CRITICAL当发生严重错误,导致应用程序不能继续运行时记录的信息logging.critical

以及logging两个常用函数

  • logging.log: 创建一条严重级别为level的日志记录
  • logging.basicConfig: 对root logger 进行一次性配置

组件列表#

组件名说明
loggers提供应用程序代码直接使用的接口
handlers用于将日志记录发送到指定的目的位置
filters提供更细粒度的日志过滤功能,用于决定哪些日志记录将会被输出
(其它的日志记录将会被忽略)
formatters于控制日志信息的最终输出格式

基础使用#

import logging
logging.debug("This is a debug log.")
logging.info("This is a info log.")
logging.warning("This is a warning log.")
logging.error("This is a error log.")
logging.critical("This is a critical log.")
# or
logging.log(logging.DEBUG, "This is a debug log.")
logging.log(logging.INFO, "This is a info log.")
logging.log(logging.WARNING, "This is a warning log.")
logging.log(logging.ERROR, "This is a error log.")
logging.log(logging.CRITICAL, "This is a critical log.")

执行结果

Terminal window
WARNING:root:This is a warning log.
ERROR:root:This is a error log.
CRITICAL:root:This is a critical log.

Q: 打印出来的日志信息中各字段表示什么意思?为什么会这样输出?

A: 上面输出结果中每行日志记录的各个字段含义分别是:日志级别:日志器名称:日志内容 。之所以会这样输出,是因为logging模块提供的日志记录函数所使用的日志器设置的日志格式默认是BASIC_FORMAT,其值为:"%(levelname)s:%(name)s:%(message)s"

basicConfig 函数说明#

该方法用于为logging日志系统做一些基本配置,方法定义如下:

logging.basicConfig(**kwargs)
参数名称描述
filename指定日志输出目标文件的文件名,指定该设置项后日志信心就不会被输出到控制台了
filemode 指定日志文件的打开模式,默认为’a’。需要注意的是,该选项要在filename指定时才有效
format指定日志格式字符串,即指定日志输出时所包含的字段信息以及它们的顺序。logging模块定义的格式字段下面会列出。
datefmt指定日期/时间格式。需要注意的是,该选项要在format中包含时间字段%(asctime)s时才有效
level指定日志器的日志级别
stream指定日志输出目标stream,如sys.stdoutsys.stderr以及网络stream。需要说明的是,stream和filename不能同时提供,否则会引发 ValueError异常
stylePython 3.2中新添加的配置项。指定format格式字符串的风格,可取值为’%’、’{‘和’$‘,默认为’%‘
handlersPython 3.3中新添加的配置项。该选项如果被指定,它应该是一个创建了多个Handler的可迭代对象,这些handler将会被添加到root logger。需要说明的是:filename、stream和handlers这三个配置项只能有一个存在,不能同时出现2个或3个,否则会引发ValueError异常。

logging模块定义的格式字符串字段#

字段/属性名称使用格式描述
asctime%(asctime)s 日志事件发生的时间—人类可读时间,如:2003-07-08 16:49:45,896
created%(created)f 日志事件发生的时间—时间戳,就是当时调用time.time()函数返回的值
relativeCreated%(relativeCreated)d 日志事件发生的时间相对于logging模块加载时间的相对毫秒数(目前还不知道干嘛用的)
msecs%(msecs)d 日志事件发生事件的毫秒部分
levelname%(levelname)s 该日志记录的文字形式的日志级别(‘DEBUG’, ‘INFO’, ‘WARNING’, ‘ERROR’, ‘CRITICAL’)
levelno%(levelno)s 该日志记录的数字形式的日志级别(10, 20, 30, 40, 50)
name%(name)s 所使用的日志器名称,默认是’root’,因为默认使用的是 rootLogger
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 进程名称,Python 3.1新增
thread %(thread)d 线程ID
threadName %(thread)s 线程名称

经过配置的日志输出

logging.basicConfig(level=logging.DEBUG)
logging.debug("This is a debug log.")
logging.info("This is a info log.")
logging.warning("This is a warning log.")
logging.error("This is a error log.")
logging.critical("This is a critical log.")
Terminal window
DEBUG:root:This is a debug log.
INFO:root:This is a info log.
WARNING:root:This is a warning log.
ERROR:root:This is a error log.
CRITICAL:root:This is a critical log.

所有等级的日志信息都被输出了,说明配置生效了。

在配置日志器日志级别的基础上,在配置下日志输出目标文件和日志格式#
LOG_FORMAT = "%(asctime)s - %(levelname)s - %(message)s"
logging.basicConfig(filename='my.log', level=logging.DEBUG, format=LOG_FORMAT)

执行完后,会在my.log日志文件中看到如下输出内容

Terminal window
05/08/2017 14:29:04 PM - DEBUG - This is a debug log.
05/08/2017 14:29:04 PM - INFO - This is a info log.
05/08/2017 14:29:04 PM - WARNING - This is a warning log.
05/08/2017 14:29:04 PM - ERROR - This is a error log.
05/08/2017 14:29:04 PM - CRITICAL - This is a critical log.

logging 模块日志流处理流程#

在使用logging模块高级用法前,很有必要了解一些logging模块包含的重要组件和其工作流程

组件名称对应类名功能描述
日志器Logger提供了应用程序可一直使用的接口
处理器Handler将logger创建的日志记录发送到合适的目的输出
过滤器Filter提供了更细粒度的控制工具来决定输出哪条日志记录,丢弃哪条日志记录
格式器Formatter决定日志记录的最终输出格式
  • 日志器(logger) 需要通过处理器(handler) 将日志信息输出到目标位置,如:文件、sys.stdout、网络等。
  • 日志器(logger) 可以设置多个处理器(handler) 将一条日志记录输出到不同的位置
  • 每个处理器(handler) 可以设置多个过滤器(filter) 实现日志过滤,从而只保留感兴趣的日志
  • 每个处理器(handler) 都可以设置自己的格式器(formatter) 实现将一条日志以不同的方式输出到不同的地方

总结: 日期器(logger)是入口,真正干活的是处理器(handler),处理器(handler)还可以通过过滤器(filter)和格式器(formatter) 对要输出的日志进行过滤和格式化等操作。

Logger#
  1. 核心任务
    1. 记录日志
    2. 根据日志等级过滤日志信息
    3. 将过滤后的日志传给 handler
  2. 常用方法
    • Logger.setLevel: 设置日志将会处理的日志最低严重等级
    • Logger.addHandlerLogger.removeHandler:为该 Logger 对象添加或一出一个handle对象
    • Logger.addFilterLogger.removeFilter: 为该Logger 对象添加或移除一个Filter 对象
    • Logger.log | Logger.info | Logger.info | Logger.warning | Logger.error | Logger.critical :创建一个与它们的方法名对应等级的日志记录
  3. 初始化方法
  4. 通过 Logger 类实例化一个对象
  5. 通过 Logging.getLogger传入name(可选参数,默认值为root)获取一个Logger对象
Handler#

Handler对象的作用是(基于日志消息的level)将消息分发到handler指定的位置(文件、网络、邮件等)。Logger对象可以通过addHandler方法为自己添加0个或者更多个handler对象。比如,一个应用程序可能想要实现以下几个日志需求:

  1. 将所有日志都发送到一个日志文件中。
  2. 把所有严重等级大于error 发送到stdout(标准输出)
  3. 把所有严重等级为 crittcal 发送到一个email 邮件地址。

这种场景就需要3个不同的handlers,每个handler复杂发送一个特定严重级别的日志到一个特定的位置。

一个handler中只有非常少数的方法是需要应用开发人员去关心的。对于使用内建handler对象的应用开发人员来说,似乎唯一相关的handler方法就是下面这几个配置方法:

方法描述
Handler.setLevel设置handler将会处理的日志消息的最低严重级别
Handler.setFormatter为handler设置一个格式器对象
Handler.addFilter
Handler.removeFilter
为handler添加 和 删除一个过滤器对象
Note

需要说明的是,应用程序代码不应该直接实例化和使用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,通常被想使用logging的library开发者使用来避免’No handlers could be found for logger XXX’信息的出现。
Formater #

Formater对象用于配置日志信息的最终顺序、结构和内容。与logging.Handler基类不同的是,应用代码可以直接实例化Formatter类。另外,如果你的应用程序需要一些特殊的处理行为,也可以实现一个Formatter的子类来完成。

Formatter类的构造方法定义如下:

logging.Formatter.__init__(fmt=None, datefmt=None, style='%')

可见,该构造方法接收3个可选参数:

  • fmt:指定消息格式化字符串,如果不指定该参数则默认使用message的原始值
  • datefmt:指定日期格式字符串,如果不指定该参数则默认使用”%Y-%m-%d %H:%M:%S”
  • style : Python 3.2新增的参数,可取值为 ’%’, ’{‘和 ’$‘,如果不指定该参数则默认使用’%‘
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表示可以通过过滤

说明

  • 如果有需要,也可以在filter(record)方法内部改变该record,比如添加、删除或修改一些属性。
  • 我们还可以通过filter做一些统计工作,比如可以计算下被一个特殊的logger或handler所处理的record数量等。

logging日志流处理流程#

Logging 工作流程图

我们来描述下上面这个图的日志流处理流程:

  1. (在用户代码中进行)日志记录函数调用,如:logger.infologger.debug等;
  2. 判断要记录的日志级别是否满足日志器设置的级别要求(要记录的日志级别要大于或等于日志器设置的级别才算满足要求),如果不满足则该日志记录会被丢弃并终止后续的操作,如果满足则继续下一步操作;
  3. 根据日志记录函数调用时掺入的参数,创建一个日志记录(LogRecord类)对象;
  4. 判断日志记录器上设置的过滤器是否拒绝这条日志记录,如果日志记录器上的某个过滤器拒绝,则该日志记录会被丢弃并终止后续的操作,如果日志记录器上设置的过滤器不拒绝这条日志记录或者日志记录器上没有设置过滤器则继续下一步操作—将日志记录分别交给该日志器上添加的各个处理器;
  5. 判断要记录的日志级别是否满足处理器设置的级别要求(要记录的日志级别要大于或等于该处理器设置的日志级别才算满足要求),如果不满足记录将会被该处理器丢弃并终止后续的操作,如果满足则继续下一步操作;
  6. 判断该处理器上设置的过滤器是否拒绝这条日志记录,如果该处理器上的某个过滤器拒绝,则该日志记录会被当前处理器丢弃并终止后续的操作,如果当前处理器上设置的过滤器不拒绝这条日志记录或当前处理器上没有设置过滤器测继续下一步操作;
  7. 如果能到这一步,说明这条日志记录经过了层层关卡允许被输出了,此时当前处理器会根据自身被设置的格式器(如果没有设置则使用默认格式)将这条日志记录进行格式化,最后将格式化后的结果输出到指定位置(文件、网络、类文件的Stream等);
  8. 如果日志器被设置了多个处理器的话,上面的第5-8步会执行多次;
  9. 这里才是完整流程的最后一步:判断该日志器输出的日志消息是否需要传递给上一级logger(之前提到过,日志器是有层级关系的)的处理器,如果propagate属性值为1则表示日志消息将会被输出到处理器指定的位置,同时还会被传递给parent日志器的handlers进行处理直到当前日志器的propagate属性为0停止,如果propagate值为0则表示不向parent日志器的handlers传递该消息,到此结束。

日志模块实现#

现在有以下几个日志记录的需求:

  1. 要求将所有级别的所有日志都写入磁盘文件中
  2. all.log文件中记录所有的日志信息,日志格式为:日期和时间 - 日志级别 - 日志信息
  3. error.log文件中单独记录error及以上级别的日志信息,日志格式为:日期和时间 - 日志级别 - 文件名[:行号] - 日志信息
  4. 要求all.log在每天凌晨进行日志切割

分析:

  1. 要记录所有级别的日志,因此日志器的有效level需要设置为最低级别—DEBUG;
  2. 日志需要被发送到两个不同的目的地,因此需要为日志器设置两个handler;另外,两个目的地都是磁盘文件,因此这两个handler都是与FileHandler相关的;
  3. all.log要求按照时间进行日志切割,因此他需要用logging.handlers.TimedRotatingFileHandler; 而error.log没有要求日志切割,因此可以使用FileHandler;
  4. 两个日志文件的格式不同,因此需要对这两个handler分别设置格式器;
import logging
import logging.handlers
import datetime
logger = logging.getLogger('mylogger')
logger.setLevel(logging.DEBUG)
rf_handler = logging.handlers.TimedRotatingFileHandler('all.log', when='midnight', interval=1, backupCount=7, atTime=datetime.time(0, 0, 0, 0))
rf_handler.setFormatter(logging.Formatter("%(asctime)s - %(levelname)s - %(message)s"))
f_handler = logging.FileHandler('error.log')
f_handler.setLevel(logging.ERROR)
f_handler.setFormatter(logging.Formatter("%(asctime)s - %(levelname)s - %(filename)s[:%(lineno)d] - %(message)s"))
logger.addHandler(rf_handler)
logger.addHandler(f_handler)
logger.debug('debug message')
logger.info('info message')
logger.warning('warning message')
logger.error('error message')
logger.critical('critical message')

配置 logging 的几种方式#

使用Python代码显式的使用logging 配置#
# 创建一个日志器logger并设置其日志级别为DEBUG
logger = logging.getLogger('simple_logger')
logger.setLevel(logging.DEBUG)
# 创建一个流处理器handler并设置其日志级别为DEBUG
handler = logging.StreamHandler(sys.stdout)
handler.setLevel(logging.DEBUG)
# 创建一个格式器formatter并将其添加到处理器handler
formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s")
handler.setFormatter(formatter)
# 为日志器logger添加上面创建的处理器handler
logger.addHandler(handler)
# 日志输出
logger.debug('debug message')
logger.info('info message')
logger.warn('warn message')
logger.error('error message')
logger.critical('critical message')
使用fileConfig()函数来读取该文件的内容;#
  1. 创建一个配置文件,如 logging.conf
[loggers]
keys=root,sampleLogger
[handlers]
keys=consoleHandler,fileHandler
[formatters]
keys=simpleFormatter
[logger_root]
level=DEBUG
handlers=consoleHandler
[logger_sampleLogger]
level=DEBUG
handlers=fileHandler, consoleHandler
qualname=sampleLogger
propagate=0
[handler_consoleHandler]
class=StreamHandler
level=DEBUG
formatter=simpleFormatter
args=(sys.stdout,)
[handler_fileHandler]
class=FileHandler
level=DEBUG
formatter=simpleFormatter
args=('app.log', 'w', 'utf-8')
[formatter_simpleFormatter]
format=%(asctime)s - %(name)s - %(levelname)s - %(message)s
datefmt=%Y-%m-%d %H:%M:%S
  1. 读取日志配置文件。
import logging.config
# 避免出现格式解析问题
with open('logging.conf', 'r', encoding='utf-8') as f:
logging.config.fileConfig(f)
logger = logging.getLogger('sampleLogger')
logger.debug('调试信息')
使用字典配置信息和dictConfig函数实现日志配置#

dictConfig函数定义在loging.config模块下:

logging.config.dictConfig(config)
# 该函数可以从一个字典对象中获取日志配置信息,config参数就是这个字典对象。关于这个字典对象的内容规则会在下面进行描述。

传递给dictConfig函数的字典对象只能包含下面这些keys,其中version是必须指定的key,其它key都是可选项:

key名称描述
version必选项,其值是一个整数值,表示配置格式的版本,当前唯一可用的值就是1
formatters可选项,其值是一个字典对象,该字典对象每个元素的key为要定义的格式器名称,value为格式器的配置信息组成的dict,如format和datefmt
filters可选项,其值是一个字典对象,该字典对象每个元素的key为要定义的过滤器名称,value为过滤器的配置信息组成的dict,如name
handlers可选项,其值是一个字典对象,该字典对象每个元素的key为要定义的处理器名称,value为处理器的配置信息组成的dcit,如class、level、formatter和filters,其中class为必选项,其它为可选项;其他配置信息将会传递给class所指定的处理器类的构造函数,如下面的handlers定义示例中的stream、filename、maxBytes和backupCount等
loggers可选项,其值是一个字典对象,该字典对象每个元素的key为要定义的日志器名称,value为日志器的配置信息组成的dcit,如level、handlers、filters 和 propagate(yes
root可选项,这是root logger的配置信息,其值也是一个字典对象。除非在定义其它logger时明确指定propagate值为no,否则root logger定义的handlers都会被作用到其它logger上
incremental可选项,默认值为False。该选项的意义在于,如果这里定义的对象已经存在,那么这里对这些对象的定义是否应用到已存在的对象上。值为False表示,已存在的对象将会被重新定义。
disable_existing_loggers可选项,默认值为True。该选项用于指定是否禁用已存在的日志器loggers,如果incremental的值为True则该选项将会被忽略

handlers定义示例:

handlers:
console:
class : logging.StreamHandler
formatter: brief
level : INFO
filters: [allow_foo]
stream : ext://sys.stdout
file:
class : logging.handlers.RotatingFileHandler
formatter: precise
filename: logconfig.log
maxBytes: 1024
backupCount: 3

添加上下文信息#

  1. 通过传递 extra 参数 引入上下文信息

    import logging
    import sys
    fmt = logging.Formatter("%(asctime)s - %(name)s - %(ip)s - %(username)s - %(message)s")
    h_console = logging.StreamHandler(sys.stdout)
    h_console.setFormatter(fmt)
    logger = logging.getLogger("myPro")
    logger.setLevel(logging.DEBUG)
    logger.addHandler(h_console)
    extra_dict = {"ip": "113.208.78.29", "username": "Petter"}
    logger.debug("User Login!", extra=extra_dict)
    extra_dict = {"ip": "223.190.65.139", "username": "Jerry"}
    logger.info("User Access!", extra=extra_dict)
  2. 通过使用LoggerAdapters 引入上下文

    使用LoggerAdapter 类传递信息到日志事件是一个非常简单的方式,可以把它看作第一张实现方式的优化方法,因为它给extra提供了默认值。

    import logging
    import sys
    # 初始化一个要传递给LoggerAdapter构造方法的logger实例
    fmt = logging.Formatter("%(asctime)s - %(name)s - %(ip)s - %(username)s - %(message)s")
    h_console = logging.StreamHandler(sys.stdout)
    h_console.setFormatter(fmt)
    init_logger = logging.getLogger("myPro")
    init_logger.setLevel(logging.DEBUG)
    init_logger.addHandler(h_console)
    # 初始化一个要传递给LoggerAdapter构造方法的上下文字典对象
    extra_dict = {"ip": "IP", "username": "USERNAME"}
    # 获取一个LoggerAdapter类的实例
    logger = logging.LoggerAdapter(init_logger, extra_dict)
    # 应用中的日志记录方法调用
    logger.info("User Login!")
    logger.info("User Login!", extra={"ip": "113.208.78.29", "username": "Petter"})
    logger.extra = {"ip": "113.208.78.29", "username": "Petter"}
    logger.info("User Login!")
    logger.info("User Login!")
  3. 通过 Filiters 引入上下文

记录日志到数据库#

import logging
# 创建日志记录器
logger = logging.getLogger(__name__)
logger.setLevel(logging.DEBUG)
# 创建文件处理器
file_handler = logging.FileHandler('app.log')
file_handler.setLevel(logging.DEBUG)
# 创建数据库处理器
import sqlite3
from logging.handlers import BaseRotatingHandler
class DatabaseHandler(BaseRotatingHandler):
def __init__(self, filename):
super().__init__(filename)
def emit(self, record):
conn = sqlite3.connect('logs.db')
conn.execute('''
INSERT INTO logs (level, message) VALUES (?, ?)
''', (record.levelname, record.getMessage()))
conn.commit()
conn.close()
database_handler = DatabaseHandler('logs.db')
database_handler.setLevel(logging.DEBUG)
# 创建日志格式
formatter = logging.Formatter('%(asctime)s [%(levelname)s] %(message)s')
# 将处理器和格式绑定到日志记录器
file_handler.setFormatter(formatter)
database_handler.setFormatter(formatter)
logger.addHandler(file_handler)
logger.addHandler(database_handler)

上面的代码创建了一个日志记录器,并将日志级别设置为 DEBUG。然后,我们创建了一个文件处理器和一个数据库处理器,它们分别将日志记录到 app.log 文件和 logs 表中。最后,我们创建了一个日志格式,并将处理器和格式绑定到日志记录器

文章分享

如果这篇文章对你有帮助,欢迎分享给更多人!

Python日志模块
https://firefly.cuteleaf.cn/posts/logging/
作者
Lireal
发布于
2026-01-20
许可协议
CC BY-NC-SA 4.0

目录