From 18f8195983203c4ddc3753f0a9018a262ec0e476 Mon Sep 17 00:00:00 2001 From: Victor Martinez Date: Thu, 14 Oct 2021 20:19:02 +0100 Subject: [PATCH] [callback][elastic] enrich stacktrace errors (#3556) * [callback][elastic] enrich stacktrace errors * [callback][elastic] add changelog fragment --- ...56-callback_elastic-enrich_stacktraces.yml | 2 ++ plugins/callback/elastic.py | 29 +++++++++++---- tests/unit/plugins/callback/test_elastic.py | 35 +++++++++++++++++++ 3 files changed, 59 insertions(+), 7 deletions(-) create mode 100644 changelogs/fragments/3556-callback_elastic-enrich_stacktraces.yml diff --git a/changelogs/fragments/3556-callback_elastic-enrich_stacktraces.yml b/changelogs/fragments/3556-callback_elastic-enrich_stacktraces.yml new file mode 100644 index 0000000000..3b13e9680a --- /dev/null +++ b/changelogs/fragments/3556-callback_elastic-enrich_stacktraces.yml @@ -0,0 +1,2 @@ +minor_changes: + - elastic callback plugin - enriched the stacktrace information with the ``message``, ``exception`` and ``stderr`` fields from the failed task (https://github.com/ansible-collections/community.general/pull/3556). diff --git a/plugins/callback/elastic.py b/plugins/callback/elastic.py index 188f168f9b..095c0993ca 100644 --- a/plugins/callback/elastic.py +++ b/plugins/callback/elastic.py @@ -226,18 +226,15 @@ class ElasticSource(object): message = "success" status = "success" + enriched_error_message = None if host_data.status == 'included': rc = 0 else: res = host_data.result._result rc = res.get('rc', 0) if host_data.status == 'failed': - if res.get('exception') is not None: - message = res['exception'].strip().split('\n')[-1] - elif 'msg' in res: - message = res['msg'] - else: - message = 'failed' + message = self.get_error_message(res) + enriched_error_message = self.enrich_error_message(res) status = "failure" elif host_data.status == 'skipped': if 'skip_reason' in res: @@ -259,7 +256,7 @@ class ElasticSource(object): "ansible.task.host.status": host_data.status}) as span: span.outcome = status if 'failure' in status: - exception = AnsibleRuntimeError(message="{0}: {1} failed with error message {2}".format(task_data.action, name, message)) + exception = AnsibleRuntimeError(message="{0}: {1} failed with error message {2}".format(task_data.action, name, enriched_error_message)) apm_cli.capture_exception(exc_info=(type(exception), exception, exception.__traceback__), handled=True) def init_apm_client(self, apm_server_url, apm_service_name, apm_verify_server_cert, apm_secret_token, apm_api_key): @@ -272,6 +269,24 @@ class ElasticSource(object): use_elastic_traceparent_header=True, debug=True) + @staticmethod + def get_error_message(result): + if result.get('exception') is not None: + return ElasticSource._last_line(result['exception']) + return result.get('msg', 'failed') + + @staticmethod + def _last_line(text): + lines = text.strip().split('\n') + return lines[-1] + + @staticmethod + def enrich_error_message(result): + message = result.get('msg', 'failed') + exception = result.get('exception') + stderr = result.get('stderr') + return ('message: "{0}"\nexception: "{1}"\nstderr: "{2}"').format(message, exception, stderr) + class CallbackModule(CallbackBase): """ diff --git a/tests/unit/plugins/callback/test_elastic.py b/tests/unit/plugins/callback/test_elastic.py index cba41bd546..8a50da038f 100644 --- a/tests/unit/plugins/callback/test_elastic.py +++ b/tests/unit/plugins/callback/test_elastic.py @@ -89,3 +89,38 @@ class TestOpentelemetry(unittest.TestCase): self.assertEqual(host_data.uuid, 'include') self.assertEqual(host_data.name, 'include') self.assertEqual(host_data.status, 'ok') + + def test_get_error_message(self): + test_cases = ( + ('my-exception', 'my-msg', None, 'my-exception'), + (None, 'my-msg', None, 'my-msg'), + (None, None, None, 'failed'), + ) + + for tc in test_cases: + result = self.elastic.get_error_message(generate_test_data(tc[0], tc[1], tc[2])) + self.assertEqual(result, tc[3]) + + def test_enrich_error_message(self): + test_cases = ( + ('my-exception', 'my-msg', 'my-stderr', 'message: "my-msg"\nexception: "my-exception"\nstderr: "my-stderr"'), + ('my-exception', None, 'my-stderr', 'message: "failed"\nexception: "my-exception"\nstderr: "my-stderr"'), + (None, 'my-msg', 'my-stderr', 'message: "my-msg"\nexception: "None"\nstderr: "my-stderr"'), + ('my-exception', 'my-msg', None, 'message: "my-msg"\nexception: "my-exception"\nstderr: "None"'), + ('my-exception', 'my-msg', '\nline1\nline2', 'message: "my-msg"\nexception: "my-exception"\nstderr: "\nline1\nline2"') + ) + + for tc in test_cases: + result = self.elastic.enrich_error_message(generate_test_data(tc[0], tc[1], tc[2])) + self.assertEqual(result, tc[3]) + + +def generate_test_data(exception=None, msg=None, stderr=None): + res_data = OrderedDict() + if exception: + res_data['exception'] = exception + if msg: + res_data['msg'] = msg + if stderr: + res_data['stderr'] = stderr + return res_data