From b10183164927b0dbd13ceb787647c8af948fc160 Mon Sep 17 00:00:00 2001 From: Peter Giacomo Lombardo Date: Wed, 10 Jun 2020 12:01:49 +0200 Subject: [PATCH 01/12] Move secrets and extra headers to BaseAgent --- instana/agent.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/instana/agent.py b/instana/agent.py index 54638412..5057710c 100644 --- a/instana/agent.py +++ b/instana/agent.py @@ -41,6 +41,9 @@ class BaseAgent(object): """ Base class for all agent flavors """ client = requests.Session() sensor = None + secrets_matcher = 'contains-ignore-case' + secrets_list = ['key', 'password', 'secret'] + extra_headers = None def __init__(self): pass @@ -68,9 +71,6 @@ class StandardAgent(BaseAgent): last_seen = None last_fork_check = None _boot_pid = os.getpid() - extra_headers = None - secrets_matcher = 'contains-ignore-case' - secrets_list = ['key', 'password', 'secret'] should_threads_shutdown = threading.Event() def __init__(self): @@ -353,7 +353,6 @@ def __init__(self): self.options = AWSLambdaOptions() self.report_headers = None self._can_send = False - self.extra_headers = None if "INSTANA_EXTRA_HTTP_HEADERS" in os.environ: self.extra_headers = str(os.environ["INSTANA_EXTRA_HTTP_HEADERS"]).lower().split(';') From 0e36162cb047f15410f98aacd0f677dee94064ac Mon Sep 17 00:00:00 2001 From: Peter Giacomo Lombardo Date: Wed, 10 Jun 2020 15:07:00 +0200 Subject: [PATCH 02/12] Update options, support service env var --- instana/agent.py | 10 +++++----- instana/options.py | 45 ++++++++++++++++++++++++-------------------- instana/recorder.py | 4 ++-- instana/span.py | 13 +++++++------ tests/test_agent.py | 28 +++++++++++++++++++++++++++ tests/test_lambda.py | 17 +++++++++++++++++ 6 files changed, 84 insertions(+), 33 deletions(-) create mode 100644 tests/test_agent.py diff --git a/instana/agent.py b/instana/agent.py index 5057710c..51a62fc8 100644 --- a/instana/agent.py +++ b/instana/agent.py @@ -39,13 +39,15 @@ def __init__(self, **kwds): class BaseAgent(object): """ Base class for all agent flavors """ - client = requests.Session() + client = None sensor = None secrets_matcher = 'contains-ignore-case' - secrets_list = ['key', 'password', 'secret'] + secrets_list = ['key', 'pass', 'secret'] extra_headers = None + options = None def __init__(self): + self.client = requests.Session() pass @@ -353,9 +355,7 @@ def __init__(self): self.options = AWSLambdaOptions() self.report_headers = None self._can_send = False - - if "INSTANA_EXTRA_HTTP_HEADERS" in os.environ: - self.extra_headers = str(os.environ["INSTANA_EXTRA_HTTP_HEADERS"]).lower().split(';') + self.extra_headers = self.options.extra_http_headers if self._validate_options(): self._can_send = True diff --git a/instana/options.py b/instana/options.py index b9be8dba..467c413c 100644 --- a/instana/options.py +++ b/instana/options.py @@ -4,22 +4,36 @@ from .util import determine_service_name -class StandardOptions(object): - """ Configurable option bits for this package """ - service = None + +class BaseOptions(object): service_name = None - agent_host = None - agent_port = None + extra_http_headers = None log_level = logging.WARN debug = None + def __init__(self, **kwds): + try: + if "INSTANA_DEBUG" in os.environ: + self.log_level = logging.DEBUG + self.debug = True + if "INSTANA_EXTRA_HTTP_HEADERS" in os.environ: + self.extra_http_headers = str(os.environ["INSTANA_EXTRA_HTTP_HEADERS"]).lower().split(';') + except: + pass + + self.__dict__.update(kwds) + + +class StandardOptions(BaseOptions): + """ Configurable option bits for this package """ AGENT_DEFAULT_HOST = "localhost" AGENT_DEFAULT_PORT = 42699 + agent_host = None + agent_port = None + def __init__(self, **kwds): - if "INSTANA_DEBUG" in os.environ: - self.log_level = logging.DEBUG - self.debug = True + super(StandardOptions, self).__init__() self.service_name = determine_service_name() self.agent_host = os.environ.get("INSTANA_AGENT_HOST", self.AGENT_DEFAULT_HOST) @@ -28,22 +42,15 @@ def __init__(self, **kwds): if type(self.agent_port) is str: self.agent_port = int(self.agent_port) - self.debug = os.environ.get("INSTANA_DEBUG", False) - self.__dict__.update(kwds) - -class AWSLambdaOptions: +class AWSLambdaOptions(BaseOptions): endpoint_url = None agent_key = None extra_http_headers = None timeout = None - log_level = logging.WARN - debug = None def __init__(self, **kwds): - if "INSTANA_DEBUG" in os.environ: - self.log_level = logging.DEBUG - self.debug = True + super(AWSLambdaOptions, self).__init__() self.endpoint_url = os.environ.get("INSTANA_ENDPOINT_URL", None) @@ -52,9 +59,7 @@ def __init__(self, **kwds): self.endpoint_url = self.endpoint_url[:-1] self.agent_key = os.environ.get("INSTANA_AGENT_KEY", None) - - self.extra_http_headers = os.environ.get("INSTANA_EXTRA_HTTP_HEADERS", None) + self.service_name = os.environ.get("INSTANA_SERVICE_NAME", None) self.timeout = os.environ.get("INSTANA_TIMEOUT", 0.5) self.log_level = os.environ.get("INSTANA_LOG_LEVEL", None) - self.__dict__.update(kwds) diff --git a/instana/recorder.py b/instana/recorder.py index ed82aeb4..a0d41093 100644 --- a/instana/recorder.py +++ b/instana/recorder.py @@ -122,11 +122,11 @@ def record_span(self, span): Convert the passed BasicSpan and add it to the span queue """ source = self.agent.get_from_structure() + service_name = self.agent.options.service_name if span.operation_name in self.REGISTERED_SPANS: - json_span = RegisteredSpan(span, source) + json_span = RegisteredSpan(span, source, service_name) else: - service_name = self.agent.options.service_name json_span = SDKSpan(span, source, service_name) # logger.debug("Recorded span: %s", json_span) diff --git a/instana/span.py b/instana/span.py index 16c5482b..7fa8172b 100644 --- a/instana/span.py +++ b/instana/span.py @@ -129,7 +129,7 @@ def __str__(self): def __repr__(self): return self.__dict__.__str__() - def __init__(self, span, source, **kwargs): + def __init__(self, span, source, service_name, **kwargs): self.t = span.context.trace_id self.p = span.parent_id self.s = span.context.span_id @@ -138,6 +138,9 @@ def __init__(self, span, source, **kwargs): self.f = source self.ec = span.tags.pop('ec', None) + self.data = DictionaryOfStan() + self.data["service"] = service_name + if span.stack: self.stack = span.stack @@ -149,11 +152,10 @@ class SDKSpan(BaseSpan): EXIT_KIND = ["exit", "client", "producer"] def __init__(self, span, source, service_name, **kwargs): - super(SDKSpan, self).__init__(span, source, **kwargs) + super(SDKSpan, self).__init__(span, source, service_name, **kwargs) self.n = "sdk" self.k = self.get_span_kind_as_int(span) - self.data = DictionaryOfStan() self.data["sdk"]["name"] = span.operation_name self.data["sdk"]["type"] = self.get_span_kind_as_string(span) self.data["sdk"]["custom"]["tags"] = span.tags @@ -223,10 +225,9 @@ class RegisteredSpan(BaseSpan): LOCAL_SPANS = ("render") - def __init__(self, span, source, **kwargs): - super(RegisteredSpan, self).__init__(span, source, **kwargs) + def __init__(self, span, source, service_name, **kwargs): + super(RegisteredSpan, self).__init__(span, source, service_name, **kwargs) self.n = span.operation_name - self.data = DictionaryOfStan() self.k = 1 if span.operation_name in self.ENTRY_SPANS: diff --git a/tests/test_agent.py b/tests/test_agent.py new file mode 100644 index 00000000..58d31786 --- /dev/null +++ b/tests/test_agent.py @@ -0,0 +1,28 @@ +from __future__ import absolute_import + +import unittest + +from instana.singletons import agent, tracer +from instana.options import StandardOptions + + +class TestAgent(unittest.TestCase): + def setUp(self): + pass + + def tearDown(self): + pass + + def test_secrets(self): + self.assertTrue(hasattr(agent, 'secrets_matcher')) + self.assertEqual(agent.secrets_matcher, 'contains-ignore-case') + self.assertTrue(hasattr(agent, 'secrets_list')) + self.assertEqual(agent.secrets_list, ['key', 'pass', 'secret']) + + def test_has_extra_headers(self): + self.assertTrue(hasattr(agent, 'extra_headers')) + + def test_has_options(self): + self.assertTrue(hasattr(agent, 'options')) + self.assertTrue(type(agent.options) is StandardOptions) + diff --git a/tests/test_lambda.py b/tests/test_lambda.py index 0ea1214f..1d478ab2 100644 --- a/tests/test_lambda.py +++ b/tests/test_lambda.py @@ -9,6 +9,7 @@ from instana.singletons import get_agent, set_agent, get_tracer, set_tracer from instana.tracer import InstanaTracer from instana.agent import AWSLambdaAgent +from instana.options import AWSLambdaOptions from instana.recorder import AWSLambdaRecorder from instana import lambda_handler from instana import get_lambda_handler_or_default @@ -89,6 +90,22 @@ def test_invalid_options(self): self.assertFalse(agent._can_send) self.assertIsNone(agent.collector) + def test_secrets(self): + self.create_agent_and_setup_tracer() + self.assertTrue(hasattr(self.agent, 'secrets_matcher')) + self.assertEqual(self.agent.secrets_matcher, 'contains-ignore-case') + self.assertTrue(hasattr(self.agent, 'secrets_list')) + self.assertEqual(self.agent.secrets_list, ['key', 'pass', 'secret']) + + def test_has_extra_headers(self): + self.create_agent_and_setup_tracer() + self.assertTrue(hasattr(self.agent, 'extra_headers')) + + def test_has_options(self): + self.create_agent_and_setup_tracer() + self.assertTrue(hasattr(self.agent, 'options')) + self.assertTrue(type(self.agent.options) is AWSLambdaOptions) + def test_get_handler(self): os.environ["LAMBDA_HANDLER"] = "tests.lambda_handler" handler_module, handler_function = get_lambda_handler_or_default() From 49da1bd44043fc1cb3ac6d2d70a5f2e0c9ee97c7 Mon Sep 17 00:00:00 2001 From: Peter Giacomo Lombardo Date: Wed, 10 Jun 2020 16:30:13 +0200 Subject: [PATCH 03/12] Handle None service_name --- instana/recorder.py | 2 +- instana/span.py | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/instana/recorder.py b/instana/recorder.py index a0d41093..3b7a6327 100644 --- a/instana/recorder.py +++ b/instana/recorder.py @@ -104,7 +104,7 @@ def record_span(self, span): source = instana.singletons.agent.get_from_structure() if span.operation_name in self.REGISTERED_SPANS: - json_span = RegisteredSpan(span, source) + json_span = RegisteredSpan(span, source, None) else: service_name = instana.singletons.agent.options.service_name json_span = SDKSpan(span, source, service_name) diff --git a/instana/span.py b/instana/span.py index 7fa8172b..7a8b5428 100644 --- a/instana/span.py +++ b/instana/span.py @@ -139,7 +139,8 @@ def __init__(self, span, source, service_name, **kwargs): self.ec = span.tags.pop('ec', None) self.data = DictionaryOfStan() - self.data["service"] = service_name + if service_name is not None: + self.data["service"] = service_name if span.stack: self.stack = span.stack From cd64cbe7767af76ddab706063d386ea05b36daa6 Mon Sep 17 00:00:00 2001 From: Peter Giacomo Lombardo Date: Wed, 10 Jun 2020 16:43:39 +0200 Subject: [PATCH 04/12] Add more Trigger lookup safeties --- instana/instrumentation/aws/triggers.py | 55 ++++++++++++++++--------- 1 file changed, 36 insertions(+), 19 deletions(-) diff --git a/instana/instrumentation/aws/triggers.py b/instana/instrumentation/aws/triggers.py index 00cee7a9..2123c651 100644 --- a/instana/instrumentation/aws/triggers.py +++ b/instana/instrumentation/aws/triggers.py @@ -35,9 +35,10 @@ def is_cloudwatch_trigger(event): def is_cloudwatch_logs_trigger(event): - if "awslogs" in event and event["awslogs"] != None: + if hasattr(event, 'get') and event.get("awslogs", False) is not False: return True - return False + else: + return False def is_s3_trigger(event): @@ -61,19 +62,26 @@ def read_http_query_params(event): @param event: lambda event dict @return: String in the form of "a=b&c=d" """ - if event is None or type(event) is not dict: - return "" - params = [] - if 'multiValueQueryStringParameters' in event and event['multiValueQueryStringParameters'] is not None: - for key in event['multiValueQueryStringParameters']: - params.append("%s=%s" % (key, event['multiValueQueryStringParameters'][key])) - return "&".join(params) - elif 'queryStringParameters' in event and event['queryStringParameters'] is not None: - for key in event['queryStringParameters']: - params.append("%s=%s" % (key, event['queryStringParameters'][key])) - return "&".join(params) - else: + try: + if event is None or type(event) is not dict: + return "" + + mvqsp = event.get('multiValueQueryStringParameters', None) + qsp = event.get('queryStringParameters', None) + + if mvqsp is not None and type(mvqsp) is dict: + for key in mvqsp: + params.append("%s=%s" % (key, mvqsp[key])) + return "&".join(params) + elif qsp is not None and type(qsp) is dict: + for key in qsp: + params.append("%s=%s" % (key, qsp[key])) + return "&".join(params) + else: + return "" + except: + logger.debug("read_http_query_params: ", exc_info=True) return "" @@ -87,10 +95,16 @@ def capture_extra_headers(event, span, extra_headers): @param extra_headers: a list of http headers to capture @return: None """ - for custom_header in extra_headers: - for key in event["headers"]: - if key.lower() == custom_header.lower(): - span.set_tag("http.%s" % custom_header, event["headers"][key]) + try: + event_headers = event.get("headers", None) + + if event_headers is not None: + for custom_header in extra_headers: + for key in event_headers: + if key.lower() == custom_header.lower(): + span.set_tag("http.%s" % custom_header, event_headers[key]) + except: + logger.debug("capture_extra_headers: ", exc_info=True) def enrich_lambda_span(agent, span, event, context): @@ -109,6 +123,10 @@ def enrich_lambda_span(agent, span, event, context): span.set_tag('lambda.name', context.function_name) span.set_tag('lambda.version', context.function_version) + if event is None or type(event) is not dict: + logger.debug("enrich_lambda_span: bad event %s", type(event)) + return + if is_api_gateway_proxy_trigger(event): span.set_tag('lambda.trigger', 'aws:api.gateway') span.set_tag('http.method', event["httpMethod"]) @@ -204,6 +222,5 @@ def enrich_lambda_span(agent, span, event, context): for item in event["Records"][:3]: events.append({'queue': item['eventSourceARN']}) span.set_tag('lambda.sqs.messages', events) - except: logger.debug("enrich_lambda_span: ", exc_info=True) From 999577e70e2ecccac1e11d7ba6962d136935393c Mon Sep 17 00:00:00 2001 From: Peter Giacomo Lombardo Date: Wed, 10 Jun 2020 17:11:12 +0200 Subject: [PATCH 05/12] Fix metrics->plugins reporting --- instana/collector.py | 6 +++-- tests/test_lambda.py | 60 +++++++++++++++++++++++++++++++------------- 2 files changed, 46 insertions(+), 20 deletions(-) diff --git a/instana/collector.py b/instana/collector.py index 0189f50d..fd229a47 100644 --- a/instana/collector.py +++ b/instana/collector.py @@ -85,8 +85,10 @@ def collect_snapshot(self, event, context): self.event = event try: - self.snapshot_data["plugins"]["name"] = "com.instana.plugin.aws.lambda" - self.snapshot_data["plugins"]["entityId"] = self.context.invoked_function_arn + plugin_data = dict() + plugin_data["name"] = "com.instana.plugin.aws.lambda" + plugin_data["entityId"] = self.context.invoked_function_arn + self.snapshot_data["plugins"] = [plugin_data] except: logger.debug("collect_snapshot error", exc_info=True) finally: diff --git a/tests/test_lambda.py b/tests/test_lambda.py index 1d478ab2..9111508a 100644 --- a/tests/test_lambda.py +++ b/tests/test_lambda.py @@ -137,9 +137,13 @@ def test_api_gateway_trigger_tracing(self): self.assertTrue("metrics" in payload) self.assertTrue("spans" in payload) self.assertEqual(2, len(payload.keys())) - self.assertEqual('com.instana.plugin.aws.lambda', payload['metrics']['plugins']['name']) - self.assertEqual('arn:aws:lambda:us-east-2:12345:function:TestPython:1', - payload['metrics']['plugins']['entityId']) + + self.assertTrue(type(payload['metrics']['plugins']) is list) + self.assertTrue(len(payload['metrics']['plugins']) is 1) + plugin_data = payload['metrics']['plugins'][0] + + self.assertEqual('com.instana.plugin.aws.lambda', plugin_data['name']) + self.assertEqual('arn:aws:lambda:us-east-2:12345:function:TestPython:1', plugin_data['entityId']) self.assertEqual(1, len(payload['spans'])) @@ -189,9 +193,13 @@ def test_application_lb_trigger_tracing(self): self.assertTrue("metrics" in payload) self.assertTrue("spans" in payload) self.assertEqual(2, len(payload.keys())) - self.assertEqual('com.instana.plugin.aws.lambda', payload['metrics']['plugins']['name']) - self.assertEqual('arn:aws:lambda:us-east-2:12345:function:TestPython:1', - payload['metrics']['plugins']['entityId']) + + self.assertTrue(type(payload['metrics']['plugins']) is list) + self.assertTrue(len(payload['metrics']['plugins']) is 1) + plugin_data = payload['metrics']['plugins'][0] + + self.assertEqual('com.instana.plugin.aws.lambda', plugin_data['name']) + self.assertEqual('arn:aws:lambda:us-east-2:12345:function:TestPython:1', plugin_data['entityId']) self.assertEqual(1, len(payload['spans'])) @@ -240,9 +248,13 @@ def test_cloudwatch_trigger_tracing(self): self.assertTrue("metrics" in payload) self.assertTrue("spans" in payload) self.assertEqual(2, len(payload.keys())) - self.assertEqual('com.instana.plugin.aws.lambda', payload['metrics']['plugins']['name']) - self.assertEqual('arn:aws:lambda:us-east-2:12345:function:TestPython:1', - payload['metrics']['plugins']['entityId']) + + self.assertTrue(type(payload['metrics']['plugins']) is list) + self.assertTrue(len(payload['metrics']['plugins']) is 1) + plugin_data = payload['metrics']['plugins'][0] + + self.assertEqual('com.instana.plugin.aws.lambda', plugin_data['name']) + self.assertEqual('arn:aws:lambda:us-east-2:12345:function:TestPython:1', plugin_data['entityId']) self.assertEqual(1, len(payload['spans'])) @@ -291,9 +303,13 @@ def test_cloudwatch_logs_trigger_tracing(self): self.assertTrue("metrics" in payload) self.assertTrue("spans" in payload) self.assertEqual(2, len(payload.keys())) - self.assertEqual('com.instana.plugin.aws.lambda', payload['metrics']['plugins']['name']) - self.assertEqual('arn:aws:lambda:us-east-2:12345:function:TestPython:1', - payload['metrics']['plugins']['entityId']) + + self.assertTrue(type(payload['metrics']['plugins']) is list) + self.assertTrue(len(payload['metrics']['plugins']) is 1) + plugin_data = payload['metrics']['plugins'][0] + + self.assertEqual('com.instana.plugin.aws.lambda', plugin_data['name']) + self.assertEqual('arn:aws:lambda:us-east-2:12345:function:TestPython:1', plugin_data['entityId']) self.assertEqual(1, len(payload['spans'])) @@ -344,9 +360,13 @@ def test_s3_trigger_tracing(self): self.assertTrue("metrics" in payload) self.assertTrue("spans" in payload) self.assertEqual(2, len(payload.keys())) - self.assertEqual('com.instana.plugin.aws.lambda', payload['metrics']['plugins']['name']) - self.assertEqual('arn:aws:lambda:us-east-2:12345:function:TestPython:1', - payload['metrics']['plugins']['entityId']) + + self.assertTrue(type(payload['metrics']['plugins']) is list) + self.assertTrue(len(payload['metrics']['plugins']) is 1) + plugin_data = payload['metrics']['plugins'][0] + + self.assertEqual('com.instana.plugin.aws.lambda', plugin_data['name']) + self.assertEqual('arn:aws:lambda:us-east-2:12345:function:TestPython:1', plugin_data['entityId']) self.assertEqual(1, len(payload['spans'])) @@ -396,9 +416,13 @@ def test_sqs_trigger_tracing(self): self.assertTrue("metrics" in payload) self.assertTrue("spans" in payload) self.assertEqual(2, len(payload.keys())) - self.assertEqual('com.instana.plugin.aws.lambda', payload['metrics']['plugins']['name']) - self.assertEqual('arn:aws:lambda:us-east-2:12345:function:TestPython:1', - payload['metrics']['plugins']['entityId']) + + self.assertTrue(type(payload['metrics']['plugins']) is list) + self.assertTrue(len(payload['metrics']['plugins']) is 1) + plugin_data = payload['metrics']['plugins'][0] + + self.assertEqual('com.instana.plugin.aws.lambda', plugin_data['name']) + self.assertEqual('arn:aws:lambda:us-east-2:12345:function:TestPython:1', plugin_data['entityId']) self.assertEqual(1, len(payload['spans'])) From e210d31a0f240efdab3efa4acad5521f543f1ade Mon Sep 17 00:00:00 2001 From: Peter Giacomo Lombardo Date: Thu, 11 Jun 2020 14:10:05 +0200 Subject: [PATCH 06/12] Span Kind, Service name updates with tests --- instana/span.py | 54 ++---- tests/test_fargate.py | 430 ++++++++++++++++++++++++++++++++++++++++++ tests/test_flask.py | 82 ++++++++ tests/test_lambda.py | 66 +++++++ 4 files changed, 594 insertions(+), 38 deletions(-) create mode 100644 tests/test_fargate.py diff --git a/instana/span.py b/instana/span.py index 7a8b5428..a81eb962 100644 --- a/instana/span.py +++ b/instana/span.py @@ -137,10 +137,7 @@ def __init__(self, span, source, service_name, **kwargs): self.d = int(round(span.duration * 1000)) self.f = source self.ec = span.tags.pop('ec', None) - self.data = DictionaryOfStan() - if service_name is not None: - self.data["service"] = service_name if span.stack: self.stack = span.stack @@ -154,19 +151,19 @@ class SDKSpan(BaseSpan): def __init__(self, span, source, service_name, **kwargs): super(SDKSpan, self).__init__(span, source, service_name, **kwargs) + + span_kind = self.get_span_kind(span) + self.n = "sdk" - self.k = self.get_span_kind_as_int(span) + self.k = span_kind[1] + + if self.k == 1 and service_name is not None: + self.data["service"] = service_name self.data["sdk"]["name"] = span.operation_name - self.data["sdk"]["type"] = self.get_span_kind_as_string(span) + self.data["sdk"]["type"] = span_kind[0] self.data["sdk"]["custom"]["tags"] = span.tags self.data["sdk"]["custom"]["logs"] = span.logs - self.data["service"] = service_name - - # self.data = Data() - # self.data.sdk = SDKData(name=span.operation_name, Type=self.get_span_kind_as_string(span)) - # self.data.sdk.custom = CustomData(tags=span.tags, logs=span.collect_logs()) - # self.data.service = service_name if "arguments" in span.tags: self.data.sdk.arguments = span.tags["arguments"] @@ -177,40 +174,20 @@ def __init__(self, span, source, service_name, **kwargs): if len(span.context.baggage) > 0: self.data["baggage"] = span.context.baggage - def get_span_kind_as_string(self, span): + def get_span_kind(self, span): """ - Will retrieve the `span.kind` tag and return the appropriate string value for the Instana backend or - None if the tag is set to something we don't recognize. + Will retrieve the `span.kind` tag and return a tuple containing the appropriate string and integer + values for the Instana backend :param span: The span to search for the `span.kind` tag - :return: String + :return: Tuple (String, Int) """ - kind = None + kind = ("intermediate", 3) if "span.kind" in span.tags: if span.tags["span.kind"] in self.ENTRY_KIND: - kind = "entry" + kind = ("entry", 1) elif span.tags["span.kind"] in self.EXIT_KIND: - kind = "exit" - else: - kind = "intermediate" - return kind - - def get_span_kind_as_int(self, span): - """ - Will retrieve the `span.kind` tag and return the appropriate integer value for the Instana backend or - None if the tag is set to something we don't recognize. - - :param span: The span to search for the `span.kind` tag - :return: Integer - """ - kind = None - if "span.kind" in span.tags: - if span.tags["span.kind"] in self.ENTRY_KIND: - kind = 1 - elif span.tags["span.kind"] in self.EXIT_KIND: - kind = 2 - else: - kind = 3 + kind = ("exit", 2) return kind @@ -234,6 +211,7 @@ def __init__(self, span, source, service_name, **kwargs): if span.operation_name in self.ENTRY_SPANS: # entry self._populate_entry_span_data(span) + self.data["service"] = service_name elif span.operation_name in self.EXIT_SPANS: self.k = 2 # exit self._populate_exit_span_data(span) diff --git a/tests/test_fargate.py b/tests/test_fargate.py new file mode 100644 index 00000000..d22bc1d2 --- /dev/null +++ b/tests/test_fargate.py @@ -0,0 +1,430 @@ +from __future__ import absolute_import + +import os +import sys +import json +import wrapt +import unittest + +from instana.singletons import get_agent, set_agent, get_tracer, set_tracer +from instana.tracer import InstanaTracer +from instana.agent.aws_fargatefargate import AWSFargateAgent +from instana.recorder import AWSFargateRecorder +from instana import lambda_handler +from instana import get_lambda_handler_or_default +from instana.instrumentation.aws.lambda_inst import lambda_handler_with_instana +from instana.instrumentation.aws.triggers import read_http_query_params + + +# Mock Context object +class TestContext(dict): + def __init__(self, **kwargs): + super(TestContext, self).__init__(**kwargs) + self.invoked_function_arn = "arn:aws:lambda:us-east-2:12345:function:TestPython:1" + self.function_name = "TestPython" + self.function_version = "1" + + +# This is the target handler that will be instrumented for these tests +def my_lambda_handler(event, context): + # print("target_handler called") + return "All Ok" + +# We only want to monkey patch the test handler once so do it here +os.environ["LAMBDA_HANDLER"] = "tests.test_lambda.my_lambda_handler" +module_name, function_name = get_lambda_handler_or_default() +wrapt.wrap_function_wrapper(module_name, function_name, lambda_handler_with_instana) + + +class TestFargate(unittest.TestCase): + def __init__(self, methodName='runTest'): + super(TestFargate, self).__init__(methodName) + self.agent = None + self.span_recorder = None + self.tracer = None + self.pwd = os.path.dirname(os.path.realpath(__file__)) + + self.original_agent = get_agent() + self.original_tracer = get_tracer() + + def setUp(self): + os.environ["LAMBDA_HANDLER"] = "tests.test_lambda.my_lambda_handler" + os.environ["INSTANA_ENDPOINT_URL"] = "https://localhost/notreal" + os.environ["INSTANA_AGENT_KEY"] = "Fake_Key" + self.context = TestContext() + + def tearDown(self): + """ Reset all environment variables of consequence """ + if "LAMBDA_HANDLER" in os.environ: + os.environ.pop("LAMBDA_HANDLER") + if "INSTANA_EXTRA_HTTP_HEADERS" in os.environ: + os.environ.pop("INSTANA_EXTRA_HTTP_HEADERS") + if "INSTANA_ENDPOINT_URL" in os.environ: + os.environ.pop("INSTANA_ENDPOINT_URL") + if "INSTANA_AGENT_KEY" in os.environ: + os.environ.pop("INSTANA_AGENT_KEY") + + set_agent(self.original_agent) + set_tracer(self.original_tracer) + + def create_agent_and_setup_tracer(self): + self.agent = AWSFargateAgent() + self.span_recorder = AWSFargateRecorder(self.agent) + self.tracer = InstanaTracer(recorder=self.span_recorder) + set_agent(self.agent) + set_tracer(self.tracer) + + def test_invalid_options(self): + # None of the required env vars are available... + if "LAMBDA_HANDLER" in os.environ: + os.environ.pop("LAMBDA_HANDLER") + if "INSTANA_EXTRA_HTTP_HEADERS" in os.environ: + os.environ.pop("INSTANA_EXTRA_HTTP_HEADERS") + if "INSTANA_ENDPOINT_URL" in os.environ: + os.environ.pop("INSTANA_ENDPOINT_URL") + if "INSTANA_AGENT_KEY" in os.environ: + os.environ.pop("INSTANA_AGENT_KEY") + + agent = AWSFargateAgent() + self.assertFalse(agent._can_send) + self.assertIsNone(agent.collector) + + def test_get_handler(self): + os.environ["LAMBDA_HANDLER"] = "tests.lambda_handler" + handler_module, handler_function = get_lambda_handler_or_default() + + self.assertEqual("tests", handler_module) + self.assertEqual("lambda_handler", handler_function) + + def test_agent_extra_headers(self): + os.environ['INSTANA_EXTRA_HTTP_HEADERS'] = "X-Test-Header;X-Another-Header;X-And-Another-Header" + self.create_agent_and_setup_tracer() + self.assertIsNotNone(self.agent.extra_headers) + should_headers = ['x-test-header', 'x-another-header', 'x-and-another-header'] + self.assertEqual(should_headers, self.agent.extra_headers) + + def test_api_gateway_trigger_tracing(self): + with open(self.pwd + '/data/lambda/api_gateway_event.json', 'r') as json_file: + event = json.load(json_file) + + self.create_agent_and_setup_tracer() + + # Call the Instana Fargate Handler as we do in the real world. It will initiate tracing and then + # figure out the original (the users') Fargate Handler and execute it. + # The original Fargate handler is set in os.environ["LAMBDA_HANDLER"] + result = lambda_handler(event, self.context) + + self.assertEqual('All Ok', result) + payload = self.agent.collector.prepare_payload() + + self.assertTrue("metrics" in payload) + self.assertTrue("spans" in payload) + self.assertEqual(2, len(payload.keys())) + self.assertEqual('com.instana.plugin.aws.lambda', payload['metrics']['plugins']['name']) + self.assertEqual('arn:aws:lambda:us-east-2:12345:function:TestPython:1', + payload['metrics']['plugins']['entityId']) + + self.assertEqual(1, len(payload['spans'])) + + span = payload['spans'][0] + self.assertEqual('aws.lambda.entry', span.n) + self.assertIsNotNone(span.t) + self.assertIsNotNone(span.s) + self.assertIsNone(span.p) + self.assertIsNotNone(span.ts) + self.assertIsNotNone(span.d) + + self.assertEqual({'hl': True, 'cp': 'aws', 'e': 'arn:aws:lambda:us-east-2:12345:function:TestPython:1'}, + span.f) + + self.assertIsNone(span.ec) + self.assertIsNone(span.data['lambda']['error']) + + self.assertEqual('arn:aws:lambda:us-east-2:12345:function:TestPython:1', span.data['lambda']['arn']) + self.assertEqual(None, span.data['lambda']['alias']) + self.assertEqual('python', span.data['lambda']['runtime']) + self.assertEqual('TestPython', span.data['lambda']['functionName']) + self.assertEqual('1', span.data['lambda']['functionVersion']) + + self.assertEqual('aws:api.gateway', span.data['lambda']['trigger']) + self.assertEqual('POST', span.data['http']['method']) + self.assertEqual('/path/to/resource', span.data['http']['url']) + self.assertEqual('/{proxy+}', span.data['http']['path_tpl']) + if sys.version[:3] == '2.7': + self.assertEqual(u"foo=[u'bar']", span.data['http']['params']) + else: + self.assertEqual("foo=['bar']", span.data['http']['params']) + + def test_application_lb_trigger_tracing(self): + with open(self.pwd + '/data/lambda/api_gateway_event.json', 'r') as json_file: + event = json.load(json_file) + + self.create_agent_and_setup_tracer() + + # Call the Instana Fargate Handler as we do in the real world. It will initiate tracing and then + # figure out the original (the users') Fargate Handler and execute it. + # The original Fargate handler is set in os.environ["LAMBDA_HANDLER"] + result = lambda_handler(event, self.context) + + self.assertEqual('All Ok', result) + payload = self.agent.collector.prepare_payload() + + self.assertTrue("metrics" in payload) + self.assertTrue("spans" in payload) + self.assertEqual(2, len(payload.keys())) + self.assertEqual('com.instana.plugin.aws.lambda', payload['metrics']['plugins']['name']) + self.assertEqual('arn:aws:lambda:us-east-2:12345:function:TestPython:1', + payload['metrics']['plugins']['entityId']) + + self.assertEqual(1, len(payload['spans'])) + + span = payload['spans'][0] + self.assertEqual('aws.lambda.entry', span.n) + self.assertIsNotNone(span.t) + self.assertIsNotNone(span.s) + self.assertIsNone(span.p) + self.assertIsNotNone(span.ts) + self.assertIsNotNone(span.d) + + self.assertEqual({'hl': True, 'cp': 'aws', 'e': 'arn:aws:lambda:us-east-2:12345:function:TestPython:1'}, + span.f) + + self.assertIsNone(span.ec) + self.assertIsNone(span.data['lambda']['error']) + + self.assertEqual('arn:aws:lambda:us-east-2:12345:function:TestPython:1', span.data['lambda']['arn']) + self.assertEqual(None, span.data['lambda']['alias']) + self.assertEqual('python', span.data['lambda']['runtime']) + self.assertEqual('TestPython', span.data['lambda']['functionName']) + self.assertEqual('1', span.data['lambda']['functionVersion']) + + self.assertEqual('aws:api.gateway', span.data['lambda']['trigger']) + self.assertEqual('POST', span.data['http']['method']) + self.assertEqual('/path/to/resource', span.data['http']['url']) + if sys.version[:3] == '2.7': + self.assertEqual(u"foo=[u'bar']", span.data['http']['params']) + else: + self.assertEqual("foo=['bar']", span.data['http']['params']) + + def test_cloudwatch_trigger_tracing(self): + with open(self.pwd + '/data/lambda/cloudwatch_event.json', 'r') as json_file: + event = json.load(json_file) + + self.create_agent_and_setup_tracer() + + # Call the Instana Fargate Handler as we do in the real world. It will initiate tracing and then + # figure out the original (the users') Fargate Handler and execute it. + # The original Fargate handler is set in os.environ["LAMBDA_HANDLER"] + result = lambda_handler(event, self.context) + + self.assertEqual('All Ok', result) + payload = self.agent.collector.prepare_payload() + + self.assertTrue("metrics" in payload) + self.assertTrue("spans" in payload) + self.assertEqual(2, len(payload.keys())) + self.assertEqual('com.instana.plugin.aws.lambda', payload['metrics']['plugins']['name']) + self.assertEqual('arn:aws:lambda:us-east-2:12345:function:TestPython:1', + payload['metrics']['plugins']['entityId']) + + self.assertEqual(1, len(payload['spans'])) + + span = payload['spans'][0] + self.assertEqual('aws.lambda.entry', span.n) + self.assertIsNotNone(span.t) + self.assertIsNotNone(span.s) + self.assertIsNone(span.p) + self.assertIsNotNone(span.ts) + self.assertIsNotNone(span.d) + + self.assertEqual({'hl': True, 'cp': 'aws', 'e': 'arn:aws:lambda:us-east-2:12345:function:TestPython:1'}, + span.f) + + self.assertIsNone(span.ec) + self.assertIsNone(span.data['lambda']['error']) + + self.assertEqual('arn:aws:lambda:us-east-2:12345:function:TestPython:1', span.data['lambda']['arn']) + self.assertEqual(None, span.data['lambda']['alias']) + self.assertEqual('python', span.data['lambda']['runtime']) + self.assertEqual('TestPython', span.data['lambda']['functionName']) + self.assertEqual('1', span.data['lambda']['functionVersion']) + + self.assertEqual('aws:cloudwatch.events', span.data['lambda']['trigger']) + self.assertEqual('cdc73f9d-aea9-11e3-9d5a-835b769c0d9c', span.data["lambda"]["cw"]["events"]["id"]) + self.assertEqual(False, span.data["lambda"]["cw"]["events"]["more"]) + self.assertTrue(type(span.data["lambda"]["cw"]["events"]["resources"]) is list) + self.assertEqual(1, len(span.data["lambda"]["cw"]["events"]["resources"])) + self.assertEqual('arn:aws:events:eu-west-1:123456789012:rule/ExampleRule', + span.data["lambda"]["cw"]["events"]["resources"][0]) + + def test_cloudwatch_logs_trigger_tracing(self): + with open(self.pwd + '/data/lambda/cloudwatch_logs_event.json', 'r') as json_file: + event = json.load(json_file) + + self.create_agent_and_setup_tracer() + + # Call the Instana Fargate Handler as we do in the real world. It will initiate tracing and then + # figure out the original (the users') Fargate Handler and execute it. + # The original Fargate handler is set in os.environ["LAMBDA_HANDLER"] + result = lambda_handler(event, self.context) + + self.assertEqual('All Ok', result) + payload = self.agent.collector.prepare_payload() + + self.assertTrue("metrics" in payload) + self.assertTrue("spans" in payload) + self.assertEqual(2, len(payload.keys())) + self.assertEqual('com.instana.plugin.aws.lambda', payload['metrics']['plugins']['name']) + self.assertEqual('arn:aws:lambda:us-east-2:12345:function:TestPython:1', + payload['metrics']['plugins']['entityId']) + + self.assertEqual(1, len(payload['spans'])) + + span = payload['spans'][0] + self.assertEqual('aws.lambda.entry', span.n) + self.assertIsNotNone(span.t) + self.assertIsNotNone(span.s) + self.assertIsNone(span.p) + self.assertIsNotNone(span.ts) + self.assertIsNotNone(span.d) + + self.assertEqual({'hl': True, 'cp': 'aws', 'e': 'arn:aws:lambda:us-east-2:12345:function:TestPython:1'}, + span.f) + + self.assertIsNone(span.ec) + self.assertIsNone(span.data['lambda']['error']) + + self.assertEqual('arn:aws:lambda:us-east-2:12345:function:TestPython:1', span.data['lambda']['arn']) + self.assertEqual(None, span.data['lambda']['alias']) + self.assertEqual('python', span.data['lambda']['runtime']) + self.assertEqual('TestPython', span.data['lambda']['functionName']) + self.assertEqual('1', span.data['lambda']['functionVersion']) + + self.assertEqual('aws:cloudwatch.logs', span.data['lambda']['trigger']) + self.assertFalse("decodingError" in span.data['lambda']['cw']['logs']) + self.assertEqual('testLogGroup', span.data['lambda']['cw']['logs']['group']) + self.assertEqual('testLogStream', span.data['lambda']['cw']['logs']['stream']) + self.assertEqual(None, span.data['lambda']['cw']['logs']['more']) + self.assertTrue(type(span.data['lambda']['cw']['logs']['events']) is list) + self.assertEqual(2, len(span.data['lambda']['cw']['logs']['events'])) + self.assertEqual('[ERROR] First test message', span.data['lambda']['cw']['logs']['events'][0]) + self.assertEqual('[ERROR] Second test message', span.data['lambda']['cw']['logs']['events'][1]) + + def test_s3_trigger_tracing(self): + with open(self.pwd + '/data/lambda/s3_event.json', 'r') as json_file: + event = json.load(json_file) + + self.create_agent_and_setup_tracer() + + # Call the Instana Fargate Handler as we do in the real world. It will initiate tracing and then + # figure out the original (the users') Fargate Handler and execute it. + # The original Fargate handler is set in os.environ["LAMBDA_HANDLER"] + result = lambda_handler(event, self.context) + + self.assertEqual('All Ok', result) + payload = self.agent.collector.prepare_payload() + + self.assertTrue("metrics" in payload) + self.assertTrue("spans" in payload) + self.assertEqual(2, len(payload.keys())) + self.assertEqual('com.instana.plugin.aws.lambda', payload['metrics']['plugins']['name']) + self.assertEqual('arn:aws:lambda:us-east-2:12345:function:TestPython:1', + payload['metrics']['plugins']['entityId']) + + self.assertEqual(1, len(payload['spans'])) + + span = payload['spans'][0] + self.assertEqual('aws.lambda.entry', span.n) + self.assertIsNotNone(span.t) + self.assertIsNotNone(span.s) + self.assertIsNone(span.p) + self.assertIsNotNone(span.ts) + self.assertIsNotNone(span.d) + + self.assertEqual({'hl': True, 'cp': 'aws', 'e': 'arn:aws:lambda:us-east-2:12345:function:TestPython:1'}, + span.f) + + self.assertIsNone(span.ec) + self.assertIsNone(span.data['lambda']['error']) + + self.assertEqual('arn:aws:lambda:us-east-2:12345:function:TestPython:1', span.data['lambda']['arn']) + self.assertEqual(None, span.data['lambda']['alias']) + self.assertEqual('python', span.data['lambda']['runtime']) + self.assertEqual('TestPython', span.data['lambda']['functionName']) + self.assertEqual('1', span.data['lambda']['functionVersion']) + + self.assertEqual('aws:s3', span.data['lambda']['trigger']) + self.assertTrue(type(span.data["lambda"]["s3"]["events"]) is list) + events = span.data["lambda"]["s3"]["events"] + self.assertEqual(1, len(events)) + event = events[0] + self.assertEqual('ObjectCreated:Put', event['event']) + self.assertEqual('example-bucket', event['bucket']) + self.assertEqual('test/key', event['object']) + + def test_sqs_trigger_tracing(self): + with open(self.pwd + '/data/lambda/sqs_event.json', 'r') as json_file: + event = json.load(json_file) + + self.create_agent_and_setup_tracer() + + # Call the Instana Fargate Handler as we do in the real world. It will initiate tracing and then + # figure out the original (the users') Fargate Handler and execute it. + # The original Fargate handler is set in os.environ["LAMBDA_HANDLER"] + result = lambda_handler(event, self.context) + + self.assertEqual('All Ok', result) + payload = self.agent.collector.prepare_payload() + + self.assertTrue("metrics" in payload) + self.assertTrue("spans" in payload) + self.assertEqual(2, len(payload.keys())) + self.assertEqual('com.instana.plugin.aws.lambda', payload['metrics']['plugins']['name']) + self.assertEqual('arn:aws:lambda:us-east-2:12345:function:TestPython:1', + payload['metrics']['plugins']['entityId']) + + self.assertEqual(1, len(payload['spans'])) + + span = payload['spans'][0] + self.assertEqual('aws.lambda.entry', span.n) + self.assertIsNotNone(span.t) + self.assertIsNotNone(span.s) + self.assertIsNone(span.p) + self.assertIsNotNone(span.ts) + self.assertIsNotNone(span.d) + + self.assertEqual({'hl': True, 'cp': 'aws', 'e': 'arn:aws:lambda:us-east-2:12345:function:TestPython:1'}, + span.f) + + self.assertIsNone(span.ec) + self.assertIsNone(span.data['lambda']['error']) + + self.assertEqual('arn:aws:lambda:us-east-2:12345:function:TestPython:1', span.data['lambda']['arn']) + self.assertEqual(None, span.data['lambda']['alias']) + self.assertEqual('python', span.data['lambda']['runtime']) + self.assertEqual('TestPython', span.data['lambda']['functionName']) + self.assertEqual('1', span.data['lambda']['functionVersion']) + + self.assertEqual('aws:sqs', span.data['lambda']['trigger']) + self.assertTrue(type(span.data["lambda"]["sqs"]["messages"]) is list) + messages = span.data["lambda"]["sqs"]["messages"] + self.assertEqual(1, len(messages)) + message = messages[0] + self.assertEqual('arn:aws:sqs:us-west-1:123456789012:MyQueue', message['queue']) + + def test_read_query_params(self): + event = { "queryStringParameters": {"foo": "bar" }, + "multiValueQueryStringParameters": { "foo": ["bar"] } } + params = read_http_query_params(event) + self.assertEqual("foo=['bar']", params) + + def test_read_query_params_with_none_data(self): + event = { "queryStringParameters": None, + "multiValueQueryStringParameters": None } + params = read_http_query_params(event) + self.assertEqual("", params) + + def test_read_query_params_with_bad_event(self): + event = None + params = read_http_query_params(event) + self.assertEqual("", params) diff --git a/tests/test_flask.py b/tests/test_flask.py index 130a7b76..29f8998d 100644 --- a/tests/test_flask.py +++ b/tests/test_flask.py @@ -1,5 +1,6 @@ from __future__ import absolute_import +import os import sys import unittest import urllib3 @@ -27,6 +28,76 @@ def test_vanilla_requests(self): spans = self.recorder.queued_spans() self.assertEqual(1, len(spans)) + def test_custom_service_name(self): + os.environ['INSTANA_SERVICE_NAME'] = 'Legion' + with tracer.start_active_span('test'): + response = self.http.request('GET', testenv["wsgi_server"] + '/') + os.environ.pop('INSTANA_SERVICE_NAME') + + spans = self.recorder.queued_spans() + self.assertEqual(3, len(spans)) + + wsgi_span = spans[0] + urllib3_span = spans[1] + test_span = spans[2] + + assert response + self.assertEqual(200, response.status) + + assert('X-Instana-T' in response.headers) + assert(int(response.headers['X-Instana-T'], 16)) + self.assertEqual(response.headers['X-Instana-T'], wsgi_span.t) + + assert('X-Instana-S' in response.headers) + assert(int(response.headers['X-Instana-S'], 16)) + self.assertEqual(response.headers['X-Instana-S'], wsgi_span.s) + + assert('X-Instana-L' in response.headers) + self.assertEqual(response.headers['X-Instana-L'], '1') + + assert('Server-Timing' in response.headers) + server_timing_value = "intid;desc=%s" % wsgi_span.t + self.assertEqual(response.headers['Server-Timing'], server_timing_value) + + self.assertIsNone(tracer.active_span) + + # Same traceId + self.assertEqual(test_span.t, urllib3_span.t) + self.assertEqual(urllib3_span.t, wsgi_span.t) + + # Parent relationships + self.assertEqual(urllib3_span.p, test_span.s) + self.assertEqual(wsgi_span.p, urllib3_span.s) + + # Error logging + self.assertIsNone(test_span.ec) + self.assertIsNone(urllib3_span.ec) + self.assertIsNone(wsgi_span.ec) + + # wsgi + self.assertEqual("wsgi", wsgi_span.n) + self.assertEqual('127.0.0.1:' + str(testenv['wsgi_port']), wsgi_span.data["http"]["host"]) + self.assertEqual('/', wsgi_span.data["http"]["url"]) + self.assertEqual('GET', wsgi_span.data["http"]["method"]) + self.assertEqual(200, wsgi_span.data["http"]["status"]) + self.assertIsNone(wsgi_span.data["http"]["error"]) + self.assertIsNotNone(wsgi_span.stack) + self.assertEqual(2, len(wsgi_span.stack)) + self.assertEqual('Legion', wsgi_span.data['service']) + + # urllib3 + self.assertEqual("test", test_span.data["sdk"]["name"]) + self.assertEqual("urllib3", urllib3_span.n) + self.assertEqual(200, urllib3_span.data["http"]["status"]) + self.assertEqual(testenv["wsgi_server"] + '/', urllib3_span.data["http"]["url"]) + self.assertEqual("GET", urllib3_span.data["http"]["method"]) + self.assertIsNotNone(urllib3_span.stack) + self.assertTrue(type(urllib3_span.stack) is list) + self.assertTrue(len(urllib3_span.stack) > 1) + + # We should NOT have a path template for this route + self.assertIsNone(wsgi_span.data["http"]["path_tpl"]) + def test_get_request(self): with tracer.start_active_span('test'): response = self.http.request('GET', testenv["wsgi_server"] + '/') @@ -80,6 +151,7 @@ def test_get_request(self): self.assertIsNone(wsgi_span.data["http"]["error"]) self.assertIsNotNone(wsgi_span.stack) self.assertEqual(2, len(wsgi_span.stack)) + self.assertIsNone(wsgi_span.data['service']) # urllib3 self.assertEqual("test", test_span.data["sdk"]["name"]) @@ -159,6 +231,7 @@ def test_render_template(self): self.assertIsNone(wsgi_span.data["http"]["error"]) self.assertIsNotNone(wsgi_span.stack) self.assertEqual(2, len(wsgi_span.stack)) + self.assertIsNone(wsgi_span.data['service']) # urllib3 self.assertEqual("test", test_span.data["sdk"]["name"]) @@ -238,6 +311,7 @@ def test_render_template_string(self): self.assertIsNone(wsgi_span.data["http"]["error"]) self.assertIsNotNone(wsgi_span.stack) self.assertEqual(2, len(wsgi_span.stack)) + self.assertIsNone(wsgi_span.data['service']) # urllib3 self.assertEqual("test", test_span.data["sdk"]["name"]) @@ -306,6 +380,7 @@ def test_301(self): self.assertIsNone(wsgi_span.data["http"]["error"]) self.assertIsNotNone(wsgi_span.stack) self.assertEqual(2, len(wsgi_span.stack)) + self.assertIsNone(wsgi_span.data['service']) # urllib3 self.assertEqual("test", test_span.data["sdk"]["name"]) @@ -374,6 +449,7 @@ def test_custom_404(self): self.assertIsNone(wsgi_span.data["http"]["error"]) self.assertIsNotNone(wsgi_span.stack) self.assertEqual(2, len(wsgi_span.stack)) + self.assertIsNone(wsgi_span.data['service']) # urllib3 self.assertEqual("test", test_span.data["sdk"]["name"]) @@ -442,6 +518,7 @@ def test_404(self): self.assertIsNone(wsgi_span.data["http"]["error"]) self.assertIsNotNone(wsgi_span.stack) self.assertEqual(2, len(wsgi_span.stack)) + self.assertIsNone(wsgi_span.data['service']) # urllib3 self.assertEqual("test", test_span.data["sdk"]["name"]) @@ -510,6 +587,7 @@ def test_500(self): self.assertIsNone(wsgi_span.data["http"]["error"]) self.assertIsNotNone(wsgi_span.stack) self.assertEqual(2, len(wsgi_span.stack)) + self.assertIsNone(wsgi_span.data['service']) # urllib3 self.assertEqual("test", test_span.data["sdk"]["name"]) @@ -587,6 +665,7 @@ def test_render_error(self): self.assertIsNone(wsgi_span.data["http"]["error"]) self.assertIsNotNone(wsgi_span.stack) self.assertEqual(2, len(wsgi_span.stack)) + self.assertIsNone(wsgi_span.data['service']) # urllib3 self.assertEqual("test", test_span.data["sdk"]["name"]) @@ -655,6 +734,7 @@ def test_exception(self): self.assertIsNone(wsgi_span.data["http"]["error"]) self.assertIsNotNone(wsgi_span.stack) self.assertEqual(2, len(wsgi_span.stack)) + self.assertIsNone(wsgi_span.data['service']) # urllib3 self.assertEqual("test", test_span.data["sdk"]["name"]) @@ -730,6 +810,7 @@ def test_custom_exception_with_log(self): self.assertEqual('Simulated custom exception', wsgi_span.data["http"]["error"]) self.assertIsNotNone(wsgi_span.stack) self.assertEqual(2, len(wsgi_span.stack)) + self.assertIsNone(wsgi_span.data['service']) # urllib3 self.assertEqual("test", test_span.data["sdk"]["name"]) @@ -797,6 +878,7 @@ def test_path_templates(self): self.assertIsNone(wsgi_span.data["http"]["error"]) self.assertIsNotNone(wsgi_span.stack) self.assertEqual(2, len(wsgi_span.stack)) + self.assertIsNone(wsgi_span.data['service']) # urllib3 self.assertEqual("test", test_span.data["sdk"]["name"]) diff --git a/tests/test_lambda.py b/tests/test_lambda.py index 9111508a..ff3615e5 100644 --- a/tests/test_lambda.py +++ b/tests/test_lambda.py @@ -120,6 +120,66 @@ def test_agent_extra_headers(self): should_headers = ['x-test-header', 'x-another-header', 'x-and-another-header'] self.assertEqual(should_headers, self.agent.extra_headers) + def test_custom_service_name(self): + os.environ['INSTANA_SERVICE_NAME'] = "Legion" + with open(self.pwd + '/data/lambda/api_gateway_event.json', 'r') as json_file: + event = json.load(json_file) + + self.create_agent_and_setup_tracer() + + # Call the Instana Lambda Handler as we do in the real world. It will initiate tracing and then + # figure out the original (the users') Lambda Handler and execute it. + # The original Lambda handler is set in os.environ["LAMBDA_HANDLER"] + result = lambda_handler(event, self.context) + os.environ.pop('INSTANA_SERVICE_NAME') + + self.assertEqual('All Ok', result) + payload = self.agent.collector.prepare_payload() + + self.assertTrue("metrics" in payload) + self.assertTrue("spans" in payload) + self.assertEqual(2, len(payload.keys())) + + self.assertTrue(type(payload['metrics']['plugins']) is list) + self.assertTrue(len(payload['metrics']['plugins']) is 1) + plugin_data = payload['metrics']['plugins'][0] + + self.assertEqual('com.instana.plugin.aws.lambda', plugin_data['name']) + self.assertEqual('arn:aws:lambda:us-east-2:12345:function:TestPython:1', plugin_data['entityId']) + + self.assertEqual(1, len(payload['spans'])) + + span = payload['spans'][0] + self.assertEqual('aws.lambda.entry', span.n) + self.assertIsNotNone(span.t) + self.assertIsNotNone(span.s) + self.assertIsNone(span.p) + self.assertIsNotNone(span.ts) + self.assertIsNotNone(span.d) + + self.assertEqual({'hl': True, 'cp': 'aws', 'e': 'arn:aws:lambda:us-east-2:12345:function:TestPython:1'}, + span.f) + + self.assertIsNone(span.ec) + self.assertIsNone(span.data['lambda']['error']) + + self.assertEqual('arn:aws:lambda:us-east-2:12345:function:TestPython:1', span.data['lambda']['arn']) + self.assertEqual(None, span.data['lambda']['alias']) + self.assertEqual('python', span.data['lambda']['runtime']) + self.assertEqual('TestPython', span.data['lambda']['functionName']) + self.assertEqual('1', span.data['lambda']['functionVersion']) + + self.assertEqual('Legion', span.data['service']) + + self.assertEqual('aws:api.gateway', span.data['lambda']['trigger']) + self.assertEqual('POST', span.data['http']['method']) + self.assertEqual('/path/to/resource', span.data['http']['url']) + self.assertEqual('/{proxy+}', span.data['http']['path_tpl']) + if sys.version[:3] == '2.7': + self.assertEqual(u"foo=[u'bar']", span.data['http']['params']) + else: + self.assertEqual("foo=['bar']", span.data['http']['params']) + def test_api_gateway_trigger_tracing(self): with open(self.pwd + '/data/lambda/api_gateway_event.json', 'r') as json_file: event = json.load(json_file) @@ -166,6 +226,7 @@ def test_api_gateway_trigger_tracing(self): self.assertEqual('python', span.data['lambda']['runtime']) self.assertEqual('TestPython', span.data['lambda']['functionName']) self.assertEqual('1', span.data['lambda']['functionVersion']) + self.assertIsNone(span.data['service']) self.assertEqual('aws:api.gateway', span.data['lambda']['trigger']) self.assertEqual('POST', span.data['http']['method']) @@ -222,6 +283,7 @@ def test_application_lb_trigger_tracing(self): self.assertEqual('python', span.data['lambda']['runtime']) self.assertEqual('TestPython', span.data['lambda']['functionName']) self.assertEqual('1', span.data['lambda']['functionVersion']) + self.assertIsNone(span.data['service']) self.assertEqual('aws:api.gateway', span.data['lambda']['trigger']) self.assertEqual('POST', span.data['http']['method']) @@ -277,6 +339,7 @@ def test_cloudwatch_trigger_tracing(self): self.assertEqual('python', span.data['lambda']['runtime']) self.assertEqual('TestPython', span.data['lambda']['functionName']) self.assertEqual('1', span.data['lambda']['functionVersion']) + self.assertIsNone(span.data['service']) self.assertEqual('aws:cloudwatch.events', span.data['lambda']['trigger']) self.assertEqual('cdc73f9d-aea9-11e3-9d5a-835b769c0d9c', span.data["lambda"]["cw"]["events"]["id"]) @@ -332,6 +395,7 @@ def test_cloudwatch_logs_trigger_tracing(self): self.assertEqual('python', span.data['lambda']['runtime']) self.assertEqual('TestPython', span.data['lambda']['functionName']) self.assertEqual('1', span.data['lambda']['functionVersion']) + self.assertIsNone(span.data['service']) self.assertEqual('aws:cloudwatch.logs', span.data['lambda']['trigger']) self.assertFalse("decodingError" in span.data['lambda']['cw']['logs']) @@ -389,6 +453,7 @@ def test_s3_trigger_tracing(self): self.assertEqual('python', span.data['lambda']['runtime']) self.assertEqual('TestPython', span.data['lambda']['functionName']) self.assertEqual('1', span.data['lambda']['functionVersion']) + self.assertIsNone(span.data['service']) self.assertEqual('aws:s3', span.data['lambda']['trigger']) self.assertTrue(type(span.data["lambda"]["s3"]["events"]) is list) @@ -445,6 +510,7 @@ def test_sqs_trigger_tracing(self): self.assertEqual('python', span.data['lambda']['runtime']) self.assertEqual('TestPython', span.data['lambda']['functionName']) self.assertEqual('1', span.data['lambda']['functionVersion']) + self.assertIsNone(span.data['service']) self.assertEqual('aws:sqs', span.data['lambda']['trigger']) self.assertTrue(type(span.data["lambda"]["sqs"]["messages"]) is list) From c8b56124aae226a3c872a9e5d8940ba45257e417 Mon Sep 17 00:00:00 2001 From: Peter Giacomo Lombardo Date: Thu, 11 Jun 2020 14:15:06 +0200 Subject: [PATCH 07/12] Add AWS Lambda layer release instructions --- RELEASE.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/RELEASE.md b/RELEASE.md index 55070575..fa308aa0 100644 --- a/RELEASE.md +++ b/RELEASE.md @@ -1,5 +1,7 @@ # Release Steps +## PyPI + _Note: To release a new Instana package, you must be a project member of the [Instana package project on Pypi](https://pypi.org/project/instana/). Contact [Peter Giacomo Lombardo](https://github.com/pglombardo) to be added._ @@ -12,3 +14,11 @@ Contact [Peter Giacomo Lombardo](https://github.com/pglombardo) to be added._ 7. Validate the new release on https://pypi.org/project/instana/ 8. Update Python documentation with latest changes: https://docs.instana.io/ecosystem/python/ 9. Publish the draft release on [Github](https://github.com/instana/python-sensor/releases) + +## AWS Lambda Layer + +To release a new AWS Lambda layer, see `bin/lambda_build_publish_layer.py`. + +./bin/lambda_build_publish_layer.py [-dev|-prod] + +This script assumes you have the AWS CLI tools installed and credentials already configured. From 547c42ae64078c58646cf105049392fd72b42f6e Mon Sep 17 00:00:00 2001 From: Peter Giacomo Lombardo Date: Thu, 11 Jun 2020 18:12:38 +0200 Subject: [PATCH 08/12] A logger just for AWS --- instana/log.py | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/instana/log.py b/instana/log.py index 782e0329..5c262d4d 100644 --- a/instana/log.py +++ b/instana/log.py @@ -10,7 +10,7 @@ def get_standard_logger(): """ Retrieves and configures a standard logger for the Instana package - :return: Logger + @return: Logger """ standard_logger = logging.getLogger("instana") @@ -26,12 +26,28 @@ def get_standard_logger(): return standard_logger +def get_aws_lambda_logger(): + """ + Retrieves the preferred logger for AWS Lambda + + @return: Logger + """ + aws_lambda_logger = logging.getLogger() + + if "INSTANA_DEBUG" in os.environ: + aws_lambda_logger.setLevel(logging.DEBUG) + else: + aws_lambda_logger.setLevel(logging.WARN) + + return aws_lambda_logger + + def running_in_gunicorn(): """ Determines if we are running inside of a gunicorn process and that the gunicorn logging package is available. - :return: Boolean + @return: Boolean """ process_check = False package_check = False @@ -70,5 +86,7 @@ def running_in_gunicorn(): if running_in_gunicorn(): logger = logging.getLogger("gunicorn.error") +elif os.environ.get("INSTANA_ENDPOINT_URL", False): + logger = get_aws_lambda_logger() else: logger = get_standard_logger() From c1c5fd6a120d4dbc5a95924d9538f675d9352494 Mon Sep 17 00:00:00 2001 From: Peter Giacomo Lombardo Date: Thu, 11 Jun 2020 18:48:50 +0200 Subject: [PATCH 09/12] Fargate tests are for another PR --- tests/test_fargate.py | 430 ------------------------------------------ 1 file changed, 430 deletions(-) delete mode 100644 tests/test_fargate.py diff --git a/tests/test_fargate.py b/tests/test_fargate.py deleted file mode 100644 index d22bc1d2..00000000 --- a/tests/test_fargate.py +++ /dev/null @@ -1,430 +0,0 @@ -from __future__ import absolute_import - -import os -import sys -import json -import wrapt -import unittest - -from instana.singletons import get_agent, set_agent, get_tracer, set_tracer -from instana.tracer import InstanaTracer -from instana.agent.aws_fargatefargate import AWSFargateAgent -from instana.recorder import AWSFargateRecorder -from instana import lambda_handler -from instana import get_lambda_handler_or_default -from instana.instrumentation.aws.lambda_inst import lambda_handler_with_instana -from instana.instrumentation.aws.triggers import read_http_query_params - - -# Mock Context object -class TestContext(dict): - def __init__(self, **kwargs): - super(TestContext, self).__init__(**kwargs) - self.invoked_function_arn = "arn:aws:lambda:us-east-2:12345:function:TestPython:1" - self.function_name = "TestPython" - self.function_version = "1" - - -# This is the target handler that will be instrumented for these tests -def my_lambda_handler(event, context): - # print("target_handler called") - return "All Ok" - -# We only want to monkey patch the test handler once so do it here -os.environ["LAMBDA_HANDLER"] = "tests.test_lambda.my_lambda_handler" -module_name, function_name = get_lambda_handler_or_default() -wrapt.wrap_function_wrapper(module_name, function_name, lambda_handler_with_instana) - - -class TestFargate(unittest.TestCase): - def __init__(self, methodName='runTest'): - super(TestFargate, self).__init__(methodName) - self.agent = None - self.span_recorder = None - self.tracer = None - self.pwd = os.path.dirname(os.path.realpath(__file__)) - - self.original_agent = get_agent() - self.original_tracer = get_tracer() - - def setUp(self): - os.environ["LAMBDA_HANDLER"] = "tests.test_lambda.my_lambda_handler" - os.environ["INSTANA_ENDPOINT_URL"] = "https://localhost/notreal" - os.environ["INSTANA_AGENT_KEY"] = "Fake_Key" - self.context = TestContext() - - def tearDown(self): - """ Reset all environment variables of consequence """ - if "LAMBDA_HANDLER" in os.environ: - os.environ.pop("LAMBDA_HANDLER") - if "INSTANA_EXTRA_HTTP_HEADERS" in os.environ: - os.environ.pop("INSTANA_EXTRA_HTTP_HEADERS") - if "INSTANA_ENDPOINT_URL" in os.environ: - os.environ.pop("INSTANA_ENDPOINT_URL") - if "INSTANA_AGENT_KEY" in os.environ: - os.environ.pop("INSTANA_AGENT_KEY") - - set_agent(self.original_agent) - set_tracer(self.original_tracer) - - def create_agent_and_setup_tracer(self): - self.agent = AWSFargateAgent() - self.span_recorder = AWSFargateRecorder(self.agent) - self.tracer = InstanaTracer(recorder=self.span_recorder) - set_agent(self.agent) - set_tracer(self.tracer) - - def test_invalid_options(self): - # None of the required env vars are available... - if "LAMBDA_HANDLER" in os.environ: - os.environ.pop("LAMBDA_HANDLER") - if "INSTANA_EXTRA_HTTP_HEADERS" in os.environ: - os.environ.pop("INSTANA_EXTRA_HTTP_HEADERS") - if "INSTANA_ENDPOINT_URL" in os.environ: - os.environ.pop("INSTANA_ENDPOINT_URL") - if "INSTANA_AGENT_KEY" in os.environ: - os.environ.pop("INSTANA_AGENT_KEY") - - agent = AWSFargateAgent() - self.assertFalse(agent._can_send) - self.assertIsNone(agent.collector) - - def test_get_handler(self): - os.environ["LAMBDA_HANDLER"] = "tests.lambda_handler" - handler_module, handler_function = get_lambda_handler_or_default() - - self.assertEqual("tests", handler_module) - self.assertEqual("lambda_handler", handler_function) - - def test_agent_extra_headers(self): - os.environ['INSTANA_EXTRA_HTTP_HEADERS'] = "X-Test-Header;X-Another-Header;X-And-Another-Header" - self.create_agent_and_setup_tracer() - self.assertIsNotNone(self.agent.extra_headers) - should_headers = ['x-test-header', 'x-another-header', 'x-and-another-header'] - self.assertEqual(should_headers, self.agent.extra_headers) - - def test_api_gateway_trigger_tracing(self): - with open(self.pwd + '/data/lambda/api_gateway_event.json', 'r') as json_file: - event = json.load(json_file) - - self.create_agent_and_setup_tracer() - - # Call the Instana Fargate Handler as we do in the real world. It will initiate tracing and then - # figure out the original (the users') Fargate Handler and execute it. - # The original Fargate handler is set in os.environ["LAMBDA_HANDLER"] - result = lambda_handler(event, self.context) - - self.assertEqual('All Ok', result) - payload = self.agent.collector.prepare_payload() - - self.assertTrue("metrics" in payload) - self.assertTrue("spans" in payload) - self.assertEqual(2, len(payload.keys())) - self.assertEqual('com.instana.plugin.aws.lambda', payload['metrics']['plugins']['name']) - self.assertEqual('arn:aws:lambda:us-east-2:12345:function:TestPython:1', - payload['metrics']['plugins']['entityId']) - - self.assertEqual(1, len(payload['spans'])) - - span = payload['spans'][0] - self.assertEqual('aws.lambda.entry', span.n) - self.assertIsNotNone(span.t) - self.assertIsNotNone(span.s) - self.assertIsNone(span.p) - self.assertIsNotNone(span.ts) - self.assertIsNotNone(span.d) - - self.assertEqual({'hl': True, 'cp': 'aws', 'e': 'arn:aws:lambda:us-east-2:12345:function:TestPython:1'}, - span.f) - - self.assertIsNone(span.ec) - self.assertIsNone(span.data['lambda']['error']) - - self.assertEqual('arn:aws:lambda:us-east-2:12345:function:TestPython:1', span.data['lambda']['arn']) - self.assertEqual(None, span.data['lambda']['alias']) - self.assertEqual('python', span.data['lambda']['runtime']) - self.assertEqual('TestPython', span.data['lambda']['functionName']) - self.assertEqual('1', span.data['lambda']['functionVersion']) - - self.assertEqual('aws:api.gateway', span.data['lambda']['trigger']) - self.assertEqual('POST', span.data['http']['method']) - self.assertEqual('/path/to/resource', span.data['http']['url']) - self.assertEqual('/{proxy+}', span.data['http']['path_tpl']) - if sys.version[:3] == '2.7': - self.assertEqual(u"foo=[u'bar']", span.data['http']['params']) - else: - self.assertEqual("foo=['bar']", span.data['http']['params']) - - def test_application_lb_trigger_tracing(self): - with open(self.pwd + '/data/lambda/api_gateway_event.json', 'r') as json_file: - event = json.load(json_file) - - self.create_agent_and_setup_tracer() - - # Call the Instana Fargate Handler as we do in the real world. It will initiate tracing and then - # figure out the original (the users') Fargate Handler and execute it. - # The original Fargate handler is set in os.environ["LAMBDA_HANDLER"] - result = lambda_handler(event, self.context) - - self.assertEqual('All Ok', result) - payload = self.agent.collector.prepare_payload() - - self.assertTrue("metrics" in payload) - self.assertTrue("spans" in payload) - self.assertEqual(2, len(payload.keys())) - self.assertEqual('com.instana.plugin.aws.lambda', payload['metrics']['plugins']['name']) - self.assertEqual('arn:aws:lambda:us-east-2:12345:function:TestPython:1', - payload['metrics']['plugins']['entityId']) - - self.assertEqual(1, len(payload['spans'])) - - span = payload['spans'][0] - self.assertEqual('aws.lambda.entry', span.n) - self.assertIsNotNone(span.t) - self.assertIsNotNone(span.s) - self.assertIsNone(span.p) - self.assertIsNotNone(span.ts) - self.assertIsNotNone(span.d) - - self.assertEqual({'hl': True, 'cp': 'aws', 'e': 'arn:aws:lambda:us-east-2:12345:function:TestPython:1'}, - span.f) - - self.assertIsNone(span.ec) - self.assertIsNone(span.data['lambda']['error']) - - self.assertEqual('arn:aws:lambda:us-east-2:12345:function:TestPython:1', span.data['lambda']['arn']) - self.assertEqual(None, span.data['lambda']['alias']) - self.assertEqual('python', span.data['lambda']['runtime']) - self.assertEqual('TestPython', span.data['lambda']['functionName']) - self.assertEqual('1', span.data['lambda']['functionVersion']) - - self.assertEqual('aws:api.gateway', span.data['lambda']['trigger']) - self.assertEqual('POST', span.data['http']['method']) - self.assertEqual('/path/to/resource', span.data['http']['url']) - if sys.version[:3] == '2.7': - self.assertEqual(u"foo=[u'bar']", span.data['http']['params']) - else: - self.assertEqual("foo=['bar']", span.data['http']['params']) - - def test_cloudwatch_trigger_tracing(self): - with open(self.pwd + '/data/lambda/cloudwatch_event.json', 'r') as json_file: - event = json.load(json_file) - - self.create_agent_and_setup_tracer() - - # Call the Instana Fargate Handler as we do in the real world. It will initiate tracing and then - # figure out the original (the users') Fargate Handler and execute it. - # The original Fargate handler is set in os.environ["LAMBDA_HANDLER"] - result = lambda_handler(event, self.context) - - self.assertEqual('All Ok', result) - payload = self.agent.collector.prepare_payload() - - self.assertTrue("metrics" in payload) - self.assertTrue("spans" in payload) - self.assertEqual(2, len(payload.keys())) - self.assertEqual('com.instana.plugin.aws.lambda', payload['metrics']['plugins']['name']) - self.assertEqual('arn:aws:lambda:us-east-2:12345:function:TestPython:1', - payload['metrics']['plugins']['entityId']) - - self.assertEqual(1, len(payload['spans'])) - - span = payload['spans'][0] - self.assertEqual('aws.lambda.entry', span.n) - self.assertIsNotNone(span.t) - self.assertIsNotNone(span.s) - self.assertIsNone(span.p) - self.assertIsNotNone(span.ts) - self.assertIsNotNone(span.d) - - self.assertEqual({'hl': True, 'cp': 'aws', 'e': 'arn:aws:lambda:us-east-2:12345:function:TestPython:1'}, - span.f) - - self.assertIsNone(span.ec) - self.assertIsNone(span.data['lambda']['error']) - - self.assertEqual('arn:aws:lambda:us-east-2:12345:function:TestPython:1', span.data['lambda']['arn']) - self.assertEqual(None, span.data['lambda']['alias']) - self.assertEqual('python', span.data['lambda']['runtime']) - self.assertEqual('TestPython', span.data['lambda']['functionName']) - self.assertEqual('1', span.data['lambda']['functionVersion']) - - self.assertEqual('aws:cloudwatch.events', span.data['lambda']['trigger']) - self.assertEqual('cdc73f9d-aea9-11e3-9d5a-835b769c0d9c', span.data["lambda"]["cw"]["events"]["id"]) - self.assertEqual(False, span.data["lambda"]["cw"]["events"]["more"]) - self.assertTrue(type(span.data["lambda"]["cw"]["events"]["resources"]) is list) - self.assertEqual(1, len(span.data["lambda"]["cw"]["events"]["resources"])) - self.assertEqual('arn:aws:events:eu-west-1:123456789012:rule/ExampleRule', - span.data["lambda"]["cw"]["events"]["resources"][0]) - - def test_cloudwatch_logs_trigger_tracing(self): - with open(self.pwd + '/data/lambda/cloudwatch_logs_event.json', 'r') as json_file: - event = json.load(json_file) - - self.create_agent_and_setup_tracer() - - # Call the Instana Fargate Handler as we do in the real world. It will initiate tracing and then - # figure out the original (the users') Fargate Handler and execute it. - # The original Fargate handler is set in os.environ["LAMBDA_HANDLER"] - result = lambda_handler(event, self.context) - - self.assertEqual('All Ok', result) - payload = self.agent.collector.prepare_payload() - - self.assertTrue("metrics" in payload) - self.assertTrue("spans" in payload) - self.assertEqual(2, len(payload.keys())) - self.assertEqual('com.instana.plugin.aws.lambda', payload['metrics']['plugins']['name']) - self.assertEqual('arn:aws:lambda:us-east-2:12345:function:TestPython:1', - payload['metrics']['plugins']['entityId']) - - self.assertEqual(1, len(payload['spans'])) - - span = payload['spans'][0] - self.assertEqual('aws.lambda.entry', span.n) - self.assertIsNotNone(span.t) - self.assertIsNotNone(span.s) - self.assertIsNone(span.p) - self.assertIsNotNone(span.ts) - self.assertIsNotNone(span.d) - - self.assertEqual({'hl': True, 'cp': 'aws', 'e': 'arn:aws:lambda:us-east-2:12345:function:TestPython:1'}, - span.f) - - self.assertIsNone(span.ec) - self.assertIsNone(span.data['lambda']['error']) - - self.assertEqual('arn:aws:lambda:us-east-2:12345:function:TestPython:1', span.data['lambda']['arn']) - self.assertEqual(None, span.data['lambda']['alias']) - self.assertEqual('python', span.data['lambda']['runtime']) - self.assertEqual('TestPython', span.data['lambda']['functionName']) - self.assertEqual('1', span.data['lambda']['functionVersion']) - - self.assertEqual('aws:cloudwatch.logs', span.data['lambda']['trigger']) - self.assertFalse("decodingError" in span.data['lambda']['cw']['logs']) - self.assertEqual('testLogGroup', span.data['lambda']['cw']['logs']['group']) - self.assertEqual('testLogStream', span.data['lambda']['cw']['logs']['stream']) - self.assertEqual(None, span.data['lambda']['cw']['logs']['more']) - self.assertTrue(type(span.data['lambda']['cw']['logs']['events']) is list) - self.assertEqual(2, len(span.data['lambda']['cw']['logs']['events'])) - self.assertEqual('[ERROR] First test message', span.data['lambda']['cw']['logs']['events'][0]) - self.assertEqual('[ERROR] Second test message', span.data['lambda']['cw']['logs']['events'][1]) - - def test_s3_trigger_tracing(self): - with open(self.pwd + '/data/lambda/s3_event.json', 'r') as json_file: - event = json.load(json_file) - - self.create_agent_and_setup_tracer() - - # Call the Instana Fargate Handler as we do in the real world. It will initiate tracing and then - # figure out the original (the users') Fargate Handler and execute it. - # The original Fargate handler is set in os.environ["LAMBDA_HANDLER"] - result = lambda_handler(event, self.context) - - self.assertEqual('All Ok', result) - payload = self.agent.collector.prepare_payload() - - self.assertTrue("metrics" in payload) - self.assertTrue("spans" in payload) - self.assertEqual(2, len(payload.keys())) - self.assertEqual('com.instana.plugin.aws.lambda', payload['metrics']['plugins']['name']) - self.assertEqual('arn:aws:lambda:us-east-2:12345:function:TestPython:1', - payload['metrics']['plugins']['entityId']) - - self.assertEqual(1, len(payload['spans'])) - - span = payload['spans'][0] - self.assertEqual('aws.lambda.entry', span.n) - self.assertIsNotNone(span.t) - self.assertIsNotNone(span.s) - self.assertIsNone(span.p) - self.assertIsNotNone(span.ts) - self.assertIsNotNone(span.d) - - self.assertEqual({'hl': True, 'cp': 'aws', 'e': 'arn:aws:lambda:us-east-2:12345:function:TestPython:1'}, - span.f) - - self.assertIsNone(span.ec) - self.assertIsNone(span.data['lambda']['error']) - - self.assertEqual('arn:aws:lambda:us-east-2:12345:function:TestPython:1', span.data['lambda']['arn']) - self.assertEqual(None, span.data['lambda']['alias']) - self.assertEqual('python', span.data['lambda']['runtime']) - self.assertEqual('TestPython', span.data['lambda']['functionName']) - self.assertEqual('1', span.data['lambda']['functionVersion']) - - self.assertEqual('aws:s3', span.data['lambda']['trigger']) - self.assertTrue(type(span.data["lambda"]["s3"]["events"]) is list) - events = span.data["lambda"]["s3"]["events"] - self.assertEqual(1, len(events)) - event = events[0] - self.assertEqual('ObjectCreated:Put', event['event']) - self.assertEqual('example-bucket', event['bucket']) - self.assertEqual('test/key', event['object']) - - def test_sqs_trigger_tracing(self): - with open(self.pwd + '/data/lambda/sqs_event.json', 'r') as json_file: - event = json.load(json_file) - - self.create_agent_and_setup_tracer() - - # Call the Instana Fargate Handler as we do in the real world. It will initiate tracing and then - # figure out the original (the users') Fargate Handler and execute it. - # The original Fargate handler is set in os.environ["LAMBDA_HANDLER"] - result = lambda_handler(event, self.context) - - self.assertEqual('All Ok', result) - payload = self.agent.collector.prepare_payload() - - self.assertTrue("metrics" in payload) - self.assertTrue("spans" in payload) - self.assertEqual(2, len(payload.keys())) - self.assertEqual('com.instana.plugin.aws.lambda', payload['metrics']['plugins']['name']) - self.assertEqual('arn:aws:lambda:us-east-2:12345:function:TestPython:1', - payload['metrics']['plugins']['entityId']) - - self.assertEqual(1, len(payload['spans'])) - - span = payload['spans'][0] - self.assertEqual('aws.lambda.entry', span.n) - self.assertIsNotNone(span.t) - self.assertIsNotNone(span.s) - self.assertIsNone(span.p) - self.assertIsNotNone(span.ts) - self.assertIsNotNone(span.d) - - self.assertEqual({'hl': True, 'cp': 'aws', 'e': 'arn:aws:lambda:us-east-2:12345:function:TestPython:1'}, - span.f) - - self.assertIsNone(span.ec) - self.assertIsNone(span.data['lambda']['error']) - - self.assertEqual('arn:aws:lambda:us-east-2:12345:function:TestPython:1', span.data['lambda']['arn']) - self.assertEqual(None, span.data['lambda']['alias']) - self.assertEqual('python', span.data['lambda']['runtime']) - self.assertEqual('TestPython', span.data['lambda']['functionName']) - self.assertEqual('1', span.data['lambda']['functionVersion']) - - self.assertEqual('aws:sqs', span.data['lambda']['trigger']) - self.assertTrue(type(span.data["lambda"]["sqs"]["messages"]) is list) - messages = span.data["lambda"]["sqs"]["messages"] - self.assertEqual(1, len(messages)) - message = messages[0] - self.assertEqual('arn:aws:sqs:us-west-1:123456789012:MyQueue', message['queue']) - - def test_read_query_params(self): - event = { "queryStringParameters": {"foo": "bar" }, - "multiValueQueryStringParameters": { "foo": ["bar"] } } - params = read_http_query_params(event) - self.assertEqual("foo=['bar']", params) - - def test_read_query_params_with_none_data(self): - event = { "queryStringParameters": None, - "multiValueQueryStringParameters": None } - params = read_http_query_params(event) - self.assertEqual("", params) - - def test_read_query_params_with_bad_event(self): - event = None - params = read_http_query_params(event) - self.assertEqual("", params) From c3e6b6f48bec06bc4f26406a9924bf20ce288a28 Mon Sep 17 00:00:00 2001 From: Peter Giacomo Lombardo Date: Thu, 11 Jun 2020 19:47:43 +0200 Subject: [PATCH 10/12] Remove infeasible test --- tests/test_flask.py | 71 --------------------------------------------- 1 file changed, 71 deletions(-) diff --git a/tests/test_flask.py b/tests/test_flask.py index 29f8998d..4c534e8d 100644 --- a/tests/test_flask.py +++ b/tests/test_flask.py @@ -1,6 +1,5 @@ from __future__ import absolute_import -import os import sys import unittest import urllib3 @@ -28,76 +27,6 @@ def test_vanilla_requests(self): spans = self.recorder.queued_spans() self.assertEqual(1, len(spans)) - def test_custom_service_name(self): - os.environ['INSTANA_SERVICE_NAME'] = 'Legion' - with tracer.start_active_span('test'): - response = self.http.request('GET', testenv["wsgi_server"] + '/') - os.environ.pop('INSTANA_SERVICE_NAME') - - spans = self.recorder.queued_spans() - self.assertEqual(3, len(spans)) - - wsgi_span = spans[0] - urllib3_span = spans[1] - test_span = spans[2] - - assert response - self.assertEqual(200, response.status) - - assert('X-Instana-T' in response.headers) - assert(int(response.headers['X-Instana-T'], 16)) - self.assertEqual(response.headers['X-Instana-T'], wsgi_span.t) - - assert('X-Instana-S' in response.headers) - assert(int(response.headers['X-Instana-S'], 16)) - self.assertEqual(response.headers['X-Instana-S'], wsgi_span.s) - - assert('X-Instana-L' in response.headers) - self.assertEqual(response.headers['X-Instana-L'], '1') - - assert('Server-Timing' in response.headers) - server_timing_value = "intid;desc=%s" % wsgi_span.t - self.assertEqual(response.headers['Server-Timing'], server_timing_value) - - self.assertIsNone(tracer.active_span) - - # Same traceId - self.assertEqual(test_span.t, urllib3_span.t) - self.assertEqual(urllib3_span.t, wsgi_span.t) - - # Parent relationships - self.assertEqual(urllib3_span.p, test_span.s) - self.assertEqual(wsgi_span.p, urllib3_span.s) - - # Error logging - self.assertIsNone(test_span.ec) - self.assertIsNone(urllib3_span.ec) - self.assertIsNone(wsgi_span.ec) - - # wsgi - self.assertEqual("wsgi", wsgi_span.n) - self.assertEqual('127.0.0.1:' + str(testenv['wsgi_port']), wsgi_span.data["http"]["host"]) - self.assertEqual('/', wsgi_span.data["http"]["url"]) - self.assertEqual('GET', wsgi_span.data["http"]["method"]) - self.assertEqual(200, wsgi_span.data["http"]["status"]) - self.assertIsNone(wsgi_span.data["http"]["error"]) - self.assertIsNotNone(wsgi_span.stack) - self.assertEqual(2, len(wsgi_span.stack)) - self.assertEqual('Legion', wsgi_span.data['service']) - - # urllib3 - self.assertEqual("test", test_span.data["sdk"]["name"]) - self.assertEqual("urllib3", urllib3_span.n) - self.assertEqual(200, urllib3_span.data["http"]["status"]) - self.assertEqual(testenv["wsgi_server"] + '/', urllib3_span.data["http"]["url"]) - self.assertEqual("GET", urllib3_span.data["http"]["method"]) - self.assertIsNotNone(urllib3_span.stack) - self.assertTrue(type(urllib3_span.stack) is list) - self.assertTrue(len(urllib3_span.stack) > 1) - - # We should NOT have a path template for this route - self.assertIsNone(wsgi_span.data["http"]["path_tpl"]) - def test_get_request(self): with tracer.start_active_span('test'): response = self.http.request('GET', testenv["wsgi_server"] + '/') From 63a6e5edce6b56abe947d1b6e4b0a92e92f1ca65 Mon Sep 17 00:00:00 2001 From: Peter Giacomo Lombardo Date: Thu, 11 Jun 2020 19:52:04 +0200 Subject: [PATCH 11/12] Better status code logging --- instana/agent.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/instana/agent.py b/instana/agent.py index 51a62fc8..0e83ba8e 100644 --- a/instana/agent.py +++ b/instana/agent.py @@ -393,7 +393,7 @@ def report_data_payload(self, payload): self.report_headers["X-Instana-Key"] = self.options.agent_key self.report_headers["X-Instana-Time"] = str(round(time.time() * 1000)) - logger.debug("using these headers: %s", self.report_headers) + # logger.debug("using these headers: %s", self.report_headers) if 'INSTANA_DISABLE_CA_CHECK' in os.environ: ssl_verify = False @@ -406,7 +406,10 @@ def report_data_payload(self, payload): timeout=self.options.timeout, verify=ssl_verify) - logger.debug("report_data_payload: response.status_code is %s", response.status_code) + if 200 <= response.status_code < 300: + logger.debug("report_data_payload: Instana responded with status code %s", response.status_code) + else: + logger.info("report_data_payload: Instana responded with status code %s", response.status_code) except Exception as e: logger.debug("report_data_payload: connection error (%s)", type(e)) finally: From 41ac5c35cb3f4b4ef93dc45a44f159e7cecc4d14 Mon Sep 17 00:00:00 2001 From: Peter Giacomo Lombardo Date: Thu, 11 Jun 2020 19:55:37 +0200 Subject: [PATCH 12/12] Linter updates --- instana/agent.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/instana/agent.py b/instana/agent.py index 0e83ba8e..32b7b0a9 100644 --- a/instana/agent.py +++ b/instana/agent.py @@ -48,7 +48,6 @@ class BaseAgent(object): def __init__(self): self.client = requests.Session() - pass class StandardAgent(BaseAgent): @@ -149,7 +148,7 @@ def set_from(self, json_string): @param json_string: source identifiers @return: None """ - if type(json_string) is bytes: + if isinstance(json_string, bytes): raw_json = json_string.decode("UTF-8") else: raw_json = json_string