menu Chancel's Blog
rss_feed lightbulb_outline

10分钟了解Python如何使用Logging记录程序日志

warning 这篇文章距离上次更新于293天前,文中部分信息可能已失效,请自行甄别无效内容。

logging Google能搜索到的教程复杂度初看都非常吓人,熟练的人并不需要这么多唠叨直接看官方文档就完事了

这一篇文章会从最基本的使用讲起,不绕弯不举复杂的例子,保证看完能简单上手可用于业务流程的日志工具(文末有按日期切割的logging Demo,嫌文章唠叨可直接看Demo代码)

1. logging 快速入门

1.1. 为什么不用Print?

日志记录是程序部署到生产环境正常运行极其重要的一环,日志记录对于程序来说,包括不限于以下3点作用

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

logging是Python自带的日志库,在强大的日志记录功能下兼具了极强的灵活性,不管你是打算将日志写入文件,打印到控制台还是远程HTTP/FTP服务器,他都能实现!

1.2. logging 使用示例

首先可以看一下一个官方示范例子,后面的讲解都围绕着官方示例展开,为了方便运行,稍做修改,目录结构如下

  • demo
    • main.py
    • auxiliary_module.py

main.py文件

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文件

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"')

我们直接运行 Main.py文件,得到如下输出

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的基础知识了,logging内置了几个对象

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

logging的工作流程官方示例图(看不懂也没关系,接着看,后面回来看这张图会发现不难了)

如果是简单使用,那么我们只需要关注三个部分,分别是Logger/Handler/Formatter

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. 异常输出

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

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支持自定义日志配置文件,只需要使用如下代码来读取日志的即可配置文件

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配置文件,也有一个配对的Demo,我放在了Github上以便测试运行,地址点我

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]

更复杂的用法建议参考官方文档结合Google使用

参考资料

阅读 - 137
分类 - 编程语言
创建于 2020-07-19 22:46:00
阅读 - 137
分类 - 编程语言
创建于 2020-07-19 22:46:00
博文目录

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

account_circle
email
web_asset
textsms

评论列表([[messageList.data.items.length]])

[[messageItem.create_time]]
[[messageItem.m_environ.browser]] [[messageItem.m_environ.os]] [[messageItem.m_environ.device]]
[[subMessage.m_author]] @ [[subMessage.parent_message.m_author]]
[[subMessage.create_time]]
[[subMessage.m_environ.browser]] [[subMessage.m_environ.os]] [[subMessage.m_environ.device]]