日志
日志管理快速入门
Django 使用 Python 内置的 logging
模块处理系统日志。关于该模块的使用,Python 文档里有更详细的讨论。不过,如果你从未用过 Python 的 logging 框架(或者即便你用过),这里是一篇快速入门。
日志框架的组成元素
一份 Python logging 配置有下面四个部分组成:
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。
使用 logging 模块
一旦你配置了你的记录器、处理程序、过滤器和格式化程序,你就需要将记录调用放入你的代码中。使用日志框架的工作原理是这样的:
# import the logging library
import logging
# Get an instance of a logger
logger = logging.getLogger(__name__)
def my_view(request, arg1, arg):
...
if bad_mojo:
# Log an error message
logger.error('Something went wrong!')
就这么简单!bad_mojo
条件每次满足都会写一条 error 日志。
为 logger 命名
对 logging.getLogger()
的调用会获取(必要时会创建)一个 logger 的实例。不同的 logger 实例用名字来区分。这个名字是为了在配置的时候指定 logger。
按照惯例,logger 的名字通常是包含该 logger 的 Python 模块名,即 __name__
。这样可以基于模块来过滤和处理日志请求。不过,如果你有其他的方式来组织你的日志消息,可以为 logger 提供点号分割的名字来标识它:
# Get an instance of a specific named logger
logger = logging.getLogger('project.interesting.stuff')
这种 logger 的名字,用点号分隔的路径定义了一种层次结构。project.interesting
这个 logger 是 project.interesting.stuff
logger 的上级;而 project
logger 是 project.interesting
logger 的上级。
为什么层次结构很重要?嗯,因为记录器可以被设置为将其记录调用 传播 到其父级。通过这种方式,你可以在记录器树的根部定义一组处理程序,并捕获记录器子树中的所有记录调用。在 project
命名空间中定义的日志记录器将捕获在 project.interest
和 project.interest.stuff
日志记录器上发出的所有日志记录消息。
可以基于 logger 来控制传播的行为。 如果你不希望某个 logger 传播给上级,可以关闭它。
发起 logging 调用
logger 实例包含了每种默认日志级别的入口方法:
logger.debug()
logger.info()
logger.warning()
logger.error()
logger.critical()
还有两种其他的调用方法:
logger.log()
:手动输出一条指定日志级别的日志消息。logger.exception()
:创建一个包含当前异常堆栈帧的ERROR
级别日志消息。
日志模块的配置
仅仅在代码中加入日志调用是不够的。你还需要配置日志记录器、处理程序、过滤器和格式化程序,以确保你能使用日志输出。
Python 的日志库提供了一些配置方法,可以使用编程接口或者配置文件。Django默认使用 dictConfig format。
为了配置 logging ,用字典的格式定义一个 LOGGING
配置项,这些配置描述了你想要的 logger、handler、filter 和 formatter,以及它们的日志级别和其他你想要的属性。
默认情况下 LOGGING
配置和 Django 默认日志配置 按照下面的方式合并在一起:
如果 LOGGING
dictConfig 中的 disable_existing_loggers
键被设置为 True
(如果该键缺失,则为 dictConfig
默认值),则默认配置中的所有记录器都将被禁用。禁用的记录器与删除的记录器不同;记录器仍将存在,但会默默地丢弃任何记录到它的内容,甚至不会将条目传播到父记录仪。因此,你应该非常小心地使用 'disable_existing_loggers': True
;这可能不是你想要的。相反,你可以将 disable_existing_loggers
设置为 False
,然后重新定义一些或所有的默认日志记录器;或者你可以将 LOGGING_CONFIG
设置为 None
,然后 自己处理日志配置。
logging 被配置成了 Django setup()
函数的一部分。因此,你可以确定的是,logger 一直都可以在项目代码里使用。
示例
dictConfig format 文档是获取日志配置细节的最好资料。不过,为了让你知道能做什么,下面有几个例子。
首先,这里有一个小配置,可以让你把所有的日志信息输出到控制台。
settings.py
import os
LOGGING = {
'version': 1,
'disable_existing_loggers': False,
'handlers': {
'console': {
'class': 'logging.StreamHandler',
},
},
'root': {
'handlers': ['console'],
'level': 'WARNING',
},
}
这将配置父 root
记录器,以向控制台处理程序发送 WARNING
级别及以上的消息。通过将级别调整为 INFO
或 DEBUG
,可以显示更多的消息。这在开发过程中可能很有用。
接下来我们可以添加更多细粒度的日志记录。下面是一个例子,说明如何让日志系统只从名为 logger 的 django 中打印更多的消息。
settings.py
import os
LOGGING = {
'version': 1,
'disable_existing_loggers': False,
'handlers': {
'console': {
'class': 'logging.StreamHandler',
},
},
'root': {
'handlers': ['console'],
'level': 'WARNING',
},
'loggers': {
'django': {
'handlers': ['console'],
'level': os.getenv('DJANGO_LOG_LEVEL', 'INFO'),
'propagate': False,
},
},
}
默认情况下,这个配置会从 django
的日志记录器中发送级别为 INFO
或更高的消息到控制台。这个级别和 Django 的默认日志配置是一样的,只是默认配置只在 DEBUG=True
时才显示日志记录。Django 不会记录很多这样的 INFO
级别的消息。不过,有了这个配置,你也可以设置环境变量 DJANGO_LOG_LEVEL=DEBUG
来查看 Django 所有的调试日志,因为它包括了所有的数据库查询,所以非常啰嗦。
你不需要把日志记录到控制台。下面是一个配置,它将所有来自 django 命名的记录器的日志记录写入本地文件。
settings.py
LOGGING = {
'version': 1,
'disable_existing_loggers': False,
'handlers': {
'file': {
'level': 'DEBUG',
'class': 'logging.FileHandler',
'filename': '/path/to/django/debug.log',
},
},
'loggers': {
'django': {
'handlers': ['file'],
'level': 'DEBUG',
'propagate': True,
},
},
}
若你使用此例子,切记要将 'filename'
指向的路径改为当前运行 Django 应用的用户可写的路径。
最后,这里是一个相当复杂的日志设置的例子。
settings.py
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': {
'special': {
'()': 'project.logging.SpecialFilter',
'foo': 'bar',
},
'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',
'filters': ['special']
}
},
'loggers': {
'django': {
'handlers': ['console'],
'propagate': True,
},
'django.request': {
'handlers': ['mail_admins'],
'level': 'ERROR',
'propagate': False,
},
'myproject.custom': {
'handlers': ['console', 'mail_admins'],
'level': 'INFO',
'filters': ['special']
}
}
}
该日志配置做了以下事情:
识别配置为 ‘dictConfig 版本 1’ 格式。目前,这是唯一的 dictConfig 格式版本。
定义两个格式化程序:
simple
,输出日志级别名称(如DEBUG
)和日志信息。format
字符串是一个普通的 Python 格式化字符串,它描述了每个日志行要输出的细节。可以输出的完整细节列表可以在 Formatter Objects 中找到。verbose
,输出日志级别名称、日志信息,以及生成日志信息的时间、进程、线程和模块。
定义两个过滤器:
project.logging.SpecialFilter
,使用别名special
。如果这个过滤器需要额外的参数,它们可以作为过滤器配置字典中的附加键提供。在这种情况下,当实例化SpecialFilter
时,参数foo
将被赋予一个bar
的值。django.utils.log.RequireDebugTrue
,当DEBUG
为True
时,传递记录。
定义两个处理程序:
console
,一个StreamHandler
,它将任何INFO
(或更高)消息打印到sys.stderr
。该处理程序使用simple
输出格式。mail_admins
,一个AdminEmailHandler
,它向网站ADMINS
发送任何ERROR
(或更高)消息。该处理程序使用special
过滤器。
配置三个记录器。
django
,将所有信息传递给console
处理程序。django.request
,它将所有ERROR
消息传递给mail_admins
处理程序。此外,这个记录器被标记为 不 传播消息。这意味着写给django.request
的日志信息不会被django
日志处理程序处理。myproject.custom
,它将所有INFO
或更高等级的消息传递给两个处理程序——console
和mail_admins
。这意味着所有INFO
级别(或更高)的消息将被打印到控制台;ERROR
和CRITICAL
消息也将通过电子邮件输出。
自定义日志记录配置
如果你不想使用 Python 的 dictConfig 格式来配置记录器,你可以指定自己的配置方案。
LOGGING_CONFIG
设置定义了用于配置 Django 日志记录器的可调用对象,默认情况下,它指向 Python 的 logging.config.dictConfig()
函数。然而,如果你想使用不同的配置过程,你可以使用其他任何一个接受单一参数的可调用。当配置日志时, LOGGING
的内容将作为该参数的值提供。
禁用日志记录配置
如果你根本不想配置日志记录(或者你想用自己的方法手动配置日志记录),你可以将 LOGGING_CONFIG
设置为 None
。这将禁用 Django 的默认日志记录 的配置过程。
将 LOGGING_CONFIG
设置为 None
只是意味着自动配置过程被禁用,而不是日志本身。如果你禁用了配置过程,Django 仍然会进行日志调用,回到默认的日志行为。
下面是一个禁用 Django 的日志配置,然后手动配置日志的例子。
settings.py
LOGGING_CONFIG = None
import logging.config
logging.config.dictConfig(...)
请注意,默认的配置过程只有在设置完全加载后才会调用 LOGGING_CONFIG
。相反,在设置文件中手动配置日志记录将立即加载你的日志记录配置。因此,你的日志配置必须出现在它所依赖的任何设置之后。
Django 的日志记录扩展
Django 提供了一些实用工具来处理 Web 服务器环境中的独特的日志记录需求。
Loggers
Django 提供了几种内置的记录器。
django
django
层次结构中消息的总记录器。不使用该名称发布消息,而是使用以下记录器之一。
django.request
记录与处理请求有关的信息。5XX 的响应以 ERROR
消息的形式出现;4XX 的响应以 WARNING
消息的形式出现。记录在 django.security
记录器中的请求不会记录在 django.request
中。
发送给此记录器的消息有以下额外的上下文:
status_code
:与请求相关的 HTTP 响应代码。request
:产生记录信息的请求对象。
django.server
记录与处理由 runserver
命令调用的服务器收到的请求有关的消息。HTTP 5XX 响应被记录为 ERROR
消息,4XX 响应被记录为 WARNING
消息,其他所有消息被记录为 INFO
。
发送给此记录器的消息有以下额外的上下文:
status_code
:与请求相关的 HTTP 响应代码。request
:产生记录信息的请求对象。
django.template
记录与模板渲染相关的消息。
- 缺少的上下文变量会被记录为
DEBUG
消息。
django.db.backends
与代码与数据库互动有关的信息。例如,请求执行的每一条应用程序级别的 SQL 语句都会以 DEBUG
级别记录到这个记录器。
发送给此记录器的消息有以下额外的上下文:
duration
:执行 SQL 语句所需时间。sql
:所执行的 SQL 语句。params
:SQL 调用中使用的参数。
出于性能考虑,只有当 settings.DEBUG
设置为 True
时,才会启用 SQL 日志记录,而不考虑日志级别或安装的处理程序。
该日志不包括框架级初始化(如 SET TIMEZONE
)或事务管理查询(如 BEGIN
、COMMIT
和 ROLLBACK
)。如果想查看所有数据库查询,请在数据库中开启查询记录。
django.security.*
安全记录器将接收任何发生 SuspiciousOperation
和其他安全相关错误的消息。每个子类型的安全错误都有一个子记录器,包括所有 SuspiciousOperation
s。日志事件的级别取决于异常处理的位置。 大多数发生的事件被记录为警告,而任何到达 WSGI 处理程序的 SuspiciousOperation
将被记录为错误。例如,当客户端的请求中包含一个 HTTP Host
头,而这个头不符合 ALLOWED_HOSTS
时,Django 会返回一个 400 的响应,并且错误信息会被记录到 django.security.DisallowedHost
记录器中。
这些日志事件默认会到达 django
日志器,当 DEBUG=False
时,记录器会将错误事件发送给管理员。由于 SuspiciousOperation
导致 400 响应的请求不会被记录到 django.request
记录器,而只会记录到 django.security
记录器。
要使某一特定类型的 SuspiciousOperation
保持沉默,你可以按照以下示例覆盖该特定的记录器:
'handlers': {
'null': {
'class': 'logging.NullHandler',
},
},
'loggers': {
'django.security.DisallowedHost': {
'handlers': ['null'],
'propagate': False,
},
},
其他不基于 SuspiciousOperation
的 django.security
记录器是:
django.security.csrf
:用于 CSRF 错误。
django.db.backends.schema
记录 migrations framework 对数据库进行模式变更时执行的 SQL 查询。请注意,它不会记录 RunPython
执行的查询。给这个记录器的消息在其额外的上下文中有 params
和 sql
(但与 django.db.backends
不同,不是 duration)。这些值的含义与 django.db.backends 中的解释相同。
Handlers
除了 Python 日志模块提供的日志处理程序外,Django 还提供了一个日志处理程序。
class AdminEmailHandler
(include_html=False, email_backend=None, reporter_class=None)
该处理程序对收到的每条日志消息都会向站点 ADMINS
发送一封邮件。
如果日志记录中包含 request
属性,电子邮件中会包含请求的全部细节。如果客户的 IP 地址在 INTERNAL_IPS
设置中,电子邮件主题将包括“内部 IP”;如果没有,则包括“外部 IP”。
如果日志记录中包含堆栈跟踪信息,该堆栈跟踪信息将包含在电子邮件中。
AdminEmailHandler
的 include_html
参数用于控制回溯邮件是否包含一个 HTML 附件,该附件包含调试网页的完整内容,如果 DEBUG
为 True
的话,该附件将被生成。要在你的配置中设置这个值,请在 django.utils.log.AdminEmailHandler
的处理程序定义中包含它,就像这样:
'handlers': {
'mail_admins': {
'level': 'ERROR',
'class': 'django.utils.log.AdminEmailHandler',
'include_html': True,
}
},
请注意,这封 HTML 版本的邮件包含了完整的跟踪信息,包括堆栈中每一层的本地变量的名称和值,以及你的Django设置值。这些信息可能非常敏感,你可能不想通过邮件发送。可以考虑使用诸如 Sentry 这样的软件来获得两全其美的效果—既能获得丰富的信息,又能保证不通过邮件发送信息的安全性。你也可以明确指定某些敏感信息从错误报告中过滤出来——在 过滤错误报告 中了解更多信息。
通过设置 AdminEmailHandler
的 email_backend
参数,处理程序使用的 email 后端 可以被覆盖,就像这样:
'handlers': {
'mail_admins': {
'level': 'ERROR',
'class': 'django.utils.log.AdminEmailHandler',
'email_backend': 'django.core.mail.backends.filebased.EmailBackend',
}
},
默认情况下,将使用 EMAIL_BACKEND
中指定的电子邮件后端实例。
AdminEmailHandler
的 reporter_class
参数允许提供一个 django.view.debug.ExceptionReporter
子类来自定义邮件正文中发送的回溯文本。你提供一个字符串的导入路径到你想使用的类,像这样:
'handlers': {
'mail_admins': {
'level': 'ERROR',
'class': 'django.utils.log.AdminEmailHandler',
'include_html': True,
'reporter_class': 'somepackage.error_reporter.CustomErrorReporter'
}
},
send_mail
(subject, message, \args, **kwargs*)向管理员用户发送邮件。要自定义这个行为,你可以将
AdminEmailHandler
类子类化,并覆盖这个方法。
过滤器
除了 Python 日志模块提供的日志过滤器外,Django 还提供了一些日志过滤器。
class CallbackFilter
(callback)
这个过滤器接受一个回调函数(它应该接受一个单一的参数,即要记录的记录),并对每个通过过滤器的记录进行调用。如果回调函数返回 False,则不会对该记录进行处理。
例如,要从管理员邮件中过滤掉 UnreadablePostError
(当用户取消上传时引发),你可以创建一个过滤函数:
from django.http import UnreadablePostError
def skip_unreadable_post(record):
if record.exc_info:
exc_type, exc_value = record.exc_info[:2]
if isinstance(exc_value, UnreadablePostError):
return False
return True
然后将其添加到你的日志记录配置中:
'filters': {
'skip_unreadable_posts': {
'()': 'django.utils.log.CallbackFilter',
'callback': skip_unreadable_post,
}
},
'handlers': {
'mail_admins': {
'level': 'ERROR',
'filters': ['skip_unreadable_posts'],
'class': 'django.utils.log.AdminEmailHandler'
}
},
class RequireDebugFalse
只有当 settings.DEBUG 为 False 时,该过滤器才会传递记录。
该过滤器在默认的 logging
配置中使用如下,以确保 AdminEmailHandler
只在 DEBUG
为 False
时向管理员发送错误邮件:
'filters': {
'require_debug_false': {
'()': 'django.utils.log.RequireDebugFalse',
}
},
'handlers': {
'mail_admins': {
'level': 'ERROR',
'filters': ['require_debug_false'],
'class': 'django.utils.log.AdminEmailHandler'
}
},
class RequireDebugTrue
该过滤器类似于 RequireDebugFalse
,但只有当 DEBUG
为 True
时才会传递记录。
Django 的默认日志配置
默认情况下,Django 配置了以下日志:
当 DEBUG
为 True
时:
django
记录器将django
层次结构(django.server
除外)中的INFO
级别或更高的消息发送到控制台。
当 DEBUG
为 False
时:
django
记录器将django
层次结构(django.server
除外)中带有ERROR
或CRITICAL
级别的消息发送到AdminEmailHandler
。
与 DEBUG
的值无关。
- django.server 记录器向控制台发送
INFO
或更高等级的消息。
除了 django.server 之外,所有的日志记录器都会将日志记录传播给它们的父辈,直到 django
的根日志记录器。console
和 mail_admins
处理程序被附加到根记录器上,以提供上述行为。
还请参见 配置日志 了解如何补充或替换 django/utils/log.py 中定义的默认日志配置。