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): improve exception handler instrumentation for PHPs 8.0+ #877

Merged
merged 9 commits into from
Apr 19, 2024
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,18 @@
*/


/*SKIPIF*/
/*
/*SKIPIF
<?php
if (version_compare(PHP_VERSION, "7.4", "<")) {
die("skip: PHP < 8.0.0 not supported\n");
}
// Fix for https://github.com/php/php-src/issues/10695, released in 8.3.0,
// makes restore_exception_handler used in exception handler work, i.e.
// it causes previous exception handler to handle exceptions thrown by
// current exception handler.
if (version_compare(PHP_VERSION, "8.3", ">=")) {
die("skip: PHP >= 8.3.0 not supported\n");
}
*/


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

/*DESCRIPTION
When a user exception handler unregisters itself as an exception handler when it handles uncaught exception,
the agent should record the error and add error attributes on all spans leading to uncaught exception as
well as the one throwing the exception. Error attributtes are not expected on the root span (because
the exception has been handled) as well as on the span created for exception handler.
*/

/*INI
newrelic.distributed_tracing_enabled=1
newrelic.transaction_tracer.threshold = 0
newrelic.span_events_enabled=1
newrelic.code_level_metrics.enabled = 0
display_errors=1
log_errors=0
*/


/*SKIPIF
<?php
if (version_compare(PHP_VERSION, "8.3", "<")) {
die("skip: PHP < 8.3.0 not supported\n");
}
// Fix for https://github.com/php/php-src/issues/13446, released in 8.3.5,
// causes infinite recursion in this test.
if (version_compare(PHP_VERSION, "8.3.5", ">=")) {
die("skip: PHP >= 8.3.5 not supported\n");
}
*/


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

/*EXPECT_SPAN_EVENTS*/
/*
[
"?? agent run id",
{
"reservoir_size": 10000,
"events_seen": 4
},
[
[
{
"category": "generic",
"type": "Span",
"guid": "??",
"traceId": "??",
"transactionId": "??",
"name": "OtherTransaction\/php__FILE__",
"timestamp": "??",
"duration": "??",
"priority": "??",
"sampled": true,
"nr.entryPoint": true,
"transaction.name": "OtherTransaction\/php__FILE__"
},
{},
{}
],
[
{
"category": "generic",
"type": "Span",
"guid": "??",
"traceId": "??",
"transactionId": "??",
"name": "Custom\/call_throw_it",
"timestamp": "??",
"duration": "??",
"priority": "??",
"sampled": true,
"parentId": "??"
},
{},
{
"error.message": "Uncaught exception 'RuntimeException' with message 'Expected unexpected happened' in __FILE__:??",
"error.class": "RuntimeException"
}
],
[
{
"category": "generic",
"type": "Span",
"guid": "??",
"traceId": "??",
"transactionId": "??",
"name": "Custom\/throw_it",
"timestamp": "??",
"duration": "??",
"priority": "??",
"sampled": true,
"parentId": "??"
},
{},
{
"error.message": "Uncaught exception 'RuntimeException' with message 'Expected unexpected happened' in __FILE__:??",
"error.class": "RuntimeException"
}
],
[
{
"category": "generic",
"type": "Span",
"guid": "??",
"traceId": "??",
"transactionId": "??",
"name": "Custom\/user_exception_handler_02",
"timestamp": "??",
"duration": "??",
"priority": "??",
"sampled": true,
"parentId": "??"
},
{},
{}
]
]
]
*/


/*EXPECT_REGEX
01 Handled uncaught exception
*/

function user_exception_handler_01(Throwable $ex) {
echo "01 Handled uncaught exception";
}

function user_exception_handler_02(Throwable $ex) {
restore_exception_handler();
throw new RuntimeException("Not able to handle, throwing another exception from handler");
echo "Should never see this";
}

function throw_it() {
throw new RuntimeException('Expected unexpected happened');
}

function call_throw_it() {
throw_it();
}

set_exception_handler('user_exception_handler_01');
set_exception_handler('user_exception_handler_02');

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

