From 21ba583abf96dab95c4198a5414d38dd74442543 Mon Sep 17 00:00:00 2001 From: Laerte Allan de Oliveira Araujo Date: Sun, 14 Oct 2018 00:18:50 -0300 Subject: [PATCH 01/14] add plugin of integration tornado Was added commit initial of integration framework tornado. --- elasticapm/contrib/tornado/__init__.py | 0 elasticapm/contrib/tornado/utils.py | 0 tests/contrib/tornado/__init__.py | 0 tests/contrib/tornado/tornado_tests.py | 0 4 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 elasticapm/contrib/tornado/__init__.py create mode 100644 elasticapm/contrib/tornado/utils.py create mode 100644 tests/contrib/tornado/__init__.py create mode 100644 tests/contrib/tornado/tornado_tests.py diff --git a/elasticapm/contrib/tornado/__init__.py b/elasticapm/contrib/tornado/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/elasticapm/contrib/tornado/utils.py b/elasticapm/contrib/tornado/utils.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/contrib/tornado/__init__.py b/tests/contrib/tornado/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/contrib/tornado/tornado_tests.py b/tests/contrib/tornado/tornado_tests.py new file mode 100644 index 000000000..e69de29bb From 995f011ad9bdbff6de5979b13db444764dc5e4ca Mon Sep 17 00:00:00 2001 From: Laerte Allan de Oliveira Araujo Date: Sun, 14 Oct 2018 00:23:09 -0300 Subject: [PATCH 02/14] add file with utils functions was added file with utils functions --- elasticapm/contrib/tornado/__init__.py | 89 ++++++++++++++++++++++++++ elasticapm/contrib/tornado/utils.py | 26 ++++++++ 2 files changed, 115 insertions(+) diff --git a/elasticapm/contrib/tornado/__init__.py b/elasticapm/contrib/tornado/__init__.py index e69de29bb..45bc00a2f 100644 --- a/elasticapm/contrib/tornado/__init__.py +++ b/elasticapm/contrib/tornado/__init__.py @@ -0,0 +1,89 @@ +import tornado +from tornado.web import RequestHandler + +import elasticapm +from elasticapm.base import Client +from elasticapm.contrib.tornado.utils import get_data_from_response, get_data_from_request + + +def make_client(client_cls, app, **defaults): + config = app.settings.get('ELASTIC_APM', {}) + + if 'framework_name' not in defaults: + defaults['framework_name'] = 'tornado' + defaults['framework_version'] = getattr(tornado, '__version__', '<0.7') + + client = client_cls(config, **defaults) + return client + + +class ElasticAPM(object): + + def __init__(self, app_tornado=None, client=None, client_cls=Client): + if not app_tornado: + raise Exception("Handle tornado invalid") + self.client = client + self.client_cls = client_cls + self.__init_app(app_tornado) + + def __init_app(self, app, **defaults): + self.app = app + if not self.client: + self.client = make_client(self.client_cls, app, **defaults) + + if self.client.config.instrument: + elasticapm.instrumentation.control.instrument() + app.settings.update({"apm_elastic": self}) + + def capture_exception(self, *args, **kwargs): + assert self.client, 'capture_exception called before application configured' + return self.client.capture_exception(*args, **kwargs) + + def capture_message(self, *args, **kwargs): + assert self.client, 'capture_message called before application configured' + return self.client.capture_message(*args, **kwargs) + + +class ApiElasticHandlerAPM(RequestHandler): + + def capture_exception(self): + apm_elastic = self.settings.get("apm_elastic") + apm_elastic.client.capture_exception( + context={ + "request": get_data_from_request(self.request) + }, + handled=False, # indicate that this exception bubbled all the way up to the user + ) + + def capture_message(self, message_error): + apm_elastic = self.settings.get("apm_elastic") + apm_elastic.client.capture_message(message_error) + + def get_url(self): + url = None + for router in self.application.wildcard_router.rules: + if router.target == self.__class__: + url = router.matcher._path + break + return url.replace("%s", "") + + def write_error(self, status_code, **kwargs): + self.capture_exception() + super(ApiElasticHandlerAPM, self).write_error(status_code, **kwargs) + + def prepare(self): + apm_elastic = self.settings.get("apm_elastic") + apm_elastic.client.begin_transaction("request") + + def on_finish(self): + apm_elastic = self.settings.get("apm_elastic") + name_trasaction = '{} {}'.format(self.request.method, self.get_url()) + status = self.get_status() + result = 'HTTP {}xx'.format(status // 100) + data_request = get_data_from_request(self.request) + data_response = get_data_from_response(self) + elasticapm.set_context(lambda: data_request, "request") + elasticapm.set_context(lambda: data_response, "response") + elasticapm.set_transaction_name(name_trasaction, override=False) + elasticapm.set_transaction_result(result, override=False) + apm_elastic.client.end_transaction() diff --git a/elasticapm/contrib/tornado/utils.py b/elasticapm/contrib/tornado/utils.py index e69de29bb..4f3ae2cd2 100644 --- a/elasticapm/contrib/tornado/utils.py +++ b/elasticapm/contrib/tornado/utils.py @@ -0,0 +1,26 @@ +from elasticapm.utils import get_url_dict + + +def get_data_from_request(request): + url = '{}://{}{}'.format(request.protocol, request.host, request.uri) + data = { + "headers": dict(**request.headers), + "method": request.method, + "socket": { + "remote_address": request.remote_ip, + "encrypted": request.protocol == 'https' + }, + "cookies": dict(**request.cookies), + "url": get_url_dict(url), + "body": request.body + } + + data["headers"].pop("Cookie", None) + return data + + +def get_data_from_response(response): + data = {"status_code": response.get_status()} + if response._headers: + data["headers"] = response._headers._dict + return data From e0022797dcf49144294bbf1a17e64b9cb1712090 Mon Sep 17 00:00:00 2001 From: Laerte Allan de Oliveira Araujo Date: Thu, 18 Oct 2018 14:41:07 -0300 Subject: [PATCH 03/14] Refactor in name class and in function Was refactored of name class and in function --- elasticapm/contrib/tornado/__init__.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/elasticapm/contrib/tornado/__init__.py b/elasticapm/contrib/tornado/__init__.py index 45bc00a2f..28d9f8c90 100644 --- a/elasticapm/contrib/tornado/__init__.py +++ b/elasticapm/contrib/tornado/__init__.py @@ -17,14 +17,17 @@ def make_client(client_cls, app, **defaults): return client -class ElasticAPM(object): +class TornadoApm(object): - def __init__(self, app_tornado=None, client=None, client_cls=Client): - if not app_tornado: + def __validate_app(self, app): + if not app: raise Exception("Handle tornado invalid") + + def __init__(self, aplication=None, client=None, client_cls=Client): + self.__validate_app(aplication) self.client = client self.client_cls = client_cls - self.__init_app(app_tornado) + self.__init_app(aplication) def __init_app(self, app, **defaults): self.app = app @@ -52,7 +55,7 @@ def capture_exception(self): context={ "request": get_data_from_request(self.request) }, - handled=False, # indicate that this exception bubbled all the way up to the user + handled=False, ) def capture_message(self, message_error): @@ -69,7 +72,6 @@ def get_url(self): def write_error(self, status_code, **kwargs): self.capture_exception() - super(ApiElasticHandlerAPM, self).write_error(status_code, **kwargs) def prepare(self): apm_elastic = self.settings.get("apm_elastic") From 4f45af36cb1321e41c1cc3fca4b7a9db2113f433 Mon Sep 17 00:00:00 2001 From: Laerte Allan de Oliveira Araujo Date: Thu, 18 Oct 2018 14:44:05 -0300 Subject: [PATCH 04/14] Added unittest Was added unittests --- tests/contrib/tornado/__init__.py | 5 ++ .../tornado/test_api_handler_elastic.py | 76 +++++++++++++++++++ .../tornado/test_elastic_tornado_apm.py | 27 +++++++ tests/contrib/tornado/tornado_tests.py | 0 4 files changed, 108 insertions(+) create mode 100644 tests/contrib/tornado/test_api_handler_elastic.py create mode 100644 tests/contrib/tornado/test_elastic_tornado_apm.py delete mode 100644 tests/contrib/tornado/tornado_tests.py diff --git a/tests/contrib/tornado/__init__.py b/tests/contrib/tornado/__init__.py index e69de29bb..63dce094e 100644 --- a/tests/contrib/tornado/__init__.py +++ b/tests/contrib/tornado/__init__.py @@ -0,0 +1,5 @@ +from unittest import TestCase + + +class BaseTestClass(TestCase): + pass diff --git a/tests/contrib/tornado/test_api_handler_elastic.py b/tests/contrib/tornado/test_api_handler_elastic.py new file mode 100644 index 000000000..b708ed481 --- /dev/null +++ b/tests/contrib/tornado/test_api_handler_elastic.py @@ -0,0 +1,76 @@ +import mock + +from elasticapm.contrib.tornado import ApiElasticHandlerAPM +from tests.contrib.tornado import BaseTestClass + + +class MockMatcher: + _path = "test/%s/extref" + + +class MockWillCardRouter: + target = "" + matcher = MockMatcher() + + +class TestApiElasticHandlerAPM(BaseTestClass): + + def test_capture_exception(self): + application = mock.MagicMock() + request = mock.MagicMock() + client = mock.MagicMock() + application().settings.get.return_value = client + handler = ApiElasticHandlerAPM(application, request) + handler.capture_exception() + self.assertTrue(application.called) + + def test_capture_message(self): + application = mock.MagicMock() + client = mock.MagicMock() + application().settings.get.return_value = client + request = mock.MagicMock() + handler = ApiElasticHandlerAPM(application, request) + message = "error" + handler.capture_message(message) + self.assertTrue(application.called) + + def test_write_error(self): + application = mock.MagicMock() + client = mock.MagicMock() + application().settings.get.return_value = client + request = mock.MagicMock() + handler = ApiElasticHandlerAPM(application, request) + handler.write_error(status_code=400) + self.assertTrue(application.called) + + def test_prepare(self): + application = mock.MagicMock() + client = mock.MagicMock() + application().settings.get.return_value = client + request = mock.MagicMock() + handler = ApiElasticHandlerAPM(application, request) + handler.prepare() + self.assertTrue(application.called) + + def test_get_url(self): + application = mock.MagicMock() + request = mock.MagicMock() + handler = ApiElasticHandlerAPM(application, request) + mock_will = MockWillCardRouter() + mock_will.target = handler.__class__ + application.wildcard_router.rules = [mock_will] + url = handler.get_url() + self.assertEqual(url, 'test//extref') + + @mock.patch("elasticapm.contrib.tornado.elasticapm") + def test_on_finish(self, mock_elastic): + application = mock.MagicMock() + client = mock.MagicMock() + application().settings.get.return_value = client + request = mock.MagicMock() + handler = ApiElasticHandlerAPM(application, request) + handler.get_url = mock.Mock() + handler.on_finish() + self.assertTrue(application.called) + self.assertTrue(handler.get_url.called) + self.assertTrue(mock_elastic.set_context.called) diff --git a/tests/contrib/tornado/test_elastic_tornado_apm.py b/tests/contrib/tornado/test_elastic_tornado_apm.py new file mode 100644 index 000000000..ff681a265 --- /dev/null +++ b/tests/contrib/tornado/test_elastic_tornado_apm.py @@ -0,0 +1,27 @@ +import mock + +from src.tornado_elastic import TornadoApm +from tests import BaseTestClass + + +class TestTornadoAPM(BaseTestClass): + + def test_app_tornado_invalid(self): + with self.assertRaises(Exception): + app = None + TornadoApm(app) + + @mock.patch("elasticapm.base.Client.capture_message") + def test_capture_message(self, mock_client): + app = mock.MagicMock() + apm_tornado = TornadoApm(app) + message_error = "Error" + apm_tornado.capture_message(message_error) + self.assertTrue(mock_client.called) + + @mock.patch("elasticapm.base.Client.capture_exception") + def test_capture_exception(self, mock_client): + app = mock.MagicMock() + apm_tornado = TornadoApm(app) + apm_tornado.capture_exception() + self.assertTrue(mock_client.called) diff --git a/tests/contrib/tornado/tornado_tests.py b/tests/contrib/tornado/tornado_tests.py deleted file mode 100644 index e69de29bb..000000000 From 5033c23fb529a2e27d2b48762b75b06cc1a2cad6 Mon Sep 17 00:00:00 2001 From: Laerte Allan de Oliveira Araujo Date: Mon, 22 Oct 2018 12:09:07 -0200 Subject: [PATCH 05/14] Added file configuration tornado framework Was added file configuration of tornado same with flask --- docs/tornado.asciidoc | 87 ++++++++++++++++++++++++++ tests/contrib/tornado/tornado_tests.py | 0 2 files changed, 87 insertions(+) create mode 100644 docs/tornado.asciidoc create mode 100644 tests/contrib/tornado/tornado_tests.py diff --git a/docs/tornado.asciidoc b/docs/tornado.asciidoc new file mode 100644 index 000000000..33683b35d --- /dev/null +++ b/docs/tornado.asciidoc @@ -0,0 +1,87 @@ +[[tornado-support]] +== Tornado support + +Getting Elastic APM set up for your Tornado project is easy, +and there are various ways you can tweak it to fit to your needs. + +This configuration is much same with flask-apm + +[float] +[[Tornado-installation]] +=== Installation + +Install the Elastic APM agent using pip: + +[source,bash] +---- +$ pip install elastic-apm +---- + + +[float] +[[tornado-setup]] +=== Setup + +To set up the agent, you need to initialize it with appropriate settings. + +The settings are configured either via environment variables, +the application's settings, or as initialization arguments. + +You can find a list of all available settings in the <> page. + +Below an example of as configure the apm with tornado framework. + +[source,python] +---- +from elasticapm.contrib.tornado import TornadoApm, ApiElasticHandlerAPM +import tornado +import tornado.ioloop +import tornado.web +from tornado.httpserver import HTTPServer + +class MainTest1(ApiElasticHandlerAPM): + + def get(self, *args, **kwargs): + self.write({'status': 'ok'}) + self.finish() + + +class MainTest2(ApiElasticHandlerAPM): + + def get(self): + raise Exception("Value Error") + + def post(self): + try: + raise Exception("erro message") + except Exception as error: + # This error(Personalized) captured an send elastic + self.capture_message("personalized error test") + self.set_status(500) + self.write("Internal Server Error") + self.finish() + + +def make_app(): + settings = { + 'ELASTIC_APM': + { + "SERVICE_NAME": "Teste tornado", + "SECRET_TOKEN": "", + "Debug": False}, + "compress_response": True, + } + application = tornado.web.Application([ + (r"/", MainTest1), + (r"/error", MainTest2), + ], **settings) + TornadoApm(application) + return application + + +if __name__ == "__main__": + app = make_app() + server = HTTPServer(app) + server.bind(8888) + server.start(1) + tornado.ioloop.IOLoop.current().start() diff --git a/tests/contrib/tornado/tornado_tests.py b/tests/contrib/tornado/tornado_tests.py new file mode 100644 index 000000000..e69de29bb From 32d82e5a72a47c44bf2fd3652c7622c7af3dce70 Mon Sep 17 00:00:00 2001 From: Laerte Allan de Oliveira Araujo Date: Tue, 6 Nov 2018 16:42:28 -0200 Subject: [PATCH 06/14] Added params from async process Was added paras from async process. Added duration from async process. --- .../packages/tornado_httplib_response.py | 52 +++++++++++++++++++ elasticapm/traces.py | 21 ++++++-- 2 files changed, 68 insertions(+), 5 deletions(-) create mode 100644 elasticapm/instrumentation/packages/tornado_httplib_response.py diff --git a/elasticapm/instrumentation/packages/tornado_httplib_response.py b/elasticapm/instrumentation/packages/tornado_httplib_response.py new file mode 100644 index 000000000..767e0dd0e --- /dev/null +++ b/elasticapm/instrumentation/packages/tornado_httplib_response.py @@ -0,0 +1,52 @@ +from elasticapm.instrumentation.packages.base import AbstractInstrumentedModule +from elasticapm.traces import capture_span +from elasticapm.utils import default_ports +from elasticapm.utils.compat import urlparse + + +def get_host_from_url(url): + parsed_url = urlparse.urlparse(url) + host = parsed_url.hostname or " " + + if parsed_url.port and default_ports.get(parsed_url.scheme) != parsed_url.port: + host += ":" + str(parsed_url.port) + + return host + + +class HttpClientTornadoInstrumentation(AbstractInstrumentedModule): + name = "tornado" + + instrument_list = [("tornado.httpclient", "HTTPResponse")] + + def call(self, module, method, wrapped, instance, args, kwargs): + + http_request_proxy = args[0] + url = http_request_proxy.url + duration = kwargs.get('request_time', 0) + start_time = kwargs.get('start_time', 0) + print("Inicio da requisicao") + signature = "{} {}".format(http_request_proxy.method.upper(), get_host_from_url(http_request_proxy.url)) + print("start time tornado") + with capture_span(signature, "ext.http.tornado", {"url": url}, leaf=True, start_time=start_time, + duration=duration): + print("Tornado test") + teste = wrapped(*args, **kwargs) + return teste + + # return wrapped(*args, **kwargs) + # http_request = kwargs.get("url", None) + # kwargs__http = vars(http_request) + # del kwargs__http['_body'] + # del kwargs__http['_headers'] + # del kwargs__http['_body_producer'] + # del kwargs__http['_streaming_callback'] + # del kwargs__http['_header_callback'] + # del kwargs__http['_prepare_curl_callback'] + # del kwargs__http['start_time'] + # url = http_request.url + # signature = "{} {}".format(http_request.method.upper(), get_host_from_url(http_request.url)) + # + # with capture_span(signature, "ext.http.tornado", {"url": url}, leaf=True): + # return wrapped(*args, **kwargs__http) + diff --git a/elasticapm/traces.py b/elasticapm/traces.py index e39c1dbe8..7e71466a8 100644 --- a/elasticapm/traces.py +++ b/elasticapm/traces.py @@ -74,7 +74,7 @@ def __init__( def end_transaction(self, skip_frames=8): self.duration = _time_func() - self.start_time - def begin_span(self, name, span_type, context=None, leaf=False): + def begin_span(self, name, span_type, context=None, leaf=False, async_process=False): # If we were already called with `leaf=True`, we'll just push # a placeholder on the stack. if self.ignore_subtree: @@ -92,11 +92,16 @@ def begin_span(self, name, span_type, context=None, leaf=False): return None start = _time_func() - self.start_time + if async_process: + start = 0 + if self.spans: + span = self.spans[len(self.spans) - 1] + start = span.start_time + span.duration span = Span(self._span_counter - 1, name, span_type, start, context) self.span_stack.append(span) return span - def end_span(self, skip_frames): + def end_span(self, skip_frames, duration=None): span = self.span_stack.pop() if span is IGNORED_SPAN: return None @@ -107,6 +112,8 @@ def end_span(self, skip_frames): return span.duration = _time_func() - span.start_time - self.start_time + if duration: + span.duration = duration if self.span_stack: span.parent = self.span_stack[-1].idx @@ -263,12 +270,15 @@ def end_transaction(self, result=None, transaction_name=None): class capture_span(object): - def __init__(self, name=None, span_type="code.custom", extra=None, skip_frames=0, leaf=False): + def __init__(self, name=None, span_type="code.custom", extra=None, skip_frames=0, leaf=False, + async_process=None, duration=None): self.name = name self.type = span_type self.extra = extra self.skip_frames = skip_frames self.leaf = leaf + self.duration = duration + self.async_process = async_process def __call__(self, func): self.name = self.name or get_name_from_func(func) @@ -283,13 +293,14 @@ def decorated(*args, **kwds): def __enter__(self): transaction = get_transaction() if transaction and transaction.is_sampled: - transaction.begin_span(self.name, self.type, context=self.extra, leaf=self.leaf) + transaction.begin_span(self.name, self.type, context=self.extra, + leaf=self.leaf, async_process=self.async_process) def __exit__(self, exc_type, exc_val, exc_tb): transaction = get_transaction() if transaction and transaction.is_sampled: try: - transaction.end_span(self.skip_frames) + transaction.end_span(self.skip_frames, duration=self.duration) except IndexError: error_logger.info("ended non-existing span %s of type %s", self.name, self.type) From c9ebd620c1ea4d7eb7b9cd9b51ee090b6fb90cb5 Mon Sep 17 00:00:00 2001 From: Laerte Allan de Oliveira Araujo Date: Tue, 6 Nov 2018 16:44:13 -0200 Subject: [PATCH 07/14] Added test unit to tornado Was adde unit test to framework tornado --- tests/contrib/tornado/__init__.py | 45 ++++++++++++++++++++++++-- tests/contrib/tornado/tornado_tests.py | 9 ++++++ 2 files changed, 51 insertions(+), 3 deletions(-) diff --git a/tests/contrib/tornado/__init__.py b/tests/contrib/tornado/__init__.py index 63dce094e..e31343207 100644 --- a/tests/contrib/tornado/__init__.py +++ b/tests/contrib/tornado/__init__.py @@ -1,5 +1,44 @@ -from unittest import TestCase +import tornado +from tornado.testing import AsyncHTTPTestCase +from elasticapm.contrib.tornado import ApiElasticHandlerAPM, TornadoApm -class BaseTestClass(TestCase): - pass + +class MainTest1(ApiElasticHandlerAPM): + + def get(self, *args, **kwargs): + self.write({'status': 'ok'}) + self.finish() + + +class MainTest2(ApiElasticHandlerAPM): + + def __raise_exception(self): + raise Exception("Value Error") + + def get(self): + self.__raise_exception() + + def post(self): + self.__raise_exception() + + +def make_app(): + settings = {'ELASTIC_APM': + {'SERVICE_NAME': 'Teste tornado', + 'SECRET_TOKEN': '', + "Debug": False}, + "compress_response": True, + } + application = tornado.web.Application([ + (r"/", MainTest1), + (r"/error", MainTest2), + ], **settings) + TornadoApm(application) + return application + + +class BaseTestClassTornado(AsyncHTTPTestCase): + + def get_app(self): + return make_app() diff --git a/tests/contrib/tornado/tornado_tests.py b/tests/contrib/tornado/tornado_tests.py index e69de29bb..31f989b27 100644 --- a/tests/contrib/tornado/tornado_tests.py +++ b/tests/contrib/tornado/tornado_tests.py @@ -0,0 +1,9 @@ +from tests.contrib.tornado import BaseTestClassTornado + + +class TestApiMcafee(BaseTestClassTornado): + + def test_error_handler(self): + url = "/error" + response = self.fetch(url, method='GET') + print(response) From 62e494fa032e69bf4999cbc0efb30b80093ad5ba Mon Sep 17 00:00:00 2001 From: Laerte Allan de Oliveira Araujo Date: Tue, 13 Nov 2018 10:53:45 -0200 Subject: [PATCH 08/14] Added Test Was added test for codes changes --- tests/contrib/tornado/__init__.py | 6 ++++++ tests/contrib/tornado/test_api_handler_elastic.py | 7 +++++-- tests/contrib/tornado/test_elastic_tornado_apm.py | 4 ++-- 3 files changed, 13 insertions(+), 4 deletions(-) diff --git a/tests/contrib/tornado/__init__.py b/tests/contrib/tornado/__init__.py index e31343207..08e0aa177 100644 --- a/tests/contrib/tornado/__init__.py +++ b/tests/contrib/tornado/__init__.py @@ -1,3 +1,5 @@ +from unittest import TestCase + import tornado from tornado.testing import AsyncHTTPTestCase @@ -38,6 +40,10 @@ def make_app(): return application +class BaseTestClass(TestCase): + pass + + class BaseTestClassTornado(AsyncHTTPTestCase): def get_app(self): diff --git a/tests/contrib/tornado/test_api_handler_elastic.py b/tests/contrib/tornado/test_api_handler_elastic.py index b708ed481..4c1d0f7e6 100644 --- a/tests/contrib/tornado/test_api_handler_elastic.py +++ b/tests/contrib/tornado/test_api_handler_elastic.py @@ -5,7 +5,8 @@ class MockMatcher: - _path = "test/%s/extref" + _path = "test/%s/extref/%s" + regex = mock.MagicMock() class MockWillCardRouter: @@ -57,10 +58,12 @@ def test_get_url(self): request = mock.MagicMock() handler = ApiElasticHandlerAPM(application, request) mock_will = MockWillCardRouter() + mock_will.matcher.regex.groupindex.values.return_value = [1, 2] + mock_will.matcher.regex.groupindex.items.return_value = [('operator', 1), ('extref', 2)] mock_will.target = handler.__class__ application.wildcard_router.rules = [mock_will] url = handler.get_url() - self.assertEqual(url, 'test//extref') + self.assertEqual(url, 'test/:operator/extref/:extref') @mock.patch("elasticapm.contrib.tornado.elasticapm") def test_on_finish(self, mock_elastic): diff --git a/tests/contrib/tornado/test_elastic_tornado_apm.py b/tests/contrib/tornado/test_elastic_tornado_apm.py index ff681a265..0bb4dd84f 100644 --- a/tests/contrib/tornado/test_elastic_tornado_apm.py +++ b/tests/contrib/tornado/test_elastic_tornado_apm.py @@ -1,7 +1,7 @@ import mock -from src.tornado_elastic import TornadoApm -from tests import BaseTestClass +from elasticapm.contrib.tornado import TornadoApm +from tests.contrib.tornado import BaseTestClass class TestTornadoAPM(BaseTestClass): From 6b2bc7c4769d1b244d9348395c02c631a5228f41 Mon Sep 17 00:00:00 2001 From: Laerte Allan de Oliveira Araujo Date: Tue, 13 Nov 2018 11:36:42 -0200 Subject: [PATCH 09/14] Removed changes from other httresponse Was moved the httresponse --- .../packages/tornado_httplib_response.py | 52 ------------------- elasticapm/traces.py | 21 ++------ 2 files changed, 5 insertions(+), 68 deletions(-) delete mode 100644 elasticapm/instrumentation/packages/tornado_httplib_response.py diff --git a/elasticapm/instrumentation/packages/tornado_httplib_response.py b/elasticapm/instrumentation/packages/tornado_httplib_response.py deleted file mode 100644 index 767e0dd0e..000000000 --- a/elasticapm/instrumentation/packages/tornado_httplib_response.py +++ /dev/null @@ -1,52 +0,0 @@ -from elasticapm.instrumentation.packages.base import AbstractInstrumentedModule -from elasticapm.traces import capture_span -from elasticapm.utils import default_ports -from elasticapm.utils.compat import urlparse - - -def get_host_from_url(url): - parsed_url = urlparse.urlparse(url) - host = parsed_url.hostname or " " - - if parsed_url.port and default_ports.get(parsed_url.scheme) != parsed_url.port: - host += ":" + str(parsed_url.port) - - return host - - -class HttpClientTornadoInstrumentation(AbstractInstrumentedModule): - name = "tornado" - - instrument_list = [("tornado.httpclient", "HTTPResponse")] - - def call(self, module, method, wrapped, instance, args, kwargs): - - http_request_proxy = args[0] - url = http_request_proxy.url - duration = kwargs.get('request_time', 0) - start_time = kwargs.get('start_time', 0) - print("Inicio da requisicao") - signature = "{} {}".format(http_request_proxy.method.upper(), get_host_from_url(http_request_proxy.url)) - print("start time tornado") - with capture_span(signature, "ext.http.tornado", {"url": url}, leaf=True, start_time=start_time, - duration=duration): - print("Tornado test") - teste = wrapped(*args, **kwargs) - return teste - - # return wrapped(*args, **kwargs) - # http_request = kwargs.get("url", None) - # kwargs__http = vars(http_request) - # del kwargs__http['_body'] - # del kwargs__http['_headers'] - # del kwargs__http['_body_producer'] - # del kwargs__http['_streaming_callback'] - # del kwargs__http['_header_callback'] - # del kwargs__http['_prepare_curl_callback'] - # del kwargs__http['start_time'] - # url = http_request.url - # signature = "{} {}".format(http_request.method.upper(), get_host_from_url(http_request.url)) - # - # with capture_span(signature, "ext.http.tornado", {"url": url}, leaf=True): - # return wrapped(*args, **kwargs__http) - diff --git a/elasticapm/traces.py b/elasticapm/traces.py index 7e71466a8..e39c1dbe8 100644 --- a/elasticapm/traces.py +++ b/elasticapm/traces.py @@ -74,7 +74,7 @@ def __init__( def end_transaction(self, skip_frames=8): self.duration = _time_func() - self.start_time - def begin_span(self, name, span_type, context=None, leaf=False, async_process=False): + def begin_span(self, name, span_type, context=None, leaf=False): # If we were already called with `leaf=True`, we'll just push # a placeholder on the stack. if self.ignore_subtree: @@ -92,16 +92,11 @@ def begin_span(self, name, span_type, context=None, leaf=False, async_process=Fa return None start = _time_func() - self.start_time - if async_process: - start = 0 - if self.spans: - span = self.spans[len(self.spans) - 1] - start = span.start_time + span.duration span = Span(self._span_counter - 1, name, span_type, start, context) self.span_stack.append(span) return span - def end_span(self, skip_frames, duration=None): + def end_span(self, skip_frames): span = self.span_stack.pop() if span is IGNORED_SPAN: return None @@ -112,8 +107,6 @@ def end_span(self, skip_frames, duration=None): return span.duration = _time_func() - span.start_time - self.start_time - if duration: - span.duration = duration if self.span_stack: span.parent = self.span_stack[-1].idx @@ -270,15 +263,12 @@ def end_transaction(self, result=None, transaction_name=None): class capture_span(object): - def __init__(self, name=None, span_type="code.custom", extra=None, skip_frames=0, leaf=False, - async_process=None, duration=None): + def __init__(self, name=None, span_type="code.custom", extra=None, skip_frames=0, leaf=False): self.name = name self.type = span_type self.extra = extra self.skip_frames = skip_frames self.leaf = leaf - self.duration = duration - self.async_process = async_process def __call__(self, func): self.name = self.name or get_name_from_func(func) @@ -293,14 +283,13 @@ def decorated(*args, **kwds): def __enter__(self): transaction = get_transaction() if transaction and transaction.is_sampled: - transaction.begin_span(self.name, self.type, context=self.extra, - leaf=self.leaf, async_process=self.async_process) + transaction.begin_span(self.name, self.type, context=self.extra, leaf=self.leaf) def __exit__(self, exc_type, exc_val, exc_tb): transaction = get_transaction() if transaction and transaction.is_sampled: try: - transaction.end_span(self.skip_frames, duration=self.duration) + transaction.end_span(self.skip_frames) except IndexError: error_logger.info("ended non-existing span %s of type %s", self.name, self.type) From f81841c20ceea46d03d5ffb4ecd1ab6c605f0b6c Mon Sep 17 00:00:00 2001 From: Laerte Allan de Oliveira Araujo Date: Tue, 13 Nov 2018 14:17:44 -0200 Subject: [PATCH 10/14] Added new fucntion Was added a new fucntion to parser url --- elasticapm/contrib/tornado/__init__.py | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/elasticapm/contrib/tornado/__init__.py b/elasticapm/contrib/tornado/__init__.py index 28d9f8c90..4ed394643 100644 --- a/elasticapm/contrib/tornado/__init__.py +++ b/elasticapm/contrib/tornado/__init__.py @@ -3,7 +3,7 @@ import elasticapm from elasticapm.base import Client -from elasticapm.contrib.tornado.utils import get_data_from_response, get_data_from_request +from elasticapm.contrib.tornado.utils import get_data_from_request, get_data_from_response def make_client(client_cls, app, **defaults): @@ -49,6 +49,18 @@ def capture_message(self, *args, **kwargs): class ApiElasticHandlerAPM(RequestHandler): + @staticmethod + def __parser_url(router): + group_index = sorted(list(router.matcher.regex.groupindex.values())) + tuple_url = () + url = getattr(router.matcher, '_path') + for param in group_index: + for key, value in router.matcher.regex.groupindex.items(): + if value == param: + tuple_url += (":" + key,) + break + return url % tuple_url + def capture_exception(self): apm_elastic = self.settings.get("apm_elastic") apm_elastic.client.capture_exception( @@ -66,9 +78,9 @@ def get_url(self): url = None for router in self.application.wildcard_router.rules: if router.target == self.__class__: - url = router.matcher._path + url = self.__parser_url(router) break - return url.replace("%s", "") + return url def write_error(self, status_code, **kwargs): self.capture_exception() From 7669d8ef68e3eef0f1da8ec4d505815db51bc316 Mon Sep 17 00:00:00 2001 From: Laerte Allan de Oliveira Araujo Date: Tue, 13 Nov 2018 14:28:13 -0200 Subject: [PATCH 11/14] Added the version from installed of Tornado Was added the version from a version of installed tornado --- docs/tornado.asciidoc | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/tornado.asciidoc b/docs/tornado.asciidoc index 33683b35d..920f11cfb 100644 --- a/docs/tornado.asciidoc +++ b/docs/tornado.asciidoc @@ -12,9 +12,12 @@ This configuration is much same with flask-apm Install the Elastic APM agent using pip: +>> Attention the tornado version should be> = 4.5 + [source,bash] ---- $ pip install elastic-apm +$ pip install tornado ---- From 0f041edd1892361fcff80276c72a8dbe44dee75d Mon Sep 17 00:00:00 2001 From: Laerte Allan de Oliveira Araujo Date: Tue, 13 Nov 2018 14:28:13 -0200 Subject: [PATCH 12/14] Added the version from installed of Tornado Was added the version from a version of installed tornado --- docs/tornado.asciidoc | 3 +++ elasticapm/contrib/tornado/__init__.py | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/docs/tornado.asciidoc b/docs/tornado.asciidoc index 33683b35d..920f11cfb 100644 --- a/docs/tornado.asciidoc +++ b/docs/tornado.asciidoc @@ -12,9 +12,12 @@ This configuration is much same with flask-apm Install the Elastic APM agent using pip: +>> Attention the tornado version should be> = 4.5 + [source,bash] ---- $ pip install elastic-apm +$ pip install tornado ---- diff --git a/elasticapm/contrib/tornado/__init__.py b/elasticapm/contrib/tornado/__init__.py index 4ed394643..80829a371 100644 --- a/elasticapm/contrib/tornado/__init__.py +++ b/elasticapm/contrib/tornado/__init__.py @@ -11,7 +11,7 @@ def make_client(client_cls, app, **defaults): if 'framework_name' not in defaults: defaults['framework_name'] = 'tornado' - defaults['framework_version'] = getattr(tornado, '__version__', '<0.7') + defaults['framework_version'] = getattr(tornado, 'version', ">=4.5") client = client_cls(config, **defaults) return client From 879350f30ea6e9e86fefa7ea89e5cb162f6ad729 Mon Sep 17 00:00:00 2001 From: Laerte Allan de Oliveira Araujo Date: Tue, 13 Nov 2018 15:45:54 -0200 Subject: [PATCH 13/14] Added file requirements Added File requirements --- tests/requirements/requirements-tornado-4.5.txt | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 tests/requirements/requirements-tornado-4.5.txt diff --git a/tests/requirements/requirements-tornado-4.5.txt b/tests/requirements/requirements-tornado-4.5.txt new file mode 100644 index 000000000..59a564323 --- /dev/null +++ b/tests/requirements/requirements-tornado-4.5.txt @@ -0,0 +1,2 @@ +tornado>=4.5,<5.1 +-r requirements-base.txt From abcaf21003a4f6b5b311b9650a7261323c56ae83 Mon Sep 17 00:00:00 2001 From: Laerte Allan de Oliveira Araujo Date: Tue, 13 Nov 2018 16:20:56 -0200 Subject: [PATCH 14/14] Add settings CI Was adde settings CI --- .travis.yml | 2 ++ tests/.jenkins_exclude.yml | 3 +++ tests/.jenkins_framework.yml | 2 ++ tests/.jenkins_framework_full.yml | 2 ++ tests/requirements/requirements-tornado-master.txt | 2 ++ 5 files changed, 11 insertions(+) create mode 100644 tests/requirements/requirements-tornado-master.txt diff --git a/.travis.yml b/.travis.yml index 810b3d713..629ea43f8 100644 --- a/.travis.yml +++ b/.travis.yml @@ -14,6 +14,7 @@ env: matrix: - WEBFRAMEWORK=django-master - WEBFRAMEWORK=flask-master + - WEBFRAMEWORK=tornado-master matrix: exclude: @@ -21,6 +22,7 @@ matrix: - env: - WEBFRAMEWORK=django-master - WEBFRAMEWORK=flask-master + - WEBFRAMEWORK=tornado-master - python: nightly include: - stage: linters diff --git a/tests/.jenkins_exclude.yml b/tests/.jenkins_exclude.yml index ae8638129..63a62d749 100644 --- a/tests/.jenkins_exclude.yml +++ b/tests/.jenkins_exclude.yml @@ -21,6 +21,9 @@ exclude: # Flask - PYTHON_VERSION: pypy-3 FRAMEWORK: flask-0.11 # see https://github.com/pallets/flask/commit/6e46d0cd, 0.11.2 was never released + #Tornado + - PYTHON_VERSION: python-2.7 + FRAMEWORK: tornado-5.1 # celery - PYTHON_VERSION: python-2.7 FRAMEWORK: celery-3-django-2.0 diff --git a/tests/.jenkins_framework.yml b/tests/.jenkins_framework.yml index fd1653f29..0dc0b4c10 100644 --- a/tests/.jenkins_framework.yml +++ b/tests/.jenkins_framework.yml @@ -6,6 +6,8 @@ FRAMEWORK: - django-2.1 - flask-0.12 - flask-1.0 + - tornado-5.1 + - tornado-4.5 - twisted-18 - celery-3-flask-1.0 - celery-3-django-2.0 diff --git a/tests/.jenkins_framework_full.yml b/tests/.jenkins_framework_full.yml index cccfcbe39..900f69ac5 100644 --- a/tests/.jenkins_framework_full.yml +++ b/tests/.jenkins_framework_full.yml @@ -8,6 +8,8 @@ FRAMEWORK: - django-2.0 - django-2.1 # - django-master + - tornado-5.1 + - tornado-4.5 - flask-0.10 - flask-0.11 - flask-0.12 diff --git a/tests/requirements/requirements-tornado-master.txt b/tests/requirements/requirements-tornado-master.txt new file mode 100644 index 000000000..9fcef4436 --- /dev/null +++ b/tests/requirements/requirements-tornado-master.txt @@ -0,0 +1,2 @@ +https://github.com/tornadoweb/tornado/archive/master.zip#egg=tornado==100 +-r requirements-base.txt