Python的logging模块:日志处理
日志概念&介绍:
日志可以追踪记录软件运行时发生时间的方法。不仅仅是报错处理,还可以做到一些事件发生时的记录,以及对时间的定性描述。因此,事件也引申出了一个重要性概念,称之为严重性级别(level)
Python的logging模块比较灵活,使得可以和任何第三方库代码相互结合进行日志生成,而且相比较于print将信息统统输出到输入输出流中,logging模块可以做到写入文件,输出到输出流,甚至写入远程服务器等,此外还具有灵活的配置方法和格式化功能,可以格式化生成信息时间,严重程度等等信息。
logging主要由如下四个模块组件组成
- Loggers: 【记录器】可供程序直接调用的接口
- Handlers: 【处理器】决定将日志记录分配至正确的目的地
- Filters: 【过滤器】提供更细粒度的日志是否输出的判断
- Formatters: 【格式化器】制定最终记录打印的格式布局
1.loggers
loggers 就是程序可以直接调用的一个日志接口,可以直接向logger写入日志信息,logger并不是直接实例化使用的,而是通过logging.getLogger(name)来获取对象,有时候简单点也可以直接把name不填写。通常情况下,logger的名字我们需要对应模块名,如通行协议模块,数据库模块 ,逻辑层模块,业务层模块,验证窗口模块,算法模块等等信息。
logging日志级别
名称 | 对应函数 | 描述 | 严重度 |
NOTSET | 0级别,相当于没有 | 0 | |
DEBUG | logging.debug() | 最低级别,用于小细节,通常只有在诊断时才关心此消息 | 10 |
INFO | logging.info() | 用于记录程序中一般时间的信息,或确认一切工作正常 | 20 |
WARNING | logging.warning() | 用于表示可能的问题,不会阻止程序工作,但是将来可能会 | 30 |
ERROR | logging.error() | 用于记录错误,它导致程序在某些功能上失败 | 40 |
CRITICAL | logging.critical() | 最高级别,表示致命错误,它将会导致程序完全停止工作 | 50 |
相关API有
- Logger.setLevel()指定logger将会处理的最低的安全等级日志信息。
- Logger.addHandler()和Logger.removeHandler()从记录器对象中添加和删除处理程序对象。处理器详见Handlers。
- Logger.addFilter()和Logger.removeFilter()从记录器对象添加和删除过滤器对象。
2.Handlers
Handlers 将logger发过来的信息进行准确地分配,送往正确的地方。举个栗子,送往控制台或者文件或者both或者其他地方(进程管道之类的)。它决定了每个日志的行为,是之后需要配置的重点区域。
每个Handler同样有一个日志级别,一个logger可以拥有多个handler也就是说logger可以根据不同的日志级别将日志传递给不同的handler。当然也可以相同的级别传递给多个handlers这就根据需求来灵活的设置了。
常用API有
- logging.StreamHandler -> 控制台输出
- logging.FileHandler -> 文件输出
- logging.handlers.RotatingFileHandler -> 按照大小自动分割日志文件,一旦达到指定的大小重新生成文件
- logging.handlers.TimedRotatingFileHandler -> 按照时间自动分割日志文件
3.Filters
Filters 提供了更细粒度的判断,来决定日志是否需要打印。原则上handler获得一个日志就必定会根据级别被统一处理,但是如果handler拥有一个Filter可以对日志进行额外的处理和判断。例如Filter能够对来自特定源的日志进行拦截or修改甚至修改其日志级别(修改后再进行级别判断)。
logger和handler都可以安装filter甚至可以安装多个filter串联起来。
4.Formatters
asctime | %(asctime)s | 将日志的时间构造成可读的形式,默认情况下是‘2021-03-08 12:00:00,123’精确到毫秒 |
filename | %(filename)s | 包含path的文件名 |
funcName | %(funcName)s | 由哪个function发出的log |
levelname | %(levelname)s | 日志的最终等级(被filter修改后的) |
message | %(message)s | 日志信息 |
lineno | %(lineno)d | 当前日志的行号 |
pathname | %(pathname)s | 完整路径 |
process | %(process)s | 当前进程 |
thread | %(thread)s | 当前线程 |
上代码
1.简单的控制台输出
一个最简单,最基本的控制台输出
import logging
#设置输出格式
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')
logger = logging.getLogger(__name__)
logger.info("Start print log")
logger.debug("Do something")
logger.warning("Something maybe fail.")
logger.info("Finish")
output: 2021-03-08 14:36:31,929 - __main__ - INFO - Start print log 2021-03-08 14:36:31,929 - __main__ - WARNING - Something maybe fail. 2021-03-08 14:36:31,929 - __main__ - INFO - Finish
2.文件输出
这是最常用的模式,毕竟在真实的代码情况下,后端程序不可能人为的时时刻刻盯着,于是就要输出到日志文件中。
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)
logger.addHandler(handler)
logger.info("Start print log")
logger.debug("Do something")
logger.warning("Something maybe fail.")
logger.info("Finish")
在同路径下的log.txt文件中存在
2021-03-08 15:00:45,289 - __main__ - INFO - Start print log 2021-03-08 15:00:45,290 - __main__ - WARNING - Something maybe fail. 2021-03-08 15:00:45,290 - __main__ - INFO - Finish
3.结合traceback进行进行文件报错输出
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("Start print log") logger.debug("Do something") logger.warning("Something maybe fail.") try: open("sklearn.txt", "rb") except (SystemExit, KeyboardInterrupt): raise except Exception: logger.error("Faild to open sklearn.txt from logger.error", exc_info=True) finally: logger.info("Whatever it will happen.") logger.info("Finish")
在log.txt文件与控制台中均中会生成如下内容:
2021-03-08 15:03:16,088 - __main__ - INFO - Start print log 2021-03-08 15:03:16,088 - __main__ - WARNING - Something maybe fail. 2021-03-08 15:03:16,089 - __main__ - ERROR - Faild to open sklearn.txt from logger.error Traceback (most recent call last): File "/Users/mt-007/Desktop/PythonCode/pythonProject/test_logging.py", line 20, in <module> open("sklearn.txt", "rb") FileNotFoundError: [Errno 2] No such file or directory: 'sklearn.txt' 2021-03-08 15:03:16,089 - __main__ - INFO - Whatever it will happen. 2021-03-08 15:03:16,089 - __main__ - INFO - Finish
封装
实际开发情况中,我们会将logging单独写成一个模块封装成类,然后在其他的功能模块比如通讯协议模块,数据库模块,业务层,API调用模块等等,中进行调用,再配合try-catch进行错误分析。
在实际情况中,在前文提到的logging.getLogger(“”)传入无名称适用于只需要小范围系统使用,一旦涉及到多个模块,需要系统划分的情况,建议把class设置一个传入参数,并且将这个参数传递给logging.getLogger(“”),以规范日志的划分。
可以参考如下的模块(这个模块也是参考网上别人发的播客,这类有很多,基本思路概念都是相同的)
import logging
import logging.handlers
import os
import time
class logs(object):
def __init__(self):
# 获取模块名称,测试的时候直接控模块即可,但是在实际使用的情况下需要针对不同需要进行日志撰写的模块进行命名
# 列如:通讯协议模块,测试模块,数据库模块,业务层模块,API调用模块
# 可以考虑 __init__(self,model_name) 这样传入,然后再用一个list规定一下模块名称
self.logger = logging.getLogger("")
# 设置输出的等级
LEVELS = {'NOSET': logging.NOTSET,
'DEBUG': logging.DEBUG,
'INFO': logging.INFO,
'WARNING': logging.WARNING,
'ERROR': logging.ERROR,
'CRITICAL': logging.CRITICAL}
# 创建文件目录
logs_dir = "logs"
if os.path.exists(logs_dir) and os.path.isdir(logs_dir):
pass
else:
os.mkdir(logs_dir)
# 修改log保存位置
timestamp = time.strftime("%Y-%m-%d", time.localtime())
logfilename = '%s.txt' % timestamp
logfilepath = os.path.join(logs_dir, logfilename)
rotatingFileHandler = logging.handlers.RotatingFileHandler(filename=logfilepath,
maxBytes=1024 * 1024 * 50,
backupCount=5)
# 设置输出格式
formatter = logging.Formatter('[%(asctime)s] [%(levelname)s] %(message)s', '%Y-%m-%d %H:%M:%S')
rotatingFileHandler.setFormatter(formatter)
# 控制台句柄
console = logging.StreamHandler()
console.setLevel(logging.NOTSET)
console.setFormatter(formatter)
# 添加内容到日志句柄中
self.logger.addHandler(rotatingFileHandler)
self.logger.addHandler(console)
self.logger.setLevel(logging.NOTSET)
def info(self, message):
self.logger.info(message)
def debug(self, message):
self.logger.debug(message)
def warning(self, message):
self.logger.warning(message)
def error(self, message):
self.logger.error(message)
其他模块可以直接调用输出,在实际情况中最好结合try-catch返回的excepte的值将其输出出来,而在实际使用中,可以try-catch中的except需要发生了错误需要进行logger.error()输出,也不要吝啬代码量,在需要进行一些提示信息的地方logger.info(),此外有一些操作可能引发错误也可以logger.warning(),总之不要吝啬,也不要画蛇添足。
最后是其他模块如何调用此模块的代码,封装好的logging模块我命名为loguil2,注意调用它的同时也需要引入logging,重复引用注意留意打包问题。
import logging
logger = logging.getLogger(__name__)
import logutil2
if __name__ == '__main__':
logger = logutil2.logs()
a = [1,2,3]
logger.info("list a only have 1,2,3")
logger.warning("操作不当可能导致错误发生")
try:
print(a[3])
logger.debug("this is debug")
except Exception as e:
logger.error(e)
finally:
logger.info("无论如何需要执行此")
在logs/的自动生成的日期文件中会产生如下输出:
[2021-03-08 15:14:29] [INFO] list a only have 1,2,3 [2021-03-08 15:14:29] [WARNING] 操作不当可能导致错误发生 [2021-03-08 15:14:29] [ERROR] list index out of range [2021-03-08 15:14:29] [INFO] 无论如何需要执行此
参考许多播客汇总写出来的
0 条评论