1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27 """
28 @summary: Sends emails with a "digest" of selected messages
29 @author: Ruben Reifenberg
30 """
31
32 from threading import Thread, currentThread, RLock, enumerate as threading_enumerate
33 from time import sleep
34 from datetime import datetime,timedelta
35
36 from rrlog.tool import mail_smtp
37 from rrlog.server.textwriter import default_format_line
38
39
40 _spoolers = []
41
43 """
44 Stops all running spooler threads.
45 """
46 for spooler in _spoolers:
47 spooler.stop()
48
49 -def now(): return datetime.now()
50
51 -def create_observer_smtp(server, serverpw, from_address, to_address, rules, format_line=None, subject="Log Digest", charset="latin-1", spooler_autostop=True):
52 """
53 Creates a mail notifier with a background worker thread.
54 Convenience method which sets default values appropriate for many use cases.
55 Appends the spooler to the modules spoolers list so that the L{stop_all} function can stop it.
56 @param rules: see L{CatBuffer.__init__}
57 @param spooler_autostop: see L{Spooler.__init__}. Defaults to True, so that explicit stop of the spooler threads is not necessary usually.
58 @return: An observer object, intended to be added into the observers list of the log.
59 """
60 buffer = CatBuffer(rules=rules, format_line=format_line)
61 mailer = SMTPMailer(server=server, serverpw=serverpw, from_address=from_address, to_address=to_address, default_subject=subject, charset=charset)
62 _spoolers.append(
63 Spooler(
64 mailer=mailer,
65 buffer=buffer,
66 autostop=spooler_autostop,
67 )
68 )
69 return buffer
70
71
72
74 - def __init__(self, cats, max_delay_secs=0):
75 """
76 @param max_delay_secs: Maximum time that a message of that categories can be buffered before sending.
77 High values result in less (but bigger) mails.
78 0 == send each line nearly immediately
79 (Note: because the worker thread pauses frequently,
80 we have still a littly delay, so that still multiple messages might be collected per mail.)
81 @param cats: tuple of cats to catch, e.g. ("E","S").
82 None == catch any category (could cause high email traffic)
83 """
84 assert not isinstance(cats,str),"cats must be a tuple/list of cat, not '%s'"%(cats)
85 self._cats = cats
86 self._max_delay_secs = max_delay_secs
87
89 """
90 @return: Max.remaining buffering time for that job. None if rule does not apply.
91 """
92 if self._cats is None or job.cat in self._cats:
93 return self._max_delay_secs
94 else:
95 return None
96
98 return "%s[%s:%s secs]"%(self.__class__.__name__,str(self._cats),self._max_delay_secs)
99
100
101
103 """
104 """
105 - def __init__(self, mailer, buffer, pauseSecs=10, autostop=False):
106 """
107 Starts a worker thread which reads buffered lines and sends them when their time has come.
108 @param autostop:
109 When True, the worker thread will check the number of non-daemon threads after each work interval.
110 When only one (itself) is left, it stops automatically.
111 But when there are more threads doing the same trick, explicit stop is required.
112 """
113 self._mailer = mailer
114 self._buffer = buffer
115 self._pauseSecs = pauseSecs
116 self._autostop = autostop
117 self._stopped = False
118 Thread(target=self.run).start()
119
121 current = currentThread()
122 for thread in threading_enumerate():
123 if not thread.isDaemon() and (thread is not current):
124 return False
125 return True
126
131
133 while not self._stopped:
134 sleep(self._pauseSecs)
135 self._work()
136 if self._autostop and self._i_am_orphan():
137 self.stop()
138 self._work()
139
141 """
142 Sets my stop flag, causing the worker thread to finish after its next working interval.
143 (All remaining buffered lines are sent before exit.)
144 """
145 self._stopped = True
146
148 return not self._stopped
149
150
151
153 - def __init__(self, rules, format_line=None):
154 """
155 @param rules: list of rules which decide about the log jobs to send. Use e.g. a tuple of L{CatRule} objects.
156 If empty list, no messages are buffered.
157 The rules are processed in the given order. If a rule applies, the following rules in the list are ignored.
158 """
159 assert hasattr(rules, "__iter__"), "rules must be iterable, not:%s (althought empty list is allowed)"%(type(rules))
160 if format_line is None: format_line = default_format_line
161 self._format_line = (format_line,)
162 self._lines = []
163 self._rules = rules
164 self._deadline = None
165 self._rlock = RLock()
166
168 return now() + timedelta(seconds=secs_remaining)
169
171 """
172 The current implementation locks the current thread if a rule applies, to safely buffer it.
173 """
174 currentJob = jobhist[-1]
175 for rule in self._rules:
176 secs = rule.max_delay_secs(currentJob)
177 if secs is not None:
178 self._rlock.acquire()
179 self._buffer(currentJob, secs)
180 self._rlock.release()
181 return
182
184 self._lines.append(
185 self._format_line[0](job)
186 )
187 deadline = self._calc_deadline(secs)
188 if self._deadline is None or deadline < self._deadline:
189 self._deadline = deadline
190
192 self._rlock.acquire()
193 for x in self._lines:
194 yield x
195 self._lines = []
196 self._deadline = None
197 self._rlock.release()
198
199
201 return (self._deadline is not None) and (now() >= self._deadline)
202
204 return len(self._lines)
205
206
208 - def __init__(self, server, serverpw, to_address, from_address, default_subject, charset="latin-1"):
209 self._server = server
210 self._serverpw = serverpw
211 self._to_address = to_address
212 self._from_address = from_address
213 self._default_subject = default_subject
214 self._attach = False
215 self._charset = charset
216
217
219 """
220 Override to dynamically create a subject
221 """
222 return self._default_subject
223
224 - def _content(self, lines):
225 def lines_():
226 for line in lines:
227 yield line+"\n"
228 return "".join(lines_())
229
230 - def mail(self, lines):
231 mail_smtp(
232 server = self._server,
233 serverpw = self._serverpw,
234 to_address = self._to_address,
235 from_address = self._from_address,
236 subject = self._subject(),
237 content = self._content(lines),
238 charset = self._charset,
239 )
240