def test_create_index(): ConfigProvider.set( config_names.THUNDRA_TRACE_INTEGRATIONS_ELASTICSEARCH_PATH_DEPTH, '3') author1 = {"name": "Sidney Sheldon", "novels_count": 18} try: es = Elasticsearch([{'host': 'test', 'port': 3737}], max_retries=0) es.index(index='authors', doc_type='authors', body=author1, id=1) except ElasticsearchException as e: pass finally: tracer = ThundraTracer.get_instance() span = tracer.get_spans()[1] assert span.operation_name == '/authors/authors/1' assert span.class_name == constants.ClassNames['ELASTICSEARCH'] assert span.domain_name == constants.DomainNames['DB'] assert span.get_tag( constants.ESTags['ES_HOSTS']) == ['http://test:3737'] assert span.get_tag(constants.ESTags['ES_URI']) == '/authors/authors/1' assert span.get_tag(constants.ESTags['ES_BODY']) == author1 assert span.get_tag(constants.DBTags['DB_TYPE']) == 'elasticsearch' assert span.get_tag(constants.SpanTags['TOPOLOGY_VERTEX'])
def test_refresh(): ConfigProvider.set( config_names.THUNDRA_TRACE_INTEGRATIONS_ELASTICSEARCH_PATH_DEPTH, '2') try: es = Elasticsearch([{'host': 'test', 'port': 3737}], max_retries=0) res = es.indices.refresh(index='test-index') print(res) except ElasticsearchException as e: pass finally: tracer = ThundraTracer.get_instance() span = tracer.get_spans()[1] assert span.operation_name == '/test-index/_refresh' assert span.class_name == constants.ClassNames['ELASTICSEARCH'] assert span.domain_name == constants.DomainNames['DB'] assert span.get_tag( constants.ESTags['ES_HOSTS']) == ['http://test:3737'] assert span.get_tag( constants.ESTags['ES_URI']) == '/test-index/_refresh' assert span.get_tag(constants.ESTags['ES_BODY']) == {} assert span.get_tag(constants.DBTags['DB_TYPE']) == 'elasticsearch' assert span.get_tag(constants.SpanTags['TOPOLOGY_VERTEX'])
def test_retrieving_function_prefix(): ConfigProvider.set(config_names.THUNDRA_TRACE_INSTRUMENT_TRACEABLECONFIG, \ "{}.{}*{}".format(target_module_name ,target_function_prefix, target_trace_arguments)) patcher = ImportPatcher() assert patcher.get_module_function_prefix( target_module_name) == target_function_prefix
def test_mysql_integration_mask_statement(): ConfigProvider.set( config_names.THUNDRA_TRACE_INTEGRATIONS_RDB_STATEMENT_MASK, 'true') query = "SELECT 1 + 1 AS solution" connection = mysql.connector.connect(user='******', host='localhost', password='******', database='db', auth_plugin='mysql_native_password') try: cursor = connection.cursor() cursor.execute(query) for table in cursor.fetchall(): print(table) finally: tracer = ThundraTracer.get_instance() mysql_span = tracer.get_spans()[1] assert mysql_span.domain_name == constants.DomainNames['DB'] assert mysql_span.class_name == constants.ClassNames['MYSQL'] assert mysql_span.operation_name == 'db' assert mysql_span.get_tag( constants.SpanTags['OPERATION_TYPE']) == 'READ' assert mysql_span.get_tag(constants.SpanTags['DB_INSTANCE']) == 'db' assert mysql_span.get_tag(constants.SpanTags['DB_HOST']) == 'localhost' assert mysql_span.get_tag(constants.SpanTags['DB_STATEMENT']) == None assert mysql_span.get_tag( constants.SpanTags['DB_STATEMENT_TYPE']) == 'SELECT' connection.close()
def test_postgre_integration_mask_statement(): ConfigProvider.set( config_names.THUNDRA_TRACE_INTEGRATIONS_RDB_STATEMENT_MASK, 'true') query = "select 1 + 1 AS solution" connection = psycopg2.connect(user='******', host='localhost', password='******', dbname='db') try: cursor = connection.cursor() cursor.execute(query) for table in cursor.fetchall(): print(table) finally: tracer = ThundraTracer.get_instance() postgre_span = tracer.get_spans()[1] assert postgre_span.domain_name == constants.DomainNames['DB'] assert postgre_span.class_name == constants.ClassNames['POSTGRESQL'] assert postgre_span.operation_name == 'db' assert postgre_span.get_tag( constants.SpanTags['OPERATION_TYPE']) == 'READ' assert postgre_span.get_tag(constants.SpanTags['DB_INSTANCE']) == 'db' assert postgre_span.get_tag( constants.SpanTags['DB_HOST']) == 'localhost' assert postgre_span.get_tag(constants.SpanTags['DB_STATEMENT']) is None assert postgre_span.get_tag( constants.SpanTags['DB_STATEMENT_TYPE']) == 'SELECT' connection.close()
def test_http_put_body_masked(): try: ConfigProvider.set( config_names.THUNDRA_TRACE_INTEGRATIONS_HTTP_BODY_MASK, 'true') url = 'https://jsonplaceholder.typicode.com/users/3' parsed_url = urlparse(url) path = parsed_url.path normalized_path = "/users" query = parsed_url.query host = parsed_url.netloc requests.put(url, data={"message": "test"}) tracer = ThundraTracer.get_instance() http_span = tracer.get_spans()[1] assert http_span.operation_name == host + normalized_path assert http_span.domain_name == constants.DomainNames['API'] assert http_span.class_name == constants.ClassNames['HTTP'] assert http_span.get_tag(constants.SpanTags['OPERATION_TYPE']) == 'PUT' assert http_span.get_tag(constants.HttpTags['HTTP_METHOD']) == 'PUT' assert http_span.get_tag(constants.HttpTags['HTTP_URL']) == host + path assert http_span.get_tag(constants.HttpTags['HTTP_HOST']) == host assert http_span.get_tag(constants.HttpTags['HTTP_PATH']) == path assert http_span.get_tag(constants.HttpTags['QUERY_PARAMS']) == query assert http_span.get_tag(constants.HttpTags['BODY']) is None except Exception: raise finally: tracer.clear()
def test_config_correct_default_value(): ConfigProvider.__init__() assert ConfigProvider.get('thundra.agent.debug.enable') is False assert ConfigProvider.get('thundra.agent.debug.enable', True) is True assert ConfigProvider.get( 'thundra.agent.lambda.debugger.logs.enable') is False
def test_http_4xx_error_with_min_status_500(mock_actual_call, monkeypatch): ConfigProvider.set( config_names.THUNDRA_TRACE_INTEGRATIONS_HTTP_ERROR_STATUS_CODE_MIN, '500') mock_actual_call.return_value = requests.Response() mock_actual_call.return_value.status_code = 404 mock_actual_call.return_value.reason = "Not Found" url = 'http://adummyurlthatnotexists.xyz/' parsed_url = urlparse(url) path = parsed_url.path query = parsed_url.query host = parsed_url.netloc requests.get(url) tracer = ThundraTracer.get_instance() http_span = tracer.get_spans()[1] assert http_span.operation_name == host + path assert http_span.domain_name == constants.DomainNames['API'] assert http_span.class_name == constants.ClassNames['HTTP'] assert http_span.get_tag(constants.SpanTags['OPERATION_TYPE']) == 'GET' assert http_span.get_tag(constants.HttpTags['HTTP_METHOD']) == 'GET' assert http_span.get_tag(constants.HttpTags['HTTP_URL']) == host + path assert http_span.get_tag(constants.HttpTags['HTTP_HOST']) == host assert http_span.get_tag(constants.HttpTags['HTTP_PATH']) == path assert http_span.get_tag(constants.HttpTags['QUERY_PARAMS']) == query assert http_span.get_tag('error') is None assert http_span.get_tag('error.kind') is None assert http_span.get_tag('error.message') is None
def test_http_path_depth(): ConfigProvider.set(config_names.THUNDRA_TRACE_INTEGRATIONS_HTTP_URL_DEPTH, '2') try: url = 'https://jsonplaceholder.typicode.com/asd/qwe/xyz' parsed_url = urlparse(url) normalized_path = "/asd/qwe" path = parsed_url.path query = parsed_url.query host = parsed_url.netloc requests.get(url) tracer = ThundraTracer.get_instance() http_span = tracer.get_spans()[1] assert http_span.operation_name == host + normalized_path assert http_span.domain_name == constants.DomainNames['API'] assert http_span.class_name == constants.ClassNames['HTTP'] assert http_span.get_tag(constants.SpanTags['OPERATION_TYPE']) == 'GET' assert http_span.get_tag(constants.HttpTags['HTTP_METHOD']) == 'GET' assert http_span.get_tag(constants.HttpTags['HTTP_URL']) == host + path assert http_span.get_tag(constants.HttpTags['HTTP_HOST']) == host assert http_span.get_tag(constants.HttpTags['HTTP_PATH']) == path assert http_span.get_tag(constants.HttpTags['QUERY_PARAMS']) == query except Exception: raise
def test_create_span_listener_with_multiple_filter_and_listener( span_listener_with_multiple_filterers_and_listeners): sl_env_var = to_json(span_listener_with_multiple_filterers_and_listeners) ConfigProvider.set(config_names.THUNDRA_TRACE_SPAN_LISTENERCONFIG, sl_env_var) trace_support._parse_span_listeners() sl = trace_support.get_span_listeners()[0] f1 = sl.filterer.span_filters[0] f2 = sl.filterer.span_filters[1] assert type(sl) is FilteringSpanListener assert type(sl.listener) is LatencyInjectorSpanListener assert sl.listener.delay == 370 assert sl.listener.distribution == 'normal' assert sl.listener.sigma == 73 assert sl.listener.variation == 37 assert f1.class_name == 'AWS-SQS' assert f1.domain_name == 'Messaging' assert f1.tags == {'foo': 'bar'} assert f2.class_name == 'HTTP' assert f2.operation_name == 'http_request' assert f2.tags == {'http.host': 'foobar.com'}
def __init__(self, plugin_context=None, config=None): self.hooks = { 'before:invocation': self.before_invocation, 'after:invocation': self.after_invocation } self.plugin_context = plugin_context self.logger = logging.getLogger('STDOUT') if isinstance(config, LogConfig): self.config = config else: self.config = LogConfig() with LogPlugin.lock: if (not LogPlugin.wrapped) and (not ConfigProvider.get( config_names.THUNDRA_LOG_CONSOLE_DISABLE)): if PY37 or PY38: wrapt.wrap_function_wrapper('builtins', 'print', self._wrapper) else: sys.stdout = StreamToLogger(self.logger, sys.stdout) LogPlugin.wrapped = True if not ConfigProvider.get(config_names.THUNDRA_LOG_CONSOLE_DISABLE): handler = ThundraLogHandler() has_thundra_log_handler = False for log_handlers in self.logger.handlers: if isinstance(log_handlers, ThundraLogHandler): has_thundra_log_handler = True if not has_thundra_log_handler: self.logger.addHandler(handler) self.logger.setLevel(logging.INFO) handler.setLevel(logging.INFO) self.logger.propagate = False
def test_get_doc(): ConfigProvider.set( config_names.THUNDRA_TRACE_INTEGRATIONS_ELASTICSEARCH_PATH_DEPTH, '3') try: es = Elasticsearch(['one_host', 'another_host'], max_retries=0) es.get(index='test-index', doc_type='tweet', id=1) except ElasticsearchException as e: pass finally: tracer = ThundraTracer.get_instance() span = tracer.get_spans()[1] hosts = span.get_tag(constants.ESTags['ES_HOSTS']) assert span.operation_name == '/test-index/tweet/1' assert span.class_name == constants.ClassNames['ELASTICSEARCH'] assert span.domain_name == constants.DomainNames['DB'] assert len(hosts) == 2 assert 'http://one_host:9200' in hosts assert 'http://another_host:9200' in hosts assert span.get_tag(constants.ESTags['ES_METHOD']) == 'GET' assert span.get_tag( constants.ESTags['ES_URI']) == '/test-index/tweet/1' assert span.get_tag(constants.ESTags['ES_BODY']) == {} assert span.get_tag(constants.DBTags['DB_TYPE']) == 'elasticsearch' assert span.get_tag(constants.SpanTags['OPERATION_TYPE']) == 'GET' assert span.get_tag(constants.SpanTags['TOPOLOGY_VERTEX'])
def get_collector_url(): use_local = ConfigProvider.get(config_names.THUNDRA_REPORT_REST_LOCAL) if use_local: return 'http://' + constants.LOCAL_COLLECTOR_ENDPOINT + '/v1' return ConfigProvider.get( config_names.THUNDRA_REPORT_REST_BASEURL, 'https://' + utils.get_nearest_collector() + '/v1')
def test_freq_from_env(): count_freq = 37 ConfigProvider.set(config_names.THUNDRA_SAMPLER_COUNTAWARE_COUNTFREQ, '{}'.format(count_freq)) cams = CountAwareSampler() assert cams.count_freq == count_freq
def test_if_can_get_integer_tag(): tag_name = 'integerField' (env_key, env_val) = (config_names.THUNDRA_APPLICATION_TAG_PREFIX + tag_name, 3773) ConfigProvider.set(env_key, str(env_val)) application_tags = ApplicationInfoProvider.parse_application_tags() assert application_tags[tag_name] == env_val
def test_with_non_existing_listener_type(): sl_env_var = '{"type": "NonExistingSpanListener", "config": {"config": {}}}' ConfigProvider.set(config_names.THUNDRA_TRACE_SPAN_LISTENERCONFIG, sl_env_var) trace_support._parse_span_listeners() assert len(trace_support.get_span_listeners()) == 0
def test_get_report_batches(mock_report): ConfigProvider.set(config_names.THUNDRA_REPORT_REST_COMPOSITE_BATCH_SIZE, '2') reporter = Reporter('api key') batches = reporter.get_report_batches([mock_report] * 3) assert len(batches) == 2 assert batches[0] == [mock_report, mock_report] assert batches[1] == [mock_report]
def send_reports(self, reports, **opts): if not self.api_key: debug_logger("API key not set, not sending report to Thundra.") return [] headers = { 'Content-Type': 'application/json', 'Authorization': 'ApiKey ' + self.api_key } test_run_event = opts.get("test_run_event", False) rest_composite_data_enabled = ConfigProvider.get( config_names.THUNDRA_REPORT_REST_COMPOSITE_ENABLE, True) if not test_run_event: path = constants.COMPOSITE_DATA_PATH if rest_composite_data_enabled else constants.PATH else: path = constants.PATH base_url = self.get_collector_url() request_url = base_url + path if ConfigProvider.get(config_names.THUNDRA_REPORT_CLOUDWATCH_ENABLE): if ConfigProvider.get( config_names.THUNDRA_REPORT_CLOUDWATCH_COMPOSITE_ENABLE, True): if not test_run_event: reports_json = self.prepare_composite_report_json(reports) else: reports_json = self.prepare_report_json(reports) for report in reports_json: print(report) else: for report in reports: try: print(to_json(report, separators=(',', ':'))) except TypeError: logger.error(( "Couldn't dump report with type {} to json string, " "probably it contains a byte array").format( report.get('type'))) return [] if not test_run_event and rest_composite_data_enabled: reports_json = self.prepare_composite_report_json(reports) else: reports_json = self.prepare_report_json(reports) responses = [] if len(reports_json) > 0: _futures = [ self.pool.submit(self.send_batch, (request_url, headers, data)) for data in reports_json ] responses = [ future.result() for future in futures.as_completed(_futures) ] if ConfigProvider.get(config_names.THUNDRA_DEBUG_ENABLE): debug_logger("Thundra API responses: " + str(responses)) return responses
def thundra_with_request_response_skip(monkeypatch): monkeypatch.setitem(os.environ, constants.AWS_REGION, 'region') ConfigProvider.set(config_names.THUNDRA_APPLICATION_ID, '[]test') ConfigProvider.set(config_names.THUNDRA_APPLICATION_VERSION, 'version') ConfigProvider.set(config_names.THUNDRA_APPLICATION_STAGE, 'dev') ConfigProvider.set(config_names.THUNDRA_TRACE_REQUEST_SKIP, 'true') ConfigProvider.set(config_names.THUNDRA_TRACE_RESPONSE_SKIP, 'true') thundra = Thundra('api key', disable_metric=True) return thundra
def test_if_thundra_is_disabled(mock_reporter, handler, mock_event, mock_context): ConfigProvider.set(config_names.THUNDRA_TRACE_DISABLE, 'true') _, handler = handler handler(mock_event, mock_context) assert not mock_reporter.add_report.called assert not mock_reporter.send_reports.called
def test_invocation_support_error_set(handler_with_user_error, mock_context, mock_event): ConfigProvider.set(config_names.THUNDRA_APPLICATION_STAGE, 'dev') thundra, handler = handler_with_user_error handler(mock_event, mock_context) execution_context = ExecutionContextManager.get() assert execution_context.invocation_data['erroneous'] is True assert execution_context.invocation_data['errorType'] == 'Exception' assert execution_context.invocation_data['errorMessage'] == 'test'
def test_prepare_report_json_batch(mock_report): ConfigProvider.set(config_names.THUNDRA_REPORT_REST_COMPOSITE_BATCH_SIZE, '1') reporter = Reporter('api key') batched_reports = reporter.prepare_report_json([mock_report] * 2) assert len(batched_reports) == 2 reports = json.loads(batched_reports[0]) assert len(reports) == 1
def test_if_enable_trace_plugin_from_environment_variable_is_prior(): ConfigProvider.set(config_names.THUNDRA_TRACE_DISABLE, 'false') thundra = Thundra('api key', disable_trace=True) trace_exist = False for plugin in thundra.plugins: if isinstance(plugin, TracePlugin): trace_exist = True assert trace_exist is True
def before_call(self, scope, wrapped, instance, args, kwargs, response, exception): scope.span.domain_name = constants.DomainNames['DB'] scope.span.class_name = constants.ClassNames['DYNAMODB'] operation_name, request_data = args operation_type = get_operation_type(scope.span.class_name, operation_name) self.request_data = request_data.copy() self.endpoint = instance._endpoint.host.split('/')[-1] tags = { constants.SpanTags['OPERATION_TYPE']: operation_type, constants.DBTags['DB_INSTANCE']: self.endpoint, constants.DBTags['DB_TYPE']: constants.DBTypes['DYNAMODB'], constants.AwsDynamoTags['TABLE_NAME']: str(self.request_data['TableName']) if 'TableName' in self.request_data else None, constants.DBTags['DB_STATEMENT_TYPE']: operation_type, constants.AwsSDKTags['REQUEST_NAME']: operation_name, } scope.span.tags = tags # Check if Key and Item fields have any byte field and convert to string if 'Key' in self.request_data: self.escape_byte_fields(self.request_data['Key']) if 'Item' in self.request_data: self.escape_byte_fields(self.request_data['Item']) # DB statement tags should not be set on span if masked if not ConfigProvider.get( config_names. THUNDRA_TRACE_INTEGRATIONS_AWS_DYNAMODB_STATEMENT_MASK): self.OPERATION.get(operation_name, dummy_func)(scope) scope.span.set_tag(constants.SpanTags['TOPOLOGY_VERTEX'], True) if ConfigProvider.get( config_names. THUNDRA_TRACE_INTEGRATIONS_AWS_DYNAMODB_TRACEINJECTION_ENABLE): if operation_name == 'PutItem': self.inject_trace_link_on_put(scope.span, request_data, instance) if operation_name == 'UpdateItem': self.inject_trace_link_on_update(scope.span, request_data, instance) if operation_name == 'DeleteItem': self.inject_trace_link_on_delete(request_data)
def test_send_report_to_url_async(mock_requests, mock_report): ConfigProvider.set(config_names.THUNDRA_REPORT_REST_BASEURL, 'different_url/api') ConfigProvider.set(config_names.THUNDRA_REPORT_CLOUDWATCH_ENABLE, 'true') test_session = mock_requests.Session() reporter = Reporter('api key', session=test_session) responses = reporter.send_reports([mock_report]) reporter.session.post.assert_not_called() assert responses == []
def test_mongo_command_masked(): ConfigProvider.set( config_names.THUNDRA_TRACE_INTEGRATIONS_MONGODB_COMMAND_MASK, 'true') client = MongoClient('localhost', 27017) db = client.test db.list_collection_names() tracer = ThundraTracer.get_instance() span = tracer.get_spans()[1] assert span.get_tag(constants.MongoDBTags['MONGODB_COMMAND']) is None
def test_create_empty_span_listener(empty_span_listener): sl_env_var = to_json(empty_span_listener) ConfigProvider.set(config_names.THUNDRA_TRACE_SPAN_LISTENERCONFIG, sl_env_var) trace_support._parse_span_listeners() sl = trace_support.get_span_listeners()[0] assert type(sl) is FilteringSpanListener assert type(sl.listener) is ErrorInjectorSpanListener assert type(sl.filterer) is StandardSpanFilterer
def get_report_batches(self, reports): batch_size = ConfigProvider.get( config_names.THUNDRA_REPORT_REST_COMPOSITE_BATCH_SIZE) if ConfigProvider.get(config_names.THUNDRA_REPORT_CLOUDWATCH_ENABLE): batch_size = ConfigProvider.get( config_names.THUNDRA_REPORT_CLOUDWATCH_COMPOSITE_BATCH_SIZE) batches = [ reports[i:i + batch_size] for i in range(0, len(reports), batch_size) ] return batches
def test_if_error_is_added_to_report(handler_with_exception, mock_context, mock_event): ConfigProvider.set(config_names.THUNDRA_APPLICATION_STAGE, 'dev') thundra, handler = handler_with_exception try: handler(mock_event, mock_context) except Exception: pass execution_context = ExecutionContextManager.get() assert execution_context.invocation_data['erroneous'] is True assert execution_context.invocation_data['errorType'] == 'Exception' assert execution_context.invocation_data['errorMessage'] == 'hello'
def test_coldstarts(handler, mock_context, mock_event): ConfigProvider.set(config_names.THUNDRA_APPLICATION_STAGE, 'dev') thundra, handler = handler handler(mock_event, mock_context) execution_context = ExecutionContextManager.get() assert execution_context.invocation_data['coldStart'] is True assert execution_context.invocation_data['tags']['aws.lambda.invocation.coldstart'] is True handler(mock_event, mock_context) execution_context = ExecutionContextManager.get() assert execution_context.invocation_data['coldStart'] is False assert execution_context.invocation_data['tags']['aws.lambda.invocation.coldstart'] is False