1""" 2Additional handlers that are used as the base classes of the buildlogger 3handler. 4""" 5 6from __future__ import absolute_import 7 8import json 9import logging 10import threading 11import urllib2 12 13from .. import utils 14from ..utils import timer 15 16_TIMEOUT_SECS = 10 17 18class BufferedHandler(logging.Handler): 19 """ 20 A handler class that buffers logging records in memory. Whenever 21 each record is added to the buffer, a check is made to see if the 22 buffer should be flushed. If it should, then flush() is expected to 23 do what's needed. 24 """ 25 26 def __init__(self, capacity, interval_secs): 27 """ 28 Initializes the handler with the buffer size and timeout after 29 which the buffer is flushed regardless. 30 """ 31 32 logging.Handler.__init__(self) 33 34 if not isinstance(capacity, int): 35 raise TypeError("capacity must be an integer") 36 elif capacity <= 0: 37 raise ValueError("capacity must be a positive integer") 38 39 if not isinstance(interval_secs, (int, float)): 40 raise TypeError("interval_secs must be a number") 41 elif interval_secs <= 0.0: 42 raise ValueError("interval_secs must be a positive number") 43 44 self.capacity = capacity 45 self.interval_secs = interval_secs 46 self.buffer = [] 47 48 self._lock = threading.Lock() 49 self._timer = None # Defer creation until actually begin to log messages. 50 51 def _new_timer(self): 52 """ 53 Returns a new timer.AlarmClock instance that will call the 54 flush() method after 'interval_secs' seconds. 55 """ 56 57 return timer.AlarmClock(self.interval_secs, self.flush, args=[self]) 58 59 def process_record(self, record): 60 """ 61 Applies a transformation to the record before it gets added to 62 the buffer. 63 64 The default implementation returns 'record' unmodified. 65 """ 66 67 return record 68 69 def emit(self, record): 70 """ 71 Emits a record. 72 73 Append the record to the buffer after it has been transformed by 74 process_record(). If the length of the buffer is greater than or 75 equal to its capacity, then flush() is called to process the 76 buffer. 77 78 After flushing the buffer, the timer is restarted so that it 79 will expire after another 'interval_secs' seconds. 80 """ 81 82 with self._lock: 83 self.buffer.append(self.process_record(record)) 84 if len(self.buffer) >= self.capacity: 85 if self._timer is not None: 86 self._timer.snooze() 87 self.flush_with_lock(False) 88 if self._timer is not None: 89 self._timer.reset() 90 91 if self._timer is None: 92 self._timer = self._new_timer() 93 self._timer.start() 94 95 def flush(self, close_called=False): 96 """ 97 Ensures all logging output has been flushed. 98 """ 99 100 with self._lock: 101 if self.buffer: 102 self.flush_with_lock(close_called) 103 104 def flush_with_lock(self, close_called): 105 """ 106 Ensures all logging output has been flushed. 107 108 This version resets the buffers back to an empty list and is 109 intended to be overridden by subclasses. 110 """ 111 112 self.buffer = [] 113 114 def close(self): 115 """ 116 Tidies up any resources used by the handler. 117 118 Stops the timer and flushes the buffer. 119 """ 120 121 if self._timer is not None: 122 self._timer.dismiss() 123 self.flush(close_called=True) 124 125 logging.Handler.close(self) 126 127 128class HTTPHandler(object): 129 """ 130 A class which sends data to a web server using POST requests. 131 """ 132 133 def __init__(self, realm, url_root, username, password): 134 """ 135 Initializes the handler with the necessary authenticaton 136 credentials. 137 """ 138 139 digest_handler = urllib2.HTTPDigestAuthHandler() 140 digest_handler.add_password( 141 realm=realm, 142 uri=url_root, 143 user=username, 144 passwd=password) 145 146 self.url_root = url_root 147 self.url_opener = urllib2.build_opener(digest_handler, urllib2.HTTPErrorProcessor()) 148 149 def _make_url(self, endpoint): 150 return "%s/%s/" % (self.url_root.rstrip("/"), endpoint.strip("/")) 151 152 def post(self, endpoint, data=None, headers=None, timeout_secs=_TIMEOUT_SECS): 153 """ 154 Sends a POST request to the specified endpoint with the supplied 155 data. 156 157 Returns the response, either as a string or a JSON object based 158 on the content type. 159 """ 160 161 data = utils.default_if_none(data, []) 162 data = json.dumps(data, encoding="utf-8") 163 164 headers = utils.default_if_none(headers, {}) 165 headers["Content-Type"] = "application/json; charset=utf-8" 166 167 url = self._make_url(endpoint) 168 request = urllib2.Request(url=url, data=data, headers=headers) 169 170 response = self.url_opener.open(request, timeout=timeout_secs) 171 headers = response.info() 172 173 content_type = headers.gettype() 174 if content_type == "application/json": 175 encoding = headers.getparam("charset") or "utf-8" 176 return json.load(response, encoding=encoding) 177 178 return response.read() 179