上一章学习了自动化测试,很好,如今咱们能够绞尽脑汁写出一份全面的测试,来保证代码永远健康了。html
话虽如此,可是做为一个独立开发者很难写出真正全面的测试代码。这是由于用户在使用你的网站时可不会循规蹈矩,而是会以各类怪异的姿式浏览网页、上传数据。但这也不是坏事,用户就是自然的测试人员,他们会很可爱的帮你找出一大堆的bug,陪你度过难眠的夜晚(伴随着编程能力的提高)。python
如今的问题是,开发者如何得知用户到底遇到了哪些问题?用户们大部分都与你素昧生平,分部在世界各地。更糟糕的是,部署在线上时因为配置了DEBUG = False
,出错时并不会出现报错页面,连用户本身都不清楚究竟是哪里有bug。git
Django给你的答案:日志。github
日志是指程序在运行过程当中,对状态、时间、错误等的记录。即把运行过程当中产生的信息输出或保存起来,供开发者查阅。sql
Django使用Python内置的logging
模块处理日志。关于该模块的使用,Python文档里有很是详细的讨论。若是你从未用过,本文提供一个快速入门。数据库
日志事件的信息流程以下:django
这个图看不懂也不要紧。之后你须要深度使用日志时,会回来仔细研究它的。编程
一份日志配置由Loggers
、Handlers
、Filters
、Formatters
四部分组成。浏览器
Logger
即记录器,是日志系统的入口。它有三个重要的工做:bash
Handler
)每一条写入logger的消息都是一条日志记录。每一条日志记录也包含级别,表明对应消息的严重程度。经常使用的级别以下:
DEBUG
:排查故障时使用的低级别系统信息,一般开发时使用INFO
:通常的系统信息,并不算问题WARNING
:描述系统发生的小问题的信息,但一般不影响功能ERROR
:描述系统发生的大问题的信息,可能会致使功能不正常CRITICAL
:描述系统发生严重问题的信息,应用程序有崩溃风险当logger处理一条消息时,会将本身的日志级别和这条消息的日志级别作对比。若是消息的级别匹配或者高于logger的日志级别,它就会被进一步处理;不然这条消息就会被忽略掉。
当logger肯定了一条消息须要处理以后,会把它传给Handler
。
Handler
即处理器,它的主要功能是决定如何处理logger中每一条消息,好比把消息输出到屏幕、文件或者Email中。
和logger同样,handler也有级别的概念。若是一条日志记录的级别不匹配或者低于handler的日志级别,则会被handler忽略。
一个logger能够有多个handler,每个handler能够有不一样的日志级别。这样就能够根据消息的重要性不一样,来提供不一样类型的输出。例如,你能够添加一个handler把ERROR
和CRITICAL
消息发到你的Email,再添加另外一个 handler把全部的消息(包括ERROR
和CRITICAL
消息)保存到文件里。
Filter
即过滤器。在日志记录从logger传到handler的过程当中,使用Filter
来作额外的控制。例如只容许某个特定来源的ERROR
消息输出。
Filter还被用来在日志输出以前对日志记录作修改。例如当知足必定条件时,把日志记录从 ERROR
降到 WARNING
级别。
Filter在logger和handler中均可以添加;多个filter能够连接起来使用,来作多重过滤操做。
Formatter
即格式化器,主要功能是肯定最终输出的形式和内容。
说了这么多脑袋都说晕了,接下来看两个示例。
在Django中能够经过字典的形式对整个项目的日志进行配置,配置的位置固然是在settings.py
中了。一个简单的配置以下:
my_blog/settings.py
...
import os
LOGGING = {
'version': 1,
'disable_existing_loggers': False,
'handlers': {
'file': {
'level': 'DEBUG',
'class': 'logging.FileHandler',
'filename': os.path.join(BASE_DIR, 'logs/debug.log'),
},
},
'loggers': {
'django': {
'handlers': ['file'],
'level': 'DEBUG',
'propagate': True,
},
},
}
复制代码
字典中的version
指明了配置的版本;disable_existing_loggers
指明是否禁止默认配置的记录器。这两项一般不须要去改动,重点看下loggers
和handlers
的配置:
Django
记录器,它会接收Django层次结构中的全部消息。而后咱们定义了须要处理DEBUG
以上级别的消息,并把这些消息传递给名叫file
的处理器。'propagate': True
意思是本记录器处理过的消息其余处理器也能够继续处理。file
的handlers
中了。这个处理器定义了消息处理级别仍然为DEBUG,在class中定义将消息输出到文件中去,文件地址为项目目录的logs/debug.log
。filters
和formatters
,所以会采用默认的设置。须要注意的是日志的输出文件的目录logs/
必定要提早建立好,而且确保项目拥有此目录的写入权限。
这个日志系统就配置好了!接下来运行项目,随便刷新几个页面看看debug.log
中有没有写入消息:
logs/debug.log
(0.001)
SELECT name, type FROM sqlite_master
WHERE type in ('table', 'view') AND NOT name='sqlite_sequence'
ORDER BY name; args=None
(0.000) SELECT "django_migrations"."app", "django_migrations"."name" FROM "django_migrations"; args=()
...
...
...
复制代码
debug.log
文件中出现了一大堆冗长的信息,由于DEBUG
级别会包含全部的数据库查询记录。
默认状况下,仅在调试模式下才会显示
DEBUG
级别的消息日志,部署在线上时只会将INFO
或以上的信息进行记录。
再试试别的。把上面代码中记录器和处理器的日志级别都改成INFO
:
LOGGING = {
...
'handlers': {
'file': {
'level': 'INFO',
...
},
},
'loggers': {
'django': {
'level': 'INFO',
...
},
},
}
复制代码
再刷新几回界面,看看输出的内容:
"GET /article/article-list/ HTTP/1.1" 200 14438
"GET /article/article-detail/32/ HTTP/1.1" 200 33364
"GET /accounts/login/ HTTP/1.1" 200 7180
...
复制代码
此次清爽多了,输出的主要是页面的拉取信息。
让咱们再看看ERROR
信息长什么样的。在地址栏输入一个不存在的文章详情页面地址:
http://127.0.0.1:8000/article/article-detail/9999/
复制代码
很明显这会获得一个数据不存在的报错:
Internal Server Error: /article/article-detail/9999/
Traceback (most recent call last):
File "E:\django_project\env\lib\site-packages\django\core\handlers\exception.py", line 34, in inner
response = get_response(request)
...
article.models.ArticlePost.DoesNotExist: ArticlePost matching query does not exist.
"GET /article/article-detail/9999/ HTTP/1.1" 500 80792
复制代码
ERROR
日志输出了整个bug的回溯,和你在浏览器中的报错是彻底同样的,这些信息就很是的有用了。基本上ERROR
信息可以暴露出用户在使用你的网站过程当中的大部分问题;也就是说每个ERROR
都是须要你去解决掉的。ERROR
信息的错误码一般都是“500”,也就是服务器内部错误的代码。
不过仔细想一想,彷佛找不到对应的资源在不少时候并非bug,而是用户在输入url时本身犯了错误。因此咱们把文章详情视图的ArticlePost.objects.get(id=id)
改为get_object_or_404(ArticlePost, id=id)
试试:
article/views.py
...
from django.shortcuts import get_object_or_404
def article_detail(request, id):
# 取出相应的文章
# article = ArticlePost.objects.get(id=id)
article = get_object_or_404(ArticlePost, id=id)
...
复制代码
服务器重启后再次刷新一个不存在的页面,看看日志:
Not Found: /article/article-detail/9999/
"GET /article/article-detail/9999/ HTTP/1.1" 404 1780
复制代码
如今它不是一条ERROR
信息了,而是变为了WARNING
,因此也没有了错误回溯(错误码也由 500 变成了 404)。这里就能看出这两个方法的重要区别了;在项目中到底选择哪一个没有定论,仍是以你的具体需求决定。
接下来再看一个复杂的日志配置:
LOGGING = {
'version': 1,
'disable_existing_loggers': False,
'formatters': {
'verbose': {
'format': '{levelname} {asctime} {module} {process:d} {thread:d} {message}',
'style': '{',
},
'simple': {
'format': '{levelname} {message}',
'style': '{',
},
},
'filters': {
'require_debug_true': {
'()': 'django.utils.log.RequireDebugTrue',
},
},
'handlers': {
'console': {
'level': 'INFO',
'filters': ['require_debug_true'],
'class': 'logging.StreamHandler',
'formatter': 'simple'
},
'mail_admins': {
'level': 'ERROR',
'class': 'django.utils.log.AdminEmailHandler',
'formatter': 'verbose',
},
'file': {
'level': 'WARNING',
'class': 'logging.FileHandler',
'filename': os.path.join(BASE_DIR, 'logs/debug.log'),
'formatter': 'verbose',
},
},
'loggers': {
'django': {
'handlers': ['console'],
'propagate': True,
},
'django.request': {
'handlers': ['file', 'mail_admins'],
'level': 'WARNING',
'propagate': False,
},
}
}
复制代码
让咱们来分解一下此配置。
配置中定义了两个格式化器:
verbose
:详细的格式化器,依次输出:消息级别、发生时间、抛出模块、进程ID、线程ID、提示信息simple
:简要的格式化器,仅输出消息级别和提示信息一个过滤器:
require_debug_true
:使用此过滤器的消息仅在调试时才会生效三个处理器:
console
:处理INFO
以上级别消息,输出简要信息到命令行中;此处理器仅在调试模式生效mail_admins
:处理ERROR
以上级别消息,输出详细信息到Email中file
:处理WARNING
以上级别消息,输出详细信息到文件中两个记录器:
django
:将django产生的全部消息转交给console
处理器django.request
:将网络请求相关消息转交给file
、mail_admins
这两个处理器。注意这里的'propagate': False
使得此记录器处理过的消息就再也不让django
记录器再次处理了读者能够尝试制造不一样级别的消息,看看日志系统是否正常工做。固然最重要的,跟Email有关的配置必定要事先把Email给设置好,即下面的内容填成你的:
# SMTP服务器
EMAIL_HOST = 'your smtp'
# 邮箱名
EMAIL_HOST_USER = 'your email'
# 邮箱密码
EMAIL_HOST_PASSWORD = 'your password'
# 发送邮件的端口
EMAIL_PORT = 25
# 是否使用 TLS
EMAIL_USE_TLS = True
# 默认的发件人
DEFAULT_FROM_EMAIL = 'your email'
复制代码
如今咱们已经能够愉快的记录日志了,接下来一个问题是如何去分割日志?假设你的网站可以有幸运行十年时间,若是不间断的往同一个文件中写日志信息,最终它会变成一个拖垮服务器的庞然大物。
最好是日志可以按天然天进行记录和分割。好在这个问题也不须要你去费脑筋,Python帮你搞定了。
只须要把处理器稍稍改一下:
my_blog/settings.py
...
LOGGING = {
...
'handlers': {
...
'file': {
...
# 注释掉 class
# 'class': 'logging.FileHandler',
#新增内容
'class': 'logging.handlers.TimedRotatingFileHandler',
'when': 'midnight',
'backupCount': 30,
},
},
...
}
复制代码
TimedRotatingFileHandler
:Python内置的随时间分割日志文件的模块when
:分割时间为凌晨backupCount
:日志文件保存日期为30天接下来把系统时间日后调一天,而后从新启动服务器:
python manage.py runserver --noreload
复制代码
注意此次启动有点不同,后面有个--noreload
后缀。这是由于一般Django的调试服务器运行时会顺带启动重载器,因此每当重载器检测到代码有变化后,会自动重启服务器,至关的方便。但问题是分割文件与重载器同时操做日志文件会产生冲突,所以这里必定要用--noreload
暂时将重载器禁止掉。
而后就能够愉快的刷几条消息到文件中啦。你会发现老日志已经改名为debug.log.2019-07-17
了,而刚刷的新消息则保存在debug.log
中。
除了上面介绍的
TimedRotatingFileHandler
,Python还提供了一个按照文件大小分割的RotatingFileHandler
。有兴趣的看Python官方文档
内置配置实际上已经可以知足90%以上的日志需求了,但总有时候你想在一些奇怪的地方进行记录,这就须要你本身在代码中插入自定义的日志记录代码了。
自定义日志用起来也是至关方便的:
from my_blog.settings import LOGGING
import logging
logging.config.dictConfig(LOGGING)
logger = logging.getLogger('django.request')
def whatever(request):
# do something
logger.warning('Something went wrong!')
# do something else
复制代码
导入刚才写的日志框架并将django.request
配置到logger
对象中。而后你就能够在任何地方安插任何级别的消息了。消息内容能够用字符串的格式化方法(str.format()
),玩出各类花样。
关于日志的入门介绍就到此为止了,想深刻学习的读者请继续阅读本文的参考文章:
和上章相似,本章的内容也是概念偏多,但愿读者尽量去理解,最起码要囫囵吞枣的把日志成功移植到你的项目中去。获取一份好的日志,有时候远比开发一个可有可无的新功能更重要。
比较起来博主认为对博客项目来讲,日志比测试还重要,毕竟用户的使用体验是最佳的实践。
但请不要误会个人意思。测试和日志就像两兄弟,测试解决开发中的问题,日志解决维护中的问题。有机的结合起来,你的项目才可以长期稳定健康。