diff --git a/agent/lib_aws_sdk_php.c b/agent/lib_aws_sdk_php.c index 46a71ce3c..02a0ef4bb 100644 --- a/agent/lib_aws_sdk_php.c +++ b/agent/lib_aws_sdk_php.c @@ -15,9 +15,16 @@ #include "fw_support.h" #include "util_logging.h" #include "nr_segment_message.h" +#include "nr_segment_external.h" #include "lib_aws_sdk_php.h" #define PHP_PACKAGE_NAME "aws/aws-sdk-php" +#define AWS_LAMBDA_ARN_REGEX "(arn:(aws[a-zA-Z-]*)?:lambda:)?" \ + "((?[a-z]{2}((-gov)|(-iso([a-z]?)))?-[a-z]+-\\d{1}):)?" \ + "((?\\d{12}):)?" \ + "(function:)?" \ + "(?[a-zA-Z0-9-\\.]+)" \ + "(:(?\\$LATEST|[a-zA-Z0-9-]+))?" #if ZEND_MODULE_API_NO >= ZEND_8_1_X_API_NO /* PHP8.1+ */ /* Service instrumentation only supported above PHP 8.1+*/ @@ -295,6 +302,194 @@ void nr_lib_aws_sdk_php_sqs_parse_queueurl( cloud_attrs->cloud_region = region; } +void nr_lib_aws_sdk_php_lambda_handle(nr_segment_t* auto_segment, + char* command_name_string, + size_t command_name_len, + NR_EXECUTE_PROTO) { + nr_segment_t* external_segment = NULL; + zval** retval_ptr = NR_GET_RETURN_VALUE_PTR; + + nr_segment_cloud_attrs_t cloud_attrs = { + .cloud_platform = "aws_lambda" + }; + + if (NULL == auto_segment) { + return; + } + + if (NULL == command_name_string || 0 == command_name_len) { + return; + } + + if (NULL == *retval_ptr) { + /* Do not instrument when an exception has happened */ + return; + } + +#define AWS_COMMAND_IS(CMD) \ + (command_name_len == (sizeof(CMD) - 1) && nr_streq(CMD, command_name_string)) + + /* Determine if we instrument this command. */ + if (AWS_COMMAND_IS("invoke")) { + /* reconstruct the ARN */ + nr_aws_sdk_lambda_client_invoke_parse_args(NR_EXECUTE_ORIG_ARGS, &cloud_attrs); + } else { + return; + } +#undef AWS_COMMAND_IS + + /* + * By this point, it's been determined that this call will be instrumented so + * only create the segment now, grab the parent segment start time, add our + * special segment attributes/metrics then close the newly created segment. + */ + external_segment = nr_segment_start(NRPRG(txn), NULL, NULL); + if (NULL == external_segment) { + nr_free(cloud_attrs.cloud_resource_id); + return; + } + /* re-use start time from auto_segment started in func_begin */ + external_segment->start_time = auto_segment->start_time; + cloud_attrs.aws_operation = command_name_string; + + /* end the segment */ + nr_segment_traces_add_cloud_attributes(external_segment, &cloud_attrs); + nr_segment_external_params_t external_params = {.library = "aws_sdk"}; + zval* data = nr_php_get_zval_object_property(*retval_ptr, "data"); + if (nr_php_is_zval_valid_array(data)) { + zval* status_code = nr_php_zend_hash_find(Z_ARRVAL_P(data), "StatusCode"); + if (nr_php_is_zval_valid_integer(status_code)) { + external_params.status = Z_LVAL_P(status_code); + } + zval* metadata = nr_php_zend_hash_find(Z_ARRVAL_P(data), "@metadata"); + if (NULL != metadata && IS_REFERENCE == Z_TYPE_P(metadata)) { + metadata = Z_REFVAL_P(metadata); + } + if (nr_php_is_zval_valid_array(metadata)) { + zval* uri = nr_php_zend_hash_find(Z_ARRVAL_P(metadata), "effectiveUri"); + if (nr_php_is_zval_non_empty_string(uri)) { + external_params.uri = Z_STRVAL_P(uri); + } + } + + } + nr_segment_external_end(&external_segment, &external_params); + nr_free(cloud_attrs.cloud_resource_id); +} + +/* This stores the compiled regex to parse AWS ARNs. The compilation happens when + * it is first needed and is destroyed in mshutdown + */ +static nr_regex_t* aws_arn_regex; + +static void nr_aws_sdk_compile_regex(void) { + aws_arn_regex = nr_regex_create(AWS_LAMBDA_ARN_REGEX, 0, 0); +} + +void nr_aws_sdk_mshutdown(void) { + nr_regex_destroy(&aws_arn_regex); +} + +void nr_aws_sdk_lambda_client_invoke_parse_args(NR_EXECUTE_PROTO, nr_segment_cloud_attrs_t* cloud_attrs) { + zval* call_args = nr_php_get_user_func_arg(2, NR_EXECUTE_ORIG_ARGS); + zval* this_obj = NR_PHP_USER_FN_THIS(); + char* arn = NULL; + char* function_name = NULL; + char* region = NULL; + zval* region_zval = NULL; + char* qualifier = NULL; + char* accountID = NULL; + bool using_account_id_ini = false; + + /* verify arguments */ + if (!nr_php_is_zval_valid_array(call_args)) { + return; + } + zval* lambda_args = nr_php_zend_hash_index_find(Z_ARRVAL_P(call_args), 0); + if (!nr_php_is_zval_valid_array(lambda_args)) { + return; + } + zval* lambda_name = nr_php_zend_hash_find(Z_ARRVAL_P(lambda_args), "FunctionName"); + if (!nr_php_is_zval_non_empty_string(lambda_name)) { + return; + } + + /* Ensure regex exists */ + if (NULL == aws_arn_regex) { + nr_aws_sdk_compile_regex(); + } + + /* Extract all information possible from the passed lambda name via regex */ + nr_regex_substrings_t* matches = + nr_regex_match_capture(aws_arn_regex, + Z_STRVAL_P(lambda_name), + Z_STRLEN_P(lambda_name)); + function_name = nr_regex_substrings_get_named(matches, "functionName"); + accountID = nr_regex_substrings_get_named(matches, "accountId"); + region = nr_regex_substrings_get_named(matches, "region"); + qualifier = nr_regex_substrings_get_named(matches, "qualifier"); + + /* supplement missing information with API calls */ + if (nr_strempty(function_name)) { + /* + * Cannot get the needed data. Function name is required in the + * argument, so this won't happen in normal operation + */ + nr_free(function_name); + nr_free(accountID); + nr_free(region); + nr_free(qualifier); + nr_regex_substrings_destroy(&matches); + return; + } + if (nr_strempty(accountID)) { + nr_free(accountID); + accountID = NRINI(aws_account_id); + using_account_id_ini = true; + } + if (nr_strempty(region)) { + zend_class_entry* base_class = NULL; + if (NULL != execute_data->func && NULL!= execute_data->func->common.scope) { + base_class = execute_data->func->common.scope; + } + region_zval + = nr_php_get_zval_object_property_with_class(this_obj, base_class, "region"); + if (nr_php_is_zval_valid_string(region_zval)) { + /* + * In this case, region is likely to be NULL, but could be an empty + * string instead, so we must free + */ + nr_free(region); + region = Z_STRVAL_P(region_zval); + } + } + + if (!nr_strempty(accountID) && !nr_strempty(region)) { + /* construct the ARN */ + if (!nr_strempty(qualifier)) { + arn = nr_formatf("arn:aws:lambda:%s:%s:function:%s:%s", + region, accountID, function_name, qualifier); + } else { + arn = nr_formatf("arn:aws:lambda:%s:%s:function:%s", + region, accountID, function_name); + } + + /* Attach the ARN */ + cloud_attrs->cloud_resource_id = arn; + } + + nr_regex_substrings_destroy(&matches); + nr_free(function_name); + if (!using_account_id_ini) { + nr_free(accountID); + } + /* if region_zval is a valid string, we have already freed region */ + if (!nr_php_is_zval_valid_string(region_zval)) { + nr_free(region); + } + nr_free(qualifier); +} + char* nr_lib_aws_sdk_php_get_command_arg_value(char* command_arg_name, NR_EXECUTE_PROTO) { zval* param_array = NULL; @@ -383,6 +578,10 @@ NR_PHP_WRAPPER(nr_aws_client_call) { nr_lib_aws_sdk_php_sqs_handle(auto_segment, command_name_string, Z_STRLEN_P(command_name), NR_EXECUTE_ORIG_ARGS); + } else if (AWS_CLASS_IS("Aws\\Lambda\\LambdaClient", "LambdaClient")) { + nr_lib_aws_sdk_php_lambda_handle(auto_segment, command_name_string, + Z_STRLEN_P(command_name), + NR_EXECUTE_ORIG_ARGS); } #undef AWS_CLASS_IS @@ -566,5 +765,6 @@ void nr_aws_sdk_php_enable() { nr_php_wrap_user_function_before_after_clean( NR_PSTR("Aws\\AwsClient::__call"), NULL, nr_aws_client_call, nr_aws_client_call); + #endif } diff --git a/agent/lib_aws_sdk_php.h b/agent/lib_aws_sdk_php.h index 6eb0c1e54..1b52a4772 100644 --- a/agent/lib_aws_sdk_php.h +++ b/agent/lib_aws_sdk_php.h @@ -74,6 +74,24 @@ extern void nr_lib_aws_sdk_php_sqs_handle(nr_segment_t* segment, size_t command_name_len, NR_EXECUTE_PROTO); +/* + * Purpose : Handle when a LambdaClient::invoke command happens + * + * Params : 1. NR_EXECUTE_ORIG_ARGS (execute_data, func_return_value) + * 2. cloud_attrs : the cloud attributes pointer to be + * populated with the ARN + * + * Returns : + * + * Note: The caller is responsible for freeing cloud_attrs->cloud_resource_id + */ +void nr_aws_sdk_lambda_client_invoke_parse_args(NR_EXECUTE_PROTO, nr_segment_cloud_attrs_t* cloud_attrs); + +/* + * Purpose : Handles regex destruction during mshutdown + */ +void nr_aws_sdk_mshutdown(void); + /* * Purpose : The second argument to the Aws/AwsClient::__call function should be * an array, the first element of which is itself an array of arguments that diff --git a/agent/php_mshutdown.c b/agent/php_mshutdown.c index 1c67d38b4..f3ac15b16 100644 --- a/agent/php_mshutdown.c +++ b/agent/php_mshutdown.c @@ -42,6 +42,10 @@ PHP_MSHUTDOWN_FUNCTION(newrelic) { nr_wordpress_mshutdown(); +#if ZEND_MODULE_API_NO >= ZEND_8_1_X_API_NO /* PHP 8.1+ */ + nr_aws_sdk_mshutdown(); +#endif + /* restore header handler */ sapi_module.header_handler = NR_PHP_PROCESS_GLOBALS(orig_header_handler); NR_PHP_PROCESS_GLOBALS(orig_header_handler) = NULL; diff --git a/agent/php_newrelic.h b/agent/php_newrelic.h index 6afaf531a..6b59d5e34 100644 --- a/agent/php_newrelic.h +++ b/agent/php_newrelic.h @@ -328,7 +328,6 @@ bool wordpress_core; /* set based on nrinistr_t wordpress_hooks_skip_filename; /* newrelic.framework.wordpress.hooks_skip_filename */ - nrinibool_t analytics_events_enabled; /* DEPRECATED newrelic.analytics_events.enabled */ nrinibool_t @@ -383,6 +382,11 @@ nrinibool_t nrinibool_t database_name_reporting_enabled; /* newrelic.datastore_tracer.database_name_reporting.enabled */ +/* + * Cloud relationship settings + */ +nrinistr_t + aws_account_id; /* newrelic.cloud.aws.account_id */ /* * Deprecated settings that control request parameter capture. @@ -464,6 +468,7 @@ nr_stack_t wordpress_tag_states; /* stack of bools indicating bool check_cufa; /* Whether we need to check cufa because we are instrumenting hooks, or whether we can skip cufa */ char* wordpress_tag; /* The current WordPress tag */ + #endif //OAPI nr_matcher_t* wordpress_plugin_matcher; /* Matcher for plugin filenames */ @@ -481,6 +486,8 @@ int php_cur_stack_depth; /* Total current depth of PHP stack, measured in PHP nrphpcufafn_t cufa_callback; /* The current call_user_func_array callback, if any */ + +nr_regex_t* aws_arn_regex; /* The compiled regex to search for ARNs */ /* * We instrument database connection constructors and store the instance * information in a hash keyed by a string containing the connection resource diff --git a/agent/php_nrini.c b/agent/php_nrini.c index 4a2f7c471..5c8c17d10 100644 --- a/agent/php_nrini.c +++ b/agent/php_nrini.c @@ -1212,6 +1212,33 @@ static PHP_INI_MH(nr_string_mh) { return FAILURE; } +static PHP_INI_MH(nr_aws_account_id_mh) { + nrinistr_t* p; + const int AWS_ACCOUNT_ID_SIZE = 12; + +#ifndef ZTS + char* base = (char*)mh_arg2; +#else + char* base = (char*)ts_resource(*((int*)mh_arg2)); +#endif + + p = (nrinistr_t*)(base + (size_t)mh_arg1); + + (void)entry; + (void)mh_arg3; + NR_UNUSED_TSRMLS; + + p->where = 0; + + if (NEW_VALUE_LEN == AWS_ACCOUNT_ID_SIZE) { + p->value = NEW_VALUE; + p->where = stage; + return SUCCESS; + } + + return FAILURE; +} + static PHP_INI_MH(nr_boolean_mh) { nrinibool_t* p; int val = 0; @@ -3100,6 +3127,18 @@ STD_PHP_INI_ENTRY_EX("newrelic.vulnerability_management.composer_api.enabled", newrelic_globals, nr_enabled_disabled_dh) +/* + * Cloud relationship settings + */ +STD_PHP_INI_ENTRY_EX("newrelic.cloud.aws.account_id", + "", + NR_PHP_REQUEST, + nr_aws_account_id_mh, + aws_account_id, + zend_newrelic_globals, + newrelic_globals, + 0) + /* * Messaging API */ diff --git a/agent/scripts/newrelic.ini.template b/agent/scripts/newrelic.ini.template index b406628a9..4bb9eb80e 100644 --- a/agent/scripts/newrelic.ini.template +++ b/agent/scripts/newrelic.ini.template @@ -1352,3 +1352,17 @@ newrelic.daemon.logfile = "/var/log/newrelic/newrelic-daemon.log" ; newrelic.span_events.attributes.include/exclude ; ;newrelic.message_tracer.segment_parameters.enabled = true + +; Setting: newrelic.cloud.aws.account_id +; Type : string +; Scope : per-directory +; Default: none +; Info : This setting is read by some cloud service instrumentation so the +; cloud.resource_id attribute can be set in the respective spans. +; Do not include any "-" characters; this should be 12 characters. +; +; AWS DynamoDB and Kinesis are services that require this value to be +; able to populate the cloud.resource_id attribute. Likewise, AWS Lambda +; requires that this value when the account ID is not part of the function name. +; +;newrelic.cloud.aws.account_id = "" diff --git a/agent/tests/test_lib_aws_sdk_php.c b/agent/tests/test_lib_aws_sdk_php.c index 0ac040424..83c6962a8 100644 --- a/agent/tests/test_lib_aws_sdk_php.c +++ b/agent/tests/test_lib_aws_sdk_php.c @@ -61,6 +61,30 @@ NR_PHP_WRAPPER(expect_arg_value_null) { } NR_PHP_WRAPPER_END +NR_PHP_WRAPPER(aws_lambda_invoke_wrapper) { + nr_segment_cloud_attrs_t cloud_attrs = {0}; + /* + * Because argument 1 is not used in instrumentation, we will use it + * to pass in the expected value + */ + zval* expected = nr_php_get_user_func_arg(1, NR_EXECUTE_ORIG_ARGS); + nr_aws_sdk_lambda_client_invoke_parse_args(NR_EXECUTE_ORIG_ARGS, &cloud_attrs); + (void)wraprec; + + if (nr_php_is_zval_valid_string(expected)) { + tlib_pass_if_str_equal("Expected should match reconstructed arn", + Z_STRVAL_P(expected), + cloud_attrs.cloud_resource_id); + } else { + tlib_pass_if_str_equal("Expected should match reconstructed arn", + NULL, + cloud_attrs.cloud_resource_id); + } + NR_PHP_WRAPPER_CALL; + nr_free(cloud_attrs.cloud_resource_id); +} +NR_PHP_WRAPPER_END + static void test_nr_lib_aws_sdk_php_get_command_arg_value() { zval* expr = NULL; zval* first_arg = NULL; @@ -458,14 +482,190 @@ static void test_nr_lib_aws_sdk_php_handle_version(void) { tlib_php_request_end(); } +#if ZEND_MODULE_API_NO >= ZEND_8_1_X_API_NO +static void test_nr_lib_aws_sdk_php_lambda_invoke() { + tlib_php_engine_create(""); + tlib_php_request_start(); + + tlib_php_request_eval("function lambda_invoke($a, $b) { return; }"); + nr_php_wrap_user_function(NR_PSTR("lambda_invoke"), aws_lambda_invoke_wrapper); + + NRINI(aws_account_id) = "111122223333"; + + /* Test full-info run */ + char* args + = "array(" + " 0 => array(" + " 'FunctionName' => 'us-east-2:012345678901:function:my-function'" + " )" + ")"; + zval* array_arg = tlib_php_request_eval_expr(args); + char* expect = "'arn:aws:lambda:us-east-2:012345678901:function:my-function'"; + zval* expect_arg = tlib_php_request_eval_expr(expect); + zval* expr = nr_php_call(NULL, "lambda_invoke", expect_arg, array_arg); + tlib_pass_if_not_null("Expression should evaluate.", expr); + nr_php_zval_free(&expr); + nr_php_zval_free(&expect_arg); + nr_php_zval_free(&array_arg); + + /* Test alias full-info run */ + args + = "array(" + " 0 => array(" + " 'FunctionName' => 'us-east-2:012345678901:function:my-function:v1'" + " )" + ")"; + array_arg = tlib_php_request_eval_expr(args); + expect = "'arn:aws:lambda:us-east-2:012345678901:function:my-function:v1'"; + expect_arg = tlib_php_request_eval_expr(expect); + expr = nr_php_call(NULL, "lambda_invoke", expect_arg, array_arg); + tlib_pass_if_not_null("Expression should evaluate.", expr); + nr_php_zval_free(&expr); + nr_php_zval_free(&expect_arg); + nr_php_zval_free(&array_arg); + + /* Test INI extract */ + args + = "array(" + " 0 => array(" + " 'FunctionName' => 'us-east-2:my-function'" + " )" + ")"; + array_arg = tlib_php_request_eval_expr(args); + expect = "'arn:aws:lambda:us-east-2:111122223333:function:my-function'"; + expect_arg = tlib_php_request_eval_expr(expect); + expr = nr_php_call(NULL, "lambda_invoke", expect_arg, array_arg); + tlib_pass_if_not_null("Expression should evaluate.", expr); + nr_php_zval_free(&expr); + nr_php_zval_free(&expect_arg); + nr_php_zval_free(&array_arg); + + /* Test failed INI extract */ + NRINI(aws_account_id) = ""; + args + = "array(" + " 0 => array(" + " 'FunctionName' => 'us-east-2:my-function'" + " )" + ")"; + array_arg = tlib_php_request_eval_expr(args); + expect = "NULL"; + expect_arg = tlib_php_request_eval_expr(expect); + expr = nr_php_call(NULL, "lambda_invoke", expect_arg, array_arg); + tlib_pass_if_not_null("Expression should evaluate.", expr); + nr_php_zval_free(&expr); + nr_php_zval_free(&array_arg); + NRINI(aws_account_id) = "111122223333"; + + /* Test NULL INI */ + NRINI(aws_account_id) = NULL; + args + = "array(" + " 0 => array(" + " 'FunctionName' => 'us-east-2:my-function'" + " )" + ")"; + array_arg = tlib_php_request_eval_expr(args); + expr = nr_php_call(NULL, "lambda_invoke", expect_arg, array_arg); + tlib_pass_if_not_null("Expression should evaluate.", expr); + nr_php_zval_free(&expr); + nr_php_zval_free(&array_arg); + NRINI(aws_account_id) = "111122223333"; + + /* Test invalid arg 1 */ + args + = "array(" + " 0 => array(" + " 'FunctionName' => 123" + " )" + ")"; + array_arg = tlib_php_request_eval_expr(args); + expr = nr_php_call(NULL, "lambda_invoke", expect_arg, array_arg); + tlib_pass_if_not_null("Expression should evaluate.", expr); + nr_php_zval_free(&expr); + nr_php_zval_free(&array_arg); + + /* Test invalid arg 2 */ + args + = "array(" + " 0 => array(" + " )" + ")"; + array_arg = tlib_php_request_eval_expr(args); + expr = nr_php_call(NULL, "lambda_invoke", expect_arg, array_arg); + tlib_pass_if_not_null("Expression should evaluate.", expr); + nr_php_zval_free(&expr); + nr_php_zval_free(&array_arg); + + /* Test invalid arg 3 */ + args = "array()"; + array_arg = tlib_php_request_eval_expr(args); + expr = nr_php_call(NULL, "lambda_invoke", expect_arg, array_arg); + tlib_pass_if_not_null("Expression should evaluate.", expr); + nr_php_zval_free(&expr); + nr_php_zval_free(&array_arg); + + /* Test invalid arg 4 */ + args + = "array(" + " 0 => array(" + " 'FunctionName' => ''" + " )" + ")"; + array_arg = tlib_php_request_eval_expr(args); + expr = nr_php_call(NULL, "lambda_invoke", expect_arg, array_arg); + tlib_pass_if_not_null("Expression should evaluate.", expr); + nr_php_zval_free(&expr); + nr_php_zval_free(&array_arg); + + /* Test invalid arg 5 */ + args + = "array(" + " 0 => array(" + " 'FunctionName' => NULL" + " )" + ")"; + array_arg = tlib_php_request_eval_expr(args); + expr = nr_php_call(NULL, "lambda_invoke", expect_arg, array_arg); + tlib_pass_if_not_null("Expression should evaluate.", expr); + nr_php_zval_free(&expr); + nr_php_zval_free(&expect_arg); + nr_php_zval_free(&array_arg); + + tlib_php_request_end(); + tlib_php_engine_destroy(); +} + +#endif /* PHP 8.1+ */ + +static void test_nr_lib_aws_sdk_ini() { + /* test too short */ + tlib_php_engine_create("newrelic.cloud.aws.account_id=\"12345678901\""); + tlib_php_request_start(); + tlib_pass_if_str_equal("Expected short account id to be dropped", + NULL, NRINI(aws_account_id)); + tlib_php_request_end(); + tlib_php_engine_destroy(); + + /* test too long */ + tlib_php_engine_create("newrelic.cloud.aws.account_id=\"1234567890123\""); + tlib_php_request_start(); + tlib_pass_if_str_equal("Expected short account id to be dropped", + NULL, NRINI(aws_account_id)); + tlib_php_request_end(); + tlib_php_engine_destroy(); +} + void test_main(void* p NRUNUSED) { tlib_php_engine_create(""); test_nr_lib_aws_sdk_php_add_supportability_service_metric(); test_nr_lib_aws_sdk_php_handle_version(); tlib_php_engine_destroy(); + test_nr_lib_aws_sdk_ini(); #if ZEND_MODULE_API_NO >= ZEND_8_1_X_API_NO test_nr_lib_aws_sdk_php_sqs_parse_queueurl(); test_nr_lib_aws_sdk_php_get_command_arg_value(); + test_nr_lib_aws_sdk_php_lambda_invoke(); #endif /* PHP 8.1+ */ } #else diff --git a/axiom/nr_segment.h b/axiom/nr_segment.h index 646f11e5f..1e9cce8ec 100644 --- a/axiom/nr_segment.h +++ b/axiom/nr_segment.h @@ -159,6 +159,8 @@ typedef struct _nr_segment_cloud_attrs_t { relationship.*/ char* cloud_account_id; /*The cloud provider account ID. Needed for SQS relationship.*/ + char* cloud_platform; /*The platform hosting the cloud. Needed for Lambda + relationship.*/ char* cloud_resource_id; /*Unique cloud provider identifier. For AWS, this is the ARN of the AWS resource being accessed.*/ char* aws_operation; /*AWS specific operation name.*/ diff --git a/axiom/nr_segment_traces.c b/axiom/nr_segment_traces.c index d2f1fddac..6d8870565 100644 --- a/axiom/nr_segment_traces.c +++ b/axiom/nr_segment_traces.c @@ -658,4 +658,9 @@ extern void nr_segment_traces_add_cloud_attributes( segment->attributes, NR_CLOUD_AGENT_ATTRIBUTE_DESTINATION, NR_ATTR_AWS_OPERATION, cloud_attrs->aws_operation); } + if (!nr_strempty(cloud_attrs->cloud_platform)) { + nr_attributes_agent_add_string( + segment->attributes, NR_CLOUD_AGENT_ATTRIBUTE_DESTINATION, + NR_ATTR_CLOUD_PLATFORM, cloud_attrs->cloud_platform); + } } diff --git a/axiom/nr_span_event.h b/axiom/nr_span_event.h index 33dced5c2..54d52e025 100644 --- a/axiom/nr_span_event.h +++ b/axiom/nr_span_event.h @@ -24,6 +24,7 @@ #define NR_ATTR_CLOUD_REGION "cloud.region" #define NR_ATTR_CLOUD_ACCOUNT_ID "cloud.account.id" #define NR_ATTR_CLOUD_RESOURCE_ID "cloud.resource_id" +#define NR_ATTR_CLOUD_PLATFORM "cloud.platform" #define NR_ATTR_AWS_OPERATION "aws.operation" typedef struct _nr_span_event_t nr_span_event_t;