From 814b8d13bf5eb2bcf98cfb1a90e717423911bd57 Mon Sep 17 00:00:00 2001 From: Elpedio Adoptante Jr Date: Wed, 27 Feb 2019 00:00:08 +0800 Subject: [PATCH 1/3] Python Logging Integration Changes: - clean up codes - created constants.py for CONSTANTS - added requirements.txt for dependencies - added time interval for sending messages --- .gitignore | 113 ++++++++++++++++++++++++++++++++------ README.md | 22 ++++++-- requirements.txt | 5 ++ setup.cfg | 23 ++++++++ setup.py | 33 ++++++----- stackify/__init__.py | 36 +++--------- stackify/application.py | 5 +- stackify/constants.py | 22 ++++++++ stackify/error.py | 21 ------- stackify/handler.py | 54 ++++++++++++------ stackify/http.py | 30 +++++----- stackify/log.py | 5 +- stackify/timer.py | 36 ++++++++++++ tests/bases.py | 3 +- tests/test_application.py | 22 ++++---- tests/test_formats.py | 5 +- tests/test_handler.py | 12 ++-- tests/test_http.py | 39 +++++++------ tests/test_init.py | 24 ++++---- tests/test_log.py | 20 ++----- 20 files changed, 334 insertions(+), 196 deletions(-) create mode 100644 requirements.txt create mode 100644 setup.cfg create mode 100644 stackify/constants.py create mode 100644 stackify/timer.py diff --git a/.gitignore b/.gitignore index 1ae3595..25344cd 100644 --- a/.gitignore +++ b/.gitignore @@ -1,32 +1,109 @@ -*.py[co] +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class -# Packages -*.egg -*.egg-info -dist -build -eggs -parts -var -sdist -develop-eggs +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +*.egg-info/ .installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec # Installer logs pip-log.txt +pip-delete-this-directory.txt # Unit test / coverage reports +htmlcov/ +.tox/ .coverage -.tox +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +.hypothesis/ +.pytest_cache/ -#Translations +# Translations *.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ -#Mr Developer -.mr.developer.cfg +# PyBuilder +target/ -# virutalenvs +# Jupyter Notebook +.ipynb_checkpoints + +# pyenv +.python-version + +# celery beat schedule file +celerybeat-schedule + +# SageMath parsed files +*.sage.py + +# Environments +.env .venv +env/ +venv/ +env*/ +venv*/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ -# debug stuff -test.py +# Intellij +.idea diff --git a/README.md b/README.md index f29f16a..d30414e 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ Stackify API for Python -======= +======================= [Stackify](https://stackify.com) support for Python programs. @@ -35,12 +35,28 @@ export STACKIFY_API_KEY=****** These options can also be provided in your code: ```python +# Standard API import stackify logger = stackify.getLogger(application="MyApp", environment="Dev", api_key=******) logger.warning('Something happened') ``` +```python +# Python Logging Integration +import logging +import stackify + +# your existing logging +logger = logging.getLogger() +... + +stackify_handler = stackify.StackifyHandler(application="MyApp", environment="Dev", api_key=******) +logger.addHandler(stackify_handler) + +logger.warning('Something happened') +``` + ## Usage stackify-python handles uploads in batches of 100 messages at a time on another thread. @@ -48,10 +64,6 @@ When your program exits, it will shut the thread down and upload the remaining m Stackify can store extra data along with your log message: ```python -import stackify - -logger = stackify.getLogger() - try: user_string = raw_input("Enter a number: ") print("You entered", int(user_string)) diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..3c83c04 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,5 @@ +mock==2.0.0 +pytest==4.3.0 +pytest-cov==2.6.1 +requests==2.21.0 +retrying==1.3.3 diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 0000000..4878226 --- /dev/null +++ b/setup.cfg @@ -0,0 +1,23 @@ +[flake8] +ignore = E501, W605 +exclude = + .git, + __pycache__, + build, + dist, + env*, + venv*, + setup.cfg, + README.md, + LICENSE.txt, + requirements.txt, + + +[coverage:run] +include = + stackify/* +omit = + *tests* + +[tool:pytest] +python_files=tests.py test.py test_*.py *_test.py tests_*.py *_tests.py diff --git a/setup.py b/setup.py index d19d42b..b0c3c9f 100755 --- a/setup.py +++ b/setup.py @@ -5,10 +5,10 @@ try: from pypandoc import convert - read_md = lambda f: convert(f, 'rst') + read_md = lambda f: convert(f, 'rst') # noqa except ImportError: print('warning: pypandoc module not found, could not convert Markdown to RST') - read_md = lambda f: open(f).read() + read_md = lambda f: open(f).read() # noqa version_re = re.compile(r'__version__\s+=\s+(.*)') @@ -17,26 +17,25 @@ version = ast.literal_eval(version_re.search(f).group(1)) setup( - name = 'stackify', - version = version, - author = 'Matthew Thompson', - author_email = 'chameleonator@gmail.com', - packages = ['stackify'], - url = 'https://github.com/stackify/stackify-api-python', - license = open('LICENSE.txt').readline(), - description = 'Stackify API for Python', - long_description = read_md('README.md'), - download_url = 'https://github.com/stackify/stackify-api-python/tarball/0.0.1', - keywords = ['logging', 'stackify', 'exception'], + name='stackify', + version=version, + author='Matthew Thompson', + author_email='chameleonator@gmail.com', + packages=['stackify'], + url='https://github.com/stackify/stackify-api-python', + license=open('LICENSE.txt').readline(), + description='Stackify API for Python', + long_description=read_md('README.md'), + download_url='https://github.com/stackify/stackify-api-python/tarball/0.0.1', + keywords=['logging', 'stackify', 'exception'], classifiers=["Programming Language :: Python"], - install_requires = [ + install_requires=[ 'retrying>=1.2.3', 'requests>=2.4.1' ], - test_suite = 'tests', - tests_requires = [ + test_suite='tests', + tests_requires=[ 'mock>=1.0.1', 'nose==1.3.4' ] ) - diff --git a/stackify/__init__.py b/stackify/__init__.py index f276a43..0e1a298 100644 --- a/stackify/__init__.py +++ b/stackify/__init__.py @@ -1,45 +1,24 @@ """ Stackify Python API """ - +__all__ = ("VERSION") __version__ = '0.0.1' - -API_URL = 'https://api.stackify.com' - -READ_TIMEOUT = 5000 - -MAX_BATCH = 100 - -QUEUE_SIZE = 1000 - import logging import inspect import atexit -DEFAULT_LEVEL = logging.ERROR - -LOGGING_LEVELS = { - logging.CRITICAL: 'CRITICAL', - logging.ERROR: 'ERROR', - logging.WARNING: 'WARNING', - logging.INFO: 'INFO', - logging.DEBUG: 'DEBUG', - logging.NOTSET: 'NOTSET' -} +from stackify.application import ApiConfiguration # noqa +from stackify.constants import DEFAULT_LEVEL +from stackify.handler import StackifyHandler class NullHandler(logging.Handler): def emit(self, record): pass -logging.getLogger(__name__).addHandler(NullHandler()) - -from stackify.application import ApiConfiguration -from stackify.http import HTTPClient - -from stackify.handler import StackifyHandler +logging.getLogger(__name__).addHandler(NullHandler()) def getLogger(name=None, auto_shutdown=True, basic_config=True, **kwargs): @@ -77,8 +56,6 @@ def getLogger(name=None, auto_shutdown=True, basic_config=True, **kwargs): if not [isinstance(x, StackifyHandler) for x in logger.handlers]: internal_logger = logging.getLogger(__name__) internal_logger.debug('Creating handler for logger %s', name) - handler = StackifyHandler(**kwargs) - logger.addHandler(handler) if auto_shutdown: internal_logger.debug('Registering atexit callback') @@ -87,6 +64,9 @@ def getLogger(name=None, auto_shutdown=True, basic_config=True, **kwargs): if logger.getEffectiveLevel() == logging.NOTSET: logger.setLevel(DEFAULT_LEVEL) + handler = StackifyHandler(ensure_at_exit=not auto_shutdown, **kwargs) + logger.addHandler(handler) + handler.listener.start() return logger diff --git a/stackify/application.py b/stackify/application.py index 69e8e71..2201df9 100644 --- a/stackify/application.py +++ b/stackify/application.py @@ -1,7 +1,7 @@ import socket import os -from stackify import API_URL +from stackify.constants import API_URL from stackify.formats import JSONObject @@ -32,8 +32,7 @@ def arg_or_env(name, args, default=None): if default: return default else: - raise NameError('You must specify the keyword argument {0} or ' - 'environment variable {1}'.format(name, env_name)) + raise NameError('You must specify the keyword argument {0} or environment variable {1}'.format(name, env_name)) def get_configuration(**kwargs): diff --git a/stackify/constants.py b/stackify/constants.py new file mode 100644 index 0000000..ddb2d36 --- /dev/null +++ b/stackify/constants.py @@ -0,0 +1,22 @@ +import logging + + +API_URL = 'https://api.stackify.com' +IDENTIFY_URL = '/Metrics/IdentifyApp' +LOG_SAVE_URL = '/Log/Save' +API_REQUEST_INTERVAL_IN_SEC = 5 + +MIN_BATCH = 10 +MAX_BATCH = 100 +QUEUE_SIZE = 1000 +READ_TIMEOUT = 5000 + +LOGGING_LEVELS = { + logging.CRITICAL: 'CRITICAL', + logging.ERROR: 'ERROR', + logging.WARNING: 'WARNING', + logging.INFO: 'INFO', + logging.DEBUG: 'DEBUG', + logging.NOTSET: 'NOTSET' +} +DEFAULT_LEVEL = logging.ERROR diff --git a/stackify/error.py b/stackify/error.py index 7ce5c42..2d59607 100644 --- a/stackify/error.py +++ b/stackify/error.py @@ -1,5 +1,4 @@ import traceback -import time import sys from stackify.formats import JSONObject @@ -37,31 +36,11 @@ def __init__(self, filename, lineno, method): self.Method = method -class WebRequestDetail(JSONObject): - def __init__(self): - self.UserIPAddress = None - self.HttpMethod = None - self.RequestProtocol = None - self.RequestUrl = None - self.RequestUrlRoot = None - self.ReferralUrl = None - self.Headers = {} - self.Cookies = {} - self.QueryString = {} - self.PostData = {} - self.SessionData = {} - self.PostDataRaw = None - self.MVCAction = None - self.MVCController = None - self.MVCArea = None - - class StackifyError(JSONObject): def __init__(self): self.EnvironmentDetail = None # environment detail object self.OccurredEpochMillis = None self.Error = None # ErrorItem object - self.WebRequestDetail = None # WebRequestDetail object self.CustomerName = None self.UserName = None diff --git a/stackify/handler.py b/stackify/handler.py index 20b44a4..9a24b9b 100644 --- a/stackify/handler.py +++ b/stackify/handler.py @@ -1,10 +1,9 @@ import logging -import threading -import os +import atexit try: from logging.handlers import QueueHandler, QueueListener -except: # pragma: no cover +except Exception: # pragma: no cover from stackify.handler_backport import QueueHandler, QueueListener try: @@ -12,11 +11,13 @@ except ImportError: # pragma: no cover import queue -from stackify import QUEUE_SIZE, API_URL, MAX_BATCH -from stackify.log import LogMsg, LogMsgGroup -from stackify.error import ErrorItem -from stackify.http import HTTPClient from stackify.application import get_configuration +from stackify.constants import API_REQUEST_INTERVAL_IN_SEC +from stackify.constants import MAX_BATCH +from stackify.constants import QUEUE_SIZE +from stackify.http import HTTPClient +from stackify.log import LogMsg, LogMsgGroup +from stackify.timer import RepeatedTimer class StackifyHandler(QueueHandler): @@ -25,10 +26,9 @@ class StackifyHandler(QueueHandler): transmission to Stackify servers. ''' - def __init__(self, queue_=None, listener=None, **kwargs): + def __init__(self, queue_=None, listener=None, ensure_at_exit=True, **kwargs): if queue_ is None: queue_ = queue.Queue(QUEUE_SIZE) - logger = logging.getLogger(__name__) super(StackifyHandler, self).__init__(queue_) @@ -36,6 +36,10 @@ def __init__(self, queue_=None, listener=None, **kwargs): listener = StackifyListener(queue_, **kwargs) self.listener = listener + self.listener.start() + + if ensure_at_exit: + atexit.register(self.listener.stop) def enqueue(self, record): ''' @@ -45,8 +49,7 @@ def enqueue(self, record): self.queue.put_nowait(record) except queue.Full: logger = logging.getLogger(__name__) - logger.warn('StackifyHandler queue is full, ' - 'evicting oldest record') + logger.warning('StackifyHandler queue is full, evicting oldest record') self.queue.get_nowait() self.queue.put_nowait(record) @@ -65,6 +68,9 @@ def __init__(self, queue_, max_batch=MAX_BATCH, config=None, **kwargs): self.max_batch = max_batch self.messages = [] self.http = HTTPClient(config) + self.timer = RepeatedTimer(API_REQUEST_INTERVAL_IN_SEC, self.send_group) + + self._started = False def handle(self, record): if not self.http.identified: @@ -80,22 +86,36 @@ def handle(self, record): self.send_group() def send_group(self): + if not self.messages: + return + group = LogMsgGroup(self.messages) try: self.http.send_log_group(group) - except: + except Exception: logger = logging.getLogger(__name__) - logger.exception('Could not send %s log messages, discarding', - len(self.messages)) + logger.exception('Could not send {} log messages, discarding'.format(len(self.messages))) del self.messages[:] + def start(self): + logger = logging.getLogger(__name__) + logger.debug('Starting up listener') + + if not self._started: + super(StackifyListener, self).start() + self.timer.start() + self._started = True + def stop(self): logger = logging.getLogger(__name__) logger.debug('Shutting down listener') - super(StackifyListener, self).stop() + + if self._started: + super(StackifyListener, self).stop() + self.timer.stop() + self._started = False # send any remaining messages if self.messages: - logger.debug('%s messages left on shutdown, uploading', - len(self.messages)) + logger.debug('{} messages left on shutdown, uploading'.format(len(self.messages))) self.send_group() diff --git a/stackify/http.py b/stackify/http.py index 82b8ca7..ee465b3 100644 --- a/stackify/http.py +++ b/stackify/http.py @@ -5,12 +5,17 @@ try: from cStringIO import StringIO -except: +except Exception: try: from StringIO import StringIO - except: + except Exception: pass # python 3, we use a new function in gzip +from stackify.application import EnvironmentDetail +from stackify.constants import IDENTIFY_URL +from stackify.constants import LOG_SAVE_URL +from stackify.constants import READ_TIMEOUT + def gzip_compress(data): if hasattr(gzip, 'compress'): @@ -23,10 +28,6 @@ def gzip_compress(data): return s.getvalue() -from stackify.application import EnvironmentDetail -from stackify import READ_TIMEOUT - - class HTTPClient: def __init__(self, api_config): self.api_config = api_config @@ -41,7 +42,7 @@ def __init__(self, api_config): def POST(self, url, json_object, use_gzip=False): request_url = self.api_config.api_url + url logger = logging.getLogger(__name__) - logger.debug('Request URL: %s', request_url) + logger.debug('Request URL: {}'.format(request_url)) headers = { 'Content-Type': 'application/json', @@ -51,7 +52,7 @@ def POST(self, url, json_object, use_gzip=False): try: payload_data = json_object.toJSON() - logger.debug('POST data: %s', payload_data) + logger.debug('POST data: {}'.format(payload_data)) if use_gzip: headers['Content-Encoding'] = 'gzip' @@ -61,19 +62,17 @@ def POST(self, url, json_object, use_gzip=False): data=payload_data, headers=headers, timeout=READ_TIMEOUT) - logger.debug('Response: %s', response.text) + logger.debug('Response: {}'.format(response.text)) return response.json() except requests.exceptions.RequestException: logger.exception('HTTP exception') - raise - except ValueError as e: + except ValueError: # could not read json response logger.exception('Cannot decode JSON response') - raise @retrying.retry(wait_exponential_multiplier=1000, stop_max_delay=10000) def identify_application(self): - result = self.POST('/Metrics/IdentifyApp', self.environment_detail) + result = self.POST(IDENTIFY_URL, self.environment_detail) self.app_name_id = result.get('AppNameID') self.app_env_id = result.get('AppEnvID') self.device_id = result.get('DeviceID') @@ -87,6 +86,5 @@ def send_log_group(self, group): group.CDAppID = self.device_app_id group.AppNameID = self.app_name_id group.ServerName = self.device_alias - if not group.ServerName: - group.ServerName = self.environment_detail.deviceName - self.POST('/Log/Save', group, True) + group.ServerName = group.ServerName or self.environment_detail.deviceName + self.POST(LOG_SAVE_URL, group, True) diff --git a/stackify/log.py b/stackify/log.py index c51f5f3..21e2e0b 100644 --- a/stackify/log.py +++ b/stackify/log.py @@ -2,14 +2,11 @@ import logging from stackify.formats import JSONObject - -from stackify import MAX_BATCH, LOGGING_LEVELS from stackify.error import StackifyError # this is used to separate builtin keys from user-specified keys -RECORD_VARS = set(logging.LogRecord('', '', '', '', - '', '', '', '').__dict__.keys()) +RECORD_VARS = set(logging.LogRecord('', '', '', '', '', '', '', '').__dict__.keys()) # the "message" attribute is saved on the record object by a Formatter RECORD_VARS.add('message') diff --git a/stackify/timer.py b/stackify/timer.py new file mode 100644 index 0000000..0bb0c0a --- /dev/null +++ b/stackify/timer.py @@ -0,0 +1,36 @@ +import time +from threading import Event, Thread + + +class RepeatedTimer(object): + ''' + Repeater class that call the function every interval seconds. + ''' + + def __init__(self, interval, function, *args, **kwargs): + self.interval = interval + self.function = function + self.args = args + self.kwargs = kwargs + self.started = time.time() + self.event = Event() + self.thread = Thread(target=self._target) + self._started = False + + def _target(self): + while not self.event.wait(self._time): + self.function(*self.args, **self.kwargs) + + @property + def _time(self): + return self.interval - ((time.time() - self.started) % self.interval) + + def start(self): + if not self._started: + self.thread.setDaemon(True) + self.thread.start() + + def stop(self): + if self._started: + self.event.set() + self.thread.join() diff --git a/tests/bases.py b/tests/bases.py index 7c4e4b0..d5575f1 100644 --- a/tests/bases.py +++ b/tests/bases.py @@ -1,6 +1,6 @@ import os import unittest -import retrying + class ClearEnvTest(unittest.TestCase): ''' @@ -27,4 +27,3 @@ def tearDown(self): for key, item in self.saved.items(): os.environ[key] = item del self.saved - diff --git a/tests/test_application.py b/tests/test_application.py index 7e43248..55b0296 100644 --- a/tests/test_application.py +++ b/tests/test_application.py @@ -4,10 +4,9 @@ import unittest from mock import patch -import os from .bases import ClearEnvTest -from stackify import API_URL +from stackify.constants import API_URL from stackify.application import get_configuration @@ -67,10 +66,11 @@ def test_kwarg_mix(self): def test_kwargs(self): '''API configuration can load from kwargs''' config = get_configuration( - application = 'test3_appname', - environment = 'test3_environment', - api_key = 'test3_apikey', - api_url = 'test3_apiurl') + application='test3_appname', + environment='test3_environment', + api_key='test3_apikey', + api_url='test3_apiurl', + ) self.assertEqual(config.application, 'test3_appname') self.assertEqual(config.environment, 'test3_environment') @@ -80,9 +80,10 @@ def test_kwargs(self): def test_api_url_default(self): '''API URL is set automatically''' config = get_configuration( - application = 'test4_appname', - environment = 'test4_environment', - api_key = 'test4_apikey') + application='test4_appname', + environment='test4_environment', + api_key='test4_apikey', + ) self.assertEqual(config.application, 'test4_appname') self.assertEqual(config.environment, 'test4_environment') @@ -90,6 +91,5 @@ def test_api_url_default(self): self.assertEqual(config.api_url, API_URL) -if __name__=='__main__': +if __name__ == '__main__': unittest.main() - diff --git a/tests/test_formats.py b/tests/test_formats.py index 6c10b64..bfa6c11 100644 --- a/tests/test_formats.py +++ b/tests/test_formats.py @@ -3,11 +3,11 @@ """ import unittest -from mock import patch, Mock import json from stackify.formats import JSONObject + class TestJSONObject(unittest.TestCase): ''' Test the JSON serializer object @@ -51,6 +51,5 @@ def __init__(self): self.assertEqual(json.loads(result), {'a': '1', 'b': False, 'd': []}) -if __name__=='__main__': +if __name__ == '__main__': unittest.main() - diff --git a/tests/test_handler.py b/tests/test_handler.py index 0ac201b..d0dfd34 100644 --- a/tests/test_handler.py +++ b/tests/test_handler.py @@ -41,10 +41,11 @@ class TestListener(unittest.TestCase): def setUp(self): self.config = ApiConfiguration( - application = 'test_appname', - environment = 'test_environment', - api_key = 'test_apikey', - api_url = 'test_apiurl') + application='test_appname', + environment='test_environment', + api_key='test_apikey', + api_url='test_apiurl', + ) # don't print warnings on http crashes, so mute stackify logger logging.getLogger('stackify').propagate = False @@ -110,6 +111,5 @@ def test_send_group_crash(self, send_log_group, logmsggroup, logmsg): self.assertEqual(send_log_group.call_count, 1) -if __name__=='__main__': +if __name__ == '__main__': unittest.main() - diff --git a/tests/test_http.py b/tests/test_http.py index 225e8c9..b1d8217 100644 --- a/tests/test_http.py +++ b/tests/test_http.py @@ -11,14 +11,16 @@ from stackify.log import LogMsgGroup from stackify.application import ApiConfiguration -from stackify import READ_TIMEOUT +from stackify.constants import READ_TIMEOUT old_retry = retrying.retry + def fake_retry_decorator(retries): def fake_retry(*args, **kwargs): - kwargs['wait_exponential_max'] = 0 # no delay between retries + kwargs['wait_exponential_max'] = 0 # no delay between retries kwargs['stop_max_attempt_number'] = retries + def inner(func): return old_retry(*args, **kwargs)(func) return inner @@ -43,10 +45,11 @@ def tearDownClass(cls): def setUp(self): self.config = ApiConfiguration( - application = 'test_appname', - environment = 'test_environment', - api_key = 'test_apikey', - api_url = 'test_apiurl') + application='test_appname', + environment='test_environment', + api_key='test_apikey', + api_url='test_apiurl', + ) self.client = stackify.http.HTTPClient(self.config) @@ -54,14 +57,16 @@ def test_logger_no_config(self): '''GZIP encoder works''' correct = list(b'\x1f\x8b\x08\x00\x00\x00\x00\x00\x02\xff\xf3H\xcd\xc9\xc9\xd7Q(\xcf/\xcaIQ\x04\x00\xe6\xc6\xe6\xeb\r\x00\x00\x00') gzipped = list(stackify.http.gzip_compress('Hello, world!')) - gzipped[4:8] = b'\x00\x00\x00\x00' # blank the mtime + gzipped[4:8] = b'\x00\x00\x00\x00' # blank the mtime self.assertEqual(gzipped, correct) def test_identify_retrying(self): '''HTTP identify should retry''' client = self.client - class CustomException(Exception): pass + class CustomException(Exception): + pass + crash = Mock(side_effect=CustomException) with patch.object(client, 'POST', crash): @@ -97,7 +102,9 @@ def test_send_log_group_retrying(self): '''HTTP sending log groups should retry''' client = self.client - class CustomException(Exception): pass + class CustomException(Exception): + pass + crash = Mock(side_effect=CustomException) group = LogMsgGroup([]) @@ -128,7 +135,6 @@ def test_send_log_group(self): self.assertEqual(group.AppNameID, client.app_name_id) self.assertEqual(group.ServerName, client.device_alias) - @patch('requests.post') def test_post_arguments(self, post): '''HTTP post has correct headers''' @@ -145,9 +151,9 @@ def test_post_arguments(self, post): self.assertTrue(post.called) args, kwargs = post.call_args - self.assertEquals(kwargs['headers'], headers) - self.assertEquals(kwargs['timeout'], READ_TIMEOUT) - self.assertEquals(kwargs['data'], payload.toJSON()) + self.assertEqual(kwargs['headers'], headers) + self.assertEqual(kwargs['timeout'], READ_TIMEOUT) + self.assertEqual(kwargs['data'], payload.toJSON()) @patch('requests.post') def test_post_gzip(self, post): @@ -162,10 +168,9 @@ def test_post_gzip(self, post): self.assertTrue(post.called) args, kwargs = post.call_args - self.assertEquals(kwargs['headers']['Content-Encoding'], 'gzip') - self.assertEquals(kwargs['data'], '1_gzipped') + self.assertEqual(kwargs['headers']['Content-Encoding'], 'gzip') + self.assertEqual(kwargs['data'], '1_gzipped') -if __name__=='__main__': +if __name__ == '__main__': unittest.main() - diff --git a/tests/test_init.py b/tests/test_init.py index 3a24a8a..bccee01 100644 --- a/tests/test_init.py +++ b/tests/test_init.py @@ -3,12 +3,9 @@ """ import unittest -from mock import patch, Mock +from mock import patch from .bases import ClearEnvTest -import os -import atexit - import stackify import logging @@ -21,10 +18,10 @@ class TestInit(ClearEnvTest): def setUp(self): super(TestInit, self).setUp() self.config = stackify.ApiConfiguration( - application = 'test_appname', - environment = 'test_environment', - api_key = 'test_apikey', - api_url = 'test_apiurl') + application='test_appname', + environment='test_environment', + api_key='test_apikey', + api_url='test_apiurl') self.loggers = [] def tearDown(self): @@ -85,11 +82,11 @@ def test_get_logger_defaults(self): config = handler.listener.http.api_config self.assertEqual(logger.name, 'tests.test_init') - self.assertEqual(config.api_url, stackify.API_URL) - self.assertEqual(handler.listener.max_batch, stackify.MAX_BATCH) - self.assertEqual(handler.queue.maxsize, stackify.QUEUE_SIZE) + self.assertEqual(config.api_url, stackify.constants.API_URL) + self.assertEqual(handler.listener.max_batch, stackify.constants.MAX_BATCH) + self.assertEqual(handler.queue.maxsize, stackify.constants.QUEUE_SIZE) # nose will goof with the following assert - #self.assertEqual(logger.getEffectiveLevel(), logging.WARNING) + # self.assertEqual(logger.getEffectiveLevel(), logging.WARNING) def test_get_logger_reuse(self): '''Grabbing a logger twice results in the same logger''' @@ -112,6 +109,5 @@ def test_get_handlers(self): self.assertEqual(logger.handlers, stackify.getHandlers(logger)) -if __name__=='__main__': +if __name__ == '__main__': unittest.main() - diff --git a/tests/test_log.py b/tests/test_log.py index 32f20a6..2d28b37 100644 --- a/tests/test_log.py +++ b/tests/test_log.py @@ -3,17 +3,12 @@ """ import unittest -from mock import patch, Mock import json import sys -import stackify.log - from stackify.log import LogMsg import logging -import json import time -#logging.LogRecord('name','level','pathname','lineno','msg','args','exc_info','func') class TestLogPopulate(unittest.TestCase): @@ -23,9 +18,8 @@ class TestLogPopulate(unittest.TestCase): def test_record_to_error(self): '''LogMsgs can load logger records''' - record = logging.LogRecord('name',logging.WARNING,'pathname',32, - 'message %s',('here'),(),'func') - record.my_extra = [1,2,3] + record = logging.LogRecord('name', logging.WARNING, 'pathname', 32, 'message %s', ('here'), (), 'func') + record.my_extra = [1, 2, 3] msg = LogMsg() msg.from_record(record) @@ -36,7 +30,7 @@ def test_record_to_error(self): self.assertEqual(msg.Th, 'MainThread') self.assertEqual(msg.Msg, 'message here') self.assertTrue(msg.EpochMs <= curr_ms) - self.assertEqual(json.loads(msg.data), {'my_extra':[1,2,3]}) + self.assertEqual(json.loads(msg.data), {'my_extra': [1, 2, 3]}) def test_record_exception(self): '''LogMsgs can parse exception information''' @@ -46,9 +40,8 @@ def __str__(self): try: raise CustomException() - except: - record = logging.LogRecord('my exception',logging.WARNING,'somepath',12, - 'a thing happened',(),sys.exc_info()) + except Exception: + record = logging.LogRecord('my exception', logging.WARNING, 'somepath', 12, 'a thing happened', (), sys.exc_info()) msg = LogMsg() msg.from_record(record) @@ -61,6 +54,5 @@ def __str__(self): self.assertEqual(msg.Ex.Error.SourceMethod, 'test_record_exception') -if __name__=='__main__': +if __name__ == '__main__': unittest.main() - From 0e3b38f559cf9d7f40ac895769c4150061703f80 Mon Sep 17 00:00:00 2001 From: Elpedio Adoptante Jr Date: Mon, 4 Mar 2019 20:27:55 +0800 Subject: [PATCH 2/3] set post interval to 30 secs --- stackify/__init__.py | 1 - stackify/constants.py | 3 +-- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/stackify/__init__.py b/stackify/__init__.py index 0e1a298..72fb7a7 100644 --- a/stackify/__init__.py +++ b/stackify/__init__.py @@ -1,7 +1,6 @@ """ Stackify Python API """ -__all__ = ("VERSION") __version__ = '0.0.1' import logging diff --git a/stackify/constants.py b/stackify/constants.py index ddb2d36..da30e56 100644 --- a/stackify/constants.py +++ b/stackify/constants.py @@ -4,9 +4,8 @@ API_URL = 'https://api.stackify.com' IDENTIFY_URL = '/Metrics/IdentifyApp' LOG_SAVE_URL = '/Log/Save' -API_REQUEST_INTERVAL_IN_SEC = 5 +API_REQUEST_INTERVAL_IN_SEC = 30 -MIN_BATCH = 10 MAX_BATCH = 100 QUEUE_SIZE = 1000 READ_TIMEOUT = 5000 From e6c65e79e3e709c960efabdded114a05f09fb13a Mon Sep 17 00:00:00 2001 From: Elpedio Adoptante Jr Date: Wed, 13 Mar 2019 17:03:45 +0800 Subject: [PATCH 3/3] update setup values --- setup.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/setup.py b/setup.py index b0c3c9f..dad6000 100755 --- a/setup.py +++ b/setup.py @@ -17,10 +17,10 @@ version = ast.literal_eval(version_re.search(f).group(1)) setup( - name='stackify', + name='stackify-api-python', version=version, - author='Matthew Thompson', - author_email='chameleonator@gmail.com', + author='Stackify', + author_email='support@stackify.com', packages=['stackify'], url='https://github.com/stackify/stackify-api-python', license=open('LICENSE.txt').readline(),