diff --git a/agent/config.m4 b/agent/config.m4 index 822467f98..497cb74fe 100644 --- a/agent/config.m4 +++ b/agent/config.m4 @@ -171,7 +171,7 @@ if test "$PHP_NEWRELIC" = "yes"; then dnl Actually define our extension, and far more importantly, its source dnl files. - PHP_NEW_EXTENSION(newrelic, fw_cakephp.c fw_codeigniter.c fw_drupal8.c fw_drupal.c fw_drupal_common.c fw_joomla.c fw_kohana.c fw_laminas3.c fw_laravel.c fw_laravel_queue.c fw_magento1.c fw_magento2.c fw_magento_common.c fw_mediawiki.c fw_silex.c fw_slim.c fw_support.c fw_symfony4.c fw_symfony2.c fw_symfony.c fw_symfony_common.c fw_wordpress.c fw_yii.c fw_zend2.c fw_zend.c lib_doctrine2.c lib_guzzle3.c lib_guzzle4.c lib_guzzle6.c lib_guzzle_common.c lib_mongodb.c lib_phpunit.c lib_predis.c lib_zend_http.c php_agent.c php_api.c php_api_datastore.c php_api_distributed_trace.c php_api_internal.c php_autorum.c php_call.c php_curl.c php_curl_md.c php_datastore.c php_environment.c php_error.c php_execute.c php_explain.c php_explain_mysqli.c php_explain_pdo_mysql.c php_extension.c php_file_get_contents.c php_globals.c php_hash.c php_header.c php_httprequest_send.c php_internal_instrument.c php_minit.c php_mshutdown.c php_mysql.c php_mysqli.c php_newrelic.c php_nrini.c php_output.c php_pdo.c php_pdo_mysql.c php_pdo_pgsql.c php_pgsql.c php_psr7.c php_redis.c php_rinit.c php_rshutdown.c php_samplers.c php_stack.c php_stacked_segment.c php_txn.c php_user_instrument.c php_vm.c php_wrapper.c, $ext_shared,, \\$(NEWRELIC_CFLAGS)) + PHP_NEW_EXTENSION(newrelic, fw_cakephp.c fw_codeigniter.c fw_drupal8.c fw_drupal.c fw_drupal_common.c fw_joomla.c fw_kohana.c fw_laminas3.c fw_laravel.c fw_laravel_queue.c fw_lumen.c fw_magento1.c fw_magento2.c fw_magento_common.c fw_mediawiki.c fw_silex.c fw_slim.c fw_support.c fw_symfony4.c fw_symfony2.c fw_symfony.c fw_symfony_common.c fw_wordpress.c fw_yii.c fw_zend2.c fw_zend.c lib_doctrine2.c lib_guzzle3.c lib_guzzle4.c lib_guzzle6.c lib_guzzle_common.c lib_mongodb.c lib_phpunit.c lib_predis.c lib_zend_http.c php_agent.c php_api.c php_api_datastore.c php_api_distributed_trace.c php_api_internal.c php_autorum.c php_call.c php_curl.c php_curl_md.c php_datastore.c php_environment.c php_error.c php_execute.c php_explain.c php_explain_mysqli.c php_explain_pdo_mysql.c php_extension.c php_file_get_contents.c php_globals.c php_hash.c php_header.c php_httprequest_send.c php_internal_instrument.c php_minit.c php_mshutdown.c php_mysql.c php_mysqli.c php_newrelic.c php_nrini.c php_output.c php_pdo.c php_pdo_mysql.c php_pdo_pgsql.c php_pgsql.c php_psr7.c php_redis.c php_rinit.c php_rshutdown.c php_samplers.c php_stack.c php_stacked_segment.c php_txn.c php_user_instrument.c php_vm.c php_wrapper.c, $ext_shared,, \\$(NEWRELIC_CFLAGS)) PHP_SUBST(NEWRELIC_CFLAGS) diff --git a/agent/fw_hooks.h b/agent/fw_hooks.h index 3f4d01bb9..9d7d54b0e 100644 --- a/agent/fw_hooks.h +++ b/agent/fw_hooks.h @@ -29,6 +29,7 @@ extern void nr_joomla_enable(TSRMLS_D); extern void nr_kohana_enable(TSRMLS_D); extern void nr_laminas3_enable(TSRMLS_D); extern void nr_laravel_enable(TSRMLS_D); +extern void nr_lumen_enable(TSRMLS_D); extern void nr_magento1_enable(TSRMLS_D); extern void nr_magento2_enable(TSRMLS_D); extern void nr_mediawiki_enable(TSRMLS_D); diff --git a/agent/fw_lumen.c b/agent/fw_lumen.c new file mode 100644 index 000000000..0d627c967 --- /dev/null +++ b/agent/fw_lumen.c @@ -0,0 +1,209 @@ +/* + * Copyright 2020 New Relic Corporation. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "php_agent.h" +#include "php_call.h" +#include "php_error.h" +#include "php_user_instrument.h" +#include "php_execute.h" +#include "php_wrapper.h" +#include "php_hash.h" +#include "fw_hooks.h" +#include "util_logging.h" +#include "util_memory.h" +#include "util_strings.h" + +/* + * Sets the web transaction name. If strip_base == true, + * leading class path components will be stripped. + */ +static int nr_lumen_name_the_wt(const char* name TSRMLS_DC, + const char* lumen_version, + bool strip_base) { + const char* path = NULL; + + if (NULL == name) { + return NR_FAILURE; + } + + if (strip_base) { + path = strrchr(name, '\\'); + // Backslash was not found + if (NULL == path) { + path = name; + } else { + path += 1; + } + } + + nr_txn_set_path( + lumen_version, NRPRG(txn), path, NR_PATH_TYPE_ACTION, + NR_OK_TO_OVERWRITE); /* Watch out: this name is OK to overwrite */ + + return NR_SUCCESS; +} + +/* + * Wrapper around nr_lumen_name_the_wt for zval strings + */ +static int nr_lumen_name_the_wt_from_zval(const zval* name TSRMLS_DC, + const char* lumen_version, + bool strip_base) { + int rc = NR_FAILURE; + if (nrlikely(nr_php_is_zval_non_empty_string(name))) { + char* name_str = nr_strndup(Z_STRVAL_P(name), Z_STRLEN_P(name)); + rc = nr_lumen_name_the_wt(name_str TSRMLS_CC, lumen_version, strip_base); + nr_free(name_str); + } + + return rc; +} + +/* + * Core transaction naming logic. Wraps the function that correlates + * requests to routes + */ +NR_PHP_WRAPPER(nr_lumen_handle_found_route) { + zval* route_info = NULL; + + /* Warning avoidance */ + (void)wraprec; + + /* Verify that we are using Lumen, otherwise bail. */ + NR_PHP_WRAPPER_REQUIRE_FRAMEWORK(NR_FW_LUMEN); + + /* $routeInfo object used by Application */ + route_info = nr_php_arg_get(1, NR_EXECUTE_ORIG_ARGS TSRMLS_CC); + + /* We expect route_info to be an array. At index 1, if we see an + * 'as' key, then we have access to the route, otherwise, if we have + * a 'uses' key we have access to the controller and action. + * See: https://lumen.laravel.com/docs/8.x/routing#named-routes + */ + if (!nr_php_is_zval_valid_array(route_info)) { + nrl_verbosedebug(NRL_TXN, "Lumen: $routeInfo was not an array"); + NR_PHP_WRAPPER_CALL; + goto end; + } + + NR_PHP_WRAPPER_CALL; + + /* obtain $routeInfo[1] */ + zend_ulong idx = 1; + zval* route_info_pos + = nr_php_zend_hash_index_find(Z_ARRVAL_P(route_info), idx); + + /* obtain $routeInfo[1]['as'] for route name */ + zval* route_name = NULL; + if (NULL != route_info_pos) { + route_name = nr_php_zend_hash_find(Z_ARRVAL_P(route_info_pos), "as"); + } + + if (NULL != route_name) { + if (NR_SUCCESS + != nr_lumen_name_the_wt_from_zval(route_name TSRMLS_CC, "Lumen", 0)) { + nrl_verbosedebug(NRL_TXN, "Lumen: located route name is a non-string"); + } + } else { + /* No route located, use controller instead */ + nrl_verbosedebug( + NRL_TXN, + "Lumen: unable locate route, attempting to use controller instead"); + + /* obtain $routeInfo[1]['uses'] for controller name */ + zval* controller_name + = nr_php_zend_hash_find(Z_ARRVAL_P(route_info_pos), "uses"); + + if (NULL != controller_name) { + if (NR_SUCCESS + != nr_lumen_name_the_wt_from_zval(controller_name TSRMLS_CC, "Lumen", + 1)) { + nrl_verbosedebug(NRL_TXN, + "Lumen: located controller name is a non-string"); + } + + } else { + nrl_verbosedebug(NRL_TXN, "Lumen: unable to locate controller or route"); + } + } + +end: + nr_php_arg_release(&route_info); +} +NR_PHP_WRAPPER_END + +/* + * Exception handling logic. Wraps the function that routes + * exceptions to their respective handlers + */ +NR_PHP_WRAPPER(nr_lumen_exception) { + zval* exception = NULL; + + NR_UNUSED_SPECIALFN; + (void)wraprec; + + NR_PHP_WRAPPER_REQUIRE_FRAMEWORK(NR_FW_LUMEN); + +#if ZEND_MODULE_API_NO >= ZEND_5_4_X_API_NO + const char* class_name = NULL; + const char* ignored = NULL; +#else + char* class_name = NULL; + char* ignored = NULL; +#endif /* PHP >= 5.4 */ + + char* name = NULL; + + /* + * When the exception handler renders the response, name the transaction + * after the exception handler using the same format used for controller + * actions. e.g. Controller@action. + */ + class_name = get_active_class_name(&ignored TSRMLS_CC); + name = nr_formatf("%s@%s", class_name, get_active_function_name(TSRMLS_C)); + nr_lumen_name_the_wt(name TSRMLS_CC, "Lumen", 1); + nr_free(name); + + exception = nr_php_arg_get(1, NR_EXECUTE_ORIG_ARGS TSRMLS_CC); + if (NULL == exception) { + nrl_verbosedebug(NRL_FRAMEWORK, "%s: $e is NULL", __func__); + NR_PHP_WRAPPER_CALL; + goto end; + } + + NR_PHP_WRAPPER_CALL; + + nr_status_t st; + int priority = nr_php_error_get_priority(E_ERROR); + + st = nr_php_error_record_exception(NRPRG(txn), exception, priority, + NULL /* use default prefix */, + &NRPRG(exception_filters) TSRMLS_CC); + + if (NR_FAILURE == st) { + nrl_verbosedebug(NRL_FRAMEWORK, "%s: unable to record exception", __func__); + } + +end: + nr_php_arg_release(&exception); +} +NR_PHP_WRAPPER_END + +void nr_lumen_enable(TSRMLS_D) { + /* + * We set the path to 'unknown' to prevent having to name routing errors. + * This follows what is done in the symfony logic + */ + nr_txn_set_path("Lumen", NRPRG(txn), "unknown", NR_PATH_TYPE_ACTION, + NR_OK_TO_OVERWRITE); + + nr_php_wrap_user_function( + NR_PSTR("Laravel\\Lumen\\Application::handleFoundRoute"), + nr_lumen_handle_found_route TSRMLS_CC); + + nr_php_wrap_user_function( + NR_PSTR("Laravel\\Lumen\\Application::sendExceptionToHandler"), + nr_lumen_exception TSRMLS_CC); +} diff --git a/agent/php_execute.c b/agent/php_execute.c index ffd2b3eba..1d08b44ee 100644 --- a/agent/php_execute.c +++ b/agent/php_execute.c @@ -374,6 +374,8 @@ static const nr_framework_table_t all_frameworks[] = { {"Laravel", "laravel", "bootstrap/cache/compiled.php", 0, nr_laravel_enable, NR_FW_LARAVEL}, /* 5.1.0-x */ + {"Lumen", "lumen", "lumen-framework/src/helpers.php", 0, nr_lumen_enable, NR_FW_LUMEN}, + {"Magento", "magento", "app/mage.php", 0, nr_magento1_enable, NR_FW_MAGENTO1}, {"Magento2", "magento2", "magento/framework/app/bootstrap.php", 0, @@ -518,7 +520,6 @@ static nr_library_table_t libraries[] = { {"CakePHP3", "cakephp/src/core/functions.php", NULL}, {"Fuel", "fuel/core/classes/fuel.php", NULL}, {"Lithium", "lithium/core/libraries.php", NULL}, - {"Lumen", "lumen-framework/src/helpers.php", NULL}, {"Phpbb", "phpbb/request/request.php", NULL}, {"Phpixie2", "phpixie/core/classes/phpixie/pixie.php", NULL}, {"Phpixie3", "phpixie/framework.php", NULL}, diff --git a/agent/php_newrelic.h b/agent/php_newrelic.h index f5a179912..782330e1e 100644 --- a/agent/php_newrelic.h +++ b/agent/php_newrelic.h @@ -109,6 +109,7 @@ typedef enum { NR_FW_JOOMLA, NR_FW_KOHANA, NR_FW_LARAVEL, + NR_FW_LUMEN, NR_FW_MAGENTO1, NR_FW_MAGENTO2, NR_FW_MEDIAWIKI,