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