1-Django4.2 - 日志管理
about
python3.10 + django4.2.3 + win11
django4.2.3的日志官档:https://docs.djangoproject.com/zh-hans/4.2/topics/logging/
Django 使用 Python 内置的logging 模块处理系统日志。
日志信息从严重程度由高到低,一共分了5个等级。
由logging模块默认提供了5个操作方法,分别可以记录以下5个等级日志的。
CRITICAL(fatal): 致命错误,程序根本跑不起来。
ERROR: 运行错误,程序运行发生错误的地方时就会退出程序。
WARNING: 运行警告,程序运行发生警告的地方时会显示警告提示,但是程序会继续往下执行。
INFO: 运行提示,一般的系统信息,并非日志
DEBUG: 调试信息,排查故障时使用的低级别系统信息
日志的配置也简单,在django的settings.py文件中添加如下配置即可。
按文件大小配置
LOGS_DIRS = os.path.join(BASE_DIR, 'logs')
if not os.path.exists(LOGS_DIRS):
os.makedirs(LOGS_DIRS)
# 日志
LOGGING = {
'version': 1, # 使用的日志模块的版本,目前官方提供的只有版本1,但是官方有可能会升级,为了避免升级出现的版本问题,所以这里固定为1
'disable_existing_loggers': False, # 是否禁用其他的已经存在的日志功能?肯定不能,有可能有些第三方模块在调用,所以禁用了以后,第三方模块无法捕获自身出现的异常了。
'formatters': { # 日志格式设置,verbose或者simple都是自定义的
'verbose': { # 详细格式,适合用于开发人员不在场的情况下的日志记录。
# 格式定义:https://docs.python.org/3/library/logging.html#logrecord-attributes
# levelname 日志等级
# asctime 发生时间
# module 文件名
# process 进程ID
# thread 线程ID
# message 异常信息
'format': '{levelname} {asctime} {module} {process:d} {thread:d} {message}',
'style': '{', # 变量格式分隔符
},
'simple': { # 简单格式,适合用于开发人员在场的情况下的终端输出
'format': '{levelname} {message}',
'style': '{',
},
},
'filters': { # 过滤器
'require_debug_true': {
'()': 'django.utils.log.RequireDebugTrue',
},
},
'handlers': { # 日志处理流程,console或者mail_admins都是自定义的。
'console': {
'level': 'DEBUG', # 设置当前日志处理流程中的日志最低等级
'filters': ['require_debug_true'], # 当前日志处理流程的日志过滤
'class': 'logging.StreamHandler', # 当前日志处理流程的核心类,StreamHandler可以帮我们把日志信息输出到终端下
'formatter': 'simple' # 当前日志处理流程的日志格式
},
# 'mail_admins': {
# 'level': 'ERROR', # 设置当前日志处理流程中的日志最低等级
# 'class': 'django.utils.log.AdminEmailHandler', # AdminEmailHandler可以帮我们把日志信息输出到管理员邮箱中。
# 'filters': ['special'] # 当前日志处理流程的日志过滤
# }
'file': {
'level': 'INFO',
'class': 'logging.handlers.RotatingFileHandler',
# 日志位置,日志文件名,日志保存目录logs必须手动创建
'filename': '%s/django.log' % LOGS_DIRS,
# 单个日志文件的最大值,这里我们设置300M
'maxBytes': 300 * 1024 * 1024,
# 备份日志文件的数量,设置最大日志数量为10
'backupCount': 10,
# 日志格式:详细格式
'formatter': 'verbose',
'encoding': 'utf-8', # 输出日志编码
},
},
'loggers': { # 日志处理的命名空间
'django': {
'handlers': ['console', 'file'], # 当基于django命名空间写入日志时,调用那几个日志处理流程
'propagate': True, # 是否在django命名空间对应的日志处理流程结束以后,冒泡通知其他的日志功能。True表示允许
},
}
}
按日期配置
LOGS_DIRS = os.path.join(BASE_DIR, 'logs')
if not os.path.exists(LOGS_DIRS):
os.makedirs(LOGS_DIRS)
# 日志
LOGGING = {
'version': 1, # 使用的日志模块的版本,目前官方提供的只有版本1,但是官方有可能会升级,为了避免升级出现的版本问题,所以这里固定为1
'disable_existing_loggers': False, # 是否禁用其他的已经存在的日志功能?肯定不能,有可能有些第三方模块在调用,所以禁用了以后,第三方模块无法捕获自身出现的异常了。
'formatters': { # 日志格式设置,verbose或者simple都是自定义的
'verbose': { # 详细格式,适合用于开发人员不在场的情况下的日志记录。
# 格式定义:https://docs.python.org/3/library/logging.html#logrecord-attributes
# levelname 日志等级
# asctime 发生时间
# module 文件名
# process 进程ID
# thread 线程ID
# message 异常信息
'format': '{levelname} {asctime} {module} {process:d} {thread:d} {message}',
'style': '{', # 变量格式分隔符
},
'simple': { # 简单格式,适合用于开发人员在场的情况下的终端输出
'format': '{levelname} {message}',
'style': '{',
},
},
'filters': { # 过滤器
'require_debug_true': {
'()': 'django.utils.log.RequireDebugTrue',
},
},
'handlers': { # 日志处理流程,console或者mail_admins都是自定义的。
'console': {
'level': 'DEBUG', # 设置当前日志处理流程中的日志最低等级
'filters': ['require_debug_true'], # 当前日志处理流程的日志过滤
'class': 'logging.StreamHandler', # 当前日志处理流程的核心类,StreamHandler可以帮我们把日志信息输出到终端下
'formatter': 'simple' # 当前日志处理流程的日志格式
},
# 'mail_admins': {
# 'level': 'ERROR', # 设置当前日志处理流程中的日志最低等级
# 'class': 'django.utils.log.AdminEmailHandler', # AdminEmailHandler可以帮我们把日志信息输出到管理员邮箱中。
# 'filters': ['special'] # 当前日志处理流程的日志过滤
# }
'file': {
'level': 'INFO',
'class': 'logging.handlers.TimedRotatingFileHandler',
# 日志位置,日志文件名,日志保存目录logs必须手动创建
'filename': '%s/django.log' % LOGS_DIRS,
# TimedRotatingFileHandler的参数
# 目前设定每天一个日志文件
# 'S' | 秒
# 'M' | 分
# 'H' | 时
# 'D' | 天
# 'W0'-'W6' | 周一至周日
# 'midnight' | 每天的凌晨
'when': 'S', # 间间隔的类型,指定秒就不要在Windows上运行测试
'interval': 5, # 时间间隔
'backupCount': 5, # 能留几个日志文件;过数量就会丢弃掉老的日志文件
'encoding': 'utf-8', # 日志文本编码
},
},
'loggers': { # 日志处理的命名空间
'django': {
'handlers': ['console', 'file'], # 当基于django命名空间写入日志时,调用那几个日志处理流程
'propagate': True, # 是否在django命名空间对应的日志处理流程结束以后,冒泡通知其他的日志功能。True表示允许
},
}
}
另外,在需要的文件,也可以调用logging模块,将简单的日志输出到控制台:
import logging
logger = logging.getLogger('django')
logger.warning('xxxx')
日志切割报错处理
参考:https://blog.csdn.net/u012887259/article/details/109590337
有个问题,我们需要处理,那就是在配置了日志切割(按文件大小和按日期都会出现)之后,当日志管理模块准备切割日志时,会出现权限报错,具体的现象就是:
PermissionError: [WinError 32] 鍙︿竴涓▼搴忔鍦ㄤ娇鐢ㄦ鏂囦欢锛岃繘绋嬫棤娉曡闂��: 'D:\\demo4\\logs\\django.log' -> 'D:\\demo4\\logs\\django.log.2023-07-28_17-42-43'
原因是源码中,会有个re.rename的操作,将django.log
原来的名成新名字django.log.2023-07-28_17-42-43
,但是因为django.log
被别的进程占用着,导致改名失败,导致权限错误。
其原因就是logging日志是线程安全的,也就是说一个进程中多个线程同时往一个日志文件中写入日志是安全的。但是在Django项目运行时,是多进程的,导致出现多进程操同一个文件,就出现了权限问题了,这个问题在Windows上尤为明显。
解决办法就是通过三方模块ConcurrentLogHandler
解决,但由于Windows下的锁机制,又有大佬高出了个Windows版本,所以,Windows这个模块叫做concurrent-log-handler
,除此之外,这个模块不支持按照日期切割日志,所以目前给出的建议。
- 如果使用上面两个默认的日志配置,那么出现权限问题只会出现在Windows上,在Linux上目前我没发现问题,而通常Django项目时部署在Linux上的,所以,这个问题,可以忽略,因为开发阶段,一般不需要配置日志,上线到Linux上,又没有这个问题了。
- 那就是在Windows下使用
concurrent-log-handler
结合按照文件大小切割的方式使用日志。到了Linux上,想要使用按照日期切割日志,那就按照原来的方式配置就完了;如果想按照文件大小切割日志,就将concurrent-log-handler
替换为ConcurrentLogHandler
就行了。
来看下这个ConcurrentLogHandler
怎么配置的。
下载模块:
# Windows下
pip install concurrent-log-handler
# Linux下
pip install ConcurrentLogHandler
Windows下配置,追加日志配置到Django的settings.py文件:
# 按文件大小切割日志
# 日志
LOGGING = {
'version': 1, # 使用的日志模块的版本,目前官方提供的只有版本1,但是官方有可能会升级,为了避免升级出现的版本问题,所以这里固定为1
'disable_existing_loggers': False, # 是否禁用其他的已经存在的日志功能?肯定不能,有可能有些第三方模块在调用,所以禁用了以后,第三方模块无法捕获自身出现的异常了。
'formatters': { # 日志格式设置,verbose或者simple都是自定义的
'verbose': { # 详细格式,适合用于开发人员不在场的情况下的日志记录。
# 格式定义:https://docs.python.org/3/library/logging.html#logrecord-attributes
# levelname 日志等级
# asctime 发生时间
# module 文件名
# process 进程ID
# thread 线程ID
# message 异常信息
'format': '{levelname} {asctime} {module} {process:d} {thread:d} {message}',
'style': '{', # 变量格式分隔符
},
'simple': { # 简单格式,适合用于开发人员在场的情况下的终端输出
'format': '{levelname} {message}',
'style': '{',
},
},
'filters': { # 过滤器
'require_debug_true': {
'()': 'django.utils.log.RequireDebugTrue',
},
},
'handlers': { # 日志处理流程,console或者mail_admins都是自定义的。
'console': {
'level': 'DEBUG', # 设置当前日志处理流程中的日志最低等级
'filters': ['require_debug_true'], # 当前日志处理流程的日志过滤
'class': 'logging.StreamHandler', # 当前日志处理流程的核心类,StreamHandler可以帮我们把日志信息输出到终端下
'formatter': 'simple' # 当前日志处理流程的日志格式
},
# 'mail_admins': {
# 'level': 'ERROR', # 设置当前日志处理流程中的日志最低等级
# 'class': 'django.utils.log.AdminEmailHandler', # AdminEmailHandler可以帮我们把日志信息输出到管理员邮箱中。
# 'filters': ['special'] # 当前日志处理流程的日志过滤
# }
'file': {
'level': 'INFO',
# 'class': 'logging.handlers.RotatingFileHandler', # 默认的按照文件大小切割日志
'class': 'concurrent_log_handler.ConcurrentRotatingFileHandler', # Windows下安装并使用 concurrent-log-handler
# 'class': 'cloghandler.ConcurrentRotatingFileHandler', # Linux安装并使用 concurrent-log-handler
'delay': True, # 同时添加delay参数
# 日志位置,日志文件名,日志保存目录logs必须手动创建
'filename': '%s/django.log' % LOGS_DIRS,
# 单个日志文件的最大值,这里我们设置300M,你也可以设置的更大
# 'maxBytes': 1024 * 1024 * 300,
'maxBytes': 1024 * 1024 * 0.1, # 为了测试方便,每个文件设置100kb,方便观察日志切割效果
# 备份日志文件的数量,设置最大日志数量为3,当然,这个数字可以设置的更大
# 这么设置之后,每当切割日志,包含正在使用的日志文件,还将保留最近的3个日志文件,其它的都将在日志切割时删除
'backupCount': 3,
# 日志格式:详细格式
'formatter': 'verbose',
'encoding': 'utf-8', # 输出日志编码
},
},
'loggers': { # 日志处理的命名空间
'django': {
'handlers': ['console', 'file'], # 当基于django命名空间写入日志时,调用那几个日志处理流程
'propagate': True, # 是否在django命名空间对应的日志处理流程结束以后,冒泡通知其他的日志功能。True表示允许
},
}
}
你可以写个request脚本去测试请求某个视图,测下日志切割效果:
import requests
count = 1
while True:
# 请求某个URL
url = 'http://127.0.0.1:8000/index/'
res = requests.get(url, headers={
"User-Agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/115.0.0.0 Safari/537.36",
'Connection': 'close'
})
print(res.text, count)
res.close()
count += 1
logger对象单独使用
在settings.py配置好了日志之后,日志就会自动的写入到日志文件中,如果想要单独使用,比某个视图中需要单独使用,你可以这样做。
1. 在项目某个目录中创建个py文件,比如我把它创建在项目根目录下的/utils/logger.py中
import logging.config
from django.conf import settings
logger = logging.getLogger('django')
logging.config.dictConfig(settings.LOGGING) # logging配置
2. 使用时这么导入就行了,例如我在某个视图函数中使用
import datetime
from django.shortcuts import render, HttpResponse
from utils.logger import logger # 导入logger对象
def index(request):
now = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
# 使用它,日志会自动输出到控制台和日志文件
logger.info('info...')
logger.warning('warning...')
logger.error('error...')
return HttpResponse(now)