为什么需要日志?
当Django项目正式部署上线后,我们需要设置DEBUG = False。这时开发者应怎样检查Django程序在生产环境运行时有什么异常或错误呢?答案就是日志(logging)。在生产环境中,Django默认是不会在服务器上自动生成log文件的,即使程序出现error级别的故障也不会通知管理员。本文将教你如何在Django项目中正确配置日志(logging),让Django生成log日志文件,并在程序运行发生error级别故障时通知管理员。
DEBUG
- 如果开启了
DEBUG
模式,那么以后我们修改了Django
项目的代码,然后按下ctrl+s
,那么Django
就会自动给我们重启项目,不需要手动重启。 - 如果开起来
DEBUG
模式,那么以后Django
项目中的代码出现bug
了,那么在浏览器中和控制台就会打印出错信息。 - 在生产环境中,禁止开启
DEBUG
模式,不然会有很大的安全隐患。 - 如果将DEBUG设置为False,那么必须要设置
ALLOWED_HOSTS
,
ALLOWED_HOSTS
- 这个变量是用来设置以后别人只能通过这个变量中的ip地址或者域名来进行访问。
日志基础知识
日志与我们的软件程序密不可分。它记录了程序的运行情况,可以给我们调试程序和故障排查提供非常有用的信息。每一条日志信息记录了一个事件的发生。具体而言,它包括了:
- 事件发生时间
- 事件发生位置
- 事件的严重程度–日志级别
- 事件内容
日志的级别又分为:
在Django
项目中,我们可以针对日志的不同级别设置不同的处理方式。比如INFO级别及以上的日志我们写入到log文件里保存,Error级别及以上的日志我们直接通过邮件发送给系统管理员。
- DEBUG:用于调试目的的低级系统信息
- INFO:一般系统信息
- WARNING:描述已发生的小问题的信息。
- ERROR:描述已发生的主要问题的信息。
- CRITICAL:描述已发生的严重问题的信息。
Django的日志模块其实就是python的logging模块。它由4部分组成:
- Logger 记录仪:生成和记录每条日志信息及级别
- Handler处理程序: 根据日志信息级别交由相应处理程序处理(比如生成文件或发送邮件)
- Filters 过滤器:日志交由处理程序处理前需要满足的过滤条件(比如Debug=True或False)
- Formaters 格式化程序:决定每条日志的打印输出格式,可以有完整版的,也有简单版的
settings中完整的配置
LOGGING = {
'version': 1,
'disable_existing_loggers': False,
'incremental':True,
'filters': {},
'formatters': {},
'handlers': {},
'loggers': {}
}
- version:配置信息的版本
- disable_existing_loggers:默认为
True
,True:设置已存在的logger
失效。False:让已存在的logger
不失效,保证日志信息完整。一般情况下设置为False
- incremental:默认为
False
。True:是将配置解释为现有配置的增量。False:配置会覆盖已有默认配置。一般此项不用配置 - filter:过滤器
- formatters:格式器
- handlers:处理器
- loggers:日志器
Formatters
日志记录最终需要呈现为文本,formatter程序描述该文本的确切格式。formatter通常由包含LogRecord属性的Python格式化字符串组成 ; 但是,也可以编写自定义formatter来实现特定的格式化行为。
-
seetings
中配置,三个参数(具体看后面的Formatter
类):-
():指定格式器的类,不指定的话,默认使用
logging.Formattr
。一般用默认即可 -
format
:格式化字符串 -
style
:样式选择 -
datefmt
:日期格式化字符串,使用的是python
中时间日期格式化符号LOGGING = { 'formatters': { 'verbose': { '()': 'logging.Formatter', 'format': '{levelname} {asctime} {module} {process:d} {thread:d} {message}', 'style': '{', }, 'simple': { 'format': '{levelname} {message}', 'style': '{', }, } }
-
配置了2个格式器:
- simple:只输出简单的:日志级别名称 日志消息
- verbose:输出:日志级别名称 生成日志消息的时间 模块 进程 线程 日志消息
-
内置格式器
Formatter:默认格式器,初始化参数:fmt=None, datefmt=None, style='%'
- fmt:格式化字符串,指定输出格式,如:
'{levelname}{process:d}{message}'
- datefmt:日期格式化字符串,为
None
则使用ISO8601
格式化,如:'2010-01-01 08:03:26,870'
- style:‘%’,‘{’ 或 ‘$’,3选一:
- ‘%’:默认是这个,使用
python
的%
格式化 , 如:%(levelname)s
- ‘{’:使用
str.format
格式化(django框架使用这个), 如:{levelname}
- ‘$’:使用类
string.Template
格式化,如:\$levelname
- ‘%’:默认是这个,使用
格式化字符串的种类
%(name)s:记录器logger的名称
%(levelno)s:日志级别对应的数字
%(levelname)s:日志级别名称
%(pathname)s:日志记录调用的源文件的完整路径
%(filename)s:日志记录调用的源文件名
%(module)s:模块名
%(lineno)d:日志调用的行数
%(funcName)s:函数名
%(created)f:日志创建时间,time.time()
%(asctime)s:日志创建时间,文本类型
%(msecs)d:日志创建时间的毫秒部分
%(relativeCreated)d:日志创建时间 - 加载日志模块的时间 的 毫秒数
%(thread)d:线程ID
%(threadName)s:线程名
%(process)d:进程ID
%(processName)s:进程名
%(message)s:日志消息
Filters
-
过滤器
filter
用于提供对日志记录从logger
传递到handler
的附加控制 -
默认情况下,
logger
和handler
将处理满足日志级别要求的任何日志消息,但是,通过安装filter
,可以在日志记录过程中添加其他条件。例如,可以安装仅允许ERROR
级别 来自特定源的消息的filter。 -
filter还可用于在发出之前修改日志记录。例如,如果满足一组特定条件,可以编写一个过滤器,将
ERROR
日志记录降级为WARNING
记录。 -
filter可以安装在
logger
或handler
上; 可以在链中使用多个filter来执行多个过滤操作。LOGGING = { 'filters': { 'require_debug_false': { '()': 'django.utils.log.RequireDebugFalse', }, 'require_debug_true': { '()': 'django.utils.log.RequireDebugTrue', }, }, }
- 配置了2个过滤器
- require_debug_false:使用类:
RequireDebugFalse
- require_debug_true:使用类:
RequireDebugTrue
- require_debug_false:使用类:
- 配置了2个过滤器
Handlers
-
这个类是确定logger中消息发生的引擎程序,描述特定的日志记录行为,譬如控制台打印、写入日志文件、通过网络进行发送等
-
与logger一样,handler也具有日志级别,如果日志记录的日志级别未达到或超过handler的级别,则handler将忽略该消息。
-
一个logger可以有多个handler,每个handler可以有不同的日志级别和记录方法
-
settings中配置,4个参数(如下),加上对应
class
类的初始化参数- class(必需):处理程序类的名称
- level(可选的):处理程序的级别
- formatter(可选的):处理程序的格式化程序
- filters(可选的):处理程序的过滤器的列表
-
内置处理器
-
python3的
logging
中的handler
:-
StreamHandler:输出到
stream
,未指定则使用sys.stderr
输出到控制台 -
FileHandler:继承自StreamHandler,输出到文件,默认情况下,文件无限增长
初始化参数:filename,mode =‘a’,encoding = None,delay = False
delay如果为True,那么会延迟到第一次调用emit写入数据时才打开文件'handlers': { 'file': { 'level': 'DEBUG', 'class': 'logging.FileHandler', 'filename': '/path/to/django/app.log', #参数配置在这里,多个参数按顺序继续配置即可, 如果要添加encoding,那么在下面添加 encoding: 'utf-8' 即可 }, }
-
RotatingFileHandler:自动按大小切分的log文件(
常用
)初始化参数:filename,mode ='a',maxBytes = 0,backupCount = 0,encoding = None,delay = False maxBytes:最大字节数,超过时创建新的日志文件,如果backupCount或maxBytes有一个为0,那么就一直使用一个文件 backupCount:最大文件个数,新文件的扩展名是指定的文件后加序号".1"等,譬如:backupCount=5,基础文件名为:app.log,那么达到指定maxBytes之后,会关闭文件app.log,将app.log重命名为app.log.1,如果app.log.1存在,那么就顺推,先将 app.log.1重命名为app.log.2,再将现在的app.log命名为app.log.1,最大创建到app.log.5(旧的app.log.5会被删除),然后重新创建app.log文件进行日志写入,也就是永远只会对app.log文件进行写入。
-
TimedRotatingFileHandler:按时间自动切分的log文件,文件后缀
%Y-%m-%d_%H-%M-%S
- 初始化参数:
filename, when='h', interval=1, backupCount=0, encoding=None, delay=False, utc=False, atTime=None
when:时间间隔类型,不区分大小写
'S':秒 'M':分钟 'H':小时 'D':天 'W0'-'W6':星期几(0 = 星期一) 'midnight':如果atTime未指定,则在 0点0分0秒 翻转,否则在atTime时间翻转
- interval:间隔的数值
backupCount: 文件个数
encoding:编码
delay:True是写入文件时才打开文件,默认False,实例化时即打开文件
utc:False则使用当地时间,True则使用UTC时间
atTime:必须是datetime.time实例,指定文件第一次切分的时间,when设置为S,M,H,D时,该设置会被忽略
- 初始化参数:
-
SMTPHandler:通过email发送日志记录消息
初始化参数:mailhost, fromaddr, toaddrs, subject, credentials=None, secure=None, timeout=5.0 mailhost:发件人邮箱服务器地址(默认25端口)或地址和指定端口的元组,如:('smtp.163.com', 25) fromaddr:发件人邮箱 toaddrs:收件人邮箱列表 subject:邮件标题 credentials:如果邮箱服务器需要登录,则传递(username, password)元组 secure:使用TLS加密协议
-
Loggers
-
settings中配置,通过在
settings
中配置LOGGING
配置项实现日志配置,共4个配置项(都是可选的,不过一般会指定handler):- level:指定记录日志的级别,没有配置则处理所有级别的日子
- propagate:设置该记录器的日志是否传播到父记录器,不设置则是True
- filters:指定过滤器列表
- handlers:指定处理器列表
LOGGING = { 'version': 1, # 固定值,现在只有这一个版本 'disable_existing_loggers': False, # 设置已存在的logger不失效 'loggers': { '': { 'handlers': ['console'], }, 'django': { 'handlers': ['console'], 'propagate': True, }, 'django.request': { 'handlers': ['mail_admins'], 'level': 'ERROR', 'propagate': False, }, 'myproject.custom': { 'handlers': ['console', 'mail_admins'], 'level': 'INFO', 'filters': ['special'] } } }
-
配置了4个 logger, 分别对应2个不同的handler(console输出日志到控制台,mail_admins输出日志到邮件)
- ‘’:默认的记录器,不指定特定名称,那么就是使用这个记录器,没有配置level,那么就是处理所有级别的日志,传递所有级别的日志到
console
控制器 - django:传递所有级别的日志到console控制器
- django.request:django记录器的子记录器,处理ERROR级别及以上的日志,propagate设置为 False,表明不传播日志给 “django”,该logger传递日志到mail_admins控制器
- myproject.custom:处理INFO级别及以上的日志,应用了一个 special 的过滤器来过滤日志,传递日志到2个控制器([‘console’, ‘mail_admins’])处理
- ‘’:默认的记录器,不指定特定名称,那么就是使用这个记录器,没有配置level,那么就是处理所有级别的日志,传递所有级别的日志到
-
注意
-
django框架有个默认的配置:DEFAULT_LOGGING,一旦配置了自己的LOGGING后,那么所有的默认的LOGGER全部都失效,失效不等于没有记录器了,而是说记录器不起作用了,即不会记录日志,也不会将日志传播给父记录器。因此你应该非常小心使用,因为你会感觉你丢了日志一样,可以手动设置同名的logger实现覆盖,如:
LOGGING = { 'version': 1, 'disable_existing_loggers': False, 'loggers': { # 覆盖了 django 记录器,所有django的记录日志最后全部写入到文件中 'django': { 'handlers': ['file'], 'level': 'DEBUG', 'propagate': True, }, }, }
-
-
Django内置的logger
内置的logger
在django项目运行中会自动记录日志,与我们手动创建的logger
的执行没有关系,除非我们也创建相同的logger
django框架调用的地方在:django.core.servers.basehttp中(如WSGIRequestHandler)
-
django:django框架中所有消息的记录器,一般使用它的子记录器,而不是它发布消息,因为默认情况下子记录器的日志会传播到根记录器django,除非设置 ‘propagate’: False
-
django.request:记录与请求处理相关的消息。5XX响应作为
ERROR
消息; 4XX响应作为WARNING
消息引发。记录到django.security
记录器的请求不会记录到django.request
中 -
发送给此记录器的消息具有以下额外上下文:
- status_code:与请求关联的HTTP响应代码
- request:生成日志消息的请求对象。
- django.server:记录与
runserver
命令调用的服务器接收的请求的处理相关的消息。5XX响应记录为ERROR
消息,4XX响应记录为WARNING
消息,其他所有响应记录为INFO
。
发送给此记录器的消息具有以下额外上下文:- status_code:与请求关联的HTTP响应代码
- request:生成日志消息的请求对象。
- django.template:记录与模板呈现相关的消息
- django.db.backends:记录代码和数据库交互相关的消息
- django.security.*:记录任何
SuspiciousOperation
和其他安全相关错误(django.security.csrf )
的消息 - django.db.backends.schema:记录数据库迁移过程中的日志,但是不记录执行的查询SQL语句等,发送给此记录器的消息具有以下额外上下文:
- sql:已执行的SQL语句。
- params:SQL调用中使用的参数
实战案例
-
通过文件分割日志
-
首先配置
settings.py
中的logging
,代码如下BASE_LOG_DIR = os.path.join(BASE_DIR, 'log') LOGGING = { 'version': 1, 'disable_existing_loggers': False, # 设置已存在的logger不失效 'filters': {}, 'formatters': { 'standard': { 'format': '[%(asctime)s][%(levelname)s][%(filename)s:%(lineno)d:%(funcName)s]:%(message)s', 'datefmt': '%Y-%m-%d %H:%M:%S' }, 'simple':{ 'format':'[%(asctime)s][%(levelname)s]:%(message)s', 'datefmt': '%Y-%m-%d %H:%M:%S' } }, 'handlers': { 'console': { 'level': 'DEBUG', 'class': 'logging.StreamHandler', 'formatter': 'simple' }, 'default': { 'level': 'DEBUG', 'class': 'logging.handlers.RotatingFileHandler', 'filename': os.path.join(BASE_LOG_DIR, 'debug.log'), 'maxBytes': 1024 * 1024 * 50, # 日志大小50M 'backupCount': 5, 'formatter': 'standard', 'encoding': 'utf-8', }, }, 'loggers': { 'django': { 'handlers': ['console', 'default'], 'level': 'INFO', 'propagate': True }, }, }
-
接下来在
views.py
和urls.py
函数中写入函数,代码如下# urls.py urlpatterns = [ path('', views.index, name="index"), ] # views.py logger = logging.getLogger('django') def index(request): logger.debug('debug 测试') logger.info('info 测试') logger.warning('warning 测试') logger.error('error 测试') return HttpResponse('success')
-
然后我们访问
127.0.0.1/logging/
,我们可以看到控制台的代码Starting development server at http://127.0.0.1:8000/ Quit the server with CONTROL-C. [2021-05-30 15:03:09][INFO]:info 测试 [2021-05-30 15:03:09][WARNING]:warning 测试 [2021-05-30 15:03:09][ERROR]:error 测试 [2021-05-30 15:03:09][INFO]:"GET /logging/ HTTP/1.1" 200 7
-
这是因为我们在
django
记录器中配置了console
控制器,格式要求也是符合我们所写的,接着查看项目的log
目录下会新增了一个debug.log
这样一个日志文件,文件内容如下[2021-05-30 15:03:04][INFO][autoreload.py:578:run_with_reloader]:Watching for file changes with StatReloader [2021-05-30 15:03:09][INFO][views.py:12:index]:info 测试 [2021-05-30 15:03:09][WARNING][views.py:13:index]:warning 测试 [2021-05-30 15:03:09][ERROR][views.py:14:index]:error 测试 [2021-05-30 15:03:09][INFO][basehttp.py:154:log_message]:"GET /logging/ HTTP/1.1" 200 7
debug.log
日志输出格式更加详细,这是因为我们在default
控制器中,使用的standard
格式器。总结:以上就是我们最常用的一种日志配置—文件日志,当中的细节例如格式啊等等的可以自己更改
-
-
通过时间分割日志
-
代码设置如下:
'time_handler': { 'level': 'INFO', 'class': 'logging.handlers.TimedRotatingFileHandler', 'filename': os.path.join(BASE_LOG_DIR, "time.log"), 'when': 'S', 'interval': 10, 'backupCount': 5, 'formatter': 'standard', 'encoding': 'utf-8', }
以上设置为间隔10秒,生成一个日志文件
-
-
通过邮箱发送日志
-
代码设置如下:
'email_handler': { 'level': 'ERROR', 'class': 'logging.handlers.SMTPHandler', 'formatter': 'standard', 'mailhost': ('smtp.163.com', 25), 'fromaddr': 'xxxx@163.com', 'toaddrs': ['xxx@qq.com'], 'subject': 'test', 'credentials': ('邮箱用户名', '邮箱密码'), },
接下来出现ERROR级别的日志,就会发送邮件,如果你出现报错代码为
550
,那么就是你邮箱的权限没有开通,到邮箱的设置中开启SMTP
服务即可
-