son*_*ind 5 python django session logging
我想在 Django 的每个日志条目中包含会话 ID(至少是其中的一部分)。这将帮助我将每个用户会话的日志组合在一起,以防多个用户同时与我的 Django API 交互。
我知道我可以从 Request 对象(当它存在时)检索会话 ID,但我必须为整个框架中的每个 logger.info() 这样做。相反,最好在 settings.py 中以某种方式提供 Request ,以便我可以在 LOGGING 配置中将 ID 添加到格式化程序的格式字符串中。
这将确保会话 ID 包含在每个日志条目中,我不需要在每个视图或序列化程序等中考虑它。
但是我还没有找到如何在 settings.py 中使请求或会话可用的方法。
编辑:
进一步解释。我有以下信号来捕获用户登录并在它发生时记录此事件。我想在此消息中添加会话 ID。但我希望 Django 默认为系统中的任何消息执行此操作 - 我不想在 log_user_login 函数和我的代码中的任何其他函数中获取会话 ID。
所以在下面的代码中,我想启动记录器事件,但没有指定会话 ID。
import logging
from django.dispatch import receiver
from django.contrib.auth.signals import user_logged_in
logger = logging.getLogger(__name__)
@receiver(user_logged_in)
def log_user_login(sender, user, request, **kwargs):
logger.info(f"User {user.username} logged in.")
Run Code Online (Sandbox Code Playgroud)
我希望 Django 通过 settings.py 中的配置自动添加会话 ID,以使用以下代码进行说明:
def add_session_id(record):
record.attrs = record.__dict__.keys()
return True
LOGGING = {
'version': 1,
'disable_existing_loggers': False,
'filters': {
'add_session_id': {
'()': 'django.utils.log.CallbackFilter',
'callback': add_session_id,
}
},
'formatters': {
'file': {
'format': '%(attrs)s',
},
},
}
Run Code Online (Sandbox Code Playgroud)
这将记录以下消息:
dict_keys(['name', 'msg', 'args', 'levelname', 'levelno', 'pathname', 'filename', 'module', 'exc_info', 'exc_text', 'stack_info', 'lineno', 'funcName', 'created', 'msecs', 'relativeCreated', 'thread', 'threadName', 'processName', 'process', 'attrs', 'message', 'asctime'])
Run Code Online (Sandbox Code Playgroud)
所以在我看来,提到的记录既不包含请求也不包含会话。
这是我的最终解决方案,对我来说效果很好。
我编写了一个中间件 thread_local.py,它从每个请求中提取用户名和会话 ID(如果可用),并将这些值存储为线程的本地变量。它允许我从项目的其他部分(包括日志记录)访问用户名和会话 ID。
thread_local.py:
import logging
from threading import local
_thread_locals = local()
logger = logging.getLogger(__name__)
def get_local_thread_value(item):
"""
Returns value of an item of this current thread.
"""
return getattr(_thread_locals, item, None)
def list_thread_locals(attrs):
"""
Returns a line of key:val pairs of current thread locals.
"""
return str("Current thread locals: " + ', '.join(
[
f"{attr}: {str(get_local_thread_value(attr))}"
for attr in attrs
]))
class ThreadLocalMiddleware():
"""
This middleware, for every request separately, reads important values
from Request or Session objects and adds them to local thread attributes.
This makes the values available throughout the project - in views, etc.
"""
def __init__(self, get_response=None):
"""
One-time configuration and initialization.
Called only once at server startup.
"""
self.get_response = get_response
self.attrs_to_remove = ['c_user', 'c_session_id', ]
def _set_user(self, request):
"""Get username from request data and add it to thread."""
c_user = getattr(request, 'user', None)
logger.debug(f"_set_user to _thread_locals: {c_user}")
setattr(_thread_locals, 'c_user', c_user)
def _set_session_id(self, request):
"""Get session ID from session store and add it to thread."""
c_session = getattr(request, 'session', {})
c_session_key = getattr(c_session, 'session_key', None)
c_session_id = c_session_key[-10:] if c_session_key else None
logger.debug(f"_set_session_id to _thread_locals: {c_session_id}")
setattr(_thread_locals, 'c_session_id', c_session_id)
def _unset_local_attr(self):
"""Remove attributes provided in attr_list from local thread."""
logger.debug(f"_unset_local_attr")
for attr in self.attrs_to_remove:
if hasattr(_thread_locals, attr):
delattr(_thread_locals, attr)
def __call__(self, request):
"""
Called for every request.
Fetch and store User information into local thread,
which is then available for logging filter.
"""
# Code to be executed for each request before
# the view (and later middlewares) are called.
self._set_user(request)
self._set_session_id(request)
logger.debug(list_thread_locals(self.attrs_to_remove) + " (request)")
# Forward Request further the satck and wait for Response
response = self.get_response(request)
# Code to be executed for each request/response after
# the view is called.
# Unset all local thread attributes
# to ensure they are not kept in next request/response cycle.
self._unset_local_attr()
logger.debug(list_thread_locals(self.attrs_to_remove) + " (response)")
# Let other middlewares execute their response part.
return response
def process_exception(self, request, exception):
"""
In case of exception, delete the local thread attributes,
and return None so that other middlewares can act too.
"""
logger.debug('process_exception: exception in thread_local')
self._unset_local_attr()
Run Code Online (Sandbox Code Playgroud)
然后当然不要忘记在settings.py中注册它。
日志记录: 对于常规日志记录,我编写了一个自定义日志记录过滤器:
from core.middleware.thread_local import get_local_thread_value
class CustomLoggingFilter(logging.Filter):
"""
If logging handler is configured with this filter,
a log record is updated with User information.
These attributes can be accessed by any logging formatter,
as for example: %(c_session_id).
The filter() method must return True, to ensure the record is logged.
"""
def filter(self, record):
record.c_user = get_local_thread_value('c_user')
record.c_session_id = get_local_thread_value('c_session_id')
return True
Run Code Online (Sandbox Code Playgroud)
然后,setting.py 中的日志记录部分可以如下所示(为了更详细而删除了一些部分):
LOGGING = {
'version': 1,
'disable_existing_loggers': False,
'filters': {
'custom_logging_filter': {
'()': 'core.utils.log.CustomLoggingFilter',
},
},
'formatters': {
'file': {
'format': ('%(asctime)s %(c_session_id)-10s '
'%(levelname)-8s %(c_user)s '
'%(message)s (%(name)s)'),
},
},
'handlers': {
'base_file': {
'level': 'INFO',
'filters': ['custom_logging_filter'],
'class': 'logging.FileHandler',
'formatter': 'file',
'filename': LOG_FILE_BASE,
},
},
'loggers': {
'': {
'handlers': ['base_file', ],
'level': 'DEBUG',
'propagate': True,
},
}
}
Run Code Online (Sandbox Code Playgroud)
可以使用与 Django 信号相同的方法直接在数据库中记录这些审计事件,例如,当 user_logged_in 信号触发自定义接收器时,该接收器调用函数来在审计模型中记录新对象。
| 归档时间: |
|
| 查看次数: |
1163 次 |
| 最近记录: |