可以使用Python内置的logging模块来实现Django项目的日志记录。
所以与其说这篇文章在讲Django的“日志功能-日志模块-日志输出”,不如说是在讲Pthon的“日志功能-日志模块-日志输出”,即Python的logging模块。
下面用一个实例来进行讲解。
现在我要在Django的视图函数index()中输出之前用print()输出的信息。
用logging模块改写前的视图函数index()的代码如下:
def index(request):
year = 2023
month = 11
day = 22
day_of_week = 'Wednesday'
print(f"Today's date is:{year}-{month}-{day}-{day_of_week}")
return render(request, 'index.html') # 将渲染结果输出到index.html模板中
在上面的代码中,print()语句根据上面设置的相关变量值输出下面的字符串:
Today's date is:2023-11-22-Wednesday
接下来,我们就根据Python内置的logging模块的使用方法来将上面的这个字符串输出到日志文件 logfile666.log 中。
首先我把完整的代码给出来,然后再慢慢讲。
视图函数index()的完整代码如下:
from django.shortcuts import render # 默认导入的模块
import logging # 导入日志记录模块
# 创建一个名为'index_log'的日志记录器
logger01 = logging.getLogger('index_log')
# Create your views here.
def index(request):
year = 2023
month = 11
day = 22
day_of_week = 'Wednesday'
logger01.debug(f"Today's date is:{year}-{month}-{day}-{day_of_week}")
return render(request, 'index.html') # 将渲染结果输出到index.html模板中
setting.py中添加如下代码:
# 设置日志记录器
LOGGING = {
'version': 1,
'disable_existing_loggers': True,
'handlers': {
'file01': {
'level': 'DEBUG',
'class': 'logging.FileHandler',
'filename': 'log/logfile666.log', # 指定日志文件的路径,相对路径时以Django项目的根目录为此路径的根路径,当然也可用绝对路径,比如E:/log/logfile666.log
},
},
'loggers': {
'index_log': {
'handlers': ['file01'],
'level': 'DEBUG',
'propagate': False,
},
},
}
用下面的命令开启Django的web服务后:
python manage.py runserver 127.0.0.1:8010
访问URL:
http://127.0.0.1:8010/index/
发现控制台没有字符串:Today's date is:2023-11-22-Wednesday
的输出。
而在目录 BASE_DIR下出现了日志文件:logfile666.log
其内容如下:
可见,实现了我们的需求。
接下来,对上面这个实例中的相关代码进行详解。
首先用语句import logging
导入日志记录模块,然后利用语句logger01 = logging.getLogger('index_log')
创建一个名为index_log
的logger对象,这个logger对象的实例化变量名为logger01
。
然后使用语句logger01.debug(f"Today's date is:{year}-{month}-{day}-{day_of_week}")
进行日志信息的输出。
语句logger01.debug(f"Today's date is:{year}-{month}-{day}-{day_of_week}")
在进行日志信息输出时,因为这是一个名叫index_log
的logger对象,所以去调用名叫index_log
的logger的配置,具体代码如下:
'loggers': {
'index_log': {
'handlers': ['file01'],
'level': 'DEBUG',
'propagate': False,
},
}
而名叫index_log
的logger具体在执行日志信息的输出时,调用的是句柄file01
,句柄file01
的设置如下:
'handlers': {
'file01': {
'level': 'DEBUG',
'class': 'logging.FileHandler',
'filename': 'log/logfile666.log', # 指定日志文件的路径,相对路径时以Django项目的根目录为此路径的根路径,当然也可用绝对路径,比如E:/log/logfile666.log
},
},
整个过程的大致介绍如上。
接下来对关键代码进行详细解释。
视图函数index()的完整代码如下:
from django.shortcuts import render # 默认导入的模块
import logging # 导入日志记录模块
# 创建一个名为'index_log'的日志记录器
logger01 = logging.getLogger('index_log')
# Create your views here.
def index(request):
year = 2023
month = 11
day = 22
day_of_week = 'Wednesday'
logger01.debug(f"Today's date is:{year}-{month}-{day}-{day_of_week}")
return render(request, 'index.html') # 将渲染结果输出到index.html模板中
第01句关键代码:
logger01 = logging.getLogger('index_log')
在这里面,注意参数'index_log'
,这是我们在setting.py中设置的记录器的名字,setting.py的相关截图如下:
第02句关键代码:
logger01.debug(f"Today's date is:{year}-{month}-{day}-{day_of_week}")
logging模块一共有五个日志输出方法,对应于五个日志级别,分别如下:
logger.debug() # 调试级别的日志输出语句
logger.info() # 信息级别的日志输出语句
logger.warning() # 警告级别的日志输出语句
logger.error() # 错误级别的日志输出语句
logger.critical() # 严重错误级别的日志输出语句
上面这个五个日志级别的级别由低到高的顺序为:
debug→info→warning→error→critical
一条日志,该用哪个级别,由用户自己定义。
值得注意的是:
方法级别越高,那么要想输出相应的日志信息,那么对应的logger的级别应等于或小于其级别,而logger的级别又应比相应的hander级别高才行。
举个例子:
假如用方法logger.warning()输出日志信息,那么logger的级别(level)可以为WARNING或比WARNING小于的INFO、DEBUG,但不能为ERROR、CRITICAL。
假如logger的级别(level)设置为INFO,那么要先想其对应的handler能最终输出日志信息到日志文件,那么就需要handler的级别为INFO或比INFO级别小的DEBUG,但不能为WARNING、ERROR、CRITICAL。
另外,上面提到的五个日志输出方法:
logger.debug() # 调试级别的日志输出语句
logger.info() # 信息级别的日志输出语句
logger.warning() # 警告级别的日志输出语句
logger.error() # 错误级别的日志输出语句
logger.critical() # 严重错误级别的日志输出语句
其用法和print()一模一样。
相关代码如下:
# 设置日志记录器
LOGGING = {
'version': 1,
'disable_existing_loggers': True,
'handlers': {
'file01': {
'level': 'DEBUG',
'class': 'logging.FileHandler',
'filename': 'log/logfile666.log', # 指定日志文件的路径,相对路径时以Django项目的根目录为此路径的根路径,当然也可用绝对路径,比如E:/log/logfile666.log
},
},
'loggers': {
'index_log': {
'handlers': ['file01'],
'level': 'DEBUG',
'propagate': False,
},
},
}
'disable_existing_loggers': False
'disable_existing_loggers': False,
'disable_existing_loggers'
是 Django 中配置日志的一个选项。它是一个布尔值,用于指定是否禁用已经存在的日志记录器(loggers)。
当 'disable_existing_loggers'
设置为 True
时,Django 将禁用所有已经存在的根记录器(root logger)和在 'loggers'
部分中未明确指定的其他记录器。这样可以确保日志记录器的配置是全新的,不受之前的全局配置的影响。
而当 'disable_existing_loggers'
设置为 False
时,Django 会保留已经存在的日志记录器,不禁用它们。这意味着在 'loggers'
部分中配置的记录器只是添加到已经存在的记录器列表中,而不是替换它们。这样的配置可能会导致全局的日志配置不够清晰,因为它们可能受到之前配置的影响。
以下是一个示例,演示了 'disable_existing_loggers'
设置为 True
和 False
时的不同行为:
LOGGING = {
'version': 1,
'disable_existing_loggers': True, # 或者 False
'handlers': {
'console': {
'class': 'logging.StreamHandler',
},
},
'loggers': {
'django': {
'handlers': ['console'],
'level': 'INFO',
},
'my_app': {
'handlers': ['console'],
'level': 'DEBUG',
},
},
}
如果 'disable_existing_loggers'
设置为 True
,那么所有已经存在的根记录器和未明确指定的其他记录器将被禁用。只有 'django'
和 'my_app'
记录器会生效。而如果设置为 False
,那么已经存在的记录器仍然有效,所有的记录器都会生效,可能受到全局配置的影响。
在这里,我们设置为False,以避免影响别的日志输出,实践证明,如果这里设置为True,那么除了这里设置的日志输出,别的日志输出全没有了。
'level': 'DEBUG'
handlers里面的level设置语句:
'level': 'DEBUG',
这个level的作用已经在本文的“04-视图函数view.py中的关键代码详解”里进行了详细的说明,这里就不再叙述了。
'class': 'logging.FileHandler'
handlers里面的class设置语句:
'class': 'logging.FileHandler'
'class': 'logging.FileHandler'
是配置 logging
模块中的处理器(handler)的一部分。这个配置指定了使用 FileHandler
类来处理日志消息,并将这些消息写入到文件中。
在 Python 的 logging
模块中,处理器是用于指定日志消息的输出目的地的对象。FileHandler
是一种处理器(handler),它将日志消息写入到文件中。
让我们来详细解释这个配置:
'class'
: 这个键指定了使用的处理器类的名称。在这里,'logging.FileHandler'
表示我们使用 FileHandler
类。FileHandler
类是 logging
模块提供的标准处理器之一,专门用于将日志消息写入到文件。使用 FileHandler
处理器的一个典型配置可能如下:
'handlers': {
'file': {
'class': 'logging.FileHandler',
'filename': 'logfile.log', # 指定日志文件的路径
'level': 'DEBUG', # 指定处理器的日志级别
},
},
在上面的配置中,我们创建了一个名为 ‘file’ 的处理器,指定了使用 FileHandler
类。配置还包括了 'filename'
键,用于指定日志文件的路径,以及 'level'
键,用于指定处理器的日志级别。 'level'
的设置将影响处理器接受的日志消息的最低级别,低于该级别的消息将被忽略。
总体而言,'class': 'logging.FileHandler'
是用于将日志消息写入文件的标准配置。如果你需要将日志记录到文件中,可以使用这个配置,并根据需要添加其他相关的设置,比如 'filename'
和 'level'
。
问:除了FileHandler类,还有哪些类,请分别介绍。
在 Python 的 logging
模块中,除了 FileHandler
类之外,还有一些其他常用的处理器类。以下是其中一些类的介绍:
StreamHandler:
StreamHandler
类用于将日志消息输出到流(例如,标准输出或标准错误)。可以通过配置 'class': 'logging.StreamHandler'
来使用它。'handlers': {
'console': {
'class': 'logging.StreamHandler',
'level': 'DEBUG',
},
},
RotatingFileHandler:
RotatingFileHandler
类用于将日志消息写入到文件,并支持日志文件的滚动(回滚),即在达到一定大小时创建新的日志文件。可以通过配置 'class': 'logging.handlers.RotatingFileHandler'
来使用它。'handlers': {
'rotating_file': {
'class': 'logging.handlers.RotatingFileHandler',
'filename': 'logfile.log',
'maxBytes': 1024, # 指定单个日志文件的最大大小
'backupCount': 3, # 指定保留的旧日志文件数量
'level': 'DEBUG',
},
},
对于类logging.handlers.RotatingFileHandler,假如日志文字的名字为 logfile.log 并超过指定大小后,会创建新的日志文件,之前的日志文件会被命名为什么呢?
答:RotatingFileHandler
类在创建新的日志文件时,会为旧的日志文件添加一个后缀,以标识其顺序。这个后缀通常是一个数字,表示日志文件的旋转顺序。在默认情况下,后缀从 1 开始,每次创建新的日志文件,后缀递增。
例如,假设你配置了一个 RotatingFileHandler
如下:
'handlers': {
'rotating_file': {
'class': 'logging.handlers.RotatingFileHandler',
'filename': 'logfile.log',
'maxBytes': 1024, # 指定单个日志文件的最大大小
'backupCount': 3, # 指定保留的旧日志文件数量
'level': 'DEBUG',
},
},
如果 logfile.log
超过了 1024
字节,RotatingFileHandler
会创建一个新的日志文件,原始的 logfile.log
会被重命名为 logfile.log.1
,而新的日志文件将继续使用 logfile.log
的文件名。
如果再次超过最大大小,会创建另一个新的日志文件 logfile.log
,而原来的 logfile.log.1
会被重命名为 logfile.log.2
。以此类推,旧的日志文件会依次向后移动并重命名。
这样,backupCount
参数指定了保留的旧日志文件数量。在上面的例子中,设置为 3
表示会保留最新的 3 个旧日志文件,即 logfile.log.1
、logfile.log.2
、logfile.log.3
。超过这个数量后,最旧的日志文件会被删除。
TimedRotatingFileHandler:
TimedRotatingFileHandler
类也用于将日志消息写入到文件,但支持基于时间的日志文件滚动。可以通过配置 'class': 'logging.handlers.TimedRotatingFileHandler'
来使用它。'handlers': {
'timed_rotating_file': {
'class': 'logging.handlers.TimedRotatingFileHandler',
'filename': 'logfile.log',
'when': 'midnight', # 指定滚动周期,可以是 'midnight', 'H', 'MIDNIGHT', 'D', 'S', 'W0' 等
'interval': 1, # 滚动周期的时间间隔
'backupCount': 3, # 指定保留的旧日志文件数量
'level': 'DEBUG',
},
},
问:对于类ogging.handlers.TimedRotatingFileHandler,假如日志文字的名字为 logfile.log 并超过指定大小后,会创建新的日志文件,之前的日志文件会被命名为什么呢?请举一个具体的例子并假设周期为天。
答:TimedRotatingFileHandler
类与 RotatingFileHandler
类不同,它的日志文件滚动是基于时间的。在配置 TimedRotatingFileHandler
时,你需要指定 when
参数来定义滚动周期。常见的值包括 'midnight'
(每天的午夜滚动)以及其他时间单位,如 'H'
(每小时滚动)等。
假设你的配置如下:
'handlers': {
'timed_rotating_file': {
'class': 'logging.handlers.TimedRotatingFileHandler',
'filename': 'logfile.log',
'when': 'midnight', # 指定滚动周期为每天的午夜
'interval': 1, # 滚动周期的时间间隔
'backupCount': 3, # 指定保留的旧日志文件数量
'level': 'DEBUG',
},
},
在这个例子中,如果 logfile.log
超过指定的大小,TimedRotatingFileHandler
会创建一个新的日志文件,并在当前日志文件名上附加日期后缀,而不是简单地增加数字序列。每天午夜时,会创建一个新的日志文件,之前的日志文件会被重命名为带有日期后缀的形式。
例如,如果当前日期是 2023-01-01
,并且 logfile.log
超过了指定的大小,那么会创建一个新的日志文件,同时原来的 logfile.log
会被重命名为 logfile.log.2023-01-01
。
下一天(2023-01-02
),再次创建新的日志文件,同时原来的 logfile.log
会被重命名为 logfile.log.2023-01-02
。依此类推,每天都会创建一个新的日志文件,并按照日期后缀的形式重命名之前的日志文件。
这样,backupCount
参数指定了保留的旧日志文件数量,而 when
和 interval
参数则定义了滚动的时间周期。
NullHandler:
NullHandler
类用于禁用日志记录,即将所有接收到的日志消息忽略。可以通过配置 'class': 'logging.NullHandler'
来使用它。'handlers': {
'null_handler': {
'class': 'logging.NullHandler',
},
},
这些处理器类提供了不同的日志记录方式和选项,可以根据项目的需求选择合适的处理器。在配置文件中,通过 'class'
键来指定使用的处理器类,然后可以根据处理器类的不同,配置相应的参数。
'propagate': False
'propagate': False
'propagate'
是配置日志记录器是否传递日志消息给其父记录器的一个属性。当 'propagate'
设置为 True
时,表示该记录器的日志消息将会传递给其父记录器,而父记录器的处理程序也将处理这些消息。如果设置为 False
,则该记录器的消息将不会传递给其父记录器。
在 Django 中,默认情况下,'propagate'
属性被设置为 True
。这意味着除非明确地指定为 False
,否则日志消息会被传递给更高级别的记录器。这种设置允许你在项目的不同部分使用不同的日志记录器,同时确保日志消息能够在整个应用程序中传递。
'propagate'
设置为 True
时,如果在记录器中记录了一条日志消息,它将传递给其父记录器(如果有的话),以便在整个日志体系中处理。
如果你希望某个特定记录器的日志消息不传递给其父记录器,可以将 'propagate'
设置为 False
。这通常在配置多个记录器时用于避免重复记录相同的日志消息。
这个问题已经在本篇本文第05点的第03句代码中说得很清楚了,这里不再赘述。
Python 的 logging
模块可以自动在每一条日志消息的前面加上时间。这是通过在日志记录器(logger)的格式化字符串中添加时间信息来实现的。
在配置日志处理器时,你可以指定一个格式化字符串,该字符串中可以包含各种信息,包括时间。常用的时间格式占位符包括:
%asctime
: 人类可读的时间,其具体格式由 formatter
参数指定。%created
: 创建日志记录的时间戳。%msecs
: 毫秒部分。%relativeCreated
: 日志记录创建时的时间戳,以毫秒为单位,相对于日志系统启动时间。%thread
: 线程ID。%levelname
: 日志级别的文本表示。接下来我们把本文中的例子加上时间戳。
其实就是把setting.py中对日志记录器的配置改成下面这段代码:
# 设置日志记录器
LOGGING = {
'version': 1,
'disable_existing_loggers': False,
'handlers': {
'file01': {
'level': 'DEBUG',
'class': 'logging.FileHandler',
'filename': 'log/logfile888.log', # 指定日志文件的路径
'formatter': 'verbose', # 将格式化器设置为 'verbose'
},
},
'loggers': {
'index_log': {
'handlers': ['file01'],
'level': 'DEBUG',
'propagate': False,
},
},
'formatters': {
'verbose': {
'format': '%(asctime)s - %(message)s',
'datefmt': '%Y-%m-%d %H:%M:%S', # 人类可读的时间格式
},
},
}
在上面这段代码中:
我添加了 'formatters'
部分,定义了一个名为 'verbose'
的格式化器,其中包含人类可读时间的占位符 %asctime
,并通过'datefmt'
指定了具体的人类可读的时间格式。
然后,我在 'handlers'
部分的 'file01'
处将格式化器设置为 'verbose'
。
这样,便可以实现在每条日志的前面加上时间戳了。
值得注意的是:如果不通过'datefmt'
指定具体的格式,那么默认情况下,%asctime
使用的是计算机友好的时间格式,即包含日期和时间的完整字符串。这种格式对于机器解析是方便的,但对人类来说不够友好。
修改完成后,我们再访问:
http://127.0.0.1:8010/index/
就产生了日志文件:logfile888.log
其内容如下:
可见,我们成功实现了在日志前面加上时间戳的需求。
在下面的代码中:
logger_time是有时间戳输出的logger,对应绑定的handler为handler01;
logger_no_time是无时间输出的looger,对应绑定的handler为handler02;
# 设置日志记录器
LOGGING = {
'version': 1,
'disable_existing_loggers': False,
'handlers': {
'handler01': {
'level': 'DEBUG',
'class': 'logging.FileHandler',
'filename': 'log/logfile-time-2013-11-23-05.log', # 指定日志文件的路径,注意,在Centos服务器中要用绝对路径
'formatter': 'verbose1', # 将格式化器设置为 'verbose1'
},
'handler02': {
'level': 'DEBUG',
'class': 'logging.FileHandler',
'filename': 'log/logfile-no-time-2023-11-23-05.log', # 指定日志文件的路径,注意,在Centos服务器中要用绝对路径
},
},
'loggers': {
'logger_time': {
'handlers': ['handler01'],
'level': 'DEBUG',
'propagate': False,
},
'logger_no_time': {
'handlers': ['handler02'],
'level': 'DEBUG',
'propagate': False,
},
},
'formatters': {
'verbose1': {
'format': '%(asctime)s - %(message)s',
'datefmt': '%Y-%m-%d %H:%M:%S', # 人类可读的时间格式
},
},
}
在上面的代码中:
logger_time是有时间戳输出的logger,对应绑定的handler为handler01;
logger_no_time是无时间输出的looger,对应绑定的handler为handler02;