日志主要目的是在程序运行过程中,输出我们感兴趣的内容,以便程序出现异常时,我们根据日志排查问题。Python中内置的日志模块是logging。

24.1 日志级别

日志输出的内容是分等级的,我们可以根据等级控制输出的内容。可以通过help(loggging)查看日志帮助文档,文档最下方列出了Python支持的日志级别。

DATA
BASIC_FORMAT = ‘%(levelname)s:%(name)s:%(message)s’
CRITICAL = 50
DEBUG = 10
ERROR = 40
FATAL = 50
INFO = 20
NOTSET = 0
WARN = 30
WARNING = 30

日志的等级是分高低的,从低到高分别是:NOTSET DEBUG INFO WARNING ERROR CRITICAL。NOTSET不太常用。

DEBUG:详细的信息,通常只出现在诊断问题上
INFO:确认一切按预期运行
WARNING:一个迹象表明,一些意想不到的事情发生了,或表明一些问题在不久的将来(例如。磁盘空间低”)。这个软件还能按预期工作。
ERROR:更严重的问题,软件没能执行一些功能
CRITICAL:一个严重的错误,这表明程序本身可能无法继续运行

这5个等级,也分别对应5种打日志的方法: debug 、info 、warning 、error 、critical。默认的是WARNING。

日志的输出规则是,输出大于等于设置的日志级别的日志信息。比如设置等级是DEBUG,那么大于等于10的级别的日志信息都会输出来。

# coding=utf-8
__author__ = 'liu.chunming'

import logging

logging.basicConfig(level=logging.INFO,
                    format='%(asctime)s - %(filename)s[line:%(lineno)d] - %(levelname)s: %(message)s')
# use logging
logging.debug('this is a loggging debug message')
logging.info('this is a loggging info message')
logging.warning('this is loggging a warning message')
logging.error('this is an loggging error message')
logging.critical('this is a loggging critical message')

通过logging.basicConfig函数对日志的输出格式及方式做相关配置,上面代码设置日志的输出等级是WARNING级别,意思是WARNING级别以上的日志才会输出。另外还制定了日志输出的格式。

24.2 日志的格式

通过format定义日志格式,通常我是用的日志格式就像上面那样:

%(asctime)s %(filename)s[%(lineno)d]: %(levelname)s %(message)s

通过这个日志格式打印出来的日志效果是:

2020-05-30 12:50:47,273 - learn_str.py[line:10] - INFO: this is a loggging info message
2020-05-30 12:50:47,274 - learn_str.py[line:11] - WARNING: this is loggging a warning message
2020-05-30 12:50:47,274 - learn_str.py[line:12] - ERROR: this is an loggging error message
2020-05-30 12:50:47,274 - learn_str.py[line:13] - CRITICAL: this is a loggging critical message

24.3 工程中如何使用日志

前面输出日志的方法,是将日志输出到控制台了。但是实际工程中,往往是输出到文件中。优点在于,它将配置信息和代码进行了分离,这一方面降低了日志的维护成本,同时还使得非开发人员也能够去很容易地修改日志配置。

24.3.1 准备日志配置文件

在项目目录下config文件夹中,创建一个日志配置文件logger_config.ini。内容如下:

[loggers]
;声明多个日志器logger,root是必须的,也可定义多个,比如这里的simpleExample,必须在下面的[logger_]部分定义
keys=root,simpleExample

[handlers]
;声明了三个处理器handlers,必须在下面[handler_]中定义。
keys=rotatingFileHandler,streamHandler,errorHandler

[formatters]
;声明格式器formatter
keys=simpleFmt

[logger_root]
;handlers必须取自handlers这个section,并且
level=DEBUG
handlers=rotatingFileHandler,streamHandler,errorHandler

[logger_simpleExample]
;非root的logger,必须定义qualname
level=DEBUG
handlers=streamHandler
qualname=simpleExample
propagate=0

;定义日志格式
[formatter_simpleFmt]
format=%(asctime)s %(filename)s[%(lineno)d]: %(levelname)s %(message)s

;定义rotatingFileHandler,输出
[handler_rotatingFileHandler]
class=handlers.TimedRotatingFileHandler
;每一天午夜12点将当天的日志转存到一份新的日志文件中,并且加上时间戳后缀,最多保存6个文件,编码格式UTF-8,支持中文。
args=(os.path.abspath(os.getcwd() + "/logs/default.log"),"midnight", 1, 6,'utf-8')
level=INFO
formatter=simpleFmt

;定义errorHandler,输出错误日志到到error.log
[handler_errorHandler]
class=handlers.TimedRotatingFileHandler
args=(os.path.abspath(os.getcwd() + "/logs/error.log"), "midnight", 1, 6,'utf-8')
level=ERROR
formatter=simpleFmt

;定义streamHandler,输出日志到控制台
[handler_streamHandler]
class=StreamHandler
args=(sys.stdout,)
level=INFO
formatter=simpleFmt

配置文件中一定要包含loggershandlersformatters这些section。它们通过keys这个option来指定该配置文件中需要定义的loggers、handlers和formatters,多个值之间用逗号分隔;另外loggers这个section中的keys一定要包含root这个值;

seciton的命名规则为[logger_loggerName][formatter_formatterName][handler_handlerName]

logger的section必须指定levelhandlers这两个option,level的可取值为DEBUGINFOWARNINGERRORCRITICALhandlers的值是以逗号分隔的handler名字列表,这里出现的handler必须出现在[handlers]这个section中,并且相应的handler必须在配置文件中有对应的section定义;

对于非root logger来说,除了levelhandlers这两个option之外,还需要一些额外的option,其中qualname是必须提供的option,它表示在logger层级中的名字,在应用代码中通过这个名字得到logger;propagate是可选项,其默认是为1,表示消息将会传递给高层次logger的handler,通常我们需要指定其值为0。

formatter的sectioin中,所有option都是可选的,但是一般都会包括format option用于指定日志格式。

handler的section中,必须指定classargs这两个option,levelformatter是可选的,class 表示用于创建handler的类名,args表示传递给class所指定的handler类初始化方法参数,它必须是一个元组(tuple)的形式,即便只有一个参数值也需要是一个元组的形式;level与logger中的level一样,而formatter指定的是该处理器所使用的格式器,来自formatters这个sections。

上面的例子中,使用的FileHandler是handlers.TimedRotatingFileHandler,可以按照时间滚动转存日志,避免一个日志文件变的非常大。另外一个支持滚动转存的日志Handler是RotatingFileHandler,根据文件大小转存。内置的RotatingFileHandlerTimedRotatingFileHandler对于多线程是安全的,但是不支持多进程。多进程的时候可以使用ConcurrentLogHandler(需要自行安装)按照文件大小轮转。

24.3.2 编写一个logger.py

在项目录下的utils目录里面,创建一个logger.py文件,通过logging.config.fileConfig读取日志配置文件logger_config.ini,并且通过logging.getLogger获取全局logger对象。

### import logging

import logging.config
import os


def get_logger(name='root'):  # 获取名字叫做root的logger配置
    conf_log = os.path.abspath(os.path.dirname(os.path.dirname(__file__)) + "/config/logger_config.ini")
    logging.config.fileConfig(conf_log)  # 读取配置文件
    return logging.getLogger(name)  # 获取logger对象

log = get_logger(__name__)  # 获取全局logger对象

24.3.3 使用log

from utils.logger import log

log.info("start")

try:
    print(1 / 0)
except Exception as e:
    log.exception(e)
log.debug("end")