Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(agent): Fixes newrelic_notice_error() API for PHP 8+ #960

Merged
merged 9 commits into from
Sep 19, 2024
31 changes: 28 additions & 3 deletions agent/php_api.c
Original file line number Diff line number Diff line change
Expand Up @@ -58,10 +58,30 @@ void nr_php_api_add_supportability_metric(const char* name TSRMLS_DC) {

/*
* Purpose : (New Relic API) Pretend that there is an error at this exact spot.
* Useful for business logic errors. newrelic_notice_error($errstr)
* Useful for business logic errors.
* - newrelic_notice_error($errstr)
* - $errstr : string : The error message to record
* - newrelic_notice_error($exception)
* - $exception : object : The exception to use to record the exception
* NOTE: This version is compatible with being a callback for set_exception_handler()
* - newrelic_notice_error($errstr,$exception)
* - $errstr : string : The error message to record
* - $exception : object : The exception to use to record the exception
* NOTE: The $errstr value is ignored! Started with agent version 4.23
* - newrelic_notice_error($errno,$errstr,$fname,$line_nr)
* - $errno : int : The error number
* - $errstr : string : The error message
* - $fname : string : The filename where the error occurred
* - $line_nr : int : The line number where the error occurred
* NOTE: This version is compatible with being a callback for set_error_handler() for PHP 8+
* - newrelic_notice_error($errno,$errstr,$fname,$line_nr,$ctx)
* - $errno : int : The error number
* - $errstr : string : The error message
* - $fname : string : The filename where the error occurred
* - $line_nr : int : The line number where the error occurred
* - $ctx : array : The context of the error
* NOTE: This version is compatible with being a callback for set_error_handler() for PHP < 8
* The $ctx is ignored!
*/
#ifdef TAGS
void zif_newrelic_notice_error(void); /* ctags landing pad only */
Expand Down Expand Up @@ -141,10 +161,15 @@ PHP_FUNCTION(newrelic_notice_error) {
}
break;

case 4:
case 5:
/* PHP 8+ will only pass the first 4 parameters so the 5th parameter is
* declared to be optional. Also this parameter is completely ignored
* so it doesn't matter if it is passed or not.
*/
if (FAILURE
== zend_parse_parameters_ex(ZEND_PARSE_PARAMS_QUIET,
ZEND_NUM_ARGS() TSRMLS_CC, "lsslz!",
ZEND_NUM_ARGS() TSRMLS_CC, "lssl|z!",
&ignore1, &errormsgstr, &errormsglen,
&ignore2, &ignore3, &ignore4, &ignore5)) {
nrl_debug(NRL_API, "newrelic_notice_error: invalid five arguments");
Expand All @@ -153,7 +178,7 @@ PHP_FUNCTION(newrelic_notice_error) {
break;

default:
nrl_debug(NRL_API, "newrelic_notice_error: invalid number of arguments");
nrl_debug(NRL_API, "newrelic_notice_error: invalid number of arguments: %d", ZEND_NUM_ARGS());
RETURN_NULL();
}

Expand Down
13 changes: 11 additions & 2 deletions tests/integration/api/notice_error/test_bad_inputs.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@
ok - 2 args
ok - 3 args
ok - 4 args
ok - 4 args
ok - 4 args
ok - 4 args
ok - 5 args
ok - 6 args
*/
Expand All @@ -32,9 +35,15 @@
tap_equal(null, newrelic_notice_error("", 42), "2 args");
tap_equal(null, newrelic_notice_error("", array()), "2 args");

// Three and four argument forms are not allowed.
// Three argument forms are not allowed.
tap_equal(null, newrelic_notice_error(42, "message", "file"), "3 args");
tap_equal(null, newrelic_notice_error(42, "message", "file", __LINE__), "4 args");

// Four argument form requires integer, string, string, integer
// This is like the five argument form but for PHP 8+ where the context is not supplied
tap_equal(null, newrelic_notice_error("", "message", "file", __LINE__), "4 args");
tap_equal(null, newrelic_notice_error(42, array(), "file", __LINE__), "4 args");
tap_equal(null, newrelic_notice_error("", "message", array(), __LINE__), "4 args");
tap_equal(null, newrelic_notice_error("", "message", "file", ""), "4 args");

// Five argument form requires second arg to be convertible to a string.
tap_equal(null, newrelic_notice_error("", curl_init()), "5 args");
Expand Down
133 changes: 133 additions & 0 deletions tests/integration/api/notice_error/test_good_1_arg_exception.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
<?php
/*
* Copyright 2020 New Relic Corporation. All rights reserved.
* SPDX-License-Identifier: Apache-2.0
*/

/*DESCRIPTION
The agent should record a traced error when newrelic_notice_error is
called with 1 argument which is an exception.
*/

/*EXPECT_TRACED_ERRORS
[
"?? agent run id",
[
[
"?? when",
"OtherTransaction/php__FILE__",
"Noticed exception 'Exception' with message '1 arg exception' in __FILE__:??",
"Exception",
{
"stack_trace": [
" in a called at __FILE__ (??)"
],
"agentAttributes": "??",
"intrinsics": "??"
},
"?? transaction ID"
]
]
]
*/

/*EXPECT_ERROR_EVENTS
[
"?? agent run id",
{
"reservoir_size": "??",
"events_seen": 1
},
[
[
{
"type": "TransactionError",
"timestamp": "??",
"error.class": "Exception",
"error.message": "Noticed exception 'Exception' with message '1 arg exception' in __FILE__:??",
"transactionName": "OtherTransaction\/php__FILE__",
"duration": "??",
"nr.transactionGuid": "??",
"guid": "??",
"sampled": true,
"priority": "??",
"traceId": "??",
"spanId": "??"
},
{},
{}
]
]
]
*/

/*EXPECT_SPAN_EVENTS_LIKE
[
[
{
"traceId": "??",
"duration": "??",
"transactionId": "??",
"name": "Custom\/a",
"guid": "??",
"type": "Span",
"category": "generic",
"priority": "??",
"sampled": true,
"timestamp": "??",
"parentId": "??"
},
{},
{
"error.message": "Noticed exception 'Exception' with message '1 arg exception' in __FILE__:??",
"error.class": "Exception",
"code.lineno": "??",
"code.filepath": "__FILE__",
"code.function": "a"
}
]
]
*/

/*EXPECT_ANALYTICS_EVENTS
[
"?? agent run id",
{
"reservoir_size": "??",
"events_seen": "??"
},
[
[
{
"type": "Transaction",
"name": "OtherTransaction\/php__FILE__",
"timestamp": "??",
"duration": "??",
"totalTime": "??",
"guid": "??",
"sampled": true,
"priority": "??",
"traceId": "??",
"error": true
},
{},
{
"errorType": "Exception",
"errorMessage": "Noticed exception 'Exception' with message '1 arg exception' in __FILE__:??"
}
]
]
]
*/

require_once(realpath(dirname(__FILE__)) . '/../../../include/tap.php');

function a()
{
// One argument with an exception - this is tricky because most
// values are implicitly convertible to strings. Use a resource to prevent this.
newrelic_notice_error("1 arg", new Exception("1 arg exception"));
}


a();
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
<?php
/*
* Copyright 2020 New Relic Corporation. All rights reserved.
* SPDX-License-Identifier: Apache-2.0
*/

/*DESCRIPTION
The agent should record a traced error when newrelic_notice_error is
used as a callback handler for set_exception_handler().
*/

/*EXPECT_TRACED_ERRORS
[
"?? agent run id",
[
[
"?? when",
"OtherTransaction/php__FILE__",
"Noticed exception 'Exception' with message '1 arg exception' in __FILE__:??",
"Exception",
{
"stack_trace": [
" in a called at __FILE__ (??)"
],
"agentAttributes": "??",
"intrinsics": "??"
},
"?? transaction ID"
]
]
]
*/

/*EXPECT_ERROR_EVENTS
[
"?? agent run id",
{
"reservoir_size": "??",
"events_seen": 1
},
[
[
{
"type": "TransactionError",
"timestamp": "??",
"error.class": "Exception",
"error.message": "Noticed exception 'Exception' with message '1 arg exception' in __FILE__:??",
"transactionName": "OtherTransaction\/php__FILE__",
"duration": "??",
"nr.transactionGuid": "??",
"guid": "??",
"sampled": true,
"priority": "??",
"traceId": "??",
"spanId": "??"
},
{},
{}
]
]
]
*/

/*EXPECT_SPAN_EVENTS_LIKE
[
[
{
"traceId": "??",
"duration": "??",
"transactionId": "??",
"name": "OtherTransaction\/php__FILE__",
"guid": "??",
"type": "Span",
"category": "generic",
"priority": "??",
"sampled": true,
"timestamp": "??",
"nr.entryPoint": true,
"transaction.name": "OtherTransaction\/php__FILE__"
},
{},
{
"error.message": "Noticed exception 'Exception' with message '1 arg exception' in __FILE__:??",
"error.class": "Exception"
}
],
[
{
"traceId": "??",
"duration": "??",
"transactionId": "??",
"name": "Custom\/a",
"guid": "??",
"type": "Span",
"category": "generic",
"priority": "??",
"sampled": true,
"timestamp": "??",
"parentId": "??"
},
{},
{
"error.message": "Uncaught exception 'Exception' with message '1 arg exception' in __FILE__:??",
"error.class": "Exception",
"code.lineno": "??",
"code.filepath": "__FILE__",
"code.function": "a"
}
]
]
*/

/*EXPECT_ANALYTICS_EVENTS
[
"?? agent run id",
{
"reservoir_size": "??",
"events_seen": "??"
},
[
[
{
"type": "Transaction",
"name": "OtherTransaction\/php__FILE__",
"timestamp": "??",
"duration": "??",
"totalTime": "??",
"guid": "??",
"sampled": true,
"priority": "??",
"traceId": "??",
"error": true
},
{},
{
"errorType": "Exception",
"errorMessage": "Noticed exception 'Exception' with message '1 arg exception' in __FILE__:??"
}
]
]
]
*/

function a()
{
throw new Exception("1 arg exception");
}

set_exception_handler('newrelic_notice_error');
a();
Loading