作者:Chancel, 更新:2022 Dec 14, 字数:8034, 已阅:768
logging Google能搜索到的教程复杂度初看都非常吓人,熟练的人并不需要这么多唠叨直接看官方文档就完事了
这一篇文章会从最基本的使用讲起,不绕弯不举复杂的例子,保证看完能简单上手可用于业务流程的日志工具(文末有按日期切割的logging Demo,嫌文章唠叨可直接看Demo代码)
日志记录是程序部署到生产环境正常运行极其重要的一环,日志记录对于程序来说,包括不限于以下3点作用
logging是Python自带的日志库,在强大的日志记录功能下兼具了极强的灵活性,不管你是打算将日志写入文件,打印到控制台还是远程HTTP/FTP服务器,他都能实现!
首先可以看一下一个官方示范例子,后面的讲解都围绕着官方示例展开,为了方便运行,稍做修改,目录结构如下
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内置了几个对象
logging的工作流程官方示例图(看不懂也没关系,接着看,后面回来看这张图会发现不难了)
如果是简单使用,那么我们只需要关注三个部分,分别是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用来应付基本的使用已经没有问题了
如果对于异常捕获/文件配置有兴趣可以继续阅读下一节
对于异常输出,相信第一次写都会想到如下输出
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') 两种方式来打印堆栈信息
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使用
参考资料