Skip to content

Conversation

hahuja2
Copy link
Contributor

@hahuja2 hahuja2 commented Jul 18, 2024

This PR does the following:

  • Create new function called nr_txn_record_error_with_additional_attributes, which takes in the additional parameters that are passed in newrelic_notice_error and adds them as error attributes.
  • Add unit tests to test new functions
  • Add integration tests to verify additional parameters are showing up as error attributes

@codecov-commenter
Copy link

codecov-commenter commented Jul 18, 2024

Codecov Report

Attention: Patch coverage is 94.56522% with 5 lines in your changes missing coverage. Please review.

Project coverage is 78.17%. Comparing base (06824b7) to head (fa99aeb).
Report is 4 commits behind head on dev.

Files Patch % Lines
axiom/nr_errors.c 85.29% 5 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##              dev     #942      +/-   ##
==========================================
- Coverage   78.56%   78.17%   -0.40%     
==========================================
  Files         193      194       +1     
  Lines       27294    26911     -383     
==========================================
- Hits        21444    21038     -406     
- Misses       5850     5873      +23     
Flag Coverage Δ
agent-for-php-7.0 ?
agent-for-php-7.1 ?
agent-for-php-7.2 78.18% <94.56%> (+0.01%) ⬆️
agent-for-php-7.3 78.20% <94.56%> (+0.01%) ⬆️
agent-for-php-7.4 77.90% <94.56%> (+0.01%) ⬆️
agent-for-php-8.0 77.92% <94.50%> (+0.01%) ⬆️
agent-for-php-8.1 77.91% <94.50%> (+0.01%) ⬆️
agent-for-php-8.2 77.51% <94.50%> (+0.01%) ⬆️
agent-for-php-8.3 77.51% <94.50%> (+0.01%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

@hahuja2 hahuja2 marked this pull request as ready for review July 18, 2024 22:28
@hahuja2 hahuja2 changed the title feat(agent): handle additional arguments correctly for newrelic_notice_error feat(agent): add additional arguments as error attributes for newrelic_notice_error Jul 18, 2024
agent/php_api.c Outdated
Comment on lines 187 to 185
char* buf1 = nr_strndup(errormsgstr, errormsglen);
char* buf2 = nr_strndup(error_file, error_file_len);
char* buf3 = nr_strndup(error_context, error_context_len);
char* stack_json = nr_php_backtrace_to_json(NULL TSRMLS_CC);

nr_txn_record_error_with_additional_attributes(
NRPRG(txn), priority, true, buf1, errclass, buf2,
error_line, buf3, error_number, stack_json);

nr_free(buf1);
nr_free(buf2);
nr_free(buf3);
nr_free(stack_json);
Copy link
Contributor

Choose a reason for hiding this comment

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

What is the purpose of copying these strings only to immediately free them? Is there a reason why we cannot just directly pass in errormsgstr, error_file, and error_context?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good point, fixed: b8e6402

Comment on lines 183 to 125
nr_free(error->message);
nr_free(error->klass);
nr_free(error->error_file);
nr_free(error->error_context);
nr_free(error->span_id);
nr_free(error->stacktrace_json);
nr_realfree((void**)error_ptr);
Copy link
Contributor

Choose a reason for hiding this comment

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

Based on the name of this function, I would expect it to only destroy the additional params, and thus still require the calling of nr_error_destroy afterward to fully free the error.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

To reduce code duplication, I have removed this function and added the additional logic in nr_error_destroy inside of a if statement: f917ced

Comment on lines 28 to 32
char* error_file; /* Error file */
int error_line; /* Error line */
char* error_context; /* Error context */
int error_no; /* Error number */
int option; /* Error option */
Copy link
Contributor

Choose a reason for hiding this comment

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

As far as I can tell, these are specifically user-provided through the API. It would be useful to specify that in the name of the variables, or at least their comment.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good point, I have updated the comments: 9f3e7e6

int error_line; /* Error line */
char* error_context; /* Error context */
int error_no; /* Error number */
int option; /* Error option */
Copy link
Contributor

Choose a reason for hiding this comment

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

So am I correct in that the option variable indicates whether the other user-provided variables are populated? Could we not just check, for example, error_file for null behind an API like nr_error_has_user_attributes rather than having this confusing variable? As far as I can tell, these attributes are either all-or-nothing.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Agree, the option variable isn't necessary and I have removed it: 9f3e7e6

@@ -1638,6 +1641,109 @@ void nr_txn_record_error(nrtxn_t* txn,
nr_free(span_id);
}

void nr_txn_record_error_with_additional_attributes(
Copy link
Contributor

Choose a reason for hiding this comment

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

There is a large amount of code duplication in this API. It would be nice to create a more unified API such that we don't have multiple copies of large functions.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good point, I have added helper functions for nr_txn_record_error, nr_segment_set_error, and nr_error_create to reduce code duplication: 23823f9 and f917ced

Copy link
Contributor

Choose a reason for hiding this comment

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

Could you remove nr_txn_record_error_helper and then just have nr_txn_record_error call nr_txn_record_error_with_additional_attributes with the unused fields filled with nothing values?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good idea, I have removed all of the helper functions: e177f40, ed191c0, 788c5a0

@newrelic-php-agent-bot
Copy link

newrelic-php-agent-bot commented Jul 24, 2024

Test Suite Status Result
Multiverse 7/7 passing
SOAK 55/56 passing

agent/php_api.c Outdated
ZEND_NUM_ARGS() TSRMLS_CC, "lsslz!",
&ignore1, &errormsgstr, &errormsglen,
&ignore2, &ignore3, &ignore4, &ignore5)) {
ZEND_NUM_ARGS() TSRMLS_CC, "lssls!",
Copy link
Contributor

Choose a reason for hiding this comment

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

The format for the parameters changes so the last value is a 's' instead of 'z' - what is the rationale behind this change?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This has been changed to a 's' because in the docs API, it mentions that error_context is a string which is the 5th parameter for this function.

agent/php_api.c Outdated
@@ -168,13 +172,23 @@ PHP_FUNCTION(newrelic_notice_error) {
}
}

