python-标准库-logging

344 阅读4分钟

通过记录日志,可以持久化保存程序的每一个历史操作或者异常,这可以帮助你轻松的定位问题或者查询历史记录。

一个简单的例子:例1

举个简单的例子来说明以下模块的基本用法:

场景

输出不同级别的日志到控制台,要求输出的日志的时间,级别信息,并且在异常情况的话打印输出执行错误的堆栈信息。

代码

# 代码1
import logging

logging.basicConfig(
    level=logging.DEBUG, 
    format='%(asctime)s %(name)s [%(levelname)s]: %(message)s', 
    datefmt='%Y-%m-%d %H:%M:%S', 
    stream=sys.stdout)

if __name__ == '__main__':
    logging.debug(1)
    logging.info(2)
    logging.warning(3)
    try:
        raise TypeError('参数类型错误')
    except TypeError as e:
        logging.error(e, exc_info=True)

    try:
        raise Exception('崩溃')  
    except Exception as e:
        logging.critical(e, exc_info=True)

输出

2020-07-12 10:17:17 root [DEBUG]: 1
2020-07-12 10:17:17 root [INFO]: 2
2020-07-12 10:17:17 root [WARNING]: 3
2020-07-12 10:17:17 root [ERROR]: 参数类型错误
Traceback (most recent call last):
  File "_logging.py", line 49, in <module>
    raise TypeError('参数类型错误')
TypeError: 参数类型错误
2020-07-12 10:17:17 root [CRITICAL]: 崩溃
Traceback (most recent call last):
  File "_logging.py", line 54, in <module>
    raise Exception('崩溃')
Exception: 崩溃

分析例1的流程

在分析之前需要了解logging模块相关的一些基础概念

日志的级别

级别 记录的信息
debug 帮助调试代码的信息,比如打印一些描述和变量信息来辅助定位问题
info 主流程信息,比如:执行xxx,执行成功/失败
warning 一些可能影响程序异常或者安全问题的提示信息, 比如:磁盘空间不足
error 非法的输入或者程序临时的特殊状态导致的执行异常, 这时并不希望程序奔溃而是可以继续执行。比如: 错误的输入,重复提交
critical 程序奔溃信息

通过分析例1的执行流程,来说明一下logging的核心概念

基础类

类名 职责
LogRecord 每一条日志
Filter 抽象类,根据条件过滤日志信息,比如根据日志级别来过滤日志信息
Logger(Filtter) 接收日志,根据过滤条件对日志进行筛选然后交给handlers进行操作
Handler(Filtter) 抽象类,过滤日志,然后处理日志
Formatter 日志的输出格式

例1的流程详解

为了更加清晰的表达流程, 换一套代码来实现以上例子, 两套代码是等价的。

from logging import Logger, StreamHandler, Formatter

logger = Logger('root', level=logging.DEBUG)
standard_formatter = Formatter(fmt='$asctime $name [$levelname]: $message', datefmt='%Y-%m-%d %H:%M:%S', style='$')
console_handler = StreamHandler(stream=sys.stdout)
console_handler.setFormatter(standard_formatter)
logger.addHandler(console_handler)

if __name__ == "__main__":
    logger.debug('1')
    logger.info('2')
    logger.warning('3')
    try:
        raise TypeError('参数类型错误')
    except TypeError as e:
        logger.error(e, exc_info=True)
    try:
        raise Exception('崩溃')  
    except Exception as e:
        logger.critical(e, exc_info=True)

现在描述一下具体流程:

  1. logging.debug(1)...产生了5条日志, 分别记为debug1, info2, error3, waring4和critical5

  2. 这些记录首先会经过logger的筛选,条件是满足日志级别>DEBUG,所以5条记录都通过过滤, logger派发给console_handler

  3. console_handler接受到5条日志(默认的handler会继承logger的日志级别),将5条记录通过standard_formatter格式化输出到命令行

更复杂的例子: 例2

上面的例子只是将日志输出的控制台,真实场景下我们往往需要将日志持久化到本地文件,并对本地日志进行管理。

场景

考虑这么个场景:

1.我们要将日志按照日记级别分别输出到不同的文件下。

2.按每天为单位记录将日志记录到文件,每天的日志按照日期来命名,并且只存储近一周的日志。

3.支持两种输出格式: "{时间} {日志等级}: {内容}" 和 "{时间} {线程名称} {日志等级}: {内容}"

代码

import logging
import time
import sys
from logging import Formatter, StreamHandler, FileHandler, Logger, config
from logging.handlers import TimedRotatingFileHandler

logger = Logger('root')
simple_formatter = Formatter(fmt='{asctime} {levelname:8s}: {message}', datefmt='%Y-%m-%d %H:%M:%S', style='{')
standard_formatter = Formatter(fmt='{asctime} {threadName:8s} {levelname:8s}: {message}', datefmt='%Y-%m-%d %H:%M:%S', style='{')
console_handler = StreamHandler(stream=sys.stdout)
# 为了方便演示效果设置成了每秒记录日志,最大文件数为5
debug_handler =  TimedRotatingFileHandler('debug', when='s',backupCount=3)
info_handler = TimedRotatingFileHandler('info', when='s', backupCount=3)
error_handler = TimedRotatingFileHandler('error', when='s', backupCount=3)
console_handler.setFormatter(standard_formatter)
debug_handler.setFormatter(standard_formatter)
info_handler.setFormatter(simple_formatter)
info_handler.setLevel(logging.INFO)
error_handler.setFormatter(standard_formatter)
error_handler.setLevel(logging.ERROR)
logger.addHandler(console_handler)
logger.addHandler(debug_handler)
logger.addHandler(info_handler)
logger.addHandler(error_handler)
if __name__ == "__main__":
    s = time.time()
    while time.time() - s < 10:
        logger.debug('1')
        logger.info('2')
        logger.warning('3')
        try:
            raise TypeError('参数类型错误')
        except TypeError as e:
            logger.error(e, exc_info=True)
        try:
            raise Exception('崩溃')  
        except Exception as e:
            logger.critical(e, exc_info=True)
        time.sleep(0.1)

输出

生成的日志文件:

debug:

info:

error:

使用配置重写例2

例2的代码过于臃肿,logging模块提供了更简单直观的配置方法

import logging
import time
CONFIG = {
    "version": 1,
    "disable_existing_loggers": False,
    "formatters": {
        "simple": {
            'format': '{asctime} {levelname:8s}: {message}',
            'style': '{'
        },
        'standard': {
            'format': '{asctime} {threadname:8s} {levelname:8s}: {message}',
            'style': '{'
        },
    },
    "handlers": {
        "console": {
            "class": "logging.StreamHandler",
            "level": "DEBUG",
            "formatter": "standard",
            "stream": "ext://sys.stdout"
        },
        "debug": {
            "class": "logging.handlers.TimedRotatingFileHandler",
            "level": "DEBUG",
            "formatter": "standard",
            "filename": '',
            "when": "d",
            "backupCount": 7,
            "encoding": "utf8"
        },
        "info": {
            "class": "logging.handlers.TimedRotatingFileHandler",
            "level": "INFO",
            "formatter": "simple",
            "filename": '',
            "when": "d",
            "backupCount": 7,
            "encoding": "utf8"
        },
        "error": {
            "class": "logging.handlers.TimedRotatingFileHandler",
            "level": "ERROR",
            "formatter": "standard",
            "filename": '',
            "when": "d",
            "backupCount": 7,
            "encoding": "utf8"
        }
    },
    "root": {
        'handlers': ["console", "debug", "info", "error"],
        'level': "DEBUG"
    }
}
logging.config.dictConfig(CONFIG)
logger = logging.getLogger()