为Zope 3中单个对象提供可定制的日志。
项目描述
objectlog包为单个对象提供可定制的日志。该系统旨在提供重要对象更改的视觉日志信息,并为度量提供可分析的信息。
它为每个日志条目自动记录时间戳和日志创建时的请求主体。
给定收集对象数据的数据模式,它自动计算并存储自最后一个日志条目以来的更改集,主要是为了快速简单地回答“有什么变化?”的问题,其次是减少数据库大小。
它接受可选的摘要和详细信息值,允许系统或用户使用可读消息对条目进行注释。
它允许每个日志条目使用零个或多个标记接口进行注释,以便日志条目可以使用接口进行分类。
此外,日志条目可以设置在转换边界发生,并且仅在自上次日志条目以来进行了更改(根据更改集)时才发生。
为了展示这一点,我们需要设置一个模拟交互。我们在下面这样做,然后创建一个带有日志的对象,然后实际创建一个日志。
>>> import zope.security.management >>> import zope.security.interfaces >>> import zope.app.security.interfaces >>> from zope import interface, schema >>> from zope.app.testing import ztapi >>> class DummyPrincipal(object): ... interface.implements(zope.security.interfaces.IPrincipal) ... def __init__(self, id, title, description): ... self.id = unicode(id) ... self.title = title ... self.description = description ... >>> alice = DummyPrincipal('alice', 'Alice Aal', 'first principal') >>> betty = DummyPrincipal('betty', 'Betty Barnes', 'second principal') >>> cathy = DummyPrincipal('cathy', 'Cathy Camero', 'third principal') >>> class DummyParticipation(object): ... interface.implements(zope.security.interfaces.IParticipation) ... interaction = principal = None ... def __init__(self, principal): ... self.principal = principal ... >>> import zope.publisher.interfaces>>> import zc.objectlog >>> import zope.location >>> WORKING = u"Where I'm working" >>> COUCH = u"On couch" >>> BED = u"On bed" >>> KITCHEN = u"In kitchen" >>> class ICat(interface.Interface): ... name = schema.TextLine(title=u"Name", required=True) ... location = schema.Choice( ... (WORKING, COUCH, BED, KITCHEN), ... title=u"Location", required=False) ... weight = schema.Int(title=u"Weight in Pounds", required=True) ... getAge, = schema.accessors( ... schema.Int(title=u"Age in Years", readonly=True, ... required=False)) ... >>> import persistent >>> class Cat(persistent.Persistent): ... interface.implements(ICat) ... def __init__(self, name, weight, age, location=None): ... self.name = name ... self.weight = weight ... self.location = location ... self._age = age ... self.log = zc.objectlog.Log(ICat) ... zope.location.locate(self.log, self, 'log') ... def getAge(self): ... return self._age ...
注意在cat的__init__中,我们在cat上定位了日志。这是一个重要的步骤,因为它启用了自动更改集。
现在我们已设置好查看示例。除了一个例外,每个示例都在一个模拟交互中运行,这样我们可以看到principal_ids属性是如何工作的。首先,我们将看到len是有效的,record_schema属性被正确设置,时间戳使用pytz.utc时区,日志迭代正常工作,以及摘要、详细信息和数据被正确设置。
>>> import pytz, datetime >>> a_p = DummyParticipation(alice) >>> interface.directlyProvides(a_p, zope.publisher.interfaces.IRequest) >>> zope.security.management.newInteraction(a_p) >>> emily = Cat(u'Emily', 16, 5, WORKING) >>> len(emily.log) 0 >>> emily.log.record_schema is ICat True >>> before = datetime.datetime.now(pytz.utc) >>> entry = emily.log( ... u'Starting to keep track of Emily', ... u'Looks like\nshe might go upstairs soon') >>> entry is emily.log[0] True >>> after = datetime.datetime.now(pytz.utc) >>> len(emily.log) 1 >>> before <= entry.timestamp <= after True >>> entry.timestamp.tzinfo is pytz.utc True >>> entry.principal_ids (u'alice',) >>> list(emily.log) == [entry] True >>> entry.record_schema is ICat True >>> entry.summary u'Starting to keep track of Emily' >>> entry.details u'Looks like\nshe might go upstairs soon'
record和record_changes应包含对象的所有值。record有一个特殊的权限检查器,允许用户访问模式上定义的任何字段,但不能访问其他字段或写入任何值。
>>> record = emily.log[0].record >>> record.name u'Emily' >>> record.location==WORKING True >>> record.weight 16 >>> record.getAge() 5 >>> ICat.providedBy(record) True >>> emily.log[0].record_changes == { ... 'name': u'Emily', 'weight': 16, 'location': u"Where I'm working", ... 'getAge': 5} True >>> from zope.security.checker import ProxyFactory >>> proxrecord = ProxyFactory(record) >>> ICat.providedBy(proxrecord) True >>> from zc.objectlog import interfaces >>> interfaces.IRecord.providedBy(proxrecord) True >>> from zope.security import canAccess, canWrite >>> canAccess(record, 'name') True >>> canAccess(record, 'weight') True >>> canAccess(record, 'location') True >>> canAccess(record, 'getAge') True >>> canAccess(record, 'shazbot') # doctest: +ELLIPSIS Traceback (most recent call last): ... ForbiddenAttribute: ('shazbot', ... >>> canWrite(record, 'name') False >>> zope.security.management.endInteraction()
多个主体的交互也被正确记录。请注意,非请求参与不包括在记录中。我们还更详细地查看记录和更改集。
>>> a_p = DummyParticipation(alice) >>> b_p = DummyParticipation(betty) >>> c_p = DummyParticipation(cathy) >>> interface.directlyProvides(a_p, zope.publisher.interfaces.IRequest) >>> interface.directlyProvides(b_p, zope.publisher.interfaces.IRequest) >>> zope.security.management.newInteraction(a_p, b_p, c_p) >>> emily.location = KITCHEN >>> entry = emily.log(u"Sounds like she's eating", u"Dry food,\nin fact.") >>> len(emily.log) 2 >>> emily.log[0].summary u'Starting to keep track of Emily' >>> emily.log[1].summary u"Sounds like she's eating" >>> after <= emily.log[1].timestamp <= datetime.datetime.now(pytz.utc) True >>> emily.log[1].principal_ids # cathy was not a request, so not included (u'alice', u'betty') >>> emily.log[1].details u'Dry food,\nin fact.' >>> emily.log[1].record_changes {'location': u'In kitchen'} >>> record = emily.log[1].record >>> record.location u'In kitchen' >>> record.name u'Emily' >>> record.weight 16 >>> zope.security.management.endInteraction()
也可以在没有交互的情况下创建日志。
>>> emily._age = 6 >>> entry = emily.log(u'Happy Birthday') # no interaction >>> len(emily.log) 3 >>> emily.log[2].principal_ids () >>> emily.log[2].record_changes {'getAge': 6} >>> record = emily.log[2].record >>> record.location u'In kitchen' >>> record.name u'Emily' >>> record.weight 16 >>> record.getAge() 6
条目可以使用标记接口进行标记以进行分类。这种方法可能与安全代理难以配合,因此可能会更改。我们将在同一个交互中进行所有其他示例。
>>> c_p = DummyParticipation(cathy) >>> interface.directlyProvides(c_p, zope.publisher.interfaces.IRequest) >>> zope.security.management.newInteraction(c_p) >>> emily.location = None >>> emily.weight = 17 >>> class IImportantLogEntry(interface.Interface): ... "A marker interface for log entries" >>> interface.directlyProvides( ... emily.log(u'Emily is in transit...and ate a bit too much'), ... IImportantLogEntry) >>> len(emily.log) 4 >>> [e for e in emily.log if IImportantLogEntry.providedBy(e)] == [ ... emily.log[3]] True >>> emily.log[3].principal_ids (u'cathy',) >>> emily.log[3].record_changes=={'weight': 17, 'location': None} True >>> record = emily.log[3].record >>> old_record = emily.log[2].record >>> record.name == old_record.name == u'Emily' True >>> record.weight 17 >>> old_record.weight 16 >>> record.location # None >>> old_record.location u'In kitchen'
如果尝试创建的记录不符合其模式,则创建日志将失败。
>>> emily.location = u'Outside' >>> emily.log(u'This should never happen') Traceback (most recent call last): ... ConstraintNotSatisfied: Outside >>> len(emily.log) 4 >>> emily.location = BED
如果传递给它的参数不正确,它也会失败。
>>> emily.log("This isn't unicode so will not succeed") Traceback (most recent call last): ... WrongType: ("This isn't unicode so will not succeed", <type 'unicode'>) >>> len(emily.log) 4 >>> success = emily.log(u"Yay, unicode")
以下内容将在我们有更多信息之前被注释掉
只要它们实现一个接口,就可以在日志条目中包含零个或多个额外的任意数据对象。
>>> class IConsumableRecord(interface.Interface): ... dry_food = schema.Int( ... title=u"Dry found consumed in ounces", required=False) ... wet_food = schema.Int( ... title=u"Wet food consumed in ounces", required=False) ... water = schema.Int( ... title=u"Water consumed in teaspoons", required=False) ...
__getitem__和__iter__对于Python序列的正常工作,包括对扩展切片的支持。
>>> list(emily.log) == [emily.log[0], emily.log[1], emily.log[2], ... emily.log[3], emily.log[4]] True >>> emily.log[-1] is emily.log[4] True >>> emily.log[0] is emily.log[-5] True >>> emily.log[5] Traceback (most recent call last): ... IndexError: list index out of range >>> emily.log[-6] Traceback (most recent call last): ... IndexError: list index out of range >>> emily.log[4:2:-1] == [emily.log[4], emily.log[3]] True
只要没有日志或接口扩展(或等于)最后日志的接口,就可以更改日志的record_schema。
>>> emily.log.record_schema = IConsumableRecord # doctest: +ELLIPSIS Traceback (most recent call last): ... ValueError: Once entries have been made, may only change schema to one... >>> class IExtendedCat(ICat): ... parent_object_intid = schema.Int(title=u"Parent Object") ... >>> emily.log.record_schema = IExtendedCat >>> emily.log.record_schema = ICat >>> emily.log.record_schema = IExtendedCat >>> class ExtendedCatAdapter(object): ... interface.implements(IExtendedCat) ... def __init__(self, cat): # getAge is left off ... self.name = cat.name ... self.weight = cat.weight ... self.location = cat.location ... self.parent_object_intid = 42 ... >>> ztapi.provideAdapter((ICat,), IExtendedCat, ExtendedCatAdapter) >>> entry = emily.log(u'First time with extended interface') >>> emily.log.record_schema = ICat # doctest: +ELLIPSIS Traceback (most recent call last): ... ValueError: Once entries have been made, may only change schema to one... >>> emily.log[5].record_changes == { ... 'parent_object_intid': 42, 'getAge': None} True >>> record = emily.log[5].record >>> record.parent_object_intid 42 >>> record.name u'Emily' >>> record.location u'On bed' >>> record.weight 17 >>> record.getAge() # None >>> IExtendedCat.providedBy(record) True >>> old_record = emily.log[3].record >>> IExtendedCat.providedBy(old_record) False >>> ICat.providedBy(old_record) True >>> old_record.parent_object_intid # doctest: +ELLIPSIS Traceback (most recent call last): ... AttributeError: ...
条目支持便利的next和previous属性,这使得它们表现得像不可变的双向链表。
>>> entry = emily.log[5] >>> entry.previous is emily.log[4] True >>> entry.next # None >>> entry.previous.previous.previous.previous.previous is emily.log[0] True >>> emily.log[0].previous # None >>> emily.log[0].next is emily.log[1] True
Objectlogs还支持延迟到事务结束。为了展示这一点,我们需要一个示例数据库、一个事务和关键引用适配器。我们首先展示最简单的示例。
>>> from ZODB.tests import util >>> import transaction>>> db = util.DB() >>> connection = db.open() >>> root = connection.root() >>> root["emily"] = emily >>> transaction.commit() >>> import zope.app.keyreference.persistent >>> import zope.app.keyreference.interfaces >>> import ZODB.interfaces >>> import persistent.interfaces >>> from zope import component >>> component.provideAdapter( ... zope.app.keyreference.persistent.KeyReferenceToPersistent, ... (persistent.interfaces.IPersistent,), ... zope.app.keyreference.interfaces.IKeyReference) >>> component.provideAdapter( ... zope.app.keyreference.persistent.connectionOfPersistent, ... (persistent.interfaces.IPersistent,), ... ZODB.interfaces.IConnection)>>> len(emily.log) 6 >>> emily.log(u'This one is deferred', defer=True) # returns None: deferred! >>> len(emily.log) 6 >>> transaction.commit() >>> len(emily.log) 7 >>> emily.log[6].summary u'This one is deferred' >>> emily.log[6].record_changes {}
虽然这很有趣,但目的是捕获对象的更改,无论这些更改是否在调用日志时发生。以下是一个更相关的示例。
>>> len(emily.log) 7 >>> emily.weight = 16 >>> emily.log(u'Also deferred', defer=True) # returns None: deferred! >>> len(emily.log) 7 >>> emily.location = COUCH >>> transaction.commit() >>> len(emily.log) 8 >>> emily.log[7].summary u'Also deferred' >>> import pprint >>> pprint.pprint(emily.log[7].record_changes) {'location': u'On couch', 'weight': 16}
如果需要,可以延迟多个延迟日志条目。
>>> emily.log(u'One log', defer=True) >>> emily.log(u'Two log', defer=True) >>> len(emily.log) 8 >>> transaction.commit() >>> len(emily.log) 10 >>> emily.log[8].summary u'One log' >>> emily.log[9].summary u'Two log'
另一个选项是if_changed。它应该在有更改的情况下才创建日志。
>>> len(emily.log) 10 >>> emily.log(u'If changed', if_changed=True) # returns None: no change! >>> len(emily.log) 10 >>> emily.location = BED >>> entry = emily.log(u'If changed', if_changed=True) >>> len(emily.log) 11 >>> emily.log[10] is entry True >>> entry.summary u'If changed' >>> pprint.pprint(entry.record_changes) {'location': u'On bed'} >>> transaction.commit()
两种选项,if_changed 和 defer,可以一起使用。这会使日志条目只在没有之前变更的情况下,在转换边界处创建。请注意,无论是否进行了更改(以下简称“必需”日志条目),如果该日志条目也被延迟,则它将始终消除任何延迟的 if_changed 日志条目,即使必需日志条目是在事务中稍后注册的。
>>> len(emily.log) 11 >>> emily.log(u'Another', defer=True, if_changed=True) # returns None >>> transaction.commit() >>> len(emily.log) 11 >>> emily.log(u'Yet another', defer=True, if_changed=True) # returns None >>> emily.location = COUCH >>> len(emily.log) 11 >>> transaction.commit() >>> len(emily.log) 12 >>> emily.log[11].summary u'Yet another' >>> emily.location = KITCHEN >>> entry = emily.log(u'non-deferred entry', if_changed=True) >>> len(emily.log) 13 >>> entry.summary u'non-deferred entry' >>> emily.log(u'will not write', defer=True, if_changed=True) >>> transaction.commit() >>> len(emily.log) 13 >>> emily.log(u'will not write', defer=True, if_changed=True) >>> emily.location = WORKING >>> emily.log(u'also will not write', defer=True, if_changed=True) >>> emily.log(u'required, deferred', defer=True) >>> len(emily.log) 13 >>> transaction.commit() >>> len(emily.log) 14 >>> emily.log[13].summary u'required, deferred'
当然,在存在多个对象的情况下,所有这些都应该工作。
>>> sam = Cat(u'Sam', 20, 4) >>> root['sam'] = sam >>> transaction.commit() >>> sam.weight = 19 >>> sam.log(u'Auto log', defer=True, if_changed=True) >>> sam.log(u'Sam lost weight!', defer=True) >>> sam.log(u'Saw sam today', defer=True) >>> emily.log(u'Auto log', defer=True, if_changed=True) >>> emily.weight = 15 >>> transaction.commit() >>> len(sam.log) 2 >>> sam.log[0].summary u'Sam lost weight!' >>> sam.log[1].summary u'Saw sam today' >>> len(emily.log) 15 >>> emily.log[14].summary u'Auto log'>>> # TEAR DOWN >>> zope.security.management.endInteraction() >>> ztapi.unprovideUtility(zope.app.security.interfaces.IAuthentication)
变更
0.2 (2008-05-16)
移除了对 zc.security 的依赖;放宽了 principal_ids 的限制。
0.1.1 (2008-04-02)
更新了 setup.py 中的路径,以便在 Linux 以外的平台上运行 setup。
0.1 (2008-04-02)
初始发布(已移除 dev 状态)
项目详情
zc.objectlog-0.2.2.tar.gz 的哈希值
算法 | 哈希摘要 | |
---|---|---|
SHA256 | 0f81c5dcc912dc403a4ec470cbb7cfb2b70e7ef49aaacb30d534adcd0239a808 |
|
MD5 | 48976ebf496ff208d5e4bf8d0e89bca1 |
|
BLAKE2b-256 | 167a04ad9aa6dc0647251885bd6365835c9bb837cd619a13027ca61825dd86e2 |