import zlib import sys import socket import os import types import json import logging try: import http.client as httplib except ImportError: import httplib _logger = logging.getLogger(__name__) # Python 3 compatibility helpers. PY2 = sys.version_info[0] == 2 PY3 = sys.version_info[0] == 3 if PY3: def b(s): return s.encode('latin-1') else: def b(s): return s # Helpers for json encoding and decoding. def json_encode(obj, **kwargs): _kwargs = {} if type(b'') is type(''): _kwargs['encoding'] = 'latin-1' def _encode(o): if isinstance(o, bytes): return o.decode('latin-1') elif isinstance(o, types.GeneratorType): return list(o) elif hasattr(o, '__iter__'): return list(iter(o)) raise TypeError(repr(o) + ' is not JSON serializable') _kwargs['default'] = _encode _kwargs['separators'] = (',', ':') _kwargs.update(kwargs) return json.dumps(obj, **_kwargs) def json_decode(s, **kwargs): return json.loads(s, **kwargs) # Platform plugin client. class Client(object): class NetworkInterfaceException(Exception): pass class DiscardDataForRequest(NetworkInterfaceException): pass class RetryDataForRequest(NetworkInterfaceException): pass class ServerIsUnavailable(RetryDataForRequest): pass USER_AGENT = 'ModWsgi-PythonPlugin/%s (Python %s %s)' % ( '1.0.0', sys.version.split()[0], sys.platform) HOST = 'platform-api.newrelic.com' URL = '/platform/v1/metrics' def __init__(self, license_key): self.license_key = license_key def send_request(self, payload=()): headers = {} config = {} license_key = self.license_key if not self.license_key: license_key = 'INVALID LICENSE KEY' headers['User-Agent'] = self.USER_AGENT headers['Content-Encoding'] = 'identity' headers['X-License-Key'] = license_key try: data = json_encode(payload) except Exception as exc: _logger.exception('Error encoding data for JSON payload ' 'with payload of %r.', payload) raise Client.DiscardDataForRequest(str(exc)) if len(data) > 64*1024: headers['Content-Encoding'] = 'deflate' level = (len(data) < 2000000) and 1 or 9 data = zlib.compress(b(data), level) try: connection = httplib.HTTPSConnection(self.HOST, timeout=30.0) connection.request('POST', self.URL, data, headers) response = connection.getresponse() content = response.read() except httplib.HTTPException as exc: raise Client.RetryDataForRequest(str(exc)) finally: connection.close() if response.status != 200: _logger.debug('Received a non 200 HTTP response from the data ' 'collector where headers=%r, status=%r and content=%r.', headers, response.status, content) if response.status == 400: if headers['Content-Encoding'] == 'deflate': data = zlib.decompress(data) _logger.error('Data collector is indicating that a bad ' 'request has been submitted for headers of %r and ' 'payload of %r with response of %r.', headers, data, content) raise Client.DiscardDataForRequest() elif response.status == 403: _logger.error('Data collector is indicating that the license ' 'key %r is not valid.', license_key) raise Client.DiscardDataForRequest() elif response.status == 413: _logger.warning('Data collector is indicating that a request ' 'was received where the request content size was over ' 'the maximum allowed size limit. The length of the ' 'request content was %d.', len(data)) raise Client.DiscardDataForRequest() elif response.status in (503, 504): _logger.warning('Data collector is unavailable.') raise Client.ServerIsUnavailable() elif response.status != 200: _logger.warning('An unexpected HTTP response was received ' 'from the data collector of %r. The payload for ' 'the request was %r.', response.status, payload) raise Client.DiscardDataForRequest() try: if PY3: content = content.decode('UTF-8') result = json_decode(content) except Exception as exc: _logger.exception('Error decoding data for JSON payload ' 'with payload of %r.', content) raise Client.DiscardDataForRequest(str(exc)) if 'status' in result: return result['status'] error_message = result['error'] raise Client.DiscardDataForRequest(error_message) def send_metrics(self, name, guid, version, duration, metrics): agent = {} agent['host'] = socket.gethostname() agent['pid'] = os.getpid() agent['version'] = version or '0.0.0.' component = {} component['name'] = name component['guid'] = guid component['duration'] = duration component['metrics'] = metrics payload = {} payload['agent'] = agent payload['components'] = [component] return self.send_request(payload)