django框架的日志设计分析

362 阅读2分钟

django框架有自带的日志管理工具,在数据库中表名是django_admin_log,模型名称是LogEntry。下面以django v2.2版本进行分析。

分析django_admin_log表的结构

使用SQL命令DESC查看django_admin_log表的结构

DESC django_admin_log;
Field          |Type             |Null|Key|Default|Extra         |
---------------+-----------------+----+---+-------+--------------+
id             |int              |NO  |PRI|       |auto_increment|
action_time    |datetime         |NO  |   |       |              |
object_id      |text             |YES |   |       |              |
object_repr    |varchar(200)     |NO  |   |       |              |
change_message |text             |NO  |   |       |              |
content_type_id|int              |YES |MUL|       |              |
user_id        |int              |NO  |MUL|       |              |
action_flag    |smallint unsigned|NO  |   |       |              |

主要业务字段如下:

  • action_time:操作时间
  • object_id:对象id,用于存放对象的主键
  • object_repr:对象显示名称
  • change_message:修改信息,一般是json格式
  • content_type_id:内容类型id,django特有
  • user_id:操作的用户id
  • action_flag:操作标志位,在代码中标识如下:1是新增,2是修改,3是删除

分析LogEntry模型

在django框架中,操作日志的模型名称是LogEntry。位置在django/contrib/admin/models.py

LogEntry类的主要方法如下:

  • is_addition:是否是新增操作
  • is_change:是否是修改操作
  • is_deletion:是否是删除操作

以上三个,主要是去判断action_flag的标识位。

  • get_change_message:生成change_message信息,这是最复杂的方法
  • get_edited_object:获取已经编辑的对象
  • get_admin_url:获取admin的URL

 

get_change_message方法分析

get_change_message方法的完整代码如下:

    def get_change_message(self):
        """
        If self.change_message is a JSON structure, interpret it as a change
        string, properly translated.
        """
        if self.change_message and self.change_message[0] == '[':
            try:
                change_message = json.loads(self.change_message)
            except json.JSONDecodeError:
                return self.change_message
            messages = []
            for sub_message in change_message:
                if 'added' in sub_message:
                    if sub_message['added']:
                        sub_message['added']['name'] = gettext(sub_message['added']['name'])
                        messages.append(gettext('Added {name} "{object}".').format(**sub_message['added']))
                    else:
                        messages.append(gettext('Added.'))

                elif 'changed' in sub_message:
                    sub_message['changed']['fields'] = get_text_list(
                        sub_message['changed']['fields'], gettext('and')
                    )
                    if 'name' in sub_message['changed']:
                        sub_message['changed']['name'] = gettext(sub_message['changed']['name'])
                        messages.append(gettext('Changed {fields} for {name} "{object}".').format(
                            **sub_message['changed']
                        ))
                    else:
                        messages.append(gettext('Changed {fields}.').format(**sub_message['changed']))

                elif 'deleted' in sub_message:
                    sub_message['deleted']['name'] = gettext(sub_message['deleted']['name'])
                    messages.append(gettext('Deleted {name} "{object}".').format(**sub_message['deleted']))

            change_message = ' '.join(msg[0].upper() + msg[1:] for msg in messages)
            return change_message or gettext('No fields changed.')
        else:
            return self.change_message

逻辑如下:

1、判断change_message字段存储的数据,第一个字符是不是方括号;

1.1 如果是,则使用json提取内容,如果提取失败,则直接返回change_message数据

1.2 如果提取成功,遍历change_message字段,分别判断key是addedchanged,还是deleted

1.2.1 如果是added,获取added中的name,拼接成Added {name} "{object}".的消息;

1.2.2 如果是changed,获取changed中的每一个fields,拼接成Changed {fields} for {name} "{object}".的消息,如果fields没有名称,则直接拼接Changed {fields}.

1.2.3 如果是,获取deleted中的name,拼接成Deleted {name} "{object}".的消息;

2、如果不是,则直接返回change_message字段的数据。

在哪调用LogEntry

那么django框架是在哪生成这些系统日志呢?查阅源码,是在ModelAdmin类里面,主要设计三个方法:

  • log_addition:添加新增日志
  • log_change:添加修改日志
  • log_deletion:添加删除日志

这几个方法分别又被_changeform_viewchangelist_view_delete_view等方法调用。当有表单提交数据过来的时候,就会被调用一次。

其中log_change的message生成非常复杂,需要调用construct_change_message方法来生成message,construct_change_message方法是在django\contrib\admin\utils.py,会对比提交的表单和字段formsets生成change_message。

 

changelog

  • 20221224:初稿