在项目开发中,如果需要调试的时候,一开始大部分会去直接使用print
, 但是print
的频繁时候会比较损耗服务的性能,并且无法将日志输出的文件中进行存储。
所以应该尽量避免使用print
来调试打印信息,最好使用logging
模块来进行日志打印以及日志存储输出。
但是logging
模块也是存在缺陷的,logging
模块是线程安全的,当如果使用uwsgi
部署django
服务的时候,就会由于多进程导致日志存储混乱。不过,这个是多进程删除、写入文件导致的,我们先不考虑,先来看看在django
服务如果使用logging
模块。
python 3.7.1
django 2.1.7
这个文档是django
官方提供关于logging日志配置的文档。当然,最好再选择对应的django
版本进行查阅。在这里,我使用的django
版本是2.1.7,那么下面我则打开 2.1 的版本进行查阅即可,如下:
image-20200320115000317
image-20200320115040980
好了,在直接上手之前,我们先从官网文档来认识一下logging模块。
logging日志机制中,有四个主要的部分:
Loggers是进入日志系统的入口点。每个Loggers都是一个命名的存储器,可以将消息写入其中以进行处理。
Loggers配置为具有日志级别。此日志级别描述Loggers将处理的消息的严重性,也是对应的打印触发条件。Python定义了以下日志级别:
DEBUG
:用于调试目的的低级系统信息INFO
:一般系统信息WARNING
:描述已发生的小问题的信息。ERROR
:描述已发生的主要问题的信息。CRITICAL
:描述已发生的严重问题的信息。优先级从低到高分别为:DEBUG -> INFO -> WARNING -> ERROR -> CRITICAL。
写入Loggers的每条消息都是一个日志记录。每个日志记录还具有指示该特定消息的严重性的日志级别。日志记录还可以包含有用的元数据,用于描述正在记录的事件。这可以包括详细信息,例如堆栈跟踪或错误代码。
将消息提供给Loggers时,会将消息的日志级别与Loggers的日志级别进行比较。如果消息的日志级别达到或超过记录器本身的日志级别,则将对消息进行进一步处理。如果没有,该消息将被忽略。
Loggers确定需要处理消息后,会将其传递给 Handler。
Handlers是确定记录器中每个消息发生什么情况的引擎。它描述了特定的日志记录行为,例如将消息写到屏幕,文件或网络套接字。
像Loggers一样,处理程序也具有日志级别。如果日志记录的日志级别不满足或超过处理程序(Handlers)的级别,则处理程序(Handlers)将忽略该消息。
一个记录器(Loggers)可以具有多个处理程序(Handlers),并且每个处理程序(Handlers)可以具有不同的日志级别。这样,可以根据消息的重要性提供不同形式的通知。例如,您可以安装一个处理程序(Handlers),该处理程序将消息ERROR
和 CRITICAL
消息转发到分页服务,而第二个处理程序将所有消息(包括ERROR
和CRITICAL
消息)记录到日志文件中,以供以后分析。
为什么logger和handler都要设置level?因为一个logger可以有多个handler,且每个handler可以有不同的log level。这样一来,一个logger可以接受一类日志的多个级别的信息,并且将不同级别的信息进行不同的处理。 ”
Filters 过滤器用于提供对哪些日志记录从记录器传递到处理程序的附加控制。
默认情况下,将处理所有符合日志级别要求的日志消息。但是,通过安装过滤器,可以在日志记录过程中放置其他条件。例如,您可以安装一个过滤器,该过滤器仅允许ERROR
发出来自特定来源的消息。
过滤器还可以用于在发出之前修改日志记录。例如,您可以编写一个过滤器,以将ERROR
日志记录降级 WARNING
为满足特定条件的记录。
Filters 过滤器可以安装在记录器Loggers或处理程序Handlers上;一个链中可以使用多个过滤器Filters 来执行多个过滤操作。
最终,日志记录需要呈现为文本。格式器描述了该文本的确切格式。格式化程序通常由包含LogRecord属性的Python格式化字符串组成 ;但是,您也可以编写自定义格式化程序以实现特定的格式化行为。
配置记录器Loggers,处理程序Handlers,过滤器Filters 和格式化程序Formatters 后,需要将记录调用放入代码中。使用日志记录框架非常简单。这是一个例子:
# 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
激活该条件时,都会写入一个错误日志记录。
单纯这个看官网用例代码可能没有直观的感受,下面我再来写一个完整的示例代码,如下:
# import the logging library
import logging
# Get an instance of a logger
logger = logging.getLogger(__name__)
def my_view(bad_mojo):
if bad_mojo:
# Log an error message
logger.error('Something went wrong!')
def main():
# 激活logger error记录信息
bad_mojo = True
my_view(bad_mojo)
if __name__ == '__main__':
main()
执行如下:
image-20200320145756111
调用以logging.getLogger()
获取(必要时创建)记录器的实例logger
。记录器实例由名称标识。此名称用于标识记录器以进行配置。
按照约定,记录器名称通常为__name__
,其中包含记录器的python模块的名称。这使您可以按模块过滤和处理日志记录调用。但是,如果想要采用其他方式来组织日志记录消息,则可以提供用点号分隔的名称来标识记录器:
# Get an instance of a specific named logger
logger = logging.getLogger('project.interesting.stuff')
记录器名称的点号路径定义了层次结构。在这里 project.interesting
记录器被认为是所述的父 project.interesting.stuff
记录器; 该project
记录器是一个父project.interesting
记录器。
为什么层次结构很重要?好吧,因为可以将记录器设置为将其日志记录传播给父母。这样,就可以在记录器树的根目录中定义一组处理程序,并在记录器的子树中捕获所有日志记录。project
名称空间中定义的日志记录处理程序将捕获在project.interesting
和project.interesting.stuff
在 logger 上发布的所有日志记录消息。
可以在每个记录器的基础上控制此传播日志的行为。如果您不希望特定的记录器logger传播日志给它的父母,则可以关闭此行为。可以通过设置'propagate': False,
来实现。
记录器logger
实例包含每个默认日志级别的输入方法:
logger.debug()
logger.info()
logger.warning()
logger.error()
logger.critical()
还有其他两个日志记录调用:
logger.log()
:手动发出具有特定日志级别的日志消息,随便你想记录什么都行。logger.exception()
:为了捕获某些异常,创建一个ERROR级别的日志(创建一个ERROR
包装当前异常堆栈框架的级别日志记录消息)当然,仅将调用记录放入代码中是不够的。还需要配置记录器 Loggers,处理程序 Handlers,过滤器 Filters 和格式化程序 Formatters,以确保以有用的方式输出记录输出。
Python的日志记录库提供了几种配置日志记录的技术,范围从编程界面到配置文件。默认情况下,Django使用dictConfig格式。
为了配置日志记录,您可以使用LOGGING
定义日志记录设置的字典。这些设置描述了您希望在日志记录设置中使用的日志记录器,处理程序,过滤器和格式化程序,以及希望这些组件具有的日志级别和其他属性。
默认情况下,使用以下方案将LOGGING
设置与Django的默认日志记录配置合并。
如果将dictConfig
中的disable_existing_loggers
键LOGGING
设置为True
(默认值),则将禁用默认配置中的所有记录器。禁用的记录器与已删除的记录器不同;记录器仍将存在,但会静默丢弃记录到它的所有内容,甚至不会将条目传播到父记录器。因此,您应该非常小心地使用;这可能不是您想要的。相反,您可以设置为并重新定义一些或所有默认记录器;或者您可以自定义设置,参考 handle logging config yourself.
配置如下:
disable_existing_loggers: True
禁用默认配置中的所有记录器
disable_existing_loggers
:False
启动默认配置中的所有记录器
日志配置是Django
项目的基本配置,下面来看看示例。
dictConfig format 是配置示例的最完整文档,更加详细信息可以参考这里。
首先,先来一个简单的配置,它将所有日志记录从Django记录器写入本地文件:
LOGGING = {
'version': 1, # 定义版本 1
'disable_existing_loggers': False, # 允许使用已有的默认过滤器
'handlers': { # 定义一个处理器
'file': { # 定义DEBUG日志信息的存储路径
'level': 'DEBUG', # 定义handelr的日志级别
'class': 'logging.FileHandler', # 使用文件类处理器,可以将日志写入文件中
'filename': '/path/to/django/debug.log', # 定义的文件路径需要确认有可写权限
},
},
'loggers': { # 定义一个日志记录器
'django': { # 记录django项目中的信息。
'handlers': ['file'], # 使用处理器 file
'level': 'DEBUG', # 日志的等级为 debug
'propagate': True, # 允许传播至上级记录器
},
},
}
在上面的注释中基本说明的比较清除了,需要注意的是请确保将'filename'
路径设置为运行Django应用程序的用户可写的位置,不然由于权限文件导致日志无法写入,也是挺让人纠结的。
第二,这是一个如何使日志记录系统将Django的日志记录打印到控制台的示例。在本地开发过程中可能会很有用。
默认情况下,此配置仅将日志等级为INFO
的日志向 console 控制台发送消息,其他级别或更高级别的消息则不发送至控制台。(与Django的默认日志记录配置相同,但默认情况下仅在时显示日志记录DEBUG=True
)。
Django不会记录许多此类消息。但是,通过此配置,您还可以设置环境变量 DJANGO_LOG_LEVEL=DEBUG
以查看Django的所有调试日志记录,这非常冗长,因为它包含所有数据库查询。
import os
LOGGING = {
'version': 1, # 定义版本 1
'disable_existing_loggers': False, # 允许使用已有的默认过滤器
'handlers': { # 定义处理器
'console': { # 定义命名为 console 的处理器
'class': 'logging.StreamHandler', # 定义使用流处理类,可以将信息打印到控制台
},
},
'loggers': { # 定义记录器
'django': { # 定义一个django命名的记录器
'handlers': ['console'], # 定义使用命名为 console 的处理器
'level': os.getenv('DJANGO_LOG_LEVEL', 'INFO'), # 定义日志级别为 INFO
},
},
}
最后,这是一个相当复杂的日志记录设置的示例:
LOGGING = {
'version': 1, # 定义版本 1
'disable_existing_loggers': False, # 允许使用已存在的默认过滤器
'formatters': { # 定义格式化
'verbose': { # 定义命名为 verbose 的 格式化
'format': '%(levelname)s %(asctime)s %(module)s %(process)d %(thread)d %(message)s'
# 输出日志级别名称,日志消息以及生成日志消息的时间,进程,线程和模块
},
'simple': { # 定义命名为 simple 的格式化
'format': '%(levelname)s %(message)s' # 仅输出日志级别名称(例如 DEBUG)和日志消息。
},
},
'filters': { # 定义过滤器
'special': { # 定义命名为 sepcial 的过滤器
'()': 'project.logging.SpecialFilter',
'foo': 'bar',
},
'require_debug_true': {
'()': 'django.utils.log.RequireDebugTrue',
},
},
'handlers': { # 定义处理器
# 定义命名为 console 的处理器,将INFO级别的日志使用 stream 流处理打印到控制台
'console': {
'level': 'INFO',
'filters': ['require_debug_true'],
'class': 'logging.StreamHandler',
'formatter': 'simple' # 过滤规则使用 simple,只输出日志等级以及 messages 信息
},
# 定义命名为 mail_admis 的处理器,将ERROR级别的日志 使用 AdminEmailHandler 处理器,
# 将错误信息发送到该网站的 admin 超级用户的邮箱上,错误信息格式采用 secial 格式处理。
'mail_admins': {
'level': 'ERROR',
'class': 'django.utils.log.AdminEmailHandler',
'filters': ['special']
}
},
'loggers': { # 定义日志记录器
# 定义命名为 django 的日志记录器,使用 console 处理器,也就是将记录的信息打印到控制台
'django': {
'handlers': ['console'],
'propagate': True, # 允许传播至上级记录器
},
# 定义命名为 django.request 的日志记录器,使用 main_admins 处理器,只处理邮件发送的错误信息
'django.request': {
'handlers': ['mail_admins'],
'level': 'ERROR',
'propagate': False, # 禁用传播至上级记录器django
},
# 定义命名为 myproject.custome 的日志记录器,同时使用 console, mail_admis 处理器。
'myproject.custom': {
'handlers': ['console', 'mail_admins'],
'level': 'INFO',
'filters': ['special']
}
}
}
所有与日志有关的配置默认在LOGGING ={}
中,distConfig格式。如果我们没有进行特别配置,Django会执行默认的日志配置。
Django settings 默认配置
django
这个logger以及其(除了django.server之外的)所有下级的INFO以上的日志,都会被StreamHandler处理(输出到console)如果我们不想使用默认的配置,可以将LOGGING中的'disable_existing_loggers'
设置为True
(默认也是True),这样一来,默认的配置就会被禁用。但是设置'disable_existing_loggers': True,
必须要非常小心,因为可能产生一些令人意外的结果(官网这么说的,还没试过),所以比较建议的方法是'disable_existing_loggers': False,
然后重写部分或者全部的默认logger。
此日志记录配置执行以下操作:
simple
,仅输出日志级别名称(例如 DEBUG
)和日志消息。
该format
字符串是普通的Python格式化字符串,描述了将在每条记录行上输出的详细信息。可以输出的详细信息的完整列表可以在Formatter Objects中找到。verbose
,它输出日志级别名称,日志消息以及生成日志消息的时间,进程,线程和模块。project.logging.SpecialFilter
,使用别名special
。如果此过滤器需要其他参数,则可以在过滤器配置字典中将它们作为其他关键字提供。在这种情况下,实例化时 foo
将为参数 提供值。bar
SpecialFilter
django.utils.log.RequireDebugTrue
,该记录在DEBUG
is 时传递 True
。console
,a StreamHandler
,它将任何INFO
(或更高版本的)消息打印到sys.stderr
。该处理程序使用simple
输出格式。mail_admins
,一个AdminEmailHandler
通过电子邮件将任何ERROR
(或更高版本)消息发送到该网站ADMINS
。该处理程序使用special
过滤器。django
,它将所有消息传递给console
处理程序。django.request
,它将所有ERROR
消息传递给mail_admins
处理程序。另外,该记录器被标记为不传播消息。这意味着记录器django.request
将不会处理写入日志消息至django
。myproject.custom
,它将所有INFO
或更高级别的消息传递给该消息,也将special
过滤器传递给两个处理程序- console
和mail_admins
。这意味着所有INFO
级别的消息(或更高级别的消息)将被打印到控制台。ERROR
和CRITICAL
消息也将通过电子邮件输出。另外,既然上面的日志配置提到需要在日志中发送错误信息至admin的邮箱,那么在Django项目中就需要在settings配置好邮件发送的相关配置了,如下:
# 邮件配置
EMAIL_HOST = 'smtp.maildomain.com' # SMTP地址
EMAIL_PORT = 25 # SMTP端口
EMAIL_HOST_USER = 'sender@maildomain.com' # 发送邮件的邮箱
EMAIL_HOST_PASSWORD = '******' # 我的邮箱密码
EMAIL_SUBJECT_PREFIX = u'[prefix]' # 为邮件Subject-line前缀,默认是'[django]'
EMAIL_USE_TLS = True # 与SMTP服务器通信时,是否启动TLS链接(安全链接)。默认是false
# 管理员站点
SERVER_EMAIL = 'adminli@163.com' # The email address that error messages come from, such as those sent to ADMINS and MANAGERS.
ADMINS = (('receiver', 'receiver@maildomain.com'),) # 接收邮件的邮箱(或邮件组)
如果您不想使用Python的dictConfig格式配置记录器,则可以指定自己的配置方案。
该LOGGING_CONFIG
设置定义了用于配置Django记录器的可调用对象。默认情况下,它指向Python的logging.config.dictConfig()
函数。但是,如果要使用其他配置过程,则可以使用带有单个参数的任何其他可调用对象。LOGGING
配置日志记录时,将提供的内容作为该参数的值。
可以在settings配置如下:
LOGGING_CONFIG = None
import logging.config
logging.config.dictConfig(...)
设置LOGGING_CONFIG
为None
仅表示禁用自动配置过程。如果禁用配置过程,Django仍将进行日志记录调用,而回退到定义的默认日志记录行为。
Django提供了一些内建的logger,比如上面例子中的'django'
。
django
只是一个最上级的logger,但实际上并不接收什么实际的信息,所有的信息都是通过下级logger接收。
与请求处理有关的日志消息。5XX响应作为ERROR
消息引发;出现4XX响应作为WARNING
消息。
发送给该记录器的消息具有以下额外的上下文:
status_code
:与请求关联的HTTP响应代码。request
:生成日志消息的请求对象。与runserver
命令调用的服务器接收的请求处理相关的日志消息。HTTP 5XX响应记录为ERROR
消息,4XX响应记录为WARNING
消息,其他所有记录为INFO
。
发送给该记录器的消息具有以下额外的上下文:
status_code
:与请求关联的HTTP响应代码。request
:生成日志消息的请求对象。记录与模板渲染有关的消息。
DEBUG
消息。{%include %}
WARNING
{% include %}
与代码与数据库的交互有关的消息。例如,请求执行的每个应用程序级SQL语句都在 DEBUG
该记录器级别记录。
发送给该记录器的消息具有以下额外的上下文:
duration
:执行SQL语句所花费的时间。sql
:执行的SQL语句。params
:在SQL调用中使用的参数。出于性能方面的考虑,仅在settings.DEBUG
将SQL日志记录设置为时才启用SQL日志记录 True
,而不考虑日志记录级别或已安装的处理程序。
此日志记录不包括框架级初始化(例如:SET TIMEZONE
)或事务管理查询(例如:SET TIMEZONE
BEGIN
COMMIT
ROLLBACK
)。
如果要查看所有数据库查询,请打开数据库中的查询日志记录。
Django除了提供Python日志记录loggging模块所提供的日志处理程序外,还提供了一个特别的日志处理程序。
class AdminEmailHandler
(include_html=False, email_backend=None**)**[source]¶
该处理程序ADMINS
针对收到的每个日志消息都会向该站点admin用户发送一封电子邮件。
如果日志记录包含request
属性,则请求的完整详细信息将包含在电子邮件中。
如果客户的IP地址在INTERNAL_IPS
设置中,则电子邮件主题将包含短语“内部IP” ;如果没有,它将包括“ EXTERNAL IP”。
如果日志记录包含堆栈跟踪信息,则该堆栈跟踪将包含在电子邮件中。
AdminEmailHandler
的include_html
参数用于控制回溯电子邮件是否包含HTML附件,该附件包含调试Web页面的完整内容。要在您的配置中设置此值,请将其包含在的处理程序定义中django.utils.log.AdminEmailHandler
,如下所示:
'handlers': {
'mail_admins': {
'level': 'ERROR',
'class': 'django.utils.log.AdminEmailHandler',
'include_html': True, # 设置为True则包含HTML附件
}
},
请注意,此电子邮件的HTML版本包含完整的追溯,在堆栈的每个级别上都包含局部变量的名称和值,以及Django设置的值。
所以此信息可能非常敏感,您可能不想通过电子邮件发送它。考虑使用诸如Sentry之类的东西来获得两全其美的体验。既有全面的回溯的丰富信息以及不通过电子邮件发送信息的安全性。
您还可以明确指定要从错误报告中过滤掉的某些敏感信息,可以了解有关过滤错误报告的更多信息 。
通过设置email_backend
的参数AdminEmailHandler
,可以覆盖处理程序正在使用的 电子邮件后端,如下所示:
'handlers': {
'mail_admins': {
'level': 'ERROR',
'class': 'django.utils.log.AdminEmailHandler',
'email_backend': 'django.core.mail.backends.filebased.EmailBackend',
}
},
默认情况下,EMAIL_BACKEND
将使用指定的电子邮件。
send_mail
(subject,message,** args*,*** kwargs*)[源代码] ¶
向管理员用户发送电子邮件。若要自定义此行为,可以对AdminEmailHandler
类进行子类化并重写此方法。Django除了提供Python日志记录模块所提供的日志过滤器外,还提供了一些特别日志过滤器。
CallbackFilter
(callback)source ¶该过滤器接受一个回调函数(该函数应接受一个参数,即要记录的信息),并为通过过滤器的每条记录调用该函数。如果回调返回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'
}
},
RequireDebugFalse
[source]¶仅当settings.DEBUG为False时,此过滤器才会传递记录。
该过滤器被用作在默认如下LOGGING
配置,以确保AdminEmailHandler
当只发送错误电子邮件到管理员:
'filters': {
'require_debug_false': {
'()': 'django.utils.log.RequireDebugFalse',
}
},
'handlers': {
'mail_admins': {
'level': 'ERROR',
'filters': ['require_debug_false'],
'class': 'django.utils.log.AdminEmailHandler'
}
},
RequireDebugTrue
[source]¶这个过滤器filter 类似于 RequireDebugFalse
,只有当 settings.DEBUG 为 False的时候,才会被调用。
看完了上面的官网文档说明,下面我们来构建一个项目,配置一个日志来熟悉一下操作步骤。
使用命令快速新建django项目,示例的项目名为 loggerpratice
。
执行命令如下:
django-admin startproject loggerpratice .
image-20200320175942151
日志部分配置,参考Django官方手册
https://docs.djangoproject.com/en/2.0/topics/logging/#examples
LOGGING属性实际上是一个dictConfig
关于dictConfig的配置,参考Python官方手册
https://docs.python.org/3/library/logging.config.html#logging-config-dictschema
image-20200320181346590
# 配置日志
LOGGING = {
'version': 1,
'disable_existing_loggers': False,
'formatters': {
'verbose': {
# 输出日志级别名称,日志消息以及生成日志消息的时间,进程,线程和模块
'format': '%(levelname)s %(asctime)s %(module)s %(process)d %(thread)d %(message)s',
'datefmt': "%Y-%m-%d %H:%M:%S"
},
'simple': { # 定义命名为 simple 的格式化
'format': '%(levelname)s %(asctime)s %(message)s' # 仅输出日志级别名称(例如 DEBUG)和日志消息。
},
},
'handlers': {
# 定义命名为 console 的处理器,将INFO级别的日志使用 stream 流处理打印到控制台
'console': {
'level': 'INFO',
'class': 'logging.StreamHandler',
'formatter': 'simple' # 使用 simple,只输出日志等级以及 messages 信息
},
# 输出日志到文件,按日期滚动
'file': {
'level': 'DEBUG',
'class': 'logging.handlers.TimedRotatingFileHandler',
# TimedRotatingFileHandler的参数
# 参照https://docs.python.org/3/library/logging.handlers.html#timedrotatingfilehandler
# 目前设定每天一个日志文件
'filename': 'logs/manage.log',
'when': 'midnight',
'interval': 1,
'backupCount': 100,
'formatter': 'verbose' # 使用复杂的verbose详细信息输出
},
# 发送邮件,目前腾讯云、阿里云的服务器对外发送邮件都有限制,暂时不使用
'email': {
'level': 'ERROR',
'class': 'django.utils.log.AdminEmailHandler',
'include_html': True,
}
},
'loggers': {
# 不同的logger
'django': {
'handlers': ['console', 'file'],
'level': 'INFO',
'propagate': True,
},
},
}
image-20200320181504641
image-20200320181642199
import logging
logger = logging.getLogger('django')
执行命令:
python manage.py startapp app
image-20200320182112519
编写一个视图函数,使用logger用于往日志文件写入日志
image-20200320182506758
from django.shortcuts import render
from logger import logger
from django.shortcuts import HttpResponse
def test_logger(request):
logger.info('test info log')
return HttpResponse('test log')
新建一个 urls.py 文件
image-20200320182949636
from django.urls import path
from . import views
urlpatterns = [
path('', views.test_logger, name='test_logger'),
]
下一步是要在根 URLconf 文件中指定我们创建的 app.urls 模块。在 项目/urls.py 文件的 urlpatterns 列表里插入一个 include(), 如下:
image-20200320183230116
from django.contrib import admin
from django.urls import path,include
urlpatterns = [
path('app/', include('app.urls')), # 导入app应用的urls.py
path('admin/', admin.site.urls),
]
执行启动服务命令,如下:
python manage.py runserver
image-20200320183410809
访问 http://127.0.0.1:8000/app/
image-20200320183453363
在访问请求了视图之后,就可以去确认查看一下日志文件了,如下:
image-20200320183628472
修改系统时间就再次请求就可以看到日志文件按照日期进行分割了。但是,在这里我使用pycharm,会提示文件使用报错。
image-20200320184127524
这个提示没有关系,只要部署到服务器上就没问题的。
日志写入仅仅对于创建的logger对象有效,如果需要使用logging直接写入,则需要再做一些修改:让logging模块使用django的dictConfig。
import logging.config
from django.conf import settings
logger = logging.getLogger('django')
logging.config.dictConfig(settings.LOGGING)
另外,Django在多进程下运行时,此方法不可靠的,因为logging模块不是进程安全的。
那么下面来继续看看如何解决多进程的日志记录问题。
参考文献:
https://www.cnblogs.com/restran/p/4743840.html
https://www.cnblogs.com/luozx207/p/10986397.html
按照官方文档的介绍,logging 是线程安全的,也就是说,在一个进程内的多个线程同时往同一个文件写日志是安全的。但是(对,这里有个但是)多个进程往同一个文件写日志不是安全的。官方的说法是这样的:
Because there is no standard way to serialize access to a single file across multiple processes in Python. If you need to log to a single file from multiple processes, one way of doing this is to have all the processes log to a SocketHandler, and have a separate process which implements a socket server which reads from the socket and logs to file. (If you prefer, you can dedicate one thread in one of the existing processes to perform this function.) ”
有的人会说,那我不用多进程不就可以了。但是 Python 有一个 GIL 的大锁(关于 GIL 的纠葛可以看这里),使用多线程是没法利用到多核 CPU 的,大部分情况下会改用多进程来利用多核 CPU,因此我们还是绕不开不开多进程下日志的问题。
为了解决这个问题,可以使用 ConcurrentLogHandler,ConcurrentLogHandler 可以在多进程环境下安全的将日志写入到同一个文件,并且可以在日志文件达到特定大小时,分割日志文件。在默认的 logging 模块中,有个 TimedRotatingFileHandler 类,可以按时间分割日志文件,可惜 ConcurrentLogHandler 不支持这种按时间分割日志文件的方式。
不过如果在windows上使用ConcurrentLogHandler库是有些卡死的进程问题的,在linux环境就不会有问题,所以这两个库需要分开环境使用。
在windows环境下,可以使用传统的logging配置调试开发,但是经常会进行文件滚动分割的时候,由于Django监控进程锁定了文件,而无法重命名文件的情况,这时候可以安装concurrent-log-handler库来解决
pip install concurrent-log-handler
image-20200323100716972
在LOGGING中,用concurrent_log_handler.ConcurrentRotatingFileHandler
代替logging.RotatingFileHandler
# settings.py
LOGGING = {
...
'handlers': {
...
# windows下 多进程的配置方式:使用concurrent_log_handler
'file': {
'level': 'INFO',
'class': 'concurrent_log_handler.ConcurrentRotatingFileHandler',
'filename': os.path.join(LOGS_DIR, 'app.log'),
'maxBytes': 1024 * 1024 * 10, # 当达到10MB时分割日志
'backupCount': 50, # 最多保留50份文件
'formatter': 'verbose' # 使用复杂的verbose详细信息输出
},
...
}
...
}
配置使用的时候,可以发现有一个 lock
文件,如下:
image-20200323104034918
在linux就需要安装ConcurrentLogHandler库了。
pip install ConcurrentLogHandler
image-20200323104804638
重新修改下 handlers 中的 class。
# settings.py
LOGGING = {
...
'handlers': {
...
# linux下 多进程的配置方式:使用ConcurrentLogHandler
'file': {
'level': 'INFO',
'class': 'cloghandler.ConcurrentRotatingFileHandler',
'filename': os.path.join(LOGS_DIR, 'app.log'),
'maxBytes': 1024 * 1024 * 10, # 当达到10MB时分割日志
'backupCount': 50, # 最多保留50份文件
'formatter': 'verbose', # 使用复杂的verbose详细信息输出
# If delay is true,
# then file opening is deferred until the first call to emit().
'delay': True,
},
...
}
...
}
运行后可以发现,会自动创建一个.lock文件,通过锁的方式来安全的写日志文件。
image-20200323111056808
(loggerpratice) [root@server01 loggerpratice]# mkdir logs
windows环境:
pip install concurrent-log-handler
linux环境:
pip install ConcurrentLogHandler
LOGS_DIR = os.path.join(BASE_DIR, 'logs') # 配置日志路径
# 配置日志
LOGGING = {
'version': 1,
'disable_existing_loggers': False,
'formatters': {
# 详细的日志格式
'standard': {
'format': '[%(asctime)s][%(threadName)s:%(thread)d][task_id:%(name)s][%(filename)s:%(lineno)d]'
'[%(levelname)s][%(message)s]'
},
'verbose': {
# 输出日志级别名称,日志消息以及生成日志消息的时间,进程,线程和模块
# 'format': '%(levelname)s %(asctime)s %(module)s %(process)d %(thread)d %(message)s',
'format': '[%(asctime)s][%(threadName)s:%(thread)d][task_id:%(name)s][%(filename)s:%(lineno)d][%(levelname)s][%(message)s]',
'datefmt': "%Y-%m-%d %H:%M:%S"
},
'simple': { # 定义命名为 simple 的格式化
'format': '%(levelname)s %(asctime)s %(message)s' # 仅输出日志级别名称(例如 DEBUG)和日志消息。
},
},
'handlers': {
# 定义命名为 console 的处理器,将INFO级别的日志使用 stream 流处理打印到控制台
'console': {
'level': 'DEBUG', # INFO DEBUG
'class': 'logging.StreamHandler',
'formatter': 'simple' # 使用 simple,只输出日志等级以及 messages 信息
},
# loggings 单进程的配置方式:输出日志到文件,按日期滚动
# 'file': {
# 'level': 'DEBUG',
# 'class': 'logging.handlers.TimedRotatingFileHandler',
# # TimedRotatingFileHandler的参数
# # 参照https://docs.python.org/3/library/logging.handlers.html#timedrotatingfilehandler
# # 目前设定每天一个日志文件
# 'filename': 'logs/loggerpratice.log',
# 'when': 'midnight',
# 'interval': 1,
# 'backupCount': 100,
# 'formatter': 'verbose', # 使用复杂的verbose详细信息输出
# 'encoding': 'utf-8' # 设置utf8编码,避免中文乱码
# },
# windows下 多进程的配置方式:使用concurrent_log_handler
'file': {
'level': 'INFO',
'class': 'concurrent_log_handler.ConcurrentRotatingFileHandler',
'filename': os.path.join(LOGS_DIR, 'app.log'),
'maxBytes': 1024 * 1024 * 10, # 当达到10MB时分割日志
'backupCount': 50, # 最多保留50份文件
'formatter': 'verbose', # 使用复杂的verbose详细信息输出
'encoding': 'utf-8' # 设置utf8编码,避免中文乱码
},
# linux下 多进程的配置方式:使用ConcurrentLogHandler
# 'file': {
# 'level': 'INFO',
# 'class': 'cloghandler.ConcurrentRotatingFileHandler',
# 'filename': os.path.join(LOGS_DIR, 'app.log'),
# 'maxBytes': 1024 * 1024 * 10, # 当达到10MB时分割日志
# 'backupCount': 50, # 最多保留50份文件
# 'formatter': 'verbose', # 使用复杂的verbose详细信息输出
# # If delay is true,
# # then file opening is deferred until the first call to emit().
# 'delay': True,
# 'encoding': 'utf-8' # 设置utf8编码,避免中文乱码
# },
# 发送邮件,目前腾讯云、阿里云的服务器对外发送邮件都有限制,暂时不使用
'email': {
'level': 'ERROR',
'class': 'django.utils.log.AdminEmailHandler',
'include_html': True,
}
},
'loggers': {
# 不同的logger
'django': {
'handlers': ['console', 'file'],
# 'level': 'INFO',
'level': 'DEBUG', # 日志的等级为 debug
'propagate': True,
},
},
}
import logging.config
from django.conf import settings
logger = logging.getLogger('django')
logging.config.dictConfig(settings.LOGGING) # logging配置
from logger import logger
logger.error('Something went wrong!') # Log an error message
logger.info('test info log')
记录器logger
实例包含每个默认日志级别的输入方法:
logger.debug()
logger.info()
logger.warning()
logger.error()
logger.critical()
还有其他两个日志记录调用:
logger.log()
:手动发出具有特定日志级别的日志消息,随便你想记录什么都行。logger.exception()
:为了捕获某些异常,创建一个ERROR级别的日志(创建一个ERROR
包装当前异常堆栈框架的级别日志记录消息)%(name)s | Logger的名字 |
---|---|
%(levelno)s | 数字形式的日志级别 |
%(levelname)s | 文本形式的日志级别 |
%(pathname)s | 调用日志输出函数的模块的完整路径名,可能没有 |
%(filename)s | 调用日志输出函数的模块的文件名 |
%(module)s | 调用日志输出函数的模块名 |
%(funcName)s | 调用日志输出函数的函数名 |
%(lineno)d | 调用日志输出函数的语句所在的代码行 |
%(created)f | 当前时间,用UNIX标准的表示时间的浮 点数表示 |
%(relativeCreated)d | 输出日志信息时的,自Logger创建以 来的毫秒数 |
%(asctime)s | 字符串形式的当前时间。默认格式是 “2003-07-08 16:49:45,896”。逗号后面的是毫秒 |
%(thread)d | 线程ID。可能没有 |
%(threadName)s | 线程名。可能没有 |
%(process)d | 进程ID。可能没有 |
%(message)s | 用户输出的消息 |