{
char* buf = nr_strndup(errormsgstr, errormsglen);
if (!determine) {
Copy link
Contributor

Choose a reason for hiding this comment

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

This code seems to be able to be reduced as there are common elements to each possible path - also use an else instead of having two separate if statements testing logically opposite conditions.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fixed: 66a069d

const char* stacktrace_json,
const char* span_id,
nrtime_t when) {
nr_error_t* error;

if (0 == message) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Why were these guards removed?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

These were removed because these guards are being checked before this helper function is called. An example of this is in nr_error_create.

error = (nr_error_t*)nr_zalloc(sizeof(nr_error_t));
error->priority = priority;
error->when = when;
error->message = nr_strdup(message);
error->klass = nr_strdup(klass);
error->stacktrace_json = nr_strdup(stacktrace_json);
if (NULL != error_file && NULL != error_context) {
Copy link
Contributor

@mfulb mfulb Jul 25, 2024

Choose a reason for hiding this comment

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

If only one of these two values is defined do we still want to create the associated field?

Is there a value for the error_line and error_no that indicates it is not defined that needs to be checked?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

After discussion, it has been decided that if one attribute is passed in as NULL, the rest of the attributes should still show up. I will create a commit for this.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I have added an if statement for each separate value to ensure all values that are defined will be reported: 788c5a0

const char* stacktrace_json,
const char* span_id,
nrtime_t when) {
if (0 == message || 0 == klass || 0 == error_file || 0 == error_context
Copy link
Contributor

Choose a reason for hiding this comment

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

Please change usages in this file of 0 to NULL if used to test a pointer value. The usage of 0 is from old code and we do not use that form now.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fixed: ace780f

@@ -94,6 +153,10 @@ void nr_error_destroy(nr_error_t** error_ptr) {
nr_free(error->klass);
nr_free(error->span_id);
nr_free(error->stacktrace_json);
if ((NULL != error->error_file) && (NULL != error->error_context)) {
Copy link
Contributor

Choose a reason for hiding this comment

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

nr_free should handle this case.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fixed: c058df8

* 8. String containing stack trace in JSON format.
* 9. Span ID
* 10. When the error occurred.
*
Copy link
Contributor

Choose a reason for hiding this comment

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

Would be good to explain if all are required or if there are certain combinations allowed.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Added: 9fad7fa

/*
* Purpose : Get the error file of an error.
*
* Returns : The error file of the error or NULL on failure.
Copy link
Contributor

Choose a reason for hiding this comment

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

Is it a 'failure' or simply it is not defined?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good point, it should instead say that it was not defined, fixed: 9fad7fa

if (NULL == segment->error) {
segment->error = nr_zalloc(sizeof(nr_segment_error_t));
}

nr_free(segment->error->error_message);
nr_free(segment->error->error_class);
if (NULL != error_file && NULL != error_context) {
Copy link
Contributor

@mfulb mfulb Jul 25, 2024

Choose a reason for hiding this comment

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

Couldn't this code be pulled into the conditional below which has the same check?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I have removed the if statement, so that nr_free() is still called: ed191c0


segment->error->error_message
= error_message ? nr_strdup(error_message) : NULL;
segment->error->error_class = error_class ? nr_strdup(error_class) : NULL;
if (NULL != error_file && NULL != error_context) {
Copy link
Contributor

Choose a reason for hiding this comment

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

I'm not clear on the behavior of this function if the segment had all fields defined previously and then this function is called and error_file and error_context are NULL. It seems the previous values would be retained. That could be confusing as it seems there is no way to clear those fields once set.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

After discussion, it has been decided that if NULL is passed in, the attribute should not show up. I will modify the code and create a commit for this.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I have modified this function where all strings are freed now, so the previous values will not be retained. New values will only be added if they are not NULL: ed191c0, bc0a82e

return;
}

static void nr_segment_set_error_helper(nr_segment_t* segment,
Copy link
Contributor

Choose a reason for hiding this comment

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

I would recommend putting a comment on each of these static helper functions throughout this file to make it clear no NULL checking is done. It is unlikely any new code would call these directly but I think it is a good safety guard to make sure a coder is aware of this.

Do you think we need unit tests for these helper functions throughout this file?

Copy link
Contributor Author

@hahuja2 hahuja2 Aug 26, 2024

Choose a reason for hiding this comment

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

I have modified the code and removed all of the helper functions: e177f40, ed191c0, 788c5a0

@@ -117,6 +117,10 @@ typedef struct _nr_segment_metric_t {
typedef struct _nr_segment_error_t {
char* error_message; /* The error message that will appear on a span event. */
char* error_class; /* The error class that will appear on a span event. */
char* error_file;
Copy link
Contributor

Choose a reason for hiding this comment

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

Can you add a comment for each of these fields like the existing ones have?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Added: eb64129

axiom/nr_txn.c Outdated
@@ -2479,6 +2536,13 @@ nr_analytics_event_t* nr_error_to_event(const nrtxn_t* txn) {
nro_set_hash_string(params, "error.class", nr_error_get_klass(txn->error));
nro_set_hash_string(params, "error.message",
nr_error_get_message(txn->error));
if (nr_error_get_file(txn->error) && nr_error_get_context(txn->error)) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Is it ever possible we'd only one to write a subset of these 4 or do they either all exist or not exist?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

After discussion, it has been decided that the values that are defined should still be reported. I have modified the code to check each value individually in separate if statements: e177f40

@@ -471,6 +471,17 @@ extern nr_status_t nr_txn_record_error_worthy(const nrtxn_t* txn, int priority);
#define NR_TXN_ALLOW_RAW_EXCEPTION_MESSAGE \
"Message removed by New Relic security settings"

extern void nr_txn_record_error_with_additional_attributes(nrtxn_t* txn,
Copy link
Contributor

Choose a reason for hiding this comment

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

Could you document the record error functions (both forms)?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Added: c5a40cc

@@ -2868,6 +2874,12 @@ static void test_segment_set_error_attributes(void) {
tlib_pass_if_str_equal("error.class", "error.class 1",
segment.error->error_class);

nr_segment_set_error_with_additional_params(&segment, "error.message", "error.class", "error.file", 125, "rand3", 100);
Copy link
Contributor

Choose a reason for hiding this comment

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

Some tests exercising the additional new params which are pointers with NULL values would be good. Because there are short circuiting conditions when checking for NULL I would recommend exercising each param individually with a NULL instead of a single test setting them all to NULL.

Copy link
Contributor Author

@hahuja2 hahuja2 Aug 21, 2024

Choose a reason for hiding this comment

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

Good point, added: 06430a2

@@ -2868,6 +2874,12 @@ static void test_segment_set_error_attributes(void) {
tlib_pass_if_str_equal("error.class", "error.class 1",
segment.error->error_class);

nr_segment_set_error_with_additional_params(&segment, "error.message", "error.class", "error.file", 125, "rand3", 100);
Copy link
Contributor

Choose a reason for hiding this comment

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

What is the expected behavior if any of the strings are ""?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

If the user passes in an empty string, then the attribute will still show up. The attribute will not show up if NULL is passed in.

@mfulb mfulb force-pushed the ha/update-notice-error branch from a2ad43b to 8b6a0e3 Compare September 6, 2024 20:41
@mfulb
Copy link
Contributor

mfulb commented Sep 11, 2024

PR #960 supersedes the work in this PR. The conclusion was that the agent spec does not support these current parameters currently as attributes, and much of the information is available from the stack trace or by adding the desired information to the error message passed to the API.

@mfulb mfulb closed this Sep 11, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants