python3中,内置了logging模块,用于进行日志相关的处理。
这篇文章将总结logging模块的基本用法及进阶用法

一、基本使用

1. 日志级别及对应函数

logging模块默认定义了6个日志级别:

import logging
print(logging._nameToLevel)
# {'CRITICAL': 50, 'FATAL': 50, 'ERROR': 40, 'WARN': 30, 'WARNING': 30, 'INFO': 20, 'DEBUG': 10, 'NOTSET': 0}
print(logging._levelToName)
# {50: 'CRITICAL', 40: 'ERROR', 30: 'WARNING', 20: 'INFO', 10: 'DEBUG', 0: 'NOTSET'}

日志优先级:CRIRICAL>ERROR>WARNING>INFO>DEBUG
当日志级别设置为某个级别时,则低于该级别的日志将不输出。如日志级别设置为INFO,则DEBUG级别的日志将不输出。

级别名称级别数值函数使用场景
NOTSET0(确切来说它不算是日志级别)
创建一个 logger 时,设置级别为 NOTSET (当 logger 是根 logger 时,将处理所有消息;当 logger 是非根 logger 时,所有消息会委派给父级)
DEBUG10logging.debug(msg, *args, **kwargs)通常在开发调试阶段使用,用来定位问题或显示程序运行细节
INFO20logging.info(msg, *args, **kwargs)通常用来输出一般信息,或确认程序能正常工作
WARNING/WARN30logging.warning(msg, *args, **kwargs)
logging.warn(msg, *args, **kwargs)
用来输出一些警告信息,但通常这些警告信息不影响程序正常运行
ERROR40logging.error(msg, *args, **kwargs)用来输出程序的报错信息,表明程序的某些功能已不可用
CRITICAL/FATAL50logging.critical(msg, *args, **kwargs)
logging.fatal(msg, *args, **kwargs)
用来输出严重的错误信息,表明程序已不能继续执行

示例:

import logging

# 根日志器默认日志级别为WARNING,这里将其重置,以保证debug、info级别的日志也能输出
logging.basicConfig(level=logging.NOTSET)

logging.debug("This is a %s message.",logging.getLevelName(logging.DEBUG))
logging.info("This is an %s message.",logging.getLevelName(logging.INFO))
logging.warn("This is a %s message.",logging.getLevelName(logging.WARN))
logging.warning("This is a %s message.",logging.getLevelName(logging.WARNING))
logging.error("This is an %s message.",logging.getLevelName(logging.ERROR))
logging.critical("This is a %s message.",logging.getLevelName(logging.CRITICAL))
logging.fatal("This is a %s message.",logging.getLevelName(logging.FATAL))

logging.log(logging.INFO,"This ia a message from logging.log().")

输出:

DEBUG:root:This is a DEBUG message.
INFO:root:This is an INFO message.
E:/Chen/python3/ExciseA/test.py:14: DeprecationWarning: The 'warn' function is deprecated, use 'warning' instead
  logging.warn("This is a %s message.",logging.getLevelName(logging.WARN))
WARNING:root:This is a WARNING message.
WARNING:root:This is a WARNING message.
ERROR:root:This is an ERROR message.
CRITICAL:root:This is a CRITICAL message.
CRITICAL:root:This is a CRITICAL message.
INFO:root:This ia a message from logging.log().

解释:

  • 输出中第3行提示logging.warn() 已遗弃,但这里为了兼容,仍可用(我这里用的是python 3.7.7版本);
  • WARNWARNING等效,CRITICALFATAL等效,通常用CRITICAL;
  • logging.log(level, msg, *args, **kwargs) 其中,level参数指定输出日志级别。

2. logging.basicConfig(**kwargs)

该函数可以对根日志记录器进行配置

支持以下关键字参数:

参数描述
filename使用指定的文件名创建FileHandler,该参数缺省则使用StreamHandler
filemode指定何种方式打开filename指定的文件,默认为a追加模式
format指定处理器使用的格式字符串,根日志器的默认格式字符串为:%(levelname)s:%(name)s:%(message)s
datefmt使用指定的日期/时间格式来替换%(asctime)s的默认格式'%Y-%m-%d %H:%M:%S,uuu',其中 uuu 部分是毫秒值,其他字母根据与 time.strftime() 所接受的格式相同
style3.2 新增参数。如果指定了 format,将为格式字符串使用此风格。 '%', '{''$' 分别对应于 printf 风格, str.format()string.Template。 默认为 '%'
level设置根日志器的级别,根记录器默认的日志级别为:logging.WARNING
stream使用指定的流初始化StreamHandler,该流可以是open(filename, mode)创建的,也可以是sys.stdoutsys.stderr等,需要注意StreamHandler不会关闭open(filename, mode)创建的流
stream不能与filename/filemode同时出现
handlers3.3 新增参数。如果指定了,这应该是一个已经创建的处理程序的可迭代对象,它将被添加到根处理程序中。列表中任何未分配格式化器的处理程序将被分配在此函数中创建的格式化器。
handlers不能与filename/filemode同时出现
handlers不能与stream同时出现

格式化字符串支持的参数:

参数名称%-style{-style$-style描述
name%(name)s{name}${name}当前日志器名称,根日志器名称为root
levelno%(levelno)s{levelno}${levelno}日志级别的数字值
{'CRITICAL': 50, 'ERROR': 40, 'WARNING': 30, 'INFO': 20, 'DEBUG': 10}
levelname%(levelname)s{levelname}${levelname}日志级别的文字值
{50: 'CRITICAL', 40: 'ERROR', 30: 'WARNING', 20: 'INFO', 10: 'DEBUG'}
pathname%(pathname)s{pathname}${pathname}发生日志记录调用的源文件完整路径名
filename%(filename)s{filename}${filename}pathname的文件名部分(不含路径)
module%(module)s{module}${module}filename的文件名部分(不含后缀),如:test.py文件,moduletest
lineno%(lineno)d{lineno:d}${lineno:d}发生日志调用处所在的行号
funcName%(funcName)s{funcName}${funcName}发生日志调用处,所在的函数名
created%(created)f{created:f}${created:f}创建日志的时间戳(time.time()的值)
asctime%(asctime)s{asctime}${asctime}日志生成的时间,默认形式为%Y-%m-%d %H:%M:%S,uuu,其中uuu是毫秒部分,如2023-08-17 10:01:05,112
msecs%(msecs)d{msecs:d}${msecs:d}日志生成时间的毫秒部分
thread%(thread)d{thread:d}${thread:d}线程ID(如果可用)
process%(process)d{process:d}${process:d}进程ID(如果可用)
message%(message)s{message}${message}记录日志的消息

示例1:

import logging

logging.basicConfig(
    level=logging.NOTSET,
    format='%(asctime)s - %(levelname)s - %(name)s - %(message)s'
)

logging.info(logging.BASIC_FORMAT)
2023-08-17 10:06:29,276 - INFO - root - %(levelname)s:%(name)s:%(message)s
# asctime 默认时间字符串格式为:%Y-%m-%d %H:%M:%S,%3d
# 根日志记录器默认的日志格式为:%(levelname)s:%(name)s:%(message)s

示例2:

import logging

logging.basicConfig(
    level=logging.NOTSET,
    format='%(asctime)s - %(levelname)s - %(name)s - %(message)s',
    datefmt="%a, %d %b %Y %H:%M:%S %z"
)

logging.info(logging.PercentStyle.asctime_format)
Thu, 17 Aug 2023 09:08:26 +0800 - INFO - root - %(asctime)s
# datefmt 指定 asctime 的格式字符串,其他字母根据与 [time.strftime()](https://docs.python.org/zh-cn/3.7/library/time.html#time.strftime) 所接受的格式相同
# format 默认使用的是 %-style 格式化形式

示例3:

import logging

logging.basicConfig(
    level=logging.NOTSET,
    format='{asctime}.{msecs:.0f} - {levelname} - {name} - {message}',
    datefmt="%a, %d %b %Y %H:%M:%S",
    style="{"
)

logging.info(logging.StrFormatStyle.asctime_format)
Thu, 17 Aug 2023 10:12:32.093 - INFO - root - {asctime}
# style 参数指定 format 格式字符串所使用的格式方式为 StrFormatStyle,即 {var} 形式
# {msecs:03.0f} 中,: 后面跟的字符串是对 msecs 进行格式控制的,如 03.0f 是指保留msecs的整数部分,且整数部分保留3位,不足3位在前面补零。详情参考:https://docs.python.org/zh-cn/3.7/library/stdtypes.html#str.format

示例4:

import logging

logging.basicConfig(
    level=logging.NOTSET,
    format='${asctime}.$msecs - ${levelname} - ${name} - ${message}',
    datefmt="%a, %d %b %Y %H:%M:%S",
    style="$"
)

logging.info(logging.StringTemplateStyle.asctime_format)
Thu, 17 Aug 2023 10:20:04.149.18017387390137 - INFO - root - ${asctime}
# style 参数指定 format 格式字符串所使用的格式方式为 StringTemplateStyle,即 ${var} 形式
# 其中,花括号可以省略,如示例中的 $msecs
# 但使用$这种方式,无法在 format 中控制变量的格式,例如,无法像示例3中那样控制 ${msecs} 仅输出3位整数值

示例5:

import logging

logging.basicConfig(
    level=logging.NOTSET,
    format='%(asctime)s - %(levelname)s - %(name)s - %(message)s',
    filename='./default.log'
)

logging.info("This is a INFO message.")
# 将日志输出到 default.log 文件中,如果没有文件则会自动创建一个 

二、进阶使用

日志库采用模块化方法,并提供几类组件:记录器、处理程序、过滤器和格式化程序

组件实例化功能描述
记录器logging.getLogger()暴露了应用程序代码直接使用的接口
处理程序logging.FileHandler()
logging.StreamHandler()
logging.NullHandler()
将日志记录(由记录器创建)发送到适当的目标
过滤器logging.Filter()提供了更精细的附加功能,用于确定要输出的日志记录
格式化程序logging.Formatter()指定最终输出中日志记录的样式

示例1:

import logging

# 创建日志器
logger = logging.getLogger("test")
# 为日志器设置日志等级,如果这里不设置,将会使用其父级日志器的等日志等级
# 这里它的父日志器是root,root的默认日志级别是 logging.WARNING
logger.setLevel(logging.INFO)

# 创建文件处理程序
fh = logging.FileHandler(filename="./test.log",encoding="utf8")
# 创建流处理程序
sh = logging.StreamHandler()

# 为文件处理程序设置日志等级
fh.setLevel(logging.ERROR)
# 为流处理程序设置日志等级
sh.setLevel(logging.DEBUG)

# 创建格式化程序
ffmt = logging.Formatter(
    fmt = "%(asctime)s - %(levelname)s - %(name)s - %(filename)s:%(lineno)d - %(message)s",
    datefmt = "%Y/%m/%d %H:%M:%S"
)
# 创建格式化程序
sfmt = logging.Formatter(
    fmt = "%(asctime)s - %(levelname)s - %(name)s - %(filename)s:%(lineno)d - %(message)s",
)

# 将 ffmt 格式化程序应用到 fh 文件处理程序
fh.setFormatter(ffmt)
# 将 sfmt 格式化程序应用到 sh 流处理程序
sh.setFormatter(sfmt)

# 将文件处理程序应用到logger日志器
logger.addHandler(fh)
# 将流处理程序应用到logger日志器
logger.addHandler(sh)

# 记录日志信息
logger.debug("This is a DEBUG log.")
logger.info("This is an INFO log.")
logger.warning("This is a WARNING log.")
logger.error("This is an ERROR log.")
logger.critical("This is a CRITICAL log.")

控制台输出:

2023-08-17 12:57:37,238 - INFO - test - test.py:53 - This is an INFO log.
2023-08-17 12:57:37,238 - WARNING - test - test.py:54 - This is a WARNING log.
2023-08-17 12:57:37,238 - ERROR - test - test.py:55 - This is an ERROR log.
2023-08-17 12:57:37,238 - CRITICAL - test - test.py:56 - This is a CRITICAL log.

test.log 文件输出:

2023/08/17 12:57:37 - ERROR - test - test.py:55 - This is an ERROR log.
2023/08/17 12:57:37 - CRITICAL - test - test.py:56 - This is a CRITICAL log.

解释:

  • 使用sh.setLevel(logging.DEBUG)对流处理程序重新设置了日志级别,但是输出中并没有debug级别的日志信息。而使用fh.setLevel(logging.ERROR)对文件处理程序重新设置的日志级别,文件输出中过滤掉了ERROR以下级别的消息。因此说明了,为处理程序设置日志级别时,如果该级别低于日志器的日志级别时,则是无效设置。

示例2:

import logging

# 创建日志器 test
logger = logging.getLogger("test")
logger.setLevel(logging.INFO)

# 创建日志器 test.env1 ,表明 env1 上级日志器是 test
logger_env1 = logging.getLogger("test.env1")
# logger_env1.propagate = False

# 创建流处理程序
sh = logging.StreamHandler()

# 创建格式处理程序
sfmt = logging.Formatter(
    fmt = "%(asctime)s - %(levelname)8s - %(name)s - %(filename)s:%(lineno)d - %(message)s",
)

# 将格式处理程序应用到流处理程序
sh.setFormatter(sfmt)

# 将流处理程序应用到test日志器
logger.addHandler(sh)
# 将流处理程序应用到env1日志器
# logger_env1.addHandler(sh)

# 日志记录
logger_env1.debug("This is a DEBUG log.")
logger_env1.info("This is an INFO log.")
logger_env1.warning("This is a WARNING log.")
logger_env1.error("This is an ERROR log.")
logger_env1.critical("This is a CRITICAL log.")

控制台输出:

2023-08-17 13:23:55,499 -     INFO - test.env1 - test.py:44 - This is an INFO log.
2023-08-17 13:23:55,499 -  WARNING - test.env1 - test.py:45 - This is a WARNING log.
2023-08-17 13:23:55,499 -    ERROR - test.env1 - test.py:46 - This is an ERROR log.
2023-08-17 13:23:55,499 - CRITICAL - test.env1 - test.py:47 - This is a CRITICAL log.

解释:

  • getLogger() 返回对具有指定名称的记录器实例的引用,如果没有指定名称则返回root根记录器;

  • getLogger() 返回的记录器具有以.分隔的层次结构,在分层列表中较低的记录器是列表中较高的记录器的子项。例如,给定一个名为 foo 的记录器,名称为 foo.barfoo.bar.bazfoo.bam 的记录器都是 foo 子项

  • 子记录器将消息传播到与其上级记录器关联的处理程序。因此,不必为应用程序使用的所有记录器定义和配置处理程序。为顶级记录器配置处理程序并根据需要创建子记录器就足够了。

    上述示例证明了这点。示例中并没有为env1日志器添加处理程序,而是将处理器添加到了它上一级的test日志器,而日志记录直接使用的env1日志器,test日志器的设置对env1生效了。

    (你可以通过logger_env1.propagate = False,关闭这种传播关系,即env1不再使用上一级日志器的配置)

示例3:

import logging

# 创建日志器 test
logger = logging.getLogger("test")
logger.setLevel(logging.DEBUG)

# 创建日志器 test.env1 ,表明 env1 上级日志器是 test
logger_env1 = logging.getLogger("test.env1")

# 创建流处理程序
sh = logging.StreamHandler()

# 创建格式处理程序
sfmt = logging.Formatter(
    fmt = "%(asctime)s - %(levelname)8s - %(name)s - %(filename)s:%(lineno)d - %(message)s",
)

# 将格式处理程序应用到流处理程序
sh.setFormatter(sfmt)

# 创建Filter对象
class MyFilter(logging.Filter):
    def filter(self, record):
        if record.name == 'test':
            return record.levelno in (logging.WARNING,logging.INFO)
        if record.name == 'test.env1':
            return record.levelno == logging.ERROR
        return True

# 将过滤器应用到流处理程序
sh.addFilter(MyFilter())

# 将流处理程序应用到test日志器
logger.addHandler(sh)

logger.debug("This is a DEBUG log.")
logger.info("This is an INFO log.")
logger.warning("This is a WARNING log.")
logger.error("This is an ERROR log.")
logger.critical("This is a CRITICAL log.")

logger_env1.debug("This is a DEBUG log.")
logger_env1.info("This is an INFO log.")
logger_env1.warning("This is a WARNING log.")
logger_env1.error("This is an ERROR log.")
logger_env1.critical("This is a CRITICAL log.")

输出:

2023-08-17 14:28:19,674 -     INFO - test - test.py:51 - This is an INFO log.
2023-08-17 14:28:19,674 -  WARNING - test - test.py:52 - This is a WARNING log.
2023-08-17 14:28:19,674 -    ERROR - test.env1 - test.py:59 - This is an ERROR log.

解释:

  • 代码中使用sh.addFilter(MyFilter()),将自定义的MyFilter()过滤器应用到处理程序,该过滤器将对使用该处理程序的日志器生效;
  • test 日志器仅生成了 INFO、WARNING 级别的日志,其余日志被过滤器过滤掉了;
  • env1 日志器仅生成了 ERROR 级别的日志,其余日志被过滤器过滤掉了。

最后总结:
一条日志的输出需要经过以下几次过滤:

  • 日志器日志等级的过滤;
  • 日志器的过滤器过滤;
  • 处理器设置的日志等级的过滤;
  • 处理器设置的过滤器的过滤;


参考资料:

模块 logging — Python 的日志记录工具 — Python 3.7.13 文档

日志 HOWTO — Python 3.7.13 文档

Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