/*DESCRIPTION
When a user exception handler unregisters itself as an exception handler when it handles uncaught exception,
the agent should record the error and add error attributes on all spans leading to uncaught exception as
well as the one throwing the exception. Error attributtes are not expected on the root span (because
the exception has been handled) as well as on the span created for exception handler.
*/

/*INI
newrelic.distributed_tracing_enabled=1
newrelic.transaction_tracer.threshold = 0
newrelic.span_events_enabled=1
newrelic.code_level_metrics.enabled = 0
display_errors=1
log_errors=0
*/


/*SKIPIF
<?php
if (version_compare(PHP_VERSION, "8.3", "<")) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

test is named php835, but this just checks for 8.3?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure how to properly name the test files that need to run for PHPs 8.3.0-8.3.4 (currently the test is named tests/integration/errors/test_oapi_uncaught_handled_exception_04.php83.php) vs test files that run correctly for all 8.3.x PHPs. I'm open to suggestions.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The 835 naming convention was perfectly fine, but the 835 test only had this check:

<?php
if (version_compare(PHP_VERSION, "8.3", "<")) {
  die("skip: PHP < 8.3.0 not supported\n");
}

shouldn't it be

if (version_compare(PHP_VERSION, "8.3.5", "<")) {
  die("skip: PHP < 8.3.5 not supported\n");
}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I assumed 835 meant php 8.3.5 and above.

Copy link
Contributor

@zsistla zsistla Apr 18, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe instead of in the name, update the description inside the test explaining why the skipif and which versions we expect it to run on.
Then you can have an 83a, 83b, etc to indicate it is the same test but differences are needed due to variants 8.3?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the suggestions. See 5013256.

die("skip: PHP < 8.3.0 not supported\n");
}
*/


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

/*EXPECT_SPAN_EVENTS*/
/*
[
"?? agent run id",
{
"reservoir_size": 10000,
"events_seen": 4
},
[
[
{
"category": "generic",
"type": "Span",
"guid": "??",
"traceId": "??",
"transactionId": "??",
"name": "OtherTransaction\/php__FILE__",
"timestamp": "??",
"duration": "??",
"priority": "??",
"sampled": true,
"nr.entryPoint": true,
"transaction.name": "OtherTransaction\/php__FILE__"
},
{},
{}
],
[
{
"category": "generic",
"type": "Span",
"guid": "??",
"traceId": "??",
"transactionId": "??",
"name": "Custom\/call_throw_it",
"timestamp": "??",
"duration": "??",
"priority": "??",
"sampled": true,
"parentId": "??"
},
{},
{
"error.message": "Uncaught exception 'RuntimeException' with message 'Expected unexpected happened' in __FILE__:??",
"error.class": "RuntimeException"
}
],
[
{
"category": "generic",
"type": "Span",
"guid": "??",
"traceId": "??",
"transactionId": "??",
"name": "Custom\/throw_it",
"timestamp": "??",
"duration": "??",
"priority": "??",
"sampled": true,
"parentId": "??"
},
{},
{
"error.message": "Uncaught exception 'RuntimeException' with message 'Expected unexpected happened' in __FILE__:??",
"error.class": "RuntimeException"
}
],
[
{
"category": "generic",
"type": "Span",
"guid": "??",
"traceId": "??",
"transactionId": "??",
"name": "Custom\/user_exception_handler_02",
"timestamp": "??",
"duration": "??",
"priority": "??",
"sampled": true,
"parentId": "??"
},
{},
{}
]
]
]
*/


/*EXPECT_REGEX
01 Handled uncaught exception
*/

function user_exception_handler_01(Throwable $ex) {
echo "01 Handled uncaught exception";
}

function user_exception_handler_02(Throwable $ex) {
set_exception_handler('user_exception_handler_01');
throw new RuntimeException("Not able to handle, throwing another exception from handler");
echo "Should never see this";
}

function throw_it() {
throw new RuntimeException('Expected unexpected happened');
}

function call_throw_it() {
throw_it();
}

set_exception_handler('user_exception_handler_02');

call_throw_it();
Loading