段子里说,使用日志的程序员鄙视使用print记录信息的程序员,所以作为一个合格的程序员,合理的记录日志实数非常必要的。本文主要介绍在python中如何使用logging模块记录日志。
我想对于程序员来说,记录日志的重要性不言而喻,各种bug的调试都离不开日志信息的参考,但是如何记录日志以及该记录一些什么信息却不是轻而易举就能掌握的。对于如何使用日志,网络大神已经给出了很好的答案。
应该将log信息对应到适当的级别,有一下经验可以遵循:
应该写有意义的log,每一个log都应该对应有其价值
logging是python内置的一个标准模块,主要用于输出或者保存程序运行日志,它有以下优点:
- 可以设置输出日志的等级、日志保存路径、日志文件回滚
- 不仅可以控制台输出,还可以文件保存,不仅可以在代码中配置,还支持从配置文件中直接加载日志的配置
与print()函数相比,logging有很多优势,如果还在用print()记录日志,从现在起,习惯使用logging吧。
logging中包含了6个等级,分别是:(log是一个logging实例)
log等级 | 使用范围 | 函数 | 备注 |
---|---|---|---|
FATAL | 致命错误 | log.fatal | |
CRITICAL | 特别糟糕的事情 | log.critical | 如内存耗尽、磁盘空间为空,一般很少使用 |
ERROR | 发生错误时 | log.error | 如 IO操作失败或者连接问题 |
WARNING | 发生很重要的事件,但是并不是错误时 | log.warning | 如用户登录密码错误 |
INFO | 处理请求或者状态变化等日常事务 | log.info | |
DEBUG | 调试过程中使用DEBUG等级 | log.debug | 如 算法中每个循环的中间状态 |
在这些等级中,由下向上严重性依次递增,也就是DEBUG是最轻的,FATAL是最严重的。每一个等级都对应于一个函数用于记录对应等级的日志。当设置了输出日志的等级后,只会输出或者保存当前等级以及更严重等级的信息,这样可以在不同的环境中输出不用的调试信息。例如设置了日志等级是DEBUG,那么所有的日志都会输出;如果设置为ERROR,那么就只有ERROR,CRITICAL,FATAL这三个等级会被保存或者输出。
logging的使用流程是:1. 配置logging,包括是在控制台输出还是保存在文件、输出内容、输出格式等(可以在代码中直接配置,也可以在配置中加载配置)2.初始化一个日志类 3. 使用不同的级别日志函数记录日志
logging的基本使用是在控制台中,使用例程:
import logging
# config the logging
logging.basicConfig(level=logging.DEBUG, format='%(lineno)d - %(asctime)s - %(levelname)s - %(message)s')
# create a logging object
log = logging.getLogger(__name__)
# using logging function to log
log.info('start logging')
log.debug('this is a debug')
log.warning('just a warning')
log.error('here is a error')
log.critical('here is a critical error')
log.fatal(' big bug')
因为设置的level是DEBUG,所以所有的信息都会打印输出:
12 - 2018-09-16 21:05:38,837 - INFO - start logging
13 - 2018-09-16 21:05:38,838 - DEBUG - this is a debug
14 - 2018-09-16 21:05:38,839 - WARNING - just a warning
15 - 2018-09-16 21:05:38,840 - ERROR - here is a error
16 - 2018-09-16 21:05:38,841 - CRITICAL - here is a critical error
17 - 2018-09-16 21:05:38,842 - CRITICAL - big bug
在logging.basicconfig中把level设置为ERROR,输出为:
15 - 2018-09-16 21:54:12,820 - ERROR - here is a error
16 - 2018-09-16 21:54:12,821 - CRITICAL - here is a critical error
17 - 2018-09-16 21:54:12,821 - CRITICAL - big bug
这个例子就验证了前面所说的:当设置了输出日志的等级后,只会输出或者保存当前等级以及更严重等级的信息,这样可以在不同的环境中输出不用的调试信息。例如设置了日志等级是DEBUG,那么所有的日志都会输出;如果设置为ERROR,那么就只有ERROR,CRITICAL,FATAL这三个等级会被保存或者输出。
参数名 | 作用 | 备注 |
---|---|---|
filename | 指定日志文件名 | |
filemode | 和file函数意义相同,指定日志文件的打开模式,’w’或者’a’; | |
format | 指定输出的格式和内容 | |
datefmt | 指定时间格式,同time.strftime() | |
level | 设置日志级别 | 默认为logging.WARNNING |
stream | 指定将日志的输出流,可以指定输出到sys.stderr,sys.stdout或者文件 | 默认输出到sys.stderr,当stream和filename同时指定时,stream被忽略; |
- format参数的选择
参数名 | 参数意义 | 备注 |
---|---|---|
%(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 | |
%(message)s | 打印日志信息 |
在实例中:format='%(lineno)d - %(asctime)s - %(levelname)s - %(message)s'
就是打印当前运行行,时间,级别,日志信息
将日志保存在文件一般有三种形式:只保存在文件、控制台显示同时保存在文件、日志回滚
这种方式的使用过程是:设置logging并创建一个FileHandler,并对输出消息的格式进行设置,将其添加到logger,然后将日志写入到指定的文件中。
import logging
# Instantiate a logging
logger = logging.getLogger(__name__)
# set level
logger.setLevel(level = logging.INFO)
# use FileHander to set file
handler = logging.FileHandler("log.txt")
# set level
handler.setLevel(logging.INFO)
# set format
formatter = logging.Formatter('%(lineno)d - %(asctime)s - %(name)s - %(levelname)s - %(message)s')
handler.setFormatter(formatter)
logger.addHandler(handler)
logger.info("this is a info")
logger.debug("just debug")
logger.warning("this ia a warning")
logger.error("sorry, this is a bug")
查看txt中的内容:
16 - 2018-09-16 22:19:31,449 - __main__ - INFO - this is a info
18 - 2018-09-16 22:19:31,449 - __main__ - WARNING - this ia a warning
19 - 2018-09-16 22:19:31,449 - __main__ - ERROR - sorry, this is a bug
因为LEVEL设置为INFO所以debug的日志没有记录(debug级别比info更低)
- NOTES
代码中有两个设置level的地方,logger.setLevel(level = logging.INFO)
是设置整个logging的level,handler.setLevel(logging.INFO)
是设置打印到文件的level,实际工作中只会保存两个level中级别最严格的那个。
import logging
logger = logging.getLogger(__name__)
logger.setLevel(level = logging.INFO)
handler = logging.FileHandler("log.txt")
handler.setLevel(logging.DEBUG)
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
handler.setFormatter(formatter)
console = logging.StreamHandler()
console.setLevel(logging.ERROR)
# TWO kind way must be added
logger.addHandler(handler)
logger.addHandler(console)
logger.info("this is a info")
logger.debug("just debug")
logger.warning("this ia a warning")
logger.error("sorry, this is a bug")
控制台输出:
sorry, this is a bug
txt文件中的内容:
2018-09-16 22:30:30,913 - __main__ - INFO - this is a info
2018-09-16 22:30:30,914 - __main__ - WARNING - this ia a warning
2018-09-16 22:30:30,914 - __main__ - ERROR - sorry, this is a bug
因为控制台和文件的level设置的不一样,所以日志信息不一样
- notes
也就是说,保存文件个控制台都可以单独设置level,实际工作中以单独了level和全局level中最严格的那个level为准
将日志信息输出到一个单一的文件中,随着应用程序的持续使用,该日志文件会越来越庞大,进而影响系统的性能。因此,有必要对日志文件按某种条件进行切分,要切分日志文件,这种方式就是日志回滚。
分割日志的触发条件:大小、日期,或者大小加上日期。说是切分,实际上是,当一个日志文件达到触发条件后,对日志文件进行重命名,之后再新建原来名称的日志文件(此时就是空文件了),新产生的日志就写入新的日志文件。
为啥叫回滚呢?当分割的日志文件达到指定数目的上限个数时,最老的日志文件就会被删除。
logging模块中使用RotatingFileHandler,可以实现日志回滚。
import logging
from logging.handlers import RotatingFileHandler
logger = logging.getLogger(__name__)
logger.setLevel(level = logging.INFO)
#定义一个RotatingFileHandler,最多备份3个日志文件,每个日志文件最大1K
rHandler = RotatingFileHandler("log.txt",maxBytes = 1*512,backupCount = 3)
rHandler.setLevel(logging.INFO)
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
rHandler.setFormatter(formatter)
console = logging.StreamHandler()
console.setLevel(logging.INFO)
console.setFormatter(formatter)
logger.addHandler(rHandler)
logger.addHandler(console)
logger.info("this is a info")
logger.debug("just debug")
logger.warning("this ia a warning")
logger.error("sorry, this is a bug")
多运行几次,就看到产生了很多txt文件,但最多三个txt日志文件
-a---- 2018/9/16 22:40 453 log.txt
-a---- 2018/9/16 22:39 512 log.txt.1
logging还可以用来捕获python异常,
import logging
logger = logging.getLogger(__name__)
logger.setLevel(level = logging.INFO)
handler = logging.FileHandler("log.txt")
handler.setLevel(logging.INFO)
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
handler.setFormatter(formatter)
console = logging.StreamHandler()
console.setLevel(logging.INFO)
logger.addHandler(handler)
logger.addHandler(console)
logger.info("this is a info")
logger.debug("just debug")
logger.warning("this ia a warning")
logger.error("sorry, this is a bug")
try:
open("a.txt","rb")
except (SystemExit,KeyboardInterrupt):
raise
except Exception:
logger.error(" ",exc_info = True)
txt文件中就可以看到:
Traceback (most recent call last):
File "g:\myfiles\home\Python\HBN\get_ibfo.py", line 21, in <module>
open("a.txt","rb")
FileNotFoundError: [Errno 2] No such file or directory: 'a.txt'
后续更新在不用的模块中同时使用log以及怎么通过配置文件加载logging的配置