django:日志
Django 使用 Python 内置的 logging 模块处理系统日志
日志框架的组成元素
Python logging 配置由下面四个部分组成:
Loggers
Handlers
过滤器
Formatters
Loggers
logger 是日志系统的入口。每个 logger 都是命名了的 bucket, 消息写入 bucket 以便进一步处理。
logger 可以配置 日志级别。日志级别描述了由该 logger 处理的消息的严重性。Python 定义了下面几种日志级别:
DEBUG:排查故障时使用的低级别系统信息
INFO:一般的系统信息
WARNING:描述系统发生了一些小问题的信息
ERROR:描述系统发生了大问题的信息
CRITICAL:描述系统发生严重问题的信息
每一条写入 logger 的消息都是一条 日志记录。每一条日志记录也包含 日志级别,代表对应消息的严重程度。日志记录还包含有用的元数据,来描述被记录了日志的事件细节,例如堆栈跟踪或者错误码。
当 logger 处理一条消息时,会将自己的日志级别和这条消息的日志级别做对比。如果消息的日志级别匹配或者高于 logger 的日志级别,它就会被进一步处理。否则这条消息就会被忽略掉。
当 logger 确定了一条消息需要处理之后,会把它传给 Handler。
Handlers
Handler 是决定如何处理 logger 中每一条消息的引擎。它描述特定的日志行为,比如把消息输出到屏幕、文件或网络 socket。
和 logger 一样,handler 也有日志级别的概念。如果一条日志记录的级别不匹配或者低于 handler 的日志级别,对应的消息会被 handler 忽略。
一个 logger 可以有多个 handler,每一个 handler 可以有不同的日志级别。这样就可以根据消息的重要性不同,来提供不同格式的输出。例如,你可以添加一个 handler 把 ERROR 和 CRITICAL 消息发到寻呼机,再添加另一个 handler 把所有的消息(包括 ERROR 和 CRITICAL 消息)保存到文件里以便日后分析。
过滤器
在日志记录从 logger 传到 handler 的过程中,使用 Filter 来做额外的控制。
默认情况下,只要级别匹配,任何日志消息都会被处理。不过,也可以通过添加 filter 来给日志处理的过程增加额外条件。例如,可以添加一个 filter 只允许某个特定来源的 ERROR 消息输出。
Filter 还被用来在日志输出之前对日志记录做修改。例如,可以写一个 filter,当满足一定条件时,把日志记录从 ERROR 降到 WARNING 级别。
Filter 在 logger 和 handler 中都可以添加;多个 filter 可以链接起来使用,来做多重过滤操作。
Formatters
日志记录最终是需要以文本来呈现的。Formatter 描述了文本的格式。一个 formatter 通常由包含 LogRecord attributes 的 Python 格式化字符串组成,不过你也可以为特定的格式来配置自定义的 formatter。
1 装饰器增加日志效果
直接使用函数装饰器来为请求增加日志的打印效果,DEBUG要设为True
#logger.py
import logging
def wrap(func):
def inner(*args, **kwargs):
try:
print("记录日志~")
ret = func(*args, **kwargs)
except Exception as e:
print(f"执行路径:{__file__}")
logger = logging.getLogger(__name__)
logger.error(__file__)
# CRITICAL = 50 FATAL = CRITICAL ERROR = 40 WARNING = 30
# WARN = WARNING INFO = 20 DEBUG = 10 NOTSET = 0
logging.basicConfig(
level=20,
format='%(asctime)s [%(levelname)s][%(filename)s line:%(lineno)d]:%(message)s',
datefmt="%Y-%m-%d %H:%M:%S"
)
logger.error(f"搓搓搓,是我的搓。--{e}")
return inner
#views.py
from utils import logger
class RegisterBasicUser(APIView):
@logger.wrap
def get(self, request):
a = []
print(a[1])
return HttpResponse({"code": 200})
ps:这种方式优点是灵活选择希望打印日志的请求,添加装饰器即可,缺点是如果是全局打印日志,那么手动一个个添加装饰器不方便
2 为类的所有方法增加装饰器
此时可以把settings.py中的DEBUG设为False了
如果类的方法很多(比如CBV模式类下实现了get、post、put、delete方法,手动为每个实例方法增加装饰器很辛苦,还可能产生遗漏,可做如下修改)
# logger.py
import logging
import inspect
import datetime
import os
wrap_flag = False
def wrap(func):
count = 0
# 这里的__name__均为utils.logger,因为外部文件导入该logger文件,
# __name__就是该模块名,如果自身作为脚本运行,就是__main__
# 所以不管调用多少次装饰器,logger的内存空间地址始终是1个
logger = logging.getLogger(__name__)
# CRITICAL = 50 FATAL = CRITICAL ERROR = 40 WARNING = 30
# WARN = WARNING INFO = 20 DEBUG = 10 NOTSET = 0
base_name = datetime.datetime.now().strftime("%Y-%m-%d") + "日志.txt"
base_path = os.path.join(os.path.dirname(os.path.dirname(__file__)), "logs")
path = os.path.join(os.path.dirname(os.path.dirname(__file__)), "logs", base_name)
if not os.path.exists(base_path):
os.mkdir(base_path)
# logger.addHandler(fh),注意,fh这个handler,每次调用装饰器都会
# 增加1次,而settings.py执行的时候,就会导致所有添加了该装饰器的
# 类的实例方法,都会添加1个handler,可能会出现,1个报错,打印n条日志
# 的情况,所以我们要限制这个handler不论调用多少次装饰器,只会添加1次
fh = logging.FileHandler(path, mode="a", encoding="utf-8")
formatter = logging.Formatter('%(asctime)s [%(levelname)s][%(filename)s line:%(lineno)d]:%(message)s')
fh.setFormatter(formatter)
logging.basicConfig(
level=20,
format='%(asctime)s [%(levelname)s][%(filename)s line:%(lineno)d]:%(message)s',
datefmt="%Y-%m-%d %H:%M:%S"
)
global wrap_flag
# logger.addHandler(fh)
if (wrap_flag == False):
logger.addHandler(fh)
wrap_flag = True
count += 1
print("添加的handler数目:", count)
def inner(*args, **kwargs):
global ret
try:
print("记录日志~")
print("dhanler", count)
ret = func(*args, **kwargs)
print("结束")
except Exception as e:
print("执行异常日志打印:")
logger.error(f"搓搓搓,是我的搓。--{e}")
return inner
# 要动态的为类中的实例方法添加装饰器,需要使用inspect内置模块
def log_trace(cls):
for func_name, func_obj in inspect.getmembers(cls, lambda x: inspect.isfunction(x) or inspect.ismethod(x)):
print(func_name, "-----------")
methods = ['get', 'post', 'delete', 'put']
if func_name in methods:
setattr(cls, func_name, wrap(func_obj))
return cls
在实际Django开发的过程中,会发现settings.py执行了两次,可如下验证:
会发现print打印了两次,查询后知,开发模式下,python manage.py runserver的方式启动django项目,会启动两个线程加载settings文件,一个是服务使用的,一个是监控settings文件的改动,加上参数 --noreload即可(如果我们的日志装饰器,没有限制只加上1个文件的handler,那么会导致同一个类的get、post请求,原本加了2个handler,执行两次,就加上了4个handler,导致日志每次输入到文件的信息,都是1次4条数据,ps:因为我现在只写了1个类,仅2个接口)
# 为CBV类添加上装饰器
@logger.log_trace
class RegisterBasicUser(APIView):
运行项目:
根据打印可知,在执行urls.py时,就为类执行了装饰器,inspect.getmembers获取了我们定义的CBV类(继承了APIView,restframework中的类)中的成员,其中就含有get和post,说明装饰器成功加上,可以调用接口测试:
调用接口:
查看日志:
可见,增加了filehandler的添加限制后,只打印了1条信息,试下去掉限制:
重新执行:
可见,没有限制,只要为类添加该装饰器,所有的类中的get、post、put、delete方法均会增加1个handler,导致异常的日志有多条,因为这里我们只有1个post和1个get方法,故而记录有两条