1 2from zope.interface import Interface 3from foolscap.remoteinterface import RemoteInterface 4from foolscap.schema import DictOf, ListOf, Any, Optional, ChoiceOf 5 6TubID = Any() # printable, base32 encoded 7Incarnation = (Any(), ChoiceOf(Any(), None)) 8Header = DictOf(Any(), Any()) 9Event = DictOf(Any(), Any()) # this has message:, level:, facility:, etc 10EventWrapper = DictOf(Any(), Any()) # this has from:, rx_time:, and d: 11 12class RILogObserver(RemoteInterface): 13 __remote_name__ = "RILogObserver.foolscap.lothar.com" 14 def msg(logmsg=Event): 15 return None 16 def done(): 17 return None 18 19 def new_incident(name=Any(), trigger=Event): 20 # should this give (tubid, incarnation, trigger) like list_incidents? 21 return None 22 def done_with_incident_catchup(): 23 return None 24 25class RILogFile(RemoteInterface): 26 __remote_name__ = "RILogFile.foolscap.lothar.com" 27 def get_header(): 28 # (tubid, incarnation, 29 # (first_event: number, time), (last_event: number, time), 30 # num_events, 31 # level_map, # maps string severity to count of messages 32 # ) 33 return (TubID, int, (int, int), (int, int), int, DictOf(Any(), int)) 34 def get_events(receiver=RILogObserver): 35 """The designated receiver will be sent every event in the logfile, 36 followed by a done() call.""" 37 return None 38 39class RISubscription(RemoteInterface): 40 __remote_name__ = "RISubscription.foolscap.lothar.com" 41 def unsubscribe(): 42 """Cancel a subscription. Once this method has been completed (and 43 its Deferred has fired), no further messages will be received by the 44 observer (i.e. the response to unsubscribe() will wait until all 45 pending messages have been queued). 46 47 This method is idempotent: calling it multiple times has the same 48 effect as calling it just once.""" 49 return None 50 51class RILogPublisher(RemoteInterface): 52 __remote_name__ = "RILogPublisher.foolscap.lothar.com" 53 def get_versions(): 54 return DictOf(Any(), Any()) 55 def get_pid(): 56 return int 57 58 def subscribe_to_all(observer=RILogObserver, 59 catch_up=Optional(bool, False)): 60 """ 61 Call unsubscribe() on the returned RISubscription object to stop 62 receiving messages. 63 """ 64 return RISubscription 65 def unsubscribe(subscription=Any()): 66 # NOTE: this is deprecated. Use subscription.unsubscribe() instead. 67 # I don't know how to get the constraint right: unsubscribe() should 68 # accept return value of subscribe_to_all() 69 return None 70 71 def enumerate_logfiles(): 72 return ListOf(RILogFile) 73 74 # Incident support 75 76 def list_incidents(since=Optional(Any(), "")): 77 """Return a dict that maps an 'incident name' (a string of the form 78 'incident-TIMESTAMP-UNIQUE') to the triggering event (a single event 79 dictionary). The incident name can be passed to get_incident() to 80 obtain the list of events (including header) contained inside the 81 incident report. Incident names will sort in chronological order. 82 83 If the optional since= argument is provided, then this will only 84 return incident names that are alphabetically greater (and thus 85 chronologically later) than the given string. This can be used to 86 poll an application for incidents that have occurred since a previous 87 query. For real-time reporting, use subscribe_to_incidents() instead. 88 """ 89 return DictOf(Any(), Event) 90 91 def subscribe_to_incidents(observer=RILogObserver, 92 catch_up=Optional(bool, False), 93 since=Optional(Any(), "")): 94 """Subscribe to hear about new Incidents, optionally catching up on 95 old ones. 96 97 Each new Incident will be reported by name+trigger to the observer by 98 a new_incident() message. This message will be sent after the 99 incident reporter has finished working (usually a few seconds after 100 the triggering event). 101 102 If catch_up=True, then old Incidents will be sent to the observer 103 before any new ones are reported. When the publisher has finished 104 sending the names of all old events, it will send a 105 done_with_incident_catchup() message to the observer. Only old 106 Incidents with a name that is alphabetically greater (and thus later) 107 than the since= argument will be sent. Use since='' to catch up on 108 all old Incidents. 109 110 Call unsubscribe() on the returned RISubscription object to stop 111 receiving messages. 112 """ 113 return RISubscription 114 115 def get_incident(incident_name=Any()): 116 """Given an incident name, return the header dict and list of event 117 dicts for that incident.""" 118 # note that this puts all the events in memory at the same time, but 119 # we expect the logfiles to be of a reasonable size: not much larger 120 # than the circular buffers that we keep around anyways. 121 return (Header, ListOf(Event)) 122 123class RILogGatherer(RemoteInterface): 124 __remote_name__ = "RILogGatherer.foolscap.lothar.com" 125 def logport(nodeid=TubID, logport=RILogPublisher): 126 return None 127 128class IIncidentReporter(Interface): 129 def incident_declared(triggering_event): 130 """This is called when an Incident needs to be recorded.""" 131 def new_trigger(triggering_event): 132 """This is called when a triggering event occurs while an incident is 133 already being reported. If the event happened later, it would trigger 134 a new incident. Since it overlapped with the existing incident, it 135 will just be added to that incident. 136 137 The triggering event will also be reported through the usual 138 event-publish-subscribe mechanism. This method is provided to give 139 the reporter the opportunity to mark the event somehow, for the 140 benefit of incident-file analysis tools. 141 """ 142 def is_active(): 143 """Returns True if the reporter is still running. While in this 144 state, new Incident triggers will be passed to the existing reporter 145 instead of causing a new Incident to be declared. This will tend to 146 coalesce back-to-back problems into a single Incident.""" 147 148