From aae7611d7dbdf418c7d312a1f13b9593f4a8d2df Mon Sep 17 00:00:00 2001 From: bduranleau-nr <106178551+bduranleau-nr@users.noreply.github.com> Date: Fri, 20 Dec 2024 12:50:48 -0600 Subject: [PATCH] fix(agent): fix Drupal error and exception handling (#995) Co-authored-by: Michal Nowacki --- agent/fw_drupal8.c | 77 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 77 insertions(+) diff --git a/agent/fw_drupal8.c b/agent/fw_drupal8.c index 541cb8b87..32976c64a 100644 --- a/agent/fw_drupal8.c +++ b/agent/fw_drupal8.c @@ -9,6 +9,7 @@ #include "php_user_instrument.h" #include "php_execute.h" #include "php_wrapper.h" +#include "php_error.h" #include "fw_drupal_common.h" #include "fw_hooks.h" #include "fw_support.h" @@ -20,6 +21,62 @@ #define PHP_PACKAGE_NAME "drupal/core" +NR_PHP_WRAPPER(nr_drupal_exception) { + int priority = nr_php_error_get_priority(E_ERROR); + zval* event = NULL; + zval* exception = NULL; + + /* Warning avoidance */ + (void)wraprec; + + NR_PHP_WRAPPER_REQUIRE_FRAMEWORK(NR_FW_DRUPAL8); + + if (NR_SUCCESS != nr_txn_record_error_worthy(NRPRG(txn), priority)) { + NR_PHP_WRAPPER_CALL; + goto end; + } + + /* Get the event that was given. */ + event = nr_php_arg_get(1, NR_EXECUTE_ORIG_ARGS); + + /* Call the original function. */ + NR_PHP_WRAPPER_CALL; + + if (0 == nr_php_is_zval_valid_object(event)) { + nrl_verbosedebug(NRL_TXN, + "Drupal: ExceptionSubscriber::onException() does not " + "have an `event` parameter"); + goto end; + } + + /* + * Get the exception from the event. + */ + exception = nr_php_call(event, "getThrowable"); + if (!nr_php_is_zval_valid_object(exception)) { + // be abundantly cautious: free exception before attempting to re-assign + nr_php_zval_free(&exception); + exception = nr_php_call(event, "getException"); + } + + if (!nr_php_is_zval_valid_object(exception)) { + nrl_verbosedebug(NRL_TXN, "Drupal: getException() returned a non-object"); + goto end; + } + + if (NR_SUCCESS + != nr_php_error_record_exception(NRPRG(txn), exception, priority, true, + NULL, + &NRPRG(exception_filters))) { + nrl_verbosedebug(NRL_TXN, "Drupal: unable to record exception"); + } + +end: + nr_php_arg_release(&event); + nr_php_zval_free(&exception); +} +NR_PHP_WRAPPER_END + /* * Purpose : Convenience function to handle adding a callback to a method, * given a class entry and a method name. This will check the @@ -730,6 +787,26 @@ void nr_drupal8_enable(TSRMLS_D) { "er::getControllerFromDefinition"), nr_drupal8_name_the_wt TSRMLS_CC); + /* + * ExceptionSubscribers handle Drupal errors and exceptions before + * the agent has the opportunity to capture them. Instrument several + * of these ExceptionSubscriber function `onException` methods in order + * to capture Exceptions and Errors in Drupal 9.x+ + */ + // clang-format off + /* + * Log exceptions without further handling. + */ + nr_php_wrap_user_function(NR_PSTR("Drupal\\Core\\EventSubscriber\\ExceptionLoggingSubscriber::onException"), + nr_drupal_exception); + + /* + * Last-chance handler for exceptions: the final exception subscriber. + */ + nr_php_wrap_user_function(NR_PSTR("Drupal\\Core\\EventSubscriber\\FinalExceptionSubscriber::onException"), + nr_drupal_exception); + // clang-format on + /* * The drupal_modules config setting controls instrumentation of modules, * hooks, and views.