通过记录日志,可以持久化保存程序的每一个历史操作或者异常,这可以帮助你轻松的定位问题或者查询历史记录。
一个简单的例子:例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)
现在描述一下具体流程:
-
logging.debug(1)...产生了5条日志, 分别记为debug1, info2, error3, waring4和critical5
-
这些记录首先会经过logger的筛选,条件是满足日志级别>DEBUG,所以5条记录都通过过滤, logger派发给console_handler
-
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()