在 Django 中设置和使用日志是一个有效的方式来监控和调试应用程序。日志可以帮助你理解应用的运行状态,记录错误信息,以及跟踪重要的系统事件。Django 使用 Python 的标准 logging 模块来配置和管理日志。
目录
配置日志
日志配置通常在 Django 的设置文件中(如 settings.py)进行。Django 允许你详细地自定义日志记录器、处理器、过滤器和格式化器。
简单的日志配置:
python
LOGGING = {
'version': 1,
#日志配置的版本,当前只支持 1
'disable_existing_loggers': False,
# 设置为 False 表示不禁用在配置加载前已经存在的日志记录器。如果设置为 True,除了那些在配置中明确声明的,所有现存的日志记录器将被禁用。
# 处理器
'handlers': {
'console': {
'level': 'ERROR',
'class': 'logging.StreamHandler',
},
# 日志输出到控制台
},
# 记录器
'loggers': {
'django': {
'handlers': ['console'],
'level': 'ERROR',
'propagate': True,
},
},
}
对于生产环境,需要将日志写入文件 ,并进行归档管理。这通常通过配置文件处理器来实现,如使用 logging.handlers.RotatingFileHandler 或 logging.handlers.TimedRotatingFileHandler 来按文件大小或时间自动分割日志文件。
正式版的settings.py的log 配置:
python
LOG_BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
# __file__ 是一个特殊变量,它包含当前文件(通常是一个 Python 脚本)的路径。
# os.path.abspath(__file__):获取 __file__ 的绝对路径。这是为了确保路径是完整的,不依赖于当前工作目录
# os.path.dirname():获取路径中的目录部分。第一次调用得到的是文件所在的目录(例如,如果 __file__ 是 /home/user/project/app/settings.py,则返回 /home/user/project/app)
LOG_DIR = os.path.join(LOG_BASE_DIR, 'logs')
# 这行代码的结果是在项目根目录下创建一个名为 logs 的文件夹,用于存储所有日志文件。
os.makedirs(LOG_DIR, exist_ok=True)
#这个函数用于创建目录。如果目录的中间部分不存在,os.makedirs() 会创建它们。
# exist_ok=True 参数表示如果目录已经存在,不会抛出异常,而是正常处理。这对于重启应用不希望因为目录已存在而出错的场景非常有用
log_format = '%(asctime)s | Request ID: %(request_id)s | %(module)s | %(funcName)s | %(message)s '
# 样式输出:
#2024-06-28 09:52:35 | Request ID: 4241ec7f-ad58-4582-bf3b-282XXXXXX3d | log_middleware | __call__ | Request URL: /XXXXX
LOGGING = {
'version': 1,
'disable_existing_loggers': False,、
# 过滤器:自定义过滤器,用于在日志消息中添加一个请求 ID,使得在并发访问日志时可以轻松追踪到特定请求的日志。假定 RequestIDFilter 是一个已定义的过滤器类,它可能从请求元数据中提取或生成唯一的请求 ID
'filters': {
'request_id': {
'()': RequestIDFilter,
},
},
# 格式化器: 定义了日志的输出格式
'formatters': {
'default': {
'format': log_format, # 调用上面的格式
'datefmt': '%Y-%m-%d %H:%M:%S',
},
},
# 处理器
'handlers': {
'info_file': {
'level': 'INFO',
'class': 'logging.handlers.TimedRotatingFileHandler',# 支持按时间自动分割日志文件。
'filename': os.path.join(LOG_DIR, 'info.log'), # 指定日志文件的存储位置
'when': 'midnight', # 指定日志文件分割的时间点,这里设置为 'midnight',表示每天午夜分割日志文件。
'backupCount': 7, #日志文件的保留数量,这里设置为 7,表示保存最近 7 天的日志文件。
'formatter': 'default', # 使用前面定义的 default 格式化器。
'filters': ['request_id'],
},
'error_file': {
'level': 'ERROR', # 处理来自 Django 请求模块的 ERROR 级别日志
'class': 'logging.handlers.TimedRotatingFileHandler',
'filename': os.path.join(LOG_DIR, 'error.log'),
'when': 'midnight',
'backupCount': 7,
'formatter': 'default',
'filters': ['request_id'],
},
'console': {
'level': 'DEBUG',
'class': 'logging.StreamHandler',
'formatter': 'default',
'filters': ['request_id'],
},
},
# 记录器
'loggers': {
# 专门用于捕获 Django 框架产生的所有日志,从 INFO 级别开始记录。
'django': {
'handlers': ['info_file', 'error_file', 'console'],
'level': 'INFO',
'propagate': True,
},
# 捕获所有未被其他记录器处理的日志,同样从 INFO 级别开始。
'': {
'handlers': ['info_file', 'error_file', 'console'],
'level': 'INFO',
'propagate': True,
},
'django.request': {
'handlers': ['error_file', 'console'],
'level': 'ERROR',
'propagate': False, # 设置为 False,阻止日志向更高级别的记录器传播,这在此记录器中很有用,以避免错误信息被重复记录
},
},
}
写日志
可以使用 logging 模块来记录日志。首先需要导入 logging 库,然后创建一个日志记录器实例:
python
import logging
logger = logging.getLogger(__name__)
def my_view(request):
try:
# 你的业务逻辑
pass
except Exception as e:
logger.error('Something went wrong: %s', e)
Django 和 Python 的日志系统支持多种日志级别,你可以根据需要记录不同级别的信息:
DEBUG: 详细信息,通常只在诊断问题时有用。
INFO: 确认事情按预期运行。
WARNING: 表明有一些意外,或在不久的将来可能出现问题(例如"磁盘空间低")。软件还能按预期工作。
ERROR: 由于更严重的问题,软件已不能执行某些功能。
CRITICAL: 严重错误,表明程序本身可能无法继续运行。
日志中间件
日志中间件可以在每次请求处理过程中提供更精细的日志记录。例如,它可以详细记录每个请求的 URL、请求体、响应体等,这些是通过标准 Django 日志设置不易实现的。
python
class LogMiddleware:
def __init__(self, get_response):
self.get_response = get_response # 这是下一个中间件或最终的视图函数。在 Django 中间件是一个请求处理的链条,每个中间件都需要调用下一个中间件来继续处理请求
self.logger = logging.getLogger(__name__) # 使用 Python 的 logging 库来创建一个日志记录器,这个记录器使用当前模块的名字(__name__)作为标识。这样,日志的输出可以根据模块来筛选和控制
def __call__(self, request):
self.logger.info('Request URL: %s', request.path_info)
self.logger.info('Request body: %s', request.body[:1024])
response = self.get_response(request) # 调用链中下一个中间件或视图函数,并获取响应对象
if response['Content-Type'] == 'application/json':
content = json.loads(response.content)
self.logger.info('Response body: %s', json.dumps(content, indent=4))
return response
Django 中的 MIDDLEWARE 设置是一个中间件的列表,定义了每个请求在处理过程中经过的中间件。这个列表中的每个中间件都需要实现 call 方法,并能接受一个请求对象并返回一个响应对象。中间件在列表中的顺序很重要,因为它决定了请求和响应的处理顺序。
python
# 使用 Python 的 contextvars 模块定义了一个上下文变量,该变量将在请求的处理过程中保持请求 ID。contextvars 提供了一种在异步应用中保持变量上下文的方法,这对于 Django 异步视图或在异步环境下工作时尤其重要。
request_id_var = contextvars.ContextVar('request_id', default='unknown')
# 这个中间件用于为每个进入的请求生成并附加一个唯一的请求 ID。
class RequestIDMiddleware:
def __init__(self, get_response):
self.get_response = get_response
# 定义一个中间件类时,__init__ 方法接收一个 get_response 参数。这个参数是在中间件初始化时由 Django 传入的,并且它指向中间件链中下一个处理请求的函数(可能是另一个中间件的 __call__ 方法或者 Django 的视图处理函数)。
def __call__(self, request):
# 从同一个用户或同一个会话中的多个请求也能区分开。
request_id = str(uuid.uuid4())
request_id_var.set(request_id)
request.request_id = request_id
response = self.get_response(request)
response['X-Request-ID'] = request_id
return response
# 日志过滤器用于将请求 ID 从上下文变量注入到日志记录中。
class RequestIDFilter(logging.Filter):
# 继承 Python 的 logging.Filter 类。这意味着它可以重写 filter 方法,该方法用于决定一个特定的日志记录是否应该被记录或丢弃。
def filter(self, record):
record.request_id = request_id_var.get()
return True
请求 ID 和用户 token 的区别
请求 ID:是为了日志记录和调试目的而生成的一个短暂的、唯一 的标识符,用于追踪单个请求的处理过程 。它通常只在服务器日志和响应头中使用,并且每个请求都不相同。
用户 Token:通常是用于认证和授权的长期有效的凭证,它表明了用户的身份,并且在多个请求间保持不变,除非用户的会话到期或被注销。
如果你想要基于用户或会话来生成一个持久的 ID,可能需要另一种实现方式,例如:
使用用户的 session ID 或 token 的某种散列值作为请求 ID 的一部分。
将请求 ID 存储在用户的会话中,并在会话持续期间重复使用该 ID。
这种方法可以帮助你跟踪一个用户在会话期间发起的所有请求,但它减少了能够跟踪单独请求的粒度。
其中这里的X-Request-ID ,查看方式:
bash
curl -i https://yourserver.com/yourpath
返回的值:
bash
HTTP/1.1 404 Not Found
Server: nginx/1.20.1
Date: Fri, 28 Jun 2024 11:06:58 GMT
Content-Type: text/html; charset=utf-8
Content-Length: 179
Connection: keep-alive
X-Frame-Options: DENY
X-Content-Type-Options: nosniff
Referrer-Policy: same-origin
Cross-Origin-Opener-Policy: same-origin
Vary: origin
X-Request-ID: 6654a63d-411d-4a9e-9380-636aXXXXXX
<!doctype html>
<html lang="en">
<head>
记录日志:
记录日志时包含 request_id:
在 Django 应用中,使用 RequestIDMiddleware 设置的 request_id 可以在任何日志消息中包括。这样做可以帮助你在查看日志文件时迅速关联到具体的请求。
客户端(如浏览器、移动应用或其他服务)在收到响应时,可以记录或显示 X-Request-ID。
request_id 成为了日志记录和错误追踪的关键部分。每个请求的处理从开始到结束都可以通过这个唯一标识符进行追踪。当系统出现问题时,通过提供 X-Request-ID ,技术支持人员可以迅速定位到问题发生时的后端日志,极大地提高了问题诊断的效率。这在分布式系统或对外提供 API 的服务中尤其有价值。
get_response 是一个可调用对象,通常指向中间件链中的下一个中间件或最终的视图 。这是 Django 中间件机制的核心组成部分,它使得中间件能够以层叠的方式处理请求和响应。
每个中间件的 call 方法可以决定在将请求传递给 get_response 前后做些什么,这提供了极大的灵活性。
日志配置与日志中间件
Django 日志系统 (LOGGING 配置)
Django 的 LOGGING 配置提供了一个灵活的日志系统,可以详细定义日志的处理方式,如何格式化,以及存储位置等。这些配置确保了应用可以记录关键的系统信息、错误、警告等,这对于监控和调试应用至关重要。
日志处理器 (handlers) : 负责将日志消息发送到指定的目的地,比如文件或控制台。
过滤器 (filters) : 决定哪些日志记录应该被传递给日志处理器。
格式器 (formatters) : 定义日志输出的格式。
记录器 (loggers) : 为不同的应用或库配置日志级别和处理方式。
中间件 (MIDDLEWARE): 中间件是 Django 请求/响应处理的一个框架,允许你插入自定义代码到请求处理的生命周期中。这包括在请求处理之前、之后或在视图函数调用前后执行代码。
RequestIDMiddleware: 用于为每个请求生成一个唯一的请求 ID,并通过响应头返回这个 ID。这有助于追踪和关联请求处理过程中发生的所有事件和日志。
LogMiddleware: 可能用于记录请求和响应的细节,如请求路径、请求体的一部分、响应状态等。这是增强日志信息的一种方式,可以帮助开发者理解请求的上下文。
相互关系和潜在冲突
在 Django 中,日志系统和中间件不会直接冲突,因为它们处理的层面不同:
日志系统 主要关注于如何记录、格式化和存储日志消息。
中间件 则提供了一种方式在请求/响应处理过程中执行自定义逻辑。
使用 RequestIDMiddleware 为每个请求生成的 request_id 通过日志系统的 RequestIDFilter 添加到日志消息中,是一种典型的使两者协同工作的方式。这确保了所有日志消息都包含了与请求相关的唯一标识符,大大提高了问题追踪和调试的效率。
总结
不会造成冲突,而是互为补充 。中间件在请求处理过程中生成和管理请求 ID,而日志系统则负责将这些信息记录下来。通过合理配置,这两者可以非常好地一起工作,提供强大的调试和监控能力。这是构建可维护和可监控 web 应用的重要组成部分。
LogMiddleware 是操作在**请求处理层面,直接记录请求和响应的详细信息。**这些日志通常更具体,更偏向于应用级别的监控。
LOGGING 配置定义的日志记录可能更广泛,不仅限于请求和响应信息,还包括应用的其他部分的日志,如数据库操作、系统错误等。
python
2024-06-28 01:57:11 | Request ID: 3e8f1840-0d19-4161-b09a-f03XXXXX555 | log_middleware | __call__ | Request URL: /xxxxx
2024-06-28 01:57:11 | Request ID: 3e8f1840-0d19-4161-b09a-f030XXXXXX5 | log_middleware | __call__ | Request body: b'{""}'
确保 LogMiddleware 正确配置在 Django 的 MIDDLEWARE 设置中,这样它就会被每个请求调用。同时,通过适当配置 LOGGING 设置中的日志级别、格式和目标(如文件、控制台或外部系统),可以确保这些信息被有效记录并且易于访问和分析。