日志¶
Python 程序员通常会在其代码中使用 print()
作为一种快速和方便的调试工具。使用日志框架只比这多花一点点工夫,但更加优雅和灵活。除了用于调试之外,日志还可以为您提供有关应用程序状态和健康状况的更多信息,而且这些信息结构更清晰。
概况¶
Django 使用并扩展了 Python 内置的 logging
模块来执行系统日志记录。这个模块在 Python 自己的文档中有详细的讨论;这一部分提供了一个快速概览。
日志框架的组成元素¶
一份 Python logging 配置有下面四个部分组成:
Loggers¶
logger 是日志系统的入口点。每个 logger 是一个命名的容器,可以将消息写入其中以进行处理。
logger 可以配置 日志级别。日志级别描述了由该 logger 处理的消息的严重性。Python 定义了下面几种日志级别:
DEBUG
:排查故障时使用的低级别系统信息INFO
:一般的系统信息WARNING
:描述系统发生了一些小问题的信息ERROR
:描述系统发生了大问题的信息CRITICAL
:描述系统发生严重问题的信息
每一条写入 logger 的消息都是一条 日志记录。每一条日志记录也包含 日志级别,代表对应消息的严重程度。日志记录还包含有用的元数据,来描述被记录了日志的事件细节,例如堆栈跟踪或者错误码。
当 logger 处理一条消息时,会将自己的日志级别和这条消息的日志级别做对比。如果消息的日志级别匹配或者高于 logger 的日志级别,它就会被进一步处理。否则这条消息就会被忽略掉。
当 logger 确定了一条消息需要处理之后,会把它传给 Handler。
Handlers¶
handler 是确定 logger 中每个消息发生什么的引擎。它描述了特定的日志行为,例如将消息写入屏幕、写入文件或写入网络套接字。
和 logger 一样,handler 也有日志级别的概念。如果一条日志记录的级别不匹配或者低于 handler 的日志级别,对应的消息会被 handler 忽略。
一个 logger 可以有多个 handler,每一个 handler 可以有不同的日志级别。这样就可以根据消息的重要性不同,来提供不同格式的输出。例如,你可以添加一个 handler 把 ERROR
和 CRITICAL
消息发到寻呼机,再添加另一个 handler 把所有的消息(包括 ERROR
和 CRITICAL
消息)保存到文件里以便日后分析。
过滤器¶
filter 用于在从 logger 传递到 handler 的日志记录中提供额外的控制。
默认情况下,只要级别匹配,任何日志消息都会被处理。不过,也可以通过添加 filter 来给日志处理的过程增加额外条件。例如,可以添加一个 filter 只允许某个特定来源的 ERROR
消息输出。
Filter 还被用来在日志输出之前对日志记录做修改。例如,可以写一个 filter,当满足一定条件时,把日志记录从 ERROR
降到 WARNING
级别。
Filter 在 logger 和 handler 中都可以添加;多个 filter 可以链接起来使用,来做多重过滤操作。
Formatters¶
最终,日志记录需要呈现为文本。Formatter 描述了文本的精确格式。Formatter 通常由一个包含 LogRecord 属性 的 Python 格式化字符串组成;但是,您也可以编写自定义的 Formatter 来实现特定的格式化行为。
安全性考虑¶
日志记录系统处理可能包含敏感信息的数据。例如,日志记录可能包含有关 Web 请求或堆栈跟踪的信息,而您自己的日志记录中收集的一些数据也可能具有安全性影响。您需要确保自己明白以下事项:
- 会收集什么信息
- 日志记录将随后存储在何处。
- 它将如何传输。
- 谁可能访问它
为了帮助控制敏感信息的收集,您可以明确指定某些敏感信息在错误报告中被过滤掉,详细了解如何 过滤错误报告。
AdminEmailHandler
¶
在安全性的背景下,应该提到内置的 AdminEmailHandler
。如果启用了它的 include_html
选项,它发送的电子邮件消息将包含完整的回溯信息,以及堆栈的每个级别的本地变量的名称和值,以及您的 Django 设置的值(换句话说,与在 DEBUG
为 True
时在 Web 页面中显示的详细程度相同)。
通常不建议通过电子邮件发送此类可能包含敏感信息的信息。相反,考虑使用其中许多第三方服务之一,将详细日志发送到这些服务,以获得多个优势:完整回溯信息的丰富信息、清晰的通知和访问信息的管理等等。这样可以更好地处理敏感信息。
日志模块的配置¶
Python 的日志库提供了一些配置方法,可以使用编程接口或者配置文件。Django默认使用 dictConfig format。
要配置日志记录,您使用 LOGGING
来定义一个日志设置字典。这些设置描述了您希望在日志设置中使用的记录器、处理程序、过滤器和格式化程序,以及您希望这些组件具有的日志级别和其他属性。
默认情况下 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 文档是获取日志配置细节的最好资料。不过,为了让你知道能做什么,下面有几个例子。
首先,这里有一个小配置,可以让你把所有的日志信息输出到控制台。
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 中打印更多的消息。
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 命名的记录器的日志记录写入本地文件。
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 应用的用户可写的路径。
最后,这里是一个相当复杂的日志设置的例子。
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
,它会将任何ERROR``(或更高级别)的消息通过电子邮件发送给站点的 :setting:`ADMINS`。这个处理程序使用了 ``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 的日志配置,然后手动配置日志的例子。
LOGGING_CONFIG = None
import logging.config
logging.config.dictConfig(...)
请注意,默认的配置过程只有在设置完全加载后才会调用 LOGGING_CONFIG
。相反,在设置文件中手动配置日志记录将立即加载你的日志记录配置。因此,你的日志配置必须出现在它所依赖的任何设置之后。