Mercurial > repos > rliterman > csp2
comparison CSP2/CSP2_env/env-d9b9114564458d9d-741b3de822f2aaca6c6caa4325c4afce/lib/python3.8/logging/handlers.py @ 68:5028fdace37b
planemo upload commit 2e9511a184a1ca667c7be0c6321a36dc4e3d116d
author | jpayne |
---|---|
date | Tue, 18 Mar 2025 16:23:26 -0400 |
parents | |
children |
comparison
equal
deleted
inserted
replaced
67:0e9998148a16 | 68:5028fdace37b |
---|---|
1 # Copyright 2001-2016 by Vinay Sajip. All Rights Reserved. | |
2 # | |
3 # Permission to use, copy, modify, and distribute this software and its | |
4 # documentation for any purpose and without fee is hereby granted, | |
5 # provided that the above copyright notice appear in all copies and that | |
6 # both that copyright notice and this permission notice appear in | |
7 # supporting documentation, and that the name of Vinay Sajip | |
8 # not be used in advertising or publicity pertaining to distribution | |
9 # of the software without specific, written prior permission. | |
10 # VINAY SAJIP DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING | |
11 # ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL | |
12 # VINAY SAJIP BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR | |
13 # ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER | |
14 # IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT | |
15 # OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. | |
16 | |
17 """ | |
18 Additional handlers for the logging package for Python. The core package is | |
19 based on PEP 282 and comments thereto in comp.lang.python. | |
20 | |
21 Copyright (C) 2001-2016 Vinay Sajip. All Rights Reserved. | |
22 | |
23 To use, simply 'import logging.handlers' and log away! | |
24 """ | |
25 | |
26 import logging, socket, os, pickle, struct, time, re | |
27 from stat import ST_DEV, ST_INO, ST_MTIME | |
28 import queue | |
29 import threading | |
30 import copy | |
31 | |
32 # | |
33 # Some constants... | |
34 # | |
35 | |
36 DEFAULT_TCP_LOGGING_PORT = 9020 | |
37 DEFAULT_UDP_LOGGING_PORT = 9021 | |
38 DEFAULT_HTTP_LOGGING_PORT = 9022 | |
39 DEFAULT_SOAP_LOGGING_PORT = 9023 | |
40 SYSLOG_UDP_PORT = 514 | |
41 SYSLOG_TCP_PORT = 514 | |
42 | |
43 _MIDNIGHT = 24 * 60 * 60 # number of seconds in a day | |
44 | |
45 class BaseRotatingHandler(logging.FileHandler): | |
46 """ | |
47 Base class for handlers that rotate log files at a certain point. | |
48 Not meant to be instantiated directly. Instead, use RotatingFileHandler | |
49 or TimedRotatingFileHandler. | |
50 """ | |
51 def __init__(self, filename, mode, encoding=None, delay=False): | |
52 """ | |
53 Use the specified filename for streamed logging | |
54 """ | |
55 logging.FileHandler.__init__(self, filename, mode, encoding, delay) | |
56 self.mode = mode | |
57 self.encoding = encoding | |
58 self.namer = None | |
59 self.rotator = None | |
60 | |
61 def emit(self, record): | |
62 """ | |
63 Emit a record. | |
64 | |
65 Output the record to the file, catering for rollover as described | |
66 in doRollover(). | |
67 """ | |
68 try: | |
69 if self.shouldRollover(record): | |
70 self.doRollover() | |
71 logging.FileHandler.emit(self, record) | |
72 except Exception: | |
73 self.handleError(record) | |
74 | |
75 def rotation_filename(self, default_name): | |
76 """ | |
77 Modify the filename of a log file when rotating. | |
78 | |
79 This is provided so that a custom filename can be provided. | |
80 | |
81 The default implementation calls the 'namer' attribute of the | |
82 handler, if it's callable, passing the default name to | |
83 it. If the attribute isn't callable (the default is None), the name | |
84 is returned unchanged. | |
85 | |
86 :param default_name: The default name for the log file. | |
87 """ | |
88 if not callable(self.namer): | |
89 result = default_name | |
90 else: | |
91 result = self.namer(default_name) | |
92 return result | |
93 | |
94 def rotate(self, source, dest): | |
95 """ | |
96 When rotating, rotate the current log. | |
97 | |
98 The default implementation calls the 'rotator' attribute of the | |
99 handler, if it's callable, passing the source and dest arguments to | |
100 it. If the attribute isn't callable (the default is None), the source | |
101 is simply renamed to the destination. | |
102 | |
103 :param source: The source filename. This is normally the base | |
104 filename, e.g. 'test.log' | |
105 :param dest: The destination filename. This is normally | |
106 what the source is rotated to, e.g. 'test.log.1'. | |
107 """ | |
108 if not callable(self.rotator): | |
109 # Issue 18940: A file may not have been created if delay is True. | |
110 if os.path.exists(source): | |
111 os.rename(source, dest) | |
112 else: | |
113 self.rotator(source, dest) | |
114 | |
115 class RotatingFileHandler(BaseRotatingHandler): | |
116 """ | |
117 Handler for logging to a set of files, which switches from one file | |
118 to the next when the current file reaches a certain size. | |
119 """ | |
120 def __init__(self, filename, mode='a', maxBytes=0, backupCount=0, encoding=None, delay=False): | |
121 """ | |
122 Open the specified file and use it as the stream for logging. | |
123 | |
124 By default, the file grows indefinitely. You can specify particular | |
125 values of maxBytes and backupCount to allow the file to rollover at | |
126 a predetermined size. | |
127 | |
128 Rollover occurs whenever the current log file is nearly maxBytes in | |
129 length. If backupCount is >= 1, the system will successively create | |
130 new files with the same pathname as the base file, but with extensions | |
131 ".1", ".2" etc. appended to it. For example, with a backupCount of 5 | |
132 and a base file name of "app.log", you would get "app.log", | |
133 "app.log.1", "app.log.2", ... through to "app.log.5". The file being | |
134 written to is always "app.log" - when it gets filled up, it is closed | |
135 and renamed to "app.log.1", and if files "app.log.1", "app.log.2" etc. | |
136 exist, then they are renamed to "app.log.2", "app.log.3" etc. | |
137 respectively. | |
138 | |
139 If maxBytes is zero, rollover never occurs. | |
140 """ | |
141 # If rotation/rollover is wanted, it doesn't make sense to use another | |
142 # mode. If for example 'w' were specified, then if there were multiple | |
143 # runs of the calling application, the logs from previous runs would be | |
144 # lost if the 'w' is respected, because the log file would be truncated | |
145 # on each run. | |
146 if maxBytes > 0: | |
147 mode = 'a' | |
148 BaseRotatingHandler.__init__(self, filename, mode, encoding, delay) | |
149 self.maxBytes = maxBytes | |
150 self.backupCount = backupCount | |
151 | |
152 def doRollover(self): | |
153 """ | |
154 Do a rollover, as described in __init__(). | |
155 """ | |
156 if self.stream: | |
157 self.stream.close() | |
158 self.stream = None | |
159 if self.backupCount > 0: | |
160 for i in range(self.backupCount - 1, 0, -1): | |
161 sfn = self.rotation_filename("%s.%d" % (self.baseFilename, i)) | |
162 dfn = self.rotation_filename("%s.%d" % (self.baseFilename, | |
163 i + 1)) | |
164 if os.path.exists(sfn): | |
165 if os.path.exists(dfn): | |
166 os.remove(dfn) | |
167 os.rename(sfn, dfn) | |
168 dfn = self.rotation_filename(self.baseFilename + ".1") | |
169 if os.path.exists(dfn): | |
170 os.remove(dfn) | |
171 self.rotate(self.baseFilename, dfn) | |
172 if not self.delay: | |
173 self.stream = self._open() | |
174 | |
175 def shouldRollover(self, record): | |
176 """ | |
177 Determine if rollover should occur. | |
178 | |
179 Basically, see if the supplied record would cause the file to exceed | |
180 the size limit we have. | |
181 """ | |
182 if self.stream is None: # delay was set... | |
183 self.stream = self._open() | |
184 if self.maxBytes > 0: # are we rolling over? | |
185 msg = "%s\n" % self.format(record) | |
186 self.stream.seek(0, 2) #due to non-posix-compliant Windows feature | |
187 if self.stream.tell() + len(msg) >= self.maxBytes: | |
188 return 1 | |
189 return 0 | |
190 | |
191 class TimedRotatingFileHandler(BaseRotatingHandler): | |
192 """ | |
193 Handler for logging to a file, rotating the log file at certain timed | |
194 intervals. | |
195 | |
196 If backupCount is > 0, when rollover is done, no more than backupCount | |
197 files are kept - the oldest ones are deleted. | |
198 """ | |
199 def __init__(self, filename, when='h', interval=1, backupCount=0, encoding=None, delay=False, utc=False, atTime=None): | |
200 BaseRotatingHandler.__init__(self, filename, 'a', encoding, delay) | |
201 self.when = when.upper() | |
202 self.backupCount = backupCount | |
203 self.utc = utc | |
204 self.atTime = atTime | |
205 # Calculate the real rollover interval, which is just the number of | |
206 # seconds between rollovers. Also set the filename suffix used when | |
207 # a rollover occurs. Current 'when' events supported: | |
208 # S - Seconds | |
209 # M - Minutes | |
210 # H - Hours | |
211 # D - Days | |
212 # midnight - roll over at midnight | |
213 # W{0-6} - roll over on a certain day; 0 - Monday | |
214 # | |
215 # Case of the 'when' specifier is not important; lower or upper case | |
216 # will work. | |
217 if self.when == 'S': | |
218 self.interval = 1 # one second | |
219 self.suffix = "%Y-%m-%d_%H-%M-%S" | |
220 self.extMatch = r"^\d{4}-\d{2}-\d{2}_\d{2}-\d{2}-\d{2}(\.\w+)?$" | |
221 elif self.when == 'M': | |
222 self.interval = 60 # one minute | |
223 self.suffix = "%Y-%m-%d_%H-%M" | |
224 self.extMatch = r"^\d{4}-\d{2}-\d{2}_\d{2}-\d{2}(\.\w+)?$" | |
225 elif self.when == 'H': | |
226 self.interval = 60 * 60 # one hour | |
227 self.suffix = "%Y-%m-%d_%H" | |
228 self.extMatch = r"^\d{4}-\d{2}-\d{2}_\d{2}(\.\w+)?$" | |
229 elif self.when == 'D' or self.when == 'MIDNIGHT': | |
230 self.interval = 60 * 60 * 24 # one day | |
231 self.suffix = "%Y-%m-%d" | |
232 self.extMatch = r"^\d{4}-\d{2}-\d{2}(\.\w+)?$" | |
233 elif self.when.startswith('W'): | |
234 self.interval = 60 * 60 * 24 * 7 # one week | |
235 if len(self.when) != 2: | |
236 raise ValueError("You must specify a day for weekly rollover from 0 to 6 (0 is Monday): %s" % self.when) | |
237 if self.when[1] < '0' or self.when[1] > '6': | |
238 raise ValueError("Invalid day specified for weekly rollover: %s" % self.when) | |
239 self.dayOfWeek = int(self.when[1]) | |
240 self.suffix = "%Y-%m-%d" | |
241 self.extMatch = r"^\d{4}-\d{2}-\d{2}(\.\w+)?$" | |
242 else: | |
243 raise ValueError("Invalid rollover interval specified: %s" % self.when) | |
244 | |
245 self.extMatch = re.compile(self.extMatch, re.ASCII) | |
246 self.interval = self.interval * interval # multiply by units requested | |
247 # The following line added because the filename passed in could be a | |
248 # path object (see Issue #27493), but self.baseFilename will be a string | |
249 filename = self.baseFilename | |
250 if os.path.exists(filename): | |
251 t = os.stat(filename)[ST_MTIME] | |
252 else: | |
253 t = int(time.time()) | |
254 self.rolloverAt = self.computeRollover(t) | |
255 | |
256 def computeRollover(self, currentTime): | |
257 """ | |
258 Work out the rollover time based on the specified time. | |
259 """ | |
260 result = currentTime + self.interval | |
261 # If we are rolling over at midnight or weekly, then the interval is already known. | |
262 # What we need to figure out is WHEN the next interval is. In other words, | |
263 # if you are rolling over at midnight, then your base interval is 1 day, | |
264 # but you want to start that one day clock at midnight, not now. So, we | |
265 # have to fudge the rolloverAt value in order to trigger the first rollover | |
266 # at the right time. After that, the regular interval will take care of | |
267 # the rest. Note that this code doesn't care about leap seconds. :) | |
268 if self.when == 'MIDNIGHT' or self.when.startswith('W'): | |
269 # This could be done with less code, but I wanted it to be clear | |
270 if self.utc: | |
271 t = time.gmtime(currentTime) | |
272 else: | |
273 t = time.localtime(currentTime) | |
274 currentHour = t[3] | |
275 currentMinute = t[4] | |
276 currentSecond = t[5] | |
277 currentDay = t[6] | |
278 # r is the number of seconds left between now and the next rotation | |
279 if self.atTime is None: | |
280 rotate_ts = _MIDNIGHT | |
281 else: | |
282 rotate_ts = ((self.atTime.hour * 60 + self.atTime.minute)*60 + | |
283 self.atTime.second) | |
284 | |
285 r = rotate_ts - ((currentHour * 60 + currentMinute) * 60 + | |
286 currentSecond) | |
287 if r < 0: | |
288 # Rotate time is before the current time (for example when | |
289 # self.rotateAt is 13:45 and it now 14:15), rotation is | |
290 # tomorrow. | |
291 r += _MIDNIGHT | |
292 currentDay = (currentDay + 1) % 7 | |
293 result = currentTime + r | |
294 # If we are rolling over on a certain day, add in the number of days until | |
295 # the next rollover, but offset by 1 since we just calculated the time | |
296 # until the next day starts. There are three cases: | |
297 # Case 1) The day to rollover is today; in this case, do nothing | |
298 # Case 2) The day to rollover is further in the interval (i.e., today is | |
299 # day 2 (Wednesday) and rollover is on day 6 (Sunday). Days to | |
300 # next rollover is simply 6 - 2 - 1, or 3. | |
301 # Case 3) The day to rollover is behind us in the interval (i.e., today | |
302 # is day 5 (Saturday) and rollover is on day 3 (Thursday). | |
303 # Days to rollover is 6 - 5 + 3, or 4. In this case, it's the | |
304 # number of days left in the current week (1) plus the number | |
305 # of days in the next week until the rollover day (3). | |
306 # The calculations described in 2) and 3) above need to have a day added. | |
307 # This is because the above time calculation takes us to midnight on this | |
308 # day, i.e. the start of the next day. | |
309 if self.when.startswith('W'): | |
310 day = currentDay # 0 is Monday | |
311 if day != self.dayOfWeek: | |
312 if day < self.dayOfWeek: | |
313 daysToWait = self.dayOfWeek - day | |
314 else: | |
315 daysToWait = 6 - day + self.dayOfWeek + 1 | |
316 newRolloverAt = result + (daysToWait * (60 * 60 * 24)) | |
317 if not self.utc: | |
318 dstNow = t[-1] | |
319 dstAtRollover = time.localtime(newRolloverAt)[-1] | |
320 if dstNow != dstAtRollover: | |
321 if not dstNow: # DST kicks in before next rollover, so we need to deduct an hour | |
322 addend = -3600 | |
323 else: # DST bows out before next rollover, so we need to add an hour | |
324 addend = 3600 | |
325 newRolloverAt += addend | |
326 result = newRolloverAt | |
327 return result | |
328 | |
329 def shouldRollover(self, record): | |
330 """ | |
331 Determine if rollover should occur. | |
332 | |
333 record is not used, as we are just comparing times, but it is needed so | |
334 the method signatures are the same | |
335 """ | |
336 t = int(time.time()) | |
337 if t >= self.rolloverAt: | |
338 return 1 | |
339 return 0 | |
340 | |
341 def getFilesToDelete(self): | |
342 """ | |
343 Determine the files to delete when rolling over. | |
344 | |
345 More specific than the earlier method, which just used glob.glob(). | |
346 """ | |
347 dirName, baseName = os.path.split(self.baseFilename) | |
348 fileNames = os.listdir(dirName) | |
349 result = [] | |
350 prefix = baseName + "." | |
351 plen = len(prefix) | |
352 for fileName in fileNames: | |
353 if fileName[:plen] == prefix: | |
354 suffix = fileName[plen:] | |
355 if self.extMatch.match(suffix): | |
356 result.append(os.path.join(dirName, fileName)) | |
357 if len(result) < self.backupCount: | |
358 result = [] | |
359 else: | |
360 result.sort() | |
361 result = result[:len(result) - self.backupCount] | |
362 return result | |
363 | |
364 def doRollover(self): | |
365 """ | |
366 do a rollover; in this case, a date/time stamp is appended to the filename | |
367 when the rollover happens. However, you want the file to be named for the | |
368 start of the interval, not the current time. If there is a backup count, | |
369 then we have to get a list of matching filenames, sort them and remove | |
370 the one with the oldest suffix. | |
371 """ | |
372 if self.stream: | |
373 self.stream.close() | |
374 self.stream = None | |
375 # get the time that this sequence started at and make it a TimeTuple | |
376 currentTime = int(time.time()) | |
377 dstNow = time.localtime(currentTime)[-1] | |
378 t = self.rolloverAt - self.interval | |
379 if self.utc: | |
380 timeTuple = time.gmtime(t) | |
381 else: | |
382 timeTuple = time.localtime(t) | |
383 dstThen = timeTuple[-1] | |
384 if dstNow != dstThen: | |
385 if dstNow: | |
386 addend = 3600 | |
387 else: | |
388 addend = -3600 | |
389 timeTuple = time.localtime(t + addend) | |
390 dfn = self.rotation_filename(self.baseFilename + "." + | |
391 time.strftime(self.suffix, timeTuple)) | |
392 if os.path.exists(dfn): | |
393 os.remove(dfn) | |
394 self.rotate(self.baseFilename, dfn) | |
395 if self.backupCount > 0: | |
396 for s in self.getFilesToDelete(): | |
397 os.remove(s) | |
398 if not self.delay: | |
399 self.stream = self._open() | |
400 newRolloverAt = self.computeRollover(currentTime) | |
401 while newRolloverAt <= currentTime: | |
402 newRolloverAt = newRolloverAt + self.interval | |
403 #If DST changes and midnight or weekly rollover, adjust for this. | |
404 if (self.when == 'MIDNIGHT' or self.when.startswith('W')) and not self.utc: | |
405 dstAtRollover = time.localtime(newRolloverAt)[-1] | |
406 if dstNow != dstAtRollover: | |
407 if not dstNow: # DST kicks in before next rollover, so we need to deduct an hour | |
408 addend = -3600 | |
409 else: # DST bows out before next rollover, so we need to add an hour | |
410 addend = 3600 | |
411 newRolloverAt += addend | |
412 self.rolloverAt = newRolloverAt | |
413 | |
414 class WatchedFileHandler(logging.FileHandler): | |
415 """ | |
416 A handler for logging to a file, which watches the file | |
417 to see if it has changed while in use. This can happen because of | |
418 usage of programs such as newsyslog and logrotate which perform | |
419 log file rotation. This handler, intended for use under Unix, | |
420 watches the file to see if it has changed since the last emit. | |
421 (A file has changed if its device or inode have changed.) | |
422 If it has changed, the old file stream is closed, and the file | |
423 opened to get a new stream. | |
424 | |
425 This handler is not appropriate for use under Windows, because | |
426 under Windows open files cannot be moved or renamed - logging | |
427 opens the files with exclusive locks - and so there is no need | |
428 for such a handler. Furthermore, ST_INO is not supported under | |
429 Windows; stat always returns zero for this value. | |
430 | |
431 This handler is based on a suggestion and patch by Chad J. | |
432 Schroeder. | |
433 """ | |
434 def __init__(self, filename, mode='a', encoding=None, delay=False): | |
435 logging.FileHandler.__init__(self, filename, mode, encoding, delay) | |
436 self.dev, self.ino = -1, -1 | |
437 self._statstream() | |
438 | |
439 def _statstream(self): | |
440 if self.stream: | |
441 sres = os.fstat(self.stream.fileno()) | |
442 self.dev, self.ino = sres[ST_DEV], sres[ST_INO] | |
443 | |
444 def reopenIfNeeded(self): | |
445 """ | |
446 Reopen log file if needed. | |
447 | |
448 Checks if the underlying file has changed, and if it | |
449 has, close the old stream and reopen the file to get the | |
450 current stream. | |
451 """ | |
452 # Reduce the chance of race conditions by stat'ing by path only | |
453 # once and then fstat'ing our new fd if we opened a new log stream. | |
454 # See issue #14632: Thanks to John Mulligan for the problem report | |
455 # and patch. | |
456 try: | |
457 # stat the file by path, checking for existence | |
458 sres = os.stat(self.baseFilename) | |
459 except FileNotFoundError: | |
460 sres = None | |
461 # compare file system stat with that of our stream file handle | |
462 if not sres or sres[ST_DEV] != self.dev or sres[ST_INO] != self.ino: | |
463 if self.stream is not None: | |
464 # we have an open file handle, clean it up | |
465 self.stream.flush() | |
466 self.stream.close() | |
467 self.stream = None # See Issue #21742: _open () might fail. | |
468 # open a new file handle and get new stat info from that fd | |
469 self.stream = self._open() | |
470 self._statstream() | |
471 | |
472 def emit(self, record): | |
473 """ | |
474 Emit a record. | |
475 | |
476 If underlying file has changed, reopen the file before emitting the | |
477 record to it. | |
478 """ | |
479 self.reopenIfNeeded() | |
480 logging.FileHandler.emit(self, record) | |
481 | |
482 | |
483 class SocketHandler(logging.Handler): | |
484 """ | |
485 A handler class which writes logging records, in pickle format, to | |
486 a streaming socket. The socket is kept open across logging calls. | |
487 If the peer resets it, an attempt is made to reconnect on the next call. | |
488 The pickle which is sent is that of the LogRecord's attribute dictionary | |
489 (__dict__), so that the receiver does not need to have the logging module | |
490 installed in order to process the logging event. | |
491 | |
492 To unpickle the record at the receiving end into a LogRecord, use the | |
493 makeLogRecord function. | |
494 """ | |
495 | |
496 def __init__(self, host, port): | |
497 """ | |
498 Initializes the handler with a specific host address and port. | |
499 | |
500 When the attribute *closeOnError* is set to True - if a socket error | |
501 occurs, the socket is silently closed and then reopened on the next | |
502 logging call. | |
503 """ | |
504 logging.Handler.__init__(self) | |
505 self.host = host | |
506 self.port = port | |
507 if port is None: | |
508 self.address = host | |
509 else: | |
510 self.address = (host, port) | |
511 self.sock = None | |
512 self.closeOnError = False | |
513 self.retryTime = None | |
514 # | |
515 # Exponential backoff parameters. | |
516 # | |
517 self.retryStart = 1.0 | |
518 self.retryMax = 30.0 | |
519 self.retryFactor = 2.0 | |
520 | |
521 def makeSocket(self, timeout=1): | |
522 """ | |
523 A factory method which allows subclasses to define the precise | |
524 type of socket they want. | |
525 """ | |
526 if self.port is not None: | |
527 result = socket.create_connection(self.address, timeout=timeout) | |
528 else: | |
529 result = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) | |
530 result.settimeout(timeout) | |
531 try: | |
532 result.connect(self.address) | |
533 except OSError: | |
534 result.close() # Issue 19182 | |
535 raise | |
536 return result | |
537 | |
538 def createSocket(self): | |
539 """ | |
540 Try to create a socket, using an exponential backoff with | |
541 a max retry time. Thanks to Robert Olson for the original patch | |
542 (SF #815911) which has been slightly refactored. | |
543 """ | |
544 now = time.time() | |
545 # Either retryTime is None, in which case this | |
546 # is the first time back after a disconnect, or | |
547 # we've waited long enough. | |
548 if self.retryTime is None: | |
549 attempt = True | |
550 else: | |
551 attempt = (now >= self.retryTime) | |
552 if attempt: | |
553 try: | |
554 self.sock = self.makeSocket() | |
555 self.retryTime = None # next time, no delay before trying | |
556 except OSError: | |
557 #Creation failed, so set the retry time and return. | |
558 if self.retryTime is None: | |
559 self.retryPeriod = self.retryStart | |
560 else: | |
561 self.retryPeriod = self.retryPeriod * self.retryFactor | |
562 if self.retryPeriod > self.retryMax: | |
563 self.retryPeriod = self.retryMax | |
564 self.retryTime = now + self.retryPeriod | |
565 | |
566 def send(self, s): | |
567 """ | |
568 Send a pickled string to the socket. | |
569 | |
570 This function allows for partial sends which can happen when the | |
571 network is busy. | |
572 """ | |
573 if self.sock is None: | |
574 self.createSocket() | |
575 #self.sock can be None either because we haven't reached the retry | |
576 #time yet, or because we have reached the retry time and retried, | |
577 #but are still unable to connect. | |
578 if self.sock: | |
579 try: | |
580 self.sock.sendall(s) | |
581 except OSError: #pragma: no cover | |
582 self.sock.close() | |
583 self.sock = None # so we can call createSocket next time | |
584 | |
585 def makePickle(self, record): | |
586 """ | |
587 Pickles the record in binary format with a length prefix, and | |
588 returns it ready for transmission across the socket. | |
589 """ | |
590 ei = record.exc_info | |
591 if ei: | |
592 # just to get traceback text into record.exc_text ... | |
593 dummy = self.format(record) | |
594 # See issue #14436: If msg or args are objects, they may not be | |
595 # available on the receiving end. So we convert the msg % args | |
596 # to a string, save it as msg and zap the args. | |
597 d = dict(record.__dict__) | |
598 d['msg'] = record.getMessage() | |
599 d['args'] = None | |
600 d['exc_info'] = None | |
601 # Issue #25685: delete 'message' if present: redundant with 'msg' | |
602 d.pop('message', None) | |
603 s = pickle.dumps(d, 1) | |
604 slen = struct.pack(">L", len(s)) | |
605 return slen + s | |
606 | |
607 def handleError(self, record): | |
608 """ | |
609 Handle an error during logging. | |
610 | |
611 An error has occurred during logging. Most likely cause - | |
612 connection lost. Close the socket so that we can retry on the | |
613 next event. | |
614 """ | |
615 if self.closeOnError and self.sock: | |
616 self.sock.close() | |
617 self.sock = None #try to reconnect next time | |
618 else: | |
619 logging.Handler.handleError(self, record) | |
620 | |
621 def emit(self, record): | |
622 """ | |
623 Emit a record. | |
624 | |
625 Pickles the record and writes it to the socket in binary format. | |
626 If there is an error with the socket, silently drop the packet. | |
627 If there was a problem with the socket, re-establishes the | |
628 socket. | |
629 """ | |
630 try: | |
631 s = self.makePickle(record) | |
632 self.send(s) | |
633 except Exception: | |
634 self.handleError(record) | |
635 | |
636 def close(self): | |
637 """ | |
638 Closes the socket. | |
639 """ | |
640 self.acquire() | |
641 try: | |
642 sock = self.sock | |
643 if sock: | |
644 self.sock = None | |
645 sock.close() | |
646 logging.Handler.close(self) | |
647 finally: | |
648 self.release() | |
649 | |
650 class DatagramHandler(SocketHandler): | |
651 """ | |
652 A handler class which writes logging records, in pickle format, to | |
653 a datagram socket. The pickle which is sent is that of the LogRecord's | |
654 attribute dictionary (__dict__), so that the receiver does not need to | |
655 have the logging module installed in order to process the logging event. | |
656 | |
657 To unpickle the record at the receiving end into a LogRecord, use the | |
658 makeLogRecord function. | |
659 | |
660 """ | |
661 def __init__(self, host, port): | |
662 """ | |
663 Initializes the handler with a specific host address and port. | |
664 """ | |
665 SocketHandler.__init__(self, host, port) | |
666 self.closeOnError = False | |
667 | |
668 def makeSocket(self): | |
669 """ | |
670 The factory method of SocketHandler is here overridden to create | |
671 a UDP socket (SOCK_DGRAM). | |
672 """ | |
673 if self.port is None: | |
674 family = socket.AF_UNIX | |
675 else: | |
676 family = socket.AF_INET | |
677 s = socket.socket(family, socket.SOCK_DGRAM) | |
678 return s | |
679 | |
680 def send(self, s): | |
681 """ | |
682 Send a pickled string to a socket. | |
683 | |
684 This function no longer allows for partial sends which can happen | |
685 when the network is busy - UDP does not guarantee delivery and | |
686 can deliver packets out of sequence. | |
687 """ | |
688 if self.sock is None: | |
689 self.createSocket() | |
690 self.sock.sendto(s, self.address) | |
691 | |
692 class SysLogHandler(logging.Handler): | |
693 """ | |
694 A handler class which sends formatted logging records to a syslog | |
695 server. Based on Sam Rushing's syslog module: | |
696 http://www.nightmare.com/squirl/python-ext/misc/syslog.py | |
697 Contributed by Nicolas Untz (after which minor refactoring changes | |
698 have been made). | |
699 """ | |
700 | |
701 # from <linux/sys/syslog.h>: | |
702 # ====================================================================== | |
703 # priorities/facilities are encoded into a single 32-bit quantity, where | |
704 # the bottom 3 bits are the priority (0-7) and the top 28 bits are the | |
705 # facility (0-big number). Both the priorities and the facilities map | |
706 # roughly one-to-one to strings in the syslogd(8) source code. This | |
707 # mapping is included in this file. | |
708 # | |
709 # priorities (these are ordered) | |
710 | |
711 LOG_EMERG = 0 # system is unusable | |
712 LOG_ALERT = 1 # action must be taken immediately | |
713 LOG_CRIT = 2 # critical conditions | |
714 LOG_ERR = 3 # error conditions | |
715 LOG_WARNING = 4 # warning conditions | |
716 LOG_NOTICE = 5 # normal but significant condition | |
717 LOG_INFO = 6 # informational | |
718 LOG_DEBUG = 7 # debug-level messages | |
719 | |
720 # facility codes | |
721 LOG_KERN = 0 # kernel messages | |
722 LOG_USER = 1 # random user-level messages | |
723 LOG_MAIL = 2 # mail system | |
724 LOG_DAEMON = 3 # system daemons | |
725 LOG_AUTH = 4 # security/authorization messages | |
726 LOG_SYSLOG = 5 # messages generated internally by syslogd | |
727 LOG_LPR = 6 # line printer subsystem | |
728 LOG_NEWS = 7 # network news subsystem | |
729 LOG_UUCP = 8 # UUCP subsystem | |
730 LOG_CRON = 9 # clock daemon | |
731 LOG_AUTHPRIV = 10 # security/authorization messages (private) | |
732 LOG_FTP = 11 # FTP daemon | |
733 | |
734 # other codes through 15 reserved for system use | |
735 LOG_LOCAL0 = 16 # reserved for local use | |
736 LOG_LOCAL1 = 17 # reserved for local use | |
737 LOG_LOCAL2 = 18 # reserved for local use | |
738 LOG_LOCAL3 = 19 # reserved for local use | |
739 LOG_LOCAL4 = 20 # reserved for local use | |
740 LOG_LOCAL5 = 21 # reserved for local use | |
741 LOG_LOCAL6 = 22 # reserved for local use | |
742 LOG_LOCAL7 = 23 # reserved for local use | |
743 | |
744 priority_names = { | |
745 "alert": LOG_ALERT, | |
746 "crit": LOG_CRIT, | |
747 "critical": LOG_CRIT, | |
748 "debug": LOG_DEBUG, | |
749 "emerg": LOG_EMERG, | |
750 "err": LOG_ERR, | |
751 "error": LOG_ERR, # DEPRECATED | |
752 "info": LOG_INFO, | |
753 "notice": LOG_NOTICE, | |
754 "panic": LOG_EMERG, # DEPRECATED | |
755 "warn": LOG_WARNING, # DEPRECATED | |
756 "warning": LOG_WARNING, | |
757 } | |
758 | |
759 facility_names = { | |
760 "auth": LOG_AUTH, | |
761 "authpriv": LOG_AUTHPRIV, | |
762 "cron": LOG_CRON, | |
763 "daemon": LOG_DAEMON, | |
764 "ftp": LOG_FTP, | |
765 "kern": LOG_KERN, | |
766 "lpr": LOG_LPR, | |
767 "mail": LOG_MAIL, | |
768 "news": LOG_NEWS, | |
769 "security": LOG_AUTH, # DEPRECATED | |
770 "syslog": LOG_SYSLOG, | |
771 "user": LOG_USER, | |
772 "uucp": LOG_UUCP, | |
773 "local0": LOG_LOCAL0, | |
774 "local1": LOG_LOCAL1, | |
775 "local2": LOG_LOCAL2, | |
776 "local3": LOG_LOCAL3, | |
777 "local4": LOG_LOCAL4, | |
778 "local5": LOG_LOCAL5, | |
779 "local6": LOG_LOCAL6, | |
780 "local7": LOG_LOCAL7, | |
781 } | |
782 | |
783 #The map below appears to be trivially lowercasing the key. However, | |
784 #there's more to it than meets the eye - in some locales, lowercasing | |
785 #gives unexpected results. See SF #1524081: in the Turkish locale, | |
786 #"INFO".lower() != "info" | |
787 priority_map = { | |
788 "DEBUG" : "debug", | |
789 "INFO" : "info", | |
790 "WARNING" : "warning", | |
791 "ERROR" : "error", | |
792 "CRITICAL" : "critical" | |
793 } | |
794 | |
795 def __init__(self, address=('localhost', SYSLOG_UDP_PORT), | |
796 facility=LOG_USER, socktype=None): | |
797 """ | |
798 Initialize a handler. | |
799 | |
800 If address is specified as a string, a UNIX socket is used. To log to a | |
801 local syslogd, "SysLogHandler(address="/dev/log")" can be used. | |
802 If facility is not specified, LOG_USER is used. If socktype is | |
803 specified as socket.SOCK_DGRAM or socket.SOCK_STREAM, that specific | |
804 socket type will be used. For Unix sockets, you can also specify a | |
805 socktype of None, in which case socket.SOCK_DGRAM will be used, falling | |
806 back to socket.SOCK_STREAM. | |
807 """ | |
808 logging.Handler.__init__(self) | |
809 | |
810 self.address = address | |
811 self.facility = facility | |
812 self.socktype = socktype | |
813 | |
814 if isinstance(address, str): | |
815 self.unixsocket = True | |
816 # Syslog server may be unavailable during handler initialisation. | |
817 # C's openlog() function also ignores connection errors. | |
818 # Moreover, we ignore these errors while logging, so it not worse | |
819 # to ignore it also here. | |
820 try: | |
821 self._connect_unixsocket(address) | |
822 except OSError: | |
823 pass | |
824 else: | |
825 self.unixsocket = False | |
826 if socktype is None: | |
827 socktype = socket.SOCK_DGRAM | |
828 host, port = address | |
829 ress = socket.getaddrinfo(host, port, 0, socktype) | |
830 if not ress: | |
831 raise OSError("getaddrinfo returns an empty list") | |
832 for res in ress: | |
833 af, socktype, proto, _, sa = res | |
834 err = sock = None | |
835 try: | |
836 sock = socket.socket(af, socktype, proto) | |
837 if socktype == socket.SOCK_STREAM: | |
838 sock.connect(sa) | |
839 break | |
840 except OSError as exc: | |
841 err = exc | |
842 if sock is not None: | |
843 sock.close() | |
844 if err is not None: | |
845 raise err | |
846 self.socket = sock | |
847 self.socktype = socktype | |
848 | |
849 def _connect_unixsocket(self, address): | |
850 use_socktype = self.socktype | |
851 if use_socktype is None: | |
852 use_socktype = socket.SOCK_DGRAM | |
853 self.socket = socket.socket(socket.AF_UNIX, use_socktype) | |
854 try: | |
855 self.socket.connect(address) | |
856 # it worked, so set self.socktype to the used type | |
857 self.socktype = use_socktype | |
858 except OSError: | |
859 self.socket.close() | |
860 if self.socktype is not None: | |
861 # user didn't specify falling back, so fail | |
862 raise | |
863 use_socktype = socket.SOCK_STREAM | |
864 self.socket = socket.socket(socket.AF_UNIX, use_socktype) | |
865 try: | |
866 self.socket.connect(address) | |
867 # it worked, so set self.socktype to the used type | |
868 self.socktype = use_socktype | |
869 except OSError: | |
870 self.socket.close() | |
871 raise | |
872 | |
873 def encodePriority(self, facility, priority): | |
874 """ | |
875 Encode the facility and priority. You can pass in strings or | |
876 integers - if strings are passed, the facility_names and | |
877 priority_names mapping dictionaries are used to convert them to | |
878 integers. | |
879 """ | |
880 if isinstance(facility, str): | |
881 facility = self.facility_names[facility] | |
882 if isinstance(priority, str): | |
883 priority = self.priority_names[priority] | |
884 return (facility << 3) | priority | |
885 | |
886 def close(self): | |
887 """ | |
888 Closes the socket. | |
889 """ | |
890 self.acquire() | |
891 try: | |
892 self.socket.close() | |
893 logging.Handler.close(self) | |
894 finally: | |
895 self.release() | |
896 | |
897 def mapPriority(self, levelName): | |
898 """ | |
899 Map a logging level name to a key in the priority_names map. | |
900 This is useful in two scenarios: when custom levels are being | |
901 used, and in the case where you can't do a straightforward | |
902 mapping by lowercasing the logging level name because of locale- | |
903 specific issues (see SF #1524081). | |
904 """ | |
905 return self.priority_map.get(levelName, "warning") | |
906 | |
907 ident = '' # prepended to all messages | |
908 append_nul = True # some old syslog daemons expect a NUL terminator | |
909 | |
910 def emit(self, record): | |
911 """ | |
912 Emit a record. | |
913 | |
914 The record is formatted, and then sent to the syslog server. If | |
915 exception information is present, it is NOT sent to the server. | |
916 """ | |
917 try: | |
918 msg = self.format(record) | |
919 if self.ident: | |
920 msg = self.ident + msg | |
921 if self.append_nul: | |
922 msg += '\000' | |
923 | |
924 # We need to convert record level to lowercase, maybe this will | |
925 # change in the future. | |
926 prio = '<%d>' % self.encodePriority(self.facility, | |
927 self.mapPriority(record.levelname)) | |
928 prio = prio.encode('utf-8') | |
929 # Message is a string. Convert to bytes as required by RFC 5424 | |
930 msg = msg.encode('utf-8') | |
931 msg = prio + msg | |
932 if self.unixsocket: | |
933 try: | |
934 self.socket.send(msg) | |
935 except OSError: | |
936 self.socket.close() | |
937 self._connect_unixsocket(self.address) | |
938 self.socket.send(msg) | |
939 elif self.socktype == socket.SOCK_DGRAM: | |
940 self.socket.sendto(msg, self.address) | |
941 else: | |
942 self.socket.sendall(msg) | |
943 except Exception: | |
944 self.handleError(record) | |
945 | |
946 class SMTPHandler(logging.Handler): | |
947 """ | |
948 A handler class which sends an SMTP email for each logging event. | |
949 """ | |
950 def __init__(self, mailhost, fromaddr, toaddrs, subject, | |
951 credentials=None, secure=None, timeout=5.0): | |
952 """ | |
953 Initialize the handler. | |
954 | |
955 Initialize the instance with the from and to addresses and subject | |
956 line of the email. To specify a non-standard SMTP port, use the | |
957 (host, port) tuple format for the mailhost argument. To specify | |
958 authentication credentials, supply a (username, password) tuple | |
959 for the credentials argument. To specify the use of a secure | |
960 protocol (TLS), pass in a tuple for the secure argument. This will | |
961 only be used when authentication credentials are supplied. The tuple | |
962 will be either an empty tuple, or a single-value tuple with the name | |
963 of a keyfile, or a 2-value tuple with the names of the keyfile and | |
964 certificate file. (This tuple is passed to the `starttls` method). | |
965 A timeout in seconds can be specified for the SMTP connection (the | |
966 default is one second). | |
967 """ | |
968 logging.Handler.__init__(self) | |
969 if isinstance(mailhost, (list, tuple)): | |
970 self.mailhost, self.mailport = mailhost | |
971 else: | |
972 self.mailhost, self.mailport = mailhost, None | |
973 if isinstance(credentials, (list, tuple)): | |
974 self.username, self.password = credentials | |
975 else: | |
976 self.username = None | |
977 self.fromaddr = fromaddr | |
978 if isinstance(toaddrs, str): | |
979 toaddrs = [toaddrs] | |
980 self.toaddrs = toaddrs | |
981 self.subject = subject | |
982 self.secure = secure | |
983 self.timeout = timeout | |
984 | |
985 def getSubject(self, record): | |
986 """ | |
987 Determine the subject for the email. | |
988 | |
989 If you want to specify a subject line which is record-dependent, | |
990 override this method. | |
991 """ | |
992 return self.subject | |
993 | |
994 def emit(self, record): | |
995 """ | |
996 Emit a record. | |
997 | |
998 Format the record and send it to the specified addressees. | |
999 """ | |
1000 try: | |
1001 import smtplib | |
1002 from email.message import EmailMessage | |
1003 import email.utils | |
1004 | |
1005 port = self.mailport | |
1006 if not port: | |
1007 port = smtplib.SMTP_PORT | |
1008 smtp = smtplib.SMTP(self.mailhost, port, timeout=self.timeout) | |
1009 msg = EmailMessage() | |
1010 msg['From'] = self.fromaddr | |
1011 msg['To'] = ','.join(self.toaddrs) | |
1012 msg['Subject'] = self.getSubject(record) | |
1013 msg['Date'] = email.utils.localtime() | |
1014 msg.set_content(self.format(record)) | |
1015 if self.username: | |
1016 if self.secure is not None: | |
1017 smtp.ehlo() | |
1018 smtp.starttls(*self.secure) | |
1019 smtp.ehlo() | |
1020 smtp.login(self.username, self.password) | |
1021 smtp.send_message(msg) | |
1022 smtp.quit() | |
1023 except Exception: | |
1024 self.handleError(record) | |
1025 | |
1026 class NTEventLogHandler(logging.Handler): | |
1027 """ | |
1028 A handler class which sends events to the NT Event Log. Adds a | |
1029 registry entry for the specified application name. If no dllname is | |
1030 provided, win32service.pyd (which contains some basic message | |
1031 placeholders) is used. Note that use of these placeholders will make | |
1032 your event logs big, as the entire message source is held in the log. | |
1033 If you want slimmer logs, you have to pass in the name of your own DLL | |
1034 which contains the message definitions you want to use in the event log. | |
1035 """ | |
1036 def __init__(self, appname, dllname=None, logtype="Application"): | |
1037 logging.Handler.__init__(self) | |
1038 try: | |
1039 import win32evtlogutil, win32evtlog | |
1040 self.appname = appname | |
1041 self._welu = win32evtlogutil | |
1042 if not dllname: | |
1043 dllname = os.path.split(self._welu.__file__) | |
1044 dllname = os.path.split(dllname[0]) | |
1045 dllname = os.path.join(dllname[0], r'win32service.pyd') | |
1046 self.dllname = dllname | |
1047 self.logtype = logtype | |
1048 self._welu.AddSourceToRegistry(appname, dllname, logtype) | |
1049 self.deftype = win32evtlog.EVENTLOG_ERROR_TYPE | |
1050 self.typemap = { | |
1051 logging.DEBUG : win32evtlog.EVENTLOG_INFORMATION_TYPE, | |
1052 logging.INFO : win32evtlog.EVENTLOG_INFORMATION_TYPE, | |
1053 logging.WARNING : win32evtlog.EVENTLOG_WARNING_TYPE, | |
1054 logging.ERROR : win32evtlog.EVENTLOG_ERROR_TYPE, | |
1055 logging.CRITICAL: win32evtlog.EVENTLOG_ERROR_TYPE, | |
1056 } | |
1057 except ImportError: | |
1058 print("The Python Win32 extensions for NT (service, event "\ | |
1059 "logging) appear not to be available.") | |
1060 self._welu = None | |
1061 | |
1062 def getMessageID(self, record): | |
1063 """ | |
1064 Return the message ID for the event record. If you are using your | |
1065 own messages, you could do this by having the msg passed to the | |
1066 logger being an ID rather than a formatting string. Then, in here, | |
1067 you could use a dictionary lookup to get the message ID. This | |
1068 version returns 1, which is the base message ID in win32service.pyd. | |
1069 """ | |
1070 return 1 | |
1071 | |
1072 def getEventCategory(self, record): | |
1073 """ | |
1074 Return the event category for the record. | |
1075 | |
1076 Override this if you want to specify your own categories. This version | |
1077 returns 0. | |
1078 """ | |
1079 return 0 | |
1080 | |
1081 def getEventType(self, record): | |
1082 """ | |
1083 Return the event type for the record. | |
1084 | |
1085 Override this if you want to specify your own types. This version does | |
1086 a mapping using the handler's typemap attribute, which is set up in | |
1087 __init__() to a dictionary which contains mappings for DEBUG, INFO, | |
1088 WARNING, ERROR and CRITICAL. If you are using your own levels you will | |
1089 either need to override this method or place a suitable dictionary in | |
1090 the handler's typemap attribute. | |
1091 """ | |
1092 return self.typemap.get(record.levelno, self.deftype) | |
1093 | |
1094 def emit(self, record): | |
1095 """ | |
1096 Emit a record. | |
1097 | |
1098 Determine the message ID, event category and event type. Then | |
1099 log the message in the NT event log. | |
1100 """ | |
1101 if self._welu: | |
1102 try: | |
1103 id = self.getMessageID(record) | |
1104 cat = self.getEventCategory(record) | |
1105 type = self.getEventType(record) | |
1106 msg = self.format(record) | |
1107 self._welu.ReportEvent(self.appname, id, cat, type, [msg]) | |
1108 except Exception: | |
1109 self.handleError(record) | |
1110 | |
1111 def close(self): | |
1112 """ | |
1113 Clean up this handler. | |
1114 | |
1115 You can remove the application name from the registry as a | |
1116 source of event log entries. However, if you do this, you will | |
1117 not be able to see the events as you intended in the Event Log | |
1118 Viewer - it needs to be able to access the registry to get the | |
1119 DLL name. | |
1120 """ | |
1121 #self._welu.RemoveSourceFromRegistry(self.appname, self.logtype) | |
1122 logging.Handler.close(self) | |
1123 | |
1124 class HTTPHandler(logging.Handler): | |
1125 """ | |
1126 A class which sends records to a Web server, using either GET or | |
1127 POST semantics. | |
1128 """ | |
1129 def __init__(self, host, url, method="GET", secure=False, credentials=None, | |
1130 context=None): | |
1131 """ | |
1132 Initialize the instance with the host, the request URL, and the method | |
1133 ("GET" or "POST") | |
1134 """ | |
1135 logging.Handler.__init__(self) | |
1136 method = method.upper() | |
1137 if method not in ["GET", "POST"]: | |
1138 raise ValueError("method must be GET or POST") | |
1139 if not secure and context is not None: | |
1140 raise ValueError("context parameter only makes sense " | |
1141 "with secure=True") | |
1142 self.host = host | |
1143 self.url = url | |
1144 self.method = method | |
1145 self.secure = secure | |
1146 self.credentials = credentials | |
1147 self.context = context | |
1148 | |
1149 def mapLogRecord(self, record): | |
1150 """ | |
1151 Default implementation of mapping the log record into a dict | |
1152 that is sent as the CGI data. Overwrite in your class. | |
1153 Contributed by Franz Glasner. | |
1154 """ | |
1155 return record.__dict__ | |
1156 | |
1157 def emit(self, record): | |
1158 """ | |
1159 Emit a record. | |
1160 | |
1161 Send the record to the Web server as a percent-encoded dictionary | |
1162 """ | |
1163 try: | |
1164 import http.client, urllib.parse | |
1165 host = self.host | |
1166 if self.secure: | |
1167 h = http.client.HTTPSConnection(host, context=self.context) | |
1168 else: | |
1169 h = http.client.HTTPConnection(host) | |
1170 url = self.url | |
1171 data = urllib.parse.urlencode(self.mapLogRecord(record)) | |
1172 if self.method == "GET": | |
1173 if (url.find('?') >= 0): | |
1174 sep = '&' | |
1175 else: | |
1176 sep = '?' | |
1177 url = url + "%c%s" % (sep, data) | |
1178 h.putrequest(self.method, url) | |
1179 # support multiple hosts on one IP address... | |
1180 # need to strip optional :port from host, if present | |
1181 i = host.find(":") | |
1182 if i >= 0: | |
1183 host = host[:i] | |
1184 # See issue #30904: putrequest call above already adds this header | |
1185 # on Python 3.x. | |
1186 # h.putheader("Host", host) | |
1187 if self.method == "POST": | |
1188 h.putheader("Content-type", | |
1189 "application/x-www-form-urlencoded") | |
1190 h.putheader("Content-length", str(len(data))) | |
1191 if self.credentials: | |
1192 import base64 | |
1193 s = ('%s:%s' % self.credentials).encode('utf-8') | |
1194 s = 'Basic ' + base64.b64encode(s).strip().decode('ascii') | |
1195 h.putheader('Authorization', s) | |
1196 h.endheaders() | |
1197 if self.method == "POST": | |
1198 h.send(data.encode('utf-8')) | |
1199 h.getresponse() #can't do anything with the result | |
1200 except Exception: | |
1201 self.handleError(record) | |
1202 | |
1203 class BufferingHandler(logging.Handler): | |
1204 """ | |
1205 A handler class which buffers logging records in memory. Whenever each | |
1206 record is added to the buffer, a check is made to see if the buffer should | |
1207 be flushed. If it should, then flush() is expected to do what's needed. | |
1208 """ | |
1209 def __init__(self, capacity): | |
1210 """ | |
1211 Initialize the handler with the buffer size. | |
1212 """ | |
1213 logging.Handler.__init__(self) | |
1214 self.capacity = capacity | |
1215 self.buffer = [] | |
1216 | |
1217 def shouldFlush(self, record): | |
1218 """ | |
1219 Should the handler flush its buffer? | |
1220 | |
1221 Returns true if the buffer is up to capacity. This method can be | |
1222 overridden to implement custom flushing strategies. | |
1223 """ | |
1224 return (len(self.buffer) >= self.capacity) | |
1225 | |
1226 def emit(self, record): | |
1227 """ | |
1228 Emit a record. | |
1229 | |
1230 Append the record. If shouldFlush() tells us to, call flush() to process | |
1231 the buffer. | |
1232 """ | |
1233 self.buffer.append(record) | |
1234 if self.shouldFlush(record): | |
1235 self.flush() | |
1236 | |
1237 def flush(self): | |
1238 """ | |
1239 Override to implement custom flushing behaviour. | |
1240 | |
1241 This version just zaps the buffer to empty. | |
1242 """ | |
1243 self.acquire() | |
1244 try: | |
1245 self.buffer = [] | |
1246 finally: | |
1247 self.release() | |
1248 | |
1249 def close(self): | |
1250 """ | |
1251 Close the handler. | |
1252 | |
1253 This version just flushes and chains to the parent class' close(). | |
1254 """ | |
1255 try: | |
1256 self.flush() | |
1257 finally: | |
1258 logging.Handler.close(self) | |
1259 | |
1260 class MemoryHandler(BufferingHandler): | |
1261 """ | |
1262 A handler class which buffers logging records in memory, periodically | |
1263 flushing them to a target handler. Flushing occurs whenever the buffer | |
1264 is full, or when an event of a certain severity or greater is seen. | |
1265 """ | |
1266 def __init__(self, capacity, flushLevel=logging.ERROR, target=None, | |
1267 flushOnClose=True): | |
1268 """ | |
1269 Initialize the handler with the buffer size, the level at which | |
1270 flushing should occur and an optional target. | |
1271 | |
1272 Note that without a target being set either here or via setTarget(), | |
1273 a MemoryHandler is no use to anyone! | |
1274 | |
1275 The ``flushOnClose`` argument is ``True`` for backward compatibility | |
1276 reasons - the old behaviour is that when the handler is closed, the | |
1277 buffer is flushed, even if the flush level hasn't been exceeded nor the | |
1278 capacity exceeded. To prevent this, set ``flushOnClose`` to ``False``. | |
1279 """ | |
1280 BufferingHandler.__init__(self, capacity) | |
1281 self.flushLevel = flushLevel | |
1282 self.target = target | |
1283 # See Issue #26559 for why this has been added | |
1284 self.flushOnClose = flushOnClose | |
1285 | |
1286 def shouldFlush(self, record): | |
1287 """ | |
1288 Check for buffer full or a record at the flushLevel or higher. | |
1289 """ | |
1290 return (len(self.buffer) >= self.capacity) or \ | |
1291 (record.levelno >= self.flushLevel) | |
1292 | |
1293 def setTarget(self, target): | |
1294 """ | |
1295 Set the target handler for this handler. | |
1296 """ | |
1297 self.target = target | |
1298 | |
1299 def flush(self): | |
1300 """ | |
1301 For a MemoryHandler, flushing means just sending the buffered | |
1302 records to the target, if there is one. Override if you want | |
1303 different behaviour. | |
1304 | |
1305 The record buffer is also cleared by this operation. | |
1306 """ | |
1307 self.acquire() | |
1308 try: | |
1309 if self.target: | |
1310 for record in self.buffer: | |
1311 self.target.handle(record) | |
1312 self.buffer = [] | |
1313 finally: | |
1314 self.release() | |
1315 | |
1316 def close(self): | |
1317 """ | |
1318 Flush, if appropriately configured, set the target to None and lose the | |
1319 buffer. | |
1320 """ | |
1321 try: | |
1322 if self.flushOnClose: | |
1323 self.flush() | |
1324 finally: | |
1325 self.acquire() | |
1326 try: | |
1327 self.target = None | |
1328 BufferingHandler.close(self) | |
1329 finally: | |
1330 self.release() | |
1331 | |
1332 | |
1333 class QueueHandler(logging.Handler): | |
1334 """ | |
1335 This handler sends events to a queue. Typically, it would be used together | |
1336 with a multiprocessing Queue to centralise logging to file in one process | |
1337 (in a multi-process application), so as to avoid file write contention | |
1338 between processes. | |
1339 | |
1340 This code is new in Python 3.2, but this class can be copy pasted into | |
1341 user code for use with earlier Python versions. | |
1342 """ | |
1343 | |
1344 def __init__(self, queue): | |
1345 """ | |
1346 Initialise an instance, using the passed queue. | |
1347 """ | |
1348 logging.Handler.__init__(self) | |
1349 self.queue = queue | |
1350 | |
1351 def enqueue(self, record): | |
1352 """ | |
1353 Enqueue a record. | |
1354 | |
1355 The base implementation uses put_nowait. You may want to override | |
1356 this method if you want to use blocking, timeouts or custom queue | |
1357 implementations. | |
1358 """ | |
1359 self.queue.put_nowait(record) | |
1360 | |
1361 def prepare(self, record): | |
1362 """ | |
1363 Prepares a record for queuing. The object returned by this method is | |
1364 enqueued. | |
1365 | |
1366 The base implementation formats the record to merge the message | |
1367 and arguments, and removes unpickleable items from the record | |
1368 in-place. | |
1369 | |
1370 You might want to override this method if you want to convert | |
1371 the record to a dict or JSON string, or send a modified copy | |
1372 of the record while leaving the original intact. | |
1373 """ | |
1374 # The format operation gets traceback text into record.exc_text | |
1375 # (if there's exception data), and also returns the formatted | |
1376 # message. We can then use this to replace the original | |
1377 # msg + args, as these might be unpickleable. We also zap the | |
1378 # exc_info and exc_text attributes, as they are no longer | |
1379 # needed and, if not None, will typically not be pickleable. | |
1380 msg = self.format(record) | |
1381 # bpo-35726: make copy of record to avoid affecting other handlers in the chain. | |
1382 record = copy.copy(record) | |
1383 record.message = msg | |
1384 record.msg = msg | |
1385 record.args = None | |
1386 record.exc_info = None | |
1387 record.exc_text = None | |
1388 return record | |
1389 | |
1390 def emit(self, record): | |
1391 """ | |
1392 Emit a record. | |
1393 | |
1394 Writes the LogRecord to the queue, preparing it for pickling first. | |
1395 """ | |
1396 try: | |
1397 self.enqueue(self.prepare(record)) | |
1398 except Exception: | |
1399 self.handleError(record) | |
1400 | |
1401 | |
1402 class QueueListener(object): | |
1403 """ | |
1404 This class implements an internal threaded listener which watches for | |
1405 LogRecords being added to a queue, removes them and passes them to a | |
1406 list of handlers for processing. | |
1407 """ | |
1408 _sentinel = None | |
1409 | |
1410 def __init__(self, queue, *handlers, respect_handler_level=False): | |
1411 """ | |
1412 Initialise an instance with the specified queue and | |
1413 handlers. | |
1414 """ | |
1415 self.queue = queue | |
1416 self.handlers = handlers | |
1417 self._thread = None | |
1418 self.respect_handler_level = respect_handler_level | |
1419 | |
1420 def dequeue(self, block): | |
1421 """ | |
1422 Dequeue a record and return it, optionally blocking. | |
1423 | |
1424 The base implementation uses get. You may want to override this method | |
1425 if you want to use timeouts or work with custom queue implementations. | |
1426 """ | |
1427 return self.queue.get(block) | |
1428 | |
1429 def start(self): | |
1430 """ | |
1431 Start the listener. | |
1432 | |
1433 This starts up a background thread to monitor the queue for | |
1434 LogRecords to process. | |
1435 """ | |
1436 self._thread = t = threading.Thread(target=self._monitor) | |
1437 t.daemon = True | |
1438 t.start() | |
1439 | |
1440 def prepare(self, record): | |
1441 """ | |
1442 Prepare a record for handling. | |
1443 | |
1444 This method just returns the passed-in record. You may want to | |
1445 override this method if you need to do any custom marshalling or | |
1446 manipulation of the record before passing it to the handlers. | |
1447 """ | |
1448 return record | |
1449 | |
1450 def handle(self, record): | |
1451 """ | |
1452 Handle a record. | |
1453 | |
1454 This just loops through the handlers offering them the record | |
1455 to handle. | |
1456 """ | |
1457 record = self.prepare(record) | |
1458 for handler in self.handlers: | |
1459 if not self.respect_handler_level: | |
1460 process = True | |
1461 else: | |
1462 process = record.levelno >= handler.level | |
1463 if process: | |
1464 handler.handle(record) | |
1465 | |
1466 def _monitor(self): | |
1467 """ | |
1468 Monitor the queue for records, and ask the handler | |
1469 to deal with them. | |
1470 | |
1471 This method runs on a separate, internal thread. | |
1472 The thread will terminate if it sees a sentinel object in the queue. | |
1473 """ | |
1474 q = self.queue | |
1475 has_task_done = hasattr(q, 'task_done') | |
1476 while True: | |
1477 try: | |
1478 record = self.dequeue(True) | |
1479 if record is self._sentinel: | |
1480 if has_task_done: | |
1481 q.task_done() | |
1482 break | |
1483 self.handle(record) | |
1484 if has_task_done: | |
1485 q.task_done() | |
1486 except queue.Empty: | |
1487 break | |
1488 | |
1489 def enqueue_sentinel(self): | |
1490 """ | |
1491 This is used to enqueue the sentinel record. | |
1492 | |
1493 The base implementation uses put_nowait. You may want to override this | |
1494 method if you want to use timeouts or work with custom queue | |
1495 implementations. | |
1496 """ | |
1497 self.queue.put_nowait(self._sentinel) | |
1498 | |
1499 def stop(self): | |
1500 """ | |
1501 Stop the listener. | |
1502 | |
1503 This asks the thread to terminate, and then waits for it to do so. | |
1504 Note that if you don't call this before your application exits, there | |
1505 may be some records still left on the queue, which won't be processed. | |
1506 """ | |
1507 self.enqueue_sentinel() | |
1508 self._thread.join() | |
1509 self._thread = None |