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. diff --git a/instana/agent.py b/instana/agent.py index 54638412..32b7b0a9 100644 --- a/instana/agent.py +++ b/instana/agent.py @@ -39,11 +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', 'pass', 'secret'] + extra_headers = None + options = None def __init__(self): - pass + self.client = requests.Session() class StandardAgent(BaseAgent): @@ -68,9 +72,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): @@ -147,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 @@ -353,10 +354,7 @@ 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(';') + self.extra_headers = self.options.extra_http_headers if self._validate_options(): self._can_send = True @@ -394,7 +392,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 @@ -407,7 +405,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: 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/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) 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() 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..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) @@ -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..a81eb962 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 @@ -137,6 +137,7 @@ def __init__(self, span, source, **kwargs): self.d = int(round(span.duration * 1000)) self.f = source self.ec = span.tags.pop('ec', None) + self.data = DictionaryOfStan() if span.stack: self.stack = span.stack @@ -149,21 +150,20 @@ 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) + + 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 = DictionaryOfStan() 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"] @@ -174,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): - """ - 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. - - :param span: The span to search for the `span.kind` tag - :return: String - """ - kind = None - if "span.kind" in span.tags: - if span.tags["span.kind"] in self.ENTRY_KIND: - kind = "entry" - elif span.tags["span.kind"] in self.EXIT_KIND: - kind = "exit" - else: - kind = "intermediate" - return kind - - def get_span_kind_as_int(self, span): + def get_span_kind(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. + 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: Integer + :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 = 1 + kind = ("entry", 1) elif span.tags["span.kind"] in self.EXIT_KIND: - kind = 2 - else: - kind = 3 + kind = ("exit", 2) return kind @@ -223,15 +203,15 @@ 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: # 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_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_flask.py b/tests/test_flask.py index 130a7b76..4c534e8d 100644 --- a/tests/test_flask.py +++ b/tests/test_flask.py @@ -80,6 +80,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 +160,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 +240,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 +309,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 +378,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 +447,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 +516,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 +594,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 +663,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 +739,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 +807,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 0ea1214f..ff3615e5 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() @@ -103,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) @@ -120,9 +197,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'])) @@ -145,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']) @@ -172,9 +254,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'])) @@ -197,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']) @@ -223,9 +310,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'])) @@ -248,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"]) @@ -274,9 +366,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'])) @@ -299,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']) @@ -327,9 +424,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'])) @@ -352,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) @@ -379,9 +481,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'])) @@ -404,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)