|
15 | 15 | #include "fw_support.h"
|
16 | 16 | #include "util_logging.h"
|
17 | 17 | #include "nr_segment_message.h"
|
| 18 | +#include "nr_segment_external.h" |
18 | 19 | #include "lib_aws_sdk_php.h"
|
19 | 20 |
|
20 | 21 | #define PHP_PACKAGE_NAME "aws/aws-sdk-php"
|
| 22 | +#define AWS_ARN_REGEX "(arn:(aws[a-zA-Z-]*)?:lambda:)?" \ |
| 23 | + "((?<region>[a-z]{2}((-gov)|(-iso([a-z]?)))?-[a-z]+-\\d{1}):)?" \ |
| 24 | + "((?<accountId>\\d{12}):)?" \ |
| 25 | + "(function:)?" \ |
| 26 | + "(?<functionName>[a-zA-Z0-9-\\.]+)" \ |
| 27 | + "(:(?<qualifier>\\$LATEST|[a-zA-Z0-9-]+))?" |
21 | 28 |
|
22 | 29 | #if ZEND_MODULE_API_NO >= ZEND_8_1_X_API_NO /* PHP8.1+ */
|
23 | 30 | /* Service instrumentation only supported above PHP 8.1+*/
|
@@ -295,6 +302,167 @@ void nr_lib_aws_sdk_php_sqs_parse_queueurl(
|
295 | 302 | cloud_attrs->cloud_region = region;
|
296 | 303 | }
|
297 | 304 |
|
| 305 | +void nr_lib_aws_sdk_php_lambda_handle(nr_segment_t* auto_segment, |
| 306 | + char* command_name_string, |
| 307 | + size_t command_name_len, |
| 308 | + NR_EXECUTE_PROTO) { |
| 309 | + nr_segment_t* external_segment = NULL; |
| 310 | + zval** retval_ptr = NR_GET_RETURN_VALUE_PTR; |
| 311 | + |
| 312 | + nr_segment_cloud_attrs_t cloud_attrs = { |
| 313 | + .cloud_platform = "aws_lambda" |
| 314 | + }; |
| 315 | + |
| 316 | + if (NULL == auto_segment) { |
| 317 | + return; |
| 318 | + } |
| 319 | + |
| 320 | + if (NULL == command_name_string || 0 == command_name_len) { |
| 321 | + return; |
| 322 | + } |
| 323 | + |
| 324 | + if (NULL == retval_ptr) { |
| 325 | + /* Do not instrument when an exception has happened */ |
| 326 | + return; |
| 327 | + } |
| 328 | + |
| 329 | +#define AWS_COMMAND_IS(CMD) \ |
| 330 | + (command_name_len == (sizeof(CMD) - 1) && nr_streq(CMD, command_name_string)) |
| 331 | + |
| 332 | + /* Determine if we instrument this command. */ |
| 333 | + if (AWS_COMMAND_IS("invoke")) { |
| 334 | + // Command can be saved here if in the future |
| 335 | + // we instrument more than 1 lambda command |
| 336 | + } else { |
| 337 | + return; |
| 338 | + } |
| 339 | +#undef AWS_COMMAND_IS |
| 340 | + |
| 341 | + /* reconstruct the ARN */ |
| 342 | + nr_aws_lambda_invoke(NR_EXECUTE_ORIG_ARGS, &cloud_attrs); |
| 343 | + if (!cloud_attrs.cloud_resource_id) { |
| 344 | + /* we do not want to instrument if we cannot reconstruct the ARN */ |
| 345 | + return; |
| 346 | + } |
| 347 | + |
| 348 | + /* |
| 349 | + * By this point, it's been determined that this call will be instrumented so |
| 350 | + * only create the segment now, grab the parent segment start time, add our |
| 351 | + * special segment attributes/metrics then close the newly created segment. |
| 352 | + */ |
| 353 | + external_segment = nr_segment_start(NRPRG(txn), NULL, NULL); |
| 354 | + if (NULL == external_segment) { |
| 355 | + return; |
| 356 | + } |
| 357 | + /* re-use start time from auto_segment started in func_begin */ |
| 358 | + external_segment->start_time = auto_segment->start_time; |
| 359 | + cloud_attrs.aws_operation = command_name_string; |
| 360 | + |
| 361 | + /* end the segment */ |
| 362 | + nr_segment_traces_add_cloud_attributes(external_segment, &cloud_attrs); |
| 363 | + nr_segment_external_params_t external_params = {.library = "aws_sdk"}; |
| 364 | + zval* data = nr_php_get_zval_object_property(*retval_ptr, "data"); |
| 365 | + if (NULL != data && IS_ARRAY == Z_TYPE_P(data)) { |
| 366 | + zval* status_code = nr_php_zend_hash_find(Z_ARRVAL_P(data), "StatusCode"); |
| 367 | + if (NULL != status_code && IS_LONG == Z_TYPE_P(status_code)) { |
| 368 | + external_params.status = Z_LVAL_P(status_code); |
| 369 | + } |
| 370 | + zval* metadata = nr_php_zend_hash_find(Z_ARRVAL_P(data), "@metadata"); |
| 371 | + if (NULL != metadata && IS_REFERENCE == Z_TYPE_P(metadata) |
| 372 | + && IS_ARRAY == Z_TYPE_P(Z_REFVAL_P(metadata))) { |
| 373 | + zval* uri = nr_php_zend_hash_find(Z_ARRVAL_P(Z_REFVAL_P(metadata)), "effectiveUri"); |
| 374 | + if (NULL != uri && IS_STRING == Z_TYPE_P(uri)) { |
| 375 | + external_params.uri = Z_STRVAL_P(uri); |
| 376 | + } |
| 377 | + } |
| 378 | + } |
| 379 | + nr_segment_external_end(&auto_segment, &external_params); |
| 380 | +} |
| 381 | + |
| 382 | +void nr_aws_lambda_invoke(NR_EXECUTE_PROTO, nr_segment_cloud_attrs_t* cloud_attrs) { |
| 383 | + zval* call_args = nr_php_get_user_func_arg(2, NR_EXECUTE_ORIG_ARGS); |
| 384 | + zval* this_obj = NR_PHP_USER_FN_THIS(); |
| 385 | + char* arn = NULL; |
| 386 | + char* function_name = NULL; |
| 387 | + char* region = NULL; |
| 388 | + zval* region_zval = NULL; |
| 389 | + char* qualifier = NULL; |
| 390 | + zval* qualifier_zval = NULL; |
| 391 | + char* accountID = NULL; |
| 392 | + |
| 393 | + /* verify arguments */ |
| 394 | + if (NULL == call_args || IS_ARRAY != Z_TYPE_P(call_args)) { |
| 395 | + return; |
| 396 | + } |
| 397 | + zval* lambda_args = nr_php_zend_hash_index_find(Z_ARRVAL_P(call_args), 0); |
| 398 | + if (NULL == lambda_args || IS_ARRAY != Z_TYPE_P(lambda_args)) { |
| 399 | + return; |
| 400 | + } |
| 401 | + zval* lambda_name = nr_php_zend_hash_find(Z_ARRVAL_P(lambda_args), "FunctionName"); |
| 402 | + if (NULL == lambda_name || IS_STRING != Z_TYPE_P(lambda_name)) { |
| 403 | + return; |
| 404 | + } |
| 405 | + |
| 406 | + /* Compile the regex */ |
| 407 | + if (NULL == NRPRG(aws_arn_regex)) { |
| 408 | + NRPRG(aws_arn_regex) = nr_regex_create(AWS_ARN_REGEX, 0, 0); |
| 409 | + } |
| 410 | + |
| 411 | + /* Extract all information possible from the passed lambda name via regex */ |
| 412 | + nr_regex_substrings_t* matches = |
| 413 | + nr_regex_match_capture(NRPRG(aws_arn_regex), |
| 414 | + Z_STRVAL_P(lambda_name), |
| 415 | + Z_STRLEN_P(lambda_name)); |
| 416 | + function_name = nr_regex_substrings_get_named(matches, "functionName"); |
| 417 | + accountID = nr_regex_substrings_get_named(matches, "accountId"); |
| 418 | + region = nr_regex_substrings_get_named(matches, "region"); |
| 419 | + qualifier = nr_regex_substrings_get_named(matches, "qualifier"); |
| 420 | + |
| 421 | + /* suppliment missing information with API calls */ |
| 422 | + if (nr_strempty(function_name)) { |
| 423 | + /* |
| 424 | + * Cannot get the needed data. Function name is required in the |
| 425 | + * argument, so this won't happen in normal operation |
| 426 | + */ |
| 427 | + nr_regex_substrings_destroy(&matches); |
| 428 | + return; |
| 429 | + } |
| 430 | + if (nr_strempty(accountID)) { |
| 431 | + accountID = NRINI(aws_account_id); |
| 432 | + accountID = "012345"; |
| 433 | + } |
| 434 | + if (nr_strempty(region)) { |
| 435 | + region_zval = nr_php_call(this_obj, "getRegion"); |
| 436 | + if (nr_php_is_zval_valid_string(region_zval)) { |
| 437 | + region = Z_STRVAL_P(region_zval); |
| 438 | + } |
| 439 | + } |
| 440 | + if (nr_strempty(qualifier)) { |
| 441 | + qualifier_zval = NULL;//nr_php_call(this_obj, "getQualifier"); |
| 442 | + if (nr_php_is_zval_valid_string(qualifier_zval)) { |
| 443 | + qualifier = Z_STRVAL_P(qualifier_zval); |
| 444 | + } |
| 445 | + } |
| 446 | + |
| 447 | + if (!nr_strempty(accountID) && !nr_strempty(region)) { |
| 448 | + // construct the ARN |
| 449 | + if (qualifier) { |
| 450 | + arn = nr_formatf("arn:aws:lambda:%s:%s:function:%s:%s", |
| 451 | + region, accountID, function_name, qualifier); |
| 452 | + } else { |
| 453 | + arn = nr_formatf("arn:aws:lambda:%s:%s:function:%s", |
| 454 | + region, accountID, function_name); |
| 455 | + } |
| 456 | + |
| 457 | + // Attatch the ARN |
| 458 | + cloud_attrs->cloud_resource_id = arn; |
| 459 | + } |
| 460 | + |
| 461 | + nr_regex_substrings_destroy(&matches); |
| 462 | + nr_php_zval_free(®ion_zval); |
| 463 | + nr_php_zval_free(&qualifier_zval); |
| 464 | +} |
| 465 | + |
298 | 466 | char* nr_lib_aws_sdk_php_get_command_arg_value(char* command_arg_name,
|
299 | 467 | NR_EXECUTE_PROTO) {
|
300 | 468 | zval* param_array = NULL;
|
@@ -383,6 +551,10 @@ NR_PHP_WRAPPER(nr_aws_client_call) {
|
383 | 551 | nr_lib_aws_sdk_php_sqs_handle(auto_segment, command_name_string,
|
384 | 552 | Z_STRLEN_P(command_name),
|
385 | 553 | NR_EXECUTE_ORIG_ARGS);
|
| 554 | + } else if (AWS_CLASS_IS("Aws\\Lambda\\LambdaClient", "LambdaClient")) { |
| 555 | + nr_lib_aws_sdk_php_lambda_handle(auto_segment, command_name_string, |
| 556 | + Z_STRLEN_P(command_name), |
| 557 | + NR_EXECUTE_ORIG_ARGS); |
386 | 558 | }
|
387 | 559 |
|
388 | 560 | #undef AWS_CLASS_IS
|
@@ -566,5 +738,6 @@ void nr_aws_sdk_php_enable() {
|
566 | 738 | nr_php_wrap_user_function_before_after_clean(
|
567 | 739 | NR_PSTR("Aws\\AwsClient::__call"), NULL, nr_aws_client_call,
|
568 | 740 | nr_aws_client_call);
|
| 741 | + |
569 | 742 | #endif
|
570 | 743 | }
|
0 commit comments