menu Chancel's blog
rss_feed
Chancel's blog
有善始者实繁,能克终者盖寡。

Python日志库logging使用实践

作者:Chancel Yang, 创建:2020-07-19, 字数:7655, 已阅:784, 最后更新:2024-03-10

logging是Python的最常见的日志库,使用简单、兼容性强,支持yaml配置,也支持代码创建

1. logging入门

1.1. 为什么不用Print?

日志记录是程序部署到生产环境正常运行重要的一环,对于程序来说,一个号的日志库包括

  • 状态记录:对于每一个时间点,记录下当时的业务运转状态非常重要,很多时候需要依靠这些信息来判断业务问题
  • 重现Bug:生产环境不具备单步调试的条件,而且生产环境上的bug极其难以重现,日志是唯一也是最有效重现Bug的依赖手段
  • 设置等级:设置不同的模块不同的输出等级,让日志文件看起来更简明扼要

logging是Python自带的日志库,在强大的日志功能下依旧兼具灵活性,不管是将日志写入文件,输出到到控制台,或者远程HTTP/FTP流服务器,都只需简单的配置即可实现

1.2. logging 使用示例

我们创建一个main.py文件

TEXT
import logging
import auxiliary_module

# create logger with 'spam_application'
logger = logging.getLogger('spam_application')
logger.setLevel(logging.DEBUG)
# create file handler which logs even debug messages
fh = logging.FileHandler('spam.log')
fh.setLevel(logging.DEBUG)
# create console handler with a higher log level
ch = logging.StreamHandler()
ch.setLevel(logging.ERROR)
# create formatter and add it to the handlers
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
fh.setFormatter(formatter)
ch.setFormatter(formatter)
# add the handlers to the logger
logger.addHandler(fh)
logger.addHandler(ch)

logger.info('creating an instance of auxiliary_module.Auxiliary')
a = auxiliary_module.Auxiliary()
logger.info('created an instance of auxiliary_module.Auxiliary')
logger.info('calling auxiliary_module.Auxiliary.do_something')
a.do_something()
logger.info('finished auxiliary_module.Auxiliary.do_something')
logger.info('calling auxiliary_module.some_function()')
auxiliary_module.some_function()
logger.info('done with auxiliary_module.some_function()')

再创建一个auxiliary_module.py

Python
import logging

# create logger
module_logger = logging.getLogger('spam_application.auxiliary')

class Auxiliary:
    def __init__(self):
        self.logger = logging.getLogger('spam_application.auxiliary.Auxiliary')
        self.logger.info('creating an instance of Auxiliary')

    def do_something(self):
        self.logger.info('doing something')
        a = 1 + 1
        self.logger.info('done doing something')

def some_function():
    module_logger.info('received a call to "some_function"')

运行 python3 main.py,可以看到控制台打印如下输出

TEXT
2020-07-21 00:39:24,997 - spam_application - INFO - creating an instance of auxiliary_module.Auxiliary
2020-07-21 00:39:24,997 - spam_application.auxiliary.Auxiliary - INFO - creating an instance of Auxiliary
2020-07-21 00:39:24,998 - spam_application - INFO - created an instance of auxiliary_module.Auxiliary
2020-07-21 00:39:24,998 - spam_application - INFO - calling auxiliary_module.Auxiliary.do_something
2020-07-21 00:39:24,998 - spam_application.auxiliary.Auxiliary - INFO - doing something
2020-07-21 00:39:24,998 - spam_application.auxiliary.Auxiliary - INFO - done doing something
2020-07-21 00:39:24,998 - spam_application - INFO - finished auxiliary_module.Auxiliary.do_something
2020-07-21 00:39:24,998 - spam_application - INFO - calling auxiliary_module.some_function()
2020-07-21 00:39:24,999 - spam_application.auxiliary - INFO - received a call to "some_function"
2020-07-21 00:39:24,999 - spam_application - INFO - done with auxiliary_module.some_function()

由上面的代码可以看出来,logging内置了几个对象

  • Logger:日志对象,如果不主动初始化,调用logging.info('hello world')时便会是一个默认的Logger对象
  • LogRecord,记录器,接收Logger对象传递的日志,并发送到对应的Handler处理器
  • Handler:核心处理器,将产生的日志输出到指定的位置(即决定日志是去往输出台/HTTP服务器/本地)
  • Filter:过滤器,提供更好的粒度控制,用于控制过滤一些制定的输出内容
  • Formatter:格式化器,搭配Handler实现不同的日志输出格式

logging的工作流程官方示例图

在大部分情况下,我们只需要控制LoggerHandlerFormatter三个对象即可

1.3. Logger、Handler、Formatter

Logger是基础的日志对象,如示例代码使用 logging.getLogger('spam_application') 这个方式时,会获得一个名为spam_application的日志对象

然后调整这个日志对象对应的一些属性,我们就能得到一个自定义化的日志对象,在其他模块中再次使用这个日志对象时,之前所做的自定义化便会生效

接下来依次分析示例代码中的每一句话,看看具体实现了什么

setLevel(logging.DEBUG)

设置这个日志对象输出Debug以上级别的日志,日志级别官方文档摘录如下,如设置了Debug级别,则输出所有数值>DEBUG级别的日志

等级 数值
CRITICAL 50
FATAL 50
ERROR 40
WARNING 30
WARN 30
INFO 20
DEBUG 10
NOTSET 0

fh = logging.FileHandler('spam.log')

创建了一个名为FH的handler日志处理格式,规定了DEBUG日志的输出目标输出格式,ch对象同理,其中 FileHandler 表示这是一个基于文件的输出处理器,会将日志输出到文件中,不同处理器有不同参数,常见处理器列表如下

处理器 说明
StreamHandler 日志输出到流,可以是 sys.stderr,sys.stdout或者自定义文件
FileHandler 最简单的输出到文件日志处理器
BaseRotatingHandler 最简单的日志回滚输出(切割日志文件)
RotatingHandler 支持自定义数量/日志文件的回滚
TimeRotatingHandler 支持按时间分割日志文件的日志处理器
SocketHandler 按sockets协议输出日志到指定的远程机器
SMTPHandler 输出日志到远程邮件
SysLogHandler 输出到sys日志文件
HTTPHandler 输出到HTTP服务器

formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')

创建了一个格式输出器,其中的参数可自定义,参数列表如下

参数 说明
%(levelno)s 打印日志级别的数值
%(levelname)s 打印日志级别的名称
%(pathname)s 打印当前执行程序的路径,其实就是sys.argv[0]
%(filename)s 打印当前执行程序名
%(funcName)s 打印日志的当前函数
%(lineno)d 打印日志的当前行号
%(asctime)s 打印日志的时间
%(thread)d 打印线程ID
%(threadName)s 打印线程名称
%(process)d 打印进程ID
%(processName)s 打印线程名称
%(module)s 打印模块名称
%(message)s 打印日志信息

剩下的四行分别设置对应的格式化对象/处理器对象到指定的对象中,从而完成输出,了解到这,Logging用来应付基本的使用已经没有问题了

如果对于异常捕获/文件配置有兴趣可以继续阅读下一节

2. 灵活的logging

2.1. 异常输出

对于异常输出,相信第一次写都会想到如下输出

Python
import logging

try:
    result = 10 / 0 
except Exception as e:
    logging.error('Error as %s', e)

事实上直接打印e显示出来的堆栈并不明显,也很难看清楚,更建议使用 logging.error('Error as',exc_info=True)/logging.exception('Error') 两种方式来打印堆栈信息

2.2. 自定义的日志文件配置

logging支持自定义日志配置文件,只需要使用如下代码来读取日志的即可配置文件

Python
import logging.config
import yaml

with open('logging.yaml','r',encoding='utf-8') as f:
    config = yaml.load(f)
    logging.config.dictConfig(config)

配置文件支持格式非常广泛,包括config/ini/yaml/json等文件都可以作为logging的配置文件

下面是一个按照日期自动切割的yaml配置文件

YAML
version: 1
formatters:
  common:
    format: "%(asctime)s - %(levelname)s- %(threadName)s - %(message)s"
    datefmt: "%Y/%m/%d %H:%M:%S"
  console:
    format: "%(asctime)s - %(levelname)s- %(pathname)s - %(message)s"
    datefmt: "%Y/%m/%d %H:%M:%S"
handlers:
  # 不同会输出大于等于此级别的信息。
  common:
    class: logging.handlers.TimedRotatingFileHandler
    formatter: common
    level: INFO
    when: D
    interval: 1
    encoding: utf8
    filename: "app.log"
    # suffix: "%Y-%m-%d.log"
    # 日志保留个数
    backupCount: 7
  console:    
    class : logging.StreamHandler
    formatter: brief
    level   : INFO
    stream  : ext://sys.stdout
loggers:
  main.business:
    level: INFO
    handlers: [common]
# 如果模块中没有使用 如logging.getLogger('main.business') 这样的配置来获取loggers中对应的配置,则默认会使用下面的root配置
root:
  level: DEBUG
  handlers: [console]

参考资料


[[replyMessage== null?"发表评论":"发表评论 @ " + replyMessage.m_author]]

account_circle
email
web_asset
textsms

评论列表([[messageResponse.total]])

还没有可以显示的留言...
[[messageItem.m_author]] [[messageItem.m_author]]
[[messageItem.create_time]]
[[getEnviron(messageItem.m_environ)]]
[[subMessage.m_author]] [[subMessage.m_author]] @ [[subMessage.parent_message.m_author]] [[subMessage.parent_message.m_author]]
[[subMessage.create_time]]
[[getEnviron(messageItem.m_environ)]]