在GAE中记录所有Python数据库对象的审计日志

1 投票
2 回答
2579 浏览
提问于 2025-04-16 19:34

我刚开始学习Python,想把我用PHP和MS-SQL写的一个应用程序的功能在Google Apps Engine上重新实现。

我想做的一件事是模拟我在MS-SQL中某些表的当前活动,比如说有一个插入/删除/更新的触发器,它会把当前记录(变更前的状态)复制一份到一个审计表里,并且标记上日期和时间。这样,我就可以在以后查询这个审计表,查看记录经历过的变化历史。

我在StackOverflow上找到了以下代码:

class HistoryEventFieldLevel(db.Model):
    # parent, you don't have to define this
    date = db.DateProperty()
    model = db.StringProperty()
    property = db.StringProperty() # Name of changed property
    action = db.StringProperty( choices=(['insert', 'update', 'delete']) )
    old = db.StringProperty() # Old value for field, empty on insert
    new = db.StringProperty() # New value for field, empty on delete

不过,我不太确定这段代码怎么能应用到我新数据库中的所有对象上。

我是不是应该为每个对象创建get()和put()函数,然后在put()函数里创建这个类的一个子对象,并设置它的特定属性呢?

2 个回答

0

我对谷歌应用引擎(GAE)有点生疏,而且手头没有SDK来测试,所以这里给你一些指导,帮你理清思路。

  1. 创建一个叫做AuditMeta的元类,然后把它应用到你想要进行审计的模型上。
  2. 在创建新模型类的时候,AuditMeta应该复制这个类,并在名字后面加上"_audit",同时也要复制这个类的属性。这个过程在GAE上有点复杂,因为属性本身就是描述符。
  3. 给每个这样的类添加一个put方法,在这个方法里为该类创建一个审计对象并保存。这样,对于表tableA中的每一行数据,你都会在tableA_audit中有一份历史记录。

比如,一个简单的Python示例(不涉及GAE)

import new

class AuditedModel(object):
    def put(self):
        print "saving",self,self.date
        audit = self._audit_class()
        audit.date = self.date
        print "saving audit",audit,audit.date

class AuditMeta(type):
    def __new__(self, name, baseclasses, _dict):
        # create model class, dervied from AuditedModel
        klass = type.__new__(self, name, (AuditedModel,)+baseclasses, _dict)

        # create a audit class, copy of klass
        # we need to copy attributes properly instead of just passing like this
        auditKlass = new.classobj(name+"_audit", baseclasses, _dict)
        klass._audit_class = auditKlass

        return klass

class MyModel(object):
    __metaclass__ = AuditMeta

    date = "XXX"

# create object
a = MyModel()
a.put()

输出:

saving <__main__.MyModel object at 0x957aaec> XXX
saving audit <__main__.MyModel_audit object at 0x957ab8c> XXX

可以阅读审计跟踪的代码,只有200行,看看他们是如何在Django中实现的。

1

这确实是可能的,虽然有点复杂。下面是一些可以帮助你入门的小建议:

  • 仅仅重写类的 put() 方法是不够的,因为实体也可以通过调用 db.put() 来存储,这样不会调用你所写类中的任何方法。
  • 你可以通过修改SDK来解决这个问题,让它在调用前后执行一些钩子函数,具体可以参考我博客中的文章 这里
  • 另外,你也可以在更底层实现RPC钩子,相关内容可以在另一篇博客文章中找到 这里
  • 把审计记录存储为被修改实体的子实体是个好主意,这样可以在事务中处理,虽然这需要进一步的、更复杂的修改。
  • 你不需要为每个字段都记录一条记录。实体有一种自然的序列化格式,叫做协议缓冲区(Protocol Buffers),你可以简单地把实体作为编码后的协议缓冲区存储在审计记录中。如果你是在模型层操作,可以使用 model_to_protobuf 将模型转换为协议缓冲区。
  • 以上所有方法都更容易在修改后存储记录,而不是在修改前存储。不过这应该不是问题——如果你需要修改前的记录,可以直接在审计日志中回溯一条记录。

撰写回答