diff --git a/agent/php_api.c b/agent/php_api.c index f9dc5c903..5500e5aa2 100644 --- a/agent/php_api.c +++ b/agent/php_api.c @@ -11,6 +11,7 @@ #include "php_hash.h" #include "php_user_instrument.h" #include "fw_drupal_common.h" +#include "nr_errors.h" #include "nr_rum.h" #include "util_logging.h" #include "util_memory.h" @@ -71,14 +72,17 @@ PHP_FUNCTION(newrelic_notice_error) { const char* errclass = "NoticedError"; char* errormsgstr = NULL; nr_string_len_t errormsglen = 0; - zend_long ignore1 = 0; - char* ignore2 = 0; - nr_string_len_t ignore3 = 0; - zend_long ignore4 = 0; + char* user_error_message = NULL; + nr_string_len_t user_error_message_len = 0; + zend_long user_error_number = 0; + char* user_error_file = NULL; + nr_string_len_t user_error_file_len = 0; + zend_long user_error_line = 0; zval* exc = NULL; - zval* ignore5 = NULL; + nr_string_len_t user_error_context_len = 0; + char* user_error_context = NULL; int priority = 0; - zval* ignore = NULL; + bool five_params = false; NR_UNUSED_RETURN_VALUE; NR_UNUSED_RETURN_VALUE_PTR; @@ -132,11 +136,11 @@ PHP_FUNCTION(newrelic_notice_error) { case 2: if (FAILURE == zend_parse_parameters_ex(ZEND_PARSE_PARAMS_QUIET, - ZEND_NUM_ARGS() TSRMLS_CC, "zo!", &ignore, + ZEND_NUM_ARGS() TSRMLS_CC, "s!o!", &user_error_message, &user_error_message_len, &exc)) { nrl_debug(NRL_API, "newrelic_notice_error: invalid two arguments: expected " - "exception as second argument"); + "string as first argument and exception as second argument"); RETURN_NULL(); } break; @@ -144,12 +148,14 @@ PHP_FUNCTION(newrelic_notice_error) { case 5: if (FAILURE == zend_parse_parameters_ex(ZEND_PARSE_PARAMS_QUIET, - ZEND_NUM_ARGS() TSRMLS_CC, "lsslz!", - &ignore1, &errormsgstr, &errormsglen, - &ignore2, &ignore3, &ignore4, &ignore5)) { + ZEND_NUM_ARGS() TSRMLS_CC, "ls!s!ls!", + &user_error_number, &user_error_message, &user_error_message_len, + &user_error_file, &user_error_file_len, &user_error_line, + &user_error_context, &user_error_context_len)) { nrl_debug(NRL_API, "newrelic_notice_error: invalid five arguments"); RETURN_NULL(); } + five_params = true; break; default: @@ -168,17 +174,22 @@ PHP_FUNCTION(newrelic_notice_error) { } } - { - char* buf = nr_strndup(errormsgstr, errormsglen); + if (five_params) { + nr_user_error_t* user_error = nr_user_error_create( + user_error_message, user_error_number, user_error_file, user_error_line, + user_error_context); char* stack_json = nr_php_backtrace_to_json(NULL TSRMLS_CC); - - nr_txn_record_error(NRPRG(txn), priority, true, buf, errclass, stack_json); - - nr_free(buf); + nr_txn_record_error_with_additional_attributes(NRPRG(txn), priority, true, + user_error_message, errclass, + stack_json, user_error); nr_free(stack_json); - RETURN_TRUE; } + char* stack_json = nr_php_backtrace_to_json(NULL TSRMLS_CC); + nr_txn_record_error(NRPRG(txn), priority, true, errormsgstr, errclass, + stack_json); + nr_free(stack_json); + RETURN_TRUE; } /* diff --git a/axiom/nr_errors.c b/axiom/nr_errors.c index e8f9768e5..4ec694372 100644 --- a/axiom/nr_errors.c +++ b/axiom/nr_errors.c @@ -20,31 +20,58 @@ nr_error_t* nr_error_create(int priority, const char* stacktrace_json, const char* span_id, nrtime_t when) { - nr_error_t* error; - - if (0 == message) { - return 0; - } - if (0 == klass) { + if (NULL == message || NULL == klass || NULL == stacktrace_json) { return 0; } - if (0 == stacktrace_json) { + return nr_error_create_additional_params(priority, message, klass, NULL, stacktrace_json, span_id, when); +} + +nr_error_t* nr_error_create_additional_params(int priority, + const char* message, + const char* klass, + nr_user_error_t* user_error, + const char* stacktrace_json, + const char* span_id, + nrtime_t when) { + nr_error_t* error; + if (NULL == klass || NULL == stacktrace_json) { return 0; } 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); - + error->user_error = user_error; + if (NULL != message) { + error->message = nr_strdup(message); + } if (NULL != span_id) { error->span_id = nr_strdup(span_id); } return error; } +nr_user_error_t* nr_user_error_create(const char* user_error_message, int user_error_number, const char* user_error_file, int user_error_line, const char* user_error_context) { + nr_user_error_t* user_error = (nr_user_error_t*)nr_malloc(sizeof(nr_user_error_t)); + user_error->user_error_message = nr_strdup(user_error_message); + user_error->user_error_number = user_error_number; + user_error->user_error_file = nr_strdup(user_error_file); + user_error->user_error_line = user_error_line; + user_error->user_error_context = nr_strdup(user_error_context); + return user_error; +} + +void nr_user_error_destroy(nr_user_error_t* user_error) { + if (NULL != user_error) { + nr_free(user_error->user_error_message); + nr_free(user_error->user_error_file); + nr_free(user_error->user_error_context); + nr_free(user_error); + } +} + const char* nr_error_get_message(const nr_error_t* error) { if (NULL == error) { return NULL; @@ -94,6 +121,7 @@ void nr_error_destroy(nr_error_t** error_ptr) { nr_free(error->klass); nr_free(error->span_id); nr_free(error->stacktrace_json); + nr_user_error_destroy(error->user_error); nr_realfree((void**)error_ptr); } diff --git a/axiom/nr_errors.h b/axiom/nr_errors.h index 0ba5cef22..4b6275b16 100644 --- a/axiom/nr_errors.h +++ b/axiom/nr_errors.h @@ -17,6 +17,14 @@ */ typedef struct _nr_error_t nr_error_t; +typedef struct _nr_user_error_t { + char* user_error_message; /* User error message */ + char* user_error_file; /* User error file */ + char* user_error_context; /* User error context */ + int user_error_line; /* User error line */ + int user_error_number; /* User error number */ +} nr_user_error_t; + /* * Purpose : Create a new error. * @@ -38,6 +46,45 @@ extern nr_error_t* nr_error_create(int priority, const char* span_id, nrtime_t when); +extern void nr_user_error_destroy(nr_user_error_t* user_error_ptr); + +extern nr_user_error_t* nr_user_error_create(const char* user_error_message, + int user_error_number, + const char* user_error_file, + int user_error_line, + const char* user_error_context); + +/* + * Purpose : Create a new error for the use case where additional parameters are + * passed in. The following parameters are required and can not be NULL: klass + * and stacktrace_json. If these are NULL, the function will return 0. The + * remaining parameters can be passed in as NULL if they are not needed. + * + * Params : 1. The importance of the error. Higher number is more important. + * 2. Message string. + * 3. Class string. This string is used to aggregate errors in RPM. + * RPM does not expect strings from a specific namespace. + * The PHP agent uses PHP's predefined error constant names. + * Examples are "E_ERROR" and "E_WARNING". + * 4. Error file provided by user. + * 5. Error line provided by user. + * 6. Error context provided by user. + * 7. Error number provided by user. + * 8. String containing stack trace in JSON format. + * 9. Span ID + * 10. When the error occurred. + * + * Returns : A newly allocated error, or 0 on failure. + */ +extern nr_error_t* nr_error_create_additional_params( + int priority, + const char* message, + const char* klass, + nr_user_error_t* user_error, + const char* stacktrace_json, + const char* span_id, + nrtime_t when); + /* * Purpose : Retrieve error fields for the purpose of creating attributes. */ @@ -45,33 +92,34 @@ extern nr_error_t* nr_error_create(int priority, /* * Purpose : Get the message of an error. * - * Returns : The message of the error or NULL on failure. + * Returns : The message of the error or NULL if not defined. */ extern const char* nr_error_get_message(const nr_error_t* error); /* * Purpose : Get the klass of an error. * - * Returns : The klass of the error or NULL on failure. + * Returns : The klass of the error or NULL if not defined. */ extern const char* nr_error_get_klass(const nr_error_t* error); + /* * Purpose : Get the span_id of an error. * - * Returns : The span_id of the error or NULL on failure. + * Returns : The span_id of the error or NULL if not defined. */ extern const char* nr_error_get_span_id(const nr_error_t* error); /* * Purpose : Get the time of an error. * - * Returns : The time of the error or 0 on failure. + * Returns : The time of the error or 0 if not defined. */ extern nrtime_t nr_error_get_time(const nr_error_t* error); /* * Purpose : Get the priority of an error. * - * Returns : The priority of the error or 0 on failure. + * Returns : The priority of the error or 0 if not defined. */ extern int nr_error_priority(const nr_error_t* error); diff --git a/axiom/nr_errors_private.h b/axiom/nr_errors_private.h index ad9eafccb..4b6029b86 100644 --- a/axiom/nr_errors_private.h +++ b/axiom/nr_errors_private.h @@ -28,6 +28,7 @@ struct _nr_error_t { char* stacktrace_json; /* Stack trace in JSON format */ char* span_id; /* ID of the current executing span at the time the error occurred */ + nr_user_error_t* user_error; }; #endif /* NR_ERRORS_PRIVATE_HDR */ diff --git a/axiom/nr_segment.c b/axiom/nr_segment.c index fa91cf1b7..2b0c38bc2 100644 --- a/axiom/nr_segment.c +++ b/axiom/nr_segment.c @@ -1176,17 +1176,30 @@ void nr_segment_set_error(nr_segment_t* segment, if ((NULL == segment) || (NULL == error_message && NULL == error_class)) { return; } + nr_segment_set_error_with_additional_params(segment, error_message, error_class, NULL); +} +void nr_segment_set_error_with_additional_params(nr_segment_t* segment, + const char* error_message, + const char* error_class, + nr_user_error_t* user_error) { + if (NULL == segment || NULL == error_class) { + return; + } + 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); + nr_free(segment->error->user_error); - segment->error->error_message - = error_message ? nr_strdup(error_message) : NULL; + if (NULL != error_message) { + segment->error->error_message = error_message ? nr_strdup(error_message) : NULL; + } segment->error->error_class = error_class ? nr_strdup(error_class) : NULL; + segment->error->user_error = user_error; } bool nr_segment_attributes_user_add(nr_segment_t* segment, diff --git a/axiom/nr_segment.h b/axiom/nr_segment.h index 56d972579..33ab204e5 100644 --- a/axiom/nr_segment.h +++ b/axiom/nr_segment.h @@ -117,6 +117,7 @@ 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. */ + nr_user_error_t* user_error; /* The user error that will appear on a span event.*/ } nr_segment_error_t; /* @@ -649,6 +650,23 @@ extern void nr_segment_record_exception(nr_segment_t* segment, const char* error_message, const char* error_class); +/* + * Purpose : Set the error attributes on a segment. + * + * Params : 1. The pointer to the segment. + * 2. The error message that will be added. + * 3. The error class that will be added. + * 4. The error file that will be added. + * 5. The error line that will be added. + * 6. The error context that will be added. + * 7. The error number that will be added. + */ +extern void nr_segment_set_error_with_additional_params( + nr_segment_t* segment, + const char* error_message, + const char* error_class, + nr_user_error_t* user_error); + /* * Purpose : Gets the child_ix of a segment. * diff --git a/axiom/nr_segment_private.c b/axiom/nr_segment_private.c index a60865afd..53f988f93 100644 --- a/axiom/nr_segment_private.c +++ b/axiom/nr_segment_private.c @@ -5,6 +5,7 @@ #include "nr_axiom.h" +#include "nr_errors.h" #include "nr_segment_private.h" #include "nr_segment.h" #include "nr_txn.h" @@ -87,5 +88,6 @@ void nr_segment_error_destroy_fields(nr_segment_error_t* segment_error) { nr_free(segment_error->error_message); nr_free(segment_error->error_class); + nr_user_error_destroy(segment_error->user_error); nr_free(segment_error); } diff --git a/axiom/nr_txn.c b/axiom/nr_txn.c index c3667db81..41bf267b6 100644 --- a/axiom/nr_txn.c +++ b/axiom/nr_txn.c @@ -14,6 +14,8 @@ #include "nr_agent.h" #include "nr_commands.h" #include "nr_custom_events.h" +#include "nr_errors.h" +#include "nr_errors_private.h" #include "nr_guid.h" #include "nr_header.h" #include "nr_limits.h" @@ -1553,20 +1555,21 @@ nr_status_t nr_txn_record_error_worthy(const nrtxn_t* txn, int priority) { return NR_SUCCESS; } -void nr_txn_record_error(nrtxn_t* txn, - int priority, - bool add_to_current_segment, - const char* errmsg, - const char* errclass, - const char* stacktrace_json) { +void nr_txn_record_error_with_additional_attributes( + nrtxn_t* txn, + int priority, + bool add_to_current_segment, + const char* error_message, + const char* error_class, + const char* stacktrace_json, + nr_user_error_t* user_error) { nr_segment_t* current_segment = NULL; char* span_id = NULL; nr_error_t* error = NULL; - if (nrunlikely((0 == txn) || (0 == txn->options.err_enabled) || (0 == errmsg) - || (0 == errclass) || (0 == txn->status.recording) - || (0 == errmsg[0]) || (0 == errclass[0]) - || (0 == stacktrace_json))) { + if (nrunlikely((0 == txn) || (0 == txn->options.err_enabled) + || (0 == txn->status.recording) || (0 == stacktrace_json) + || (0 == error_class))) { return; } @@ -1576,11 +1579,11 @@ void nr_txn_record_error(nrtxn_t* txn, } if (txn->high_security) { - errmsg = NR_TXN_HIGH_SECURITY_ERROR_MESSAGE; + error_message = NR_TXN_HIGH_SECURITY_ERROR_MESSAGE; } if (0 == txn->options.allow_raw_exception_messages) { - errmsg = NR_TXN_ALLOW_RAW_EXCEPTION_MESSAGE; + error_message = NR_TXN_ALLOW_RAW_EXCEPTION_MESSAGE; } /* Only try to get a span_id in cases where we know spans should be created. @@ -1603,17 +1606,28 @@ void nr_txn_record_error(nrtxn_t* txn, current_segment = nr_txn_get_current_segment(txn, NULL); if (current_segment) { - nr_segment_set_error(current_segment, errmsg, errclass); - nrl_verbosedebug(NRL_TXN, - "recording segment error: msg='%.48s' cls='%.48s'" - "span_id='%.48s'", - NRSAFESTR(errmsg), NRSAFESTR(errclass), - NRSAFESTR(span_id)); + if (NULL == user_error) { + nr_segment_set_error(current_segment, error_message, error_class); + nrl_verbosedebug(NRL_TXN, + "recording segment error: msg='%.48s' cls='%.48s' " + "span_id='%.48s'", + NRSAFESTR(error_message), NRSAFESTR(error_class), + NRSAFESTR(span_id)); + } else { + nr_segment_set_error_with_additional_params( + current_segment, error_message, error_class, user_error); + } } } } - error = nr_error_create(priority, errmsg, errclass, stacktrace_json, span_id, - nr_get_time()); + if (NULL == user_error) { + error = nr_error_create(priority, error_message, error_class, stacktrace_json, + span_id, nr_get_time()); + } else { + error = nr_error_create_additional_params( + priority, error_message, error_class, user_error, stacktrace_json, span_id, nr_get_time()); + } + /* * Ensure previous error is destroyed only we have a valid one to replace it * with. @@ -1622,7 +1636,7 @@ void nr_txn_record_error(nrtxn_t* txn, nrl_verbosedebug(NRL_TXN, "The following returned NULL from create error: " "priority=%d msg='%.48s' cls='%.48s' span_id='%.48s'", - priority, NRSAFESTR(errmsg), NRSAFESTR(errclass), + priority, NRSAFESTR(error_message), NRSAFESTR(error_class), NRSAFESTR(span_id)); return; @@ -1634,11 +1648,27 @@ void nr_txn_record_error(nrtxn_t* txn, nrl_verbosedebug(NRL_TXN, "recording error priority=%d msg='%.48s' cls='%.48s'" "span_id='%.48s'", - priority, NRSAFESTR(errmsg), NRSAFESTR(errclass), + priority, NRSAFESTR(error_message), NRSAFESTR(error_class), NRSAFESTR(span_id)); nr_free(span_id); } +void nr_txn_record_error(nrtxn_t* txn, + int priority, + bool add_to_current_segment, + const char* errmsg, + const char* errclass, + const char* stacktrace_json) { + if (nrunlikely((0 == txn) || (0 == txn->options.err_enabled) || (0 == errmsg) + || (0 == errclass) || (0 == txn->status.recording) + || (0 == errmsg[0]) || (0 == errclass[0]) + || (0 == stacktrace_json))) { + return; + } + nr_txn_record_error_with_additional_attributes( + txn, priority, add_to_current_segment, errmsg, errclass, stacktrace_json, NULL); +} + char* nr_txn_create_fn_supportability_metric(const char* function_name, const char* class_name) { return nr_formatf("Supportability/InstrumentedFunction/%s%s%s", @@ -2448,6 +2478,34 @@ static void nr_txn_add_metric_count_as_attribute(nrobj_t* attributes, } } +static nrobj_t* nr_error_user_attributes_to_nro(nr_user_error_t* user_error, nrobj_t* user_attributes) { + if (NULL == user_error) { + return user_attributes; + } + + if (user_error->user_error_message) { + nro_set_hash_string(user_attributes, "user.error.message", + user_error->user_error_message); + } + if (user_error->user_error_file) { + nro_set_hash_string(user_attributes, "user.error.file", + user_error->user_error_file); + } + if (user_error->user_error_context) { + nro_set_hash_string(user_attributes, "user.error.context", + user_error->user_error_context); + } + if (user_error->user_error_number) { + nro_set_hash_int(user_attributes, "user.error.number", + user_error->user_error_number); + } + if (user_error->user_error_line) { + nro_set_hash_int(user_attributes, "user.error.line", + user_error->user_error_line); + } + return user_attributes; +} + /* * This implements the agent Error Events spec: * We only omit 'gcCumulative' which doesn't apply and 'port' which is too @@ -2478,8 +2536,10 @@ nr_analytics_event_t* nr_error_to_event(const nrtxn_t* txn) { nro_set_hash_string(params, "type", "TransactionError"); nro_set_hash_double(params, "timestamp", ((double)when) / NR_TIME_DIVISOR_D); 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 (NULL != nr_error_get_message(txn->error)) { + nro_set_hash_string(params, "error.message", + nr_error_get_message(txn->error)); + } nro_set_hash_string(params, "transactionName", txn->name); nro_set_hash_double(params, "duration", ((double)duration) / NR_TIME_DIVISOR_D); @@ -2527,6 +2587,12 @@ nr_analytics_event_t* nr_error_to_event(const nrtxn_t* txn) { NR_ATTRIBUTE_DESTINATION_ERROR); user_attributes = nr_attributes_user_to_obj(txn->attributes, NR_ATTRIBUTE_DESTINATION_ERROR); + + if (NULL == user_attributes) { + user_attributes = nro_new_hash(); + } + + user_attributes = nr_error_user_attributes_to_nro(txn->error->user_error, user_attributes); event = nr_analytics_event_create(params, agent_attributes, user_attributes); nro_delete(params); diff --git a/axiom/nr_txn.h b/axiom/nr_txn.h index a6f942f66..d87e99d6a 100644 --- a/axiom/nr_txn.h +++ b/axiom/nr_txn.h @@ -452,6 +452,42 @@ extern void nr_txn_set_request_uri(nrtxn_t* txn, const char* uri); */ extern nr_status_t nr_txn_record_error_worthy(const nrtxn_t* txn, int priority); +#define NR_TXN_HIGH_SECURITY_ERROR_MESSAGE \ + "Message removed by New Relic high_security setting" + +#define NR_TXN_ALLOW_RAW_EXCEPTION_MESSAGE \ + "Message removed by New Relic security settings" + +/* + * Purpose : Record the given error in the transaction with additional + * attributes being reported as user attributes. If the additional attributes + * are passed in as NULL, then they will not be reported. + * + * Params : 1. The transaction pointer. + * 2. The priority of the error. A higher number indicates a more + * serious error. + * 3. Whether to add the error to the current segment. + * 4. The error message. + * 5. The error class. + * 6. The error line. + * 7. The error file. + * 8. The error context. + * 9. The error number. + * 10. The stack trace in JSON format. + * + * Returns : Nothing. + * + * Notes : This function will still record an error when high security is + * enabled but the message will be replaced with a placeholder. + */ +extern void nr_txn_record_error_with_additional_attributes(nrtxn_t* txn, + int priority, + bool add_to_current_segment, + const char* error_message, + const char* error_class, + const char* stacktrace_json, + nr_user_error_t* user_error); + /* * Purpose : Record the given error in the transaction. * @@ -468,12 +504,6 @@ extern nr_status_t nr_txn_record_error_worthy(const nrtxn_t* txn, int priority); * Notes : This function will still record an error when high security is * enabled but the message will be replaced with a placeholder. */ -#define NR_TXN_HIGH_SECURITY_ERROR_MESSAGE \ - "Message removed by New Relic high_security setting" - -#define NR_TXN_ALLOW_RAW_EXCEPTION_MESSAGE \ - "Message removed by New Relic security settings" - extern void nr_txn_record_error(nrtxn_t* txn, int priority, bool add_to_segment, diff --git a/axiom/tests/test_cmd_txndata.c b/axiom/tests/test_cmd_txndata.c index fc9272e1c..457c63ee2 100644 --- a/axiom/tests/test_cmd_txndata.c +++ b/axiom/tests/test_cmd_txndata.c @@ -673,7 +673,9 @@ static void test_encode_error_events(void) { "\"nr.transactionGuid\":\"abcd\"," "\"guid\":\"abcd\"" "}," - "{}," + "{" + "\"user.error.message\":\"msg\"" + "}," "{}" "]"), nr_flatbuffers_table_read_bytes(&tbl, EVENT_FIELD_DATA), diff --git a/axiom/tests/test_segment.c b/axiom/tests/test_segment.c index 65f137408..7c89cbbf9 100644 --- a/axiom/tests/test_segment.c +++ b/axiom/tests/test_segment.c @@ -2841,6 +2841,19 @@ static void test_segment_to_span_event(void) { nr_txn_destroy(&txn); } +#define test_segment_set_error_with_additional_params( \ + SEGMENT, MESSAGE, CLASS, ERRFILE, LINE, CONTEXT, NUM) \ + nr_segment_set_error_with_additional_params(&SEGMENT, MESSAGE, CLASS, \ + ERRFILE, LINE, CONTEXT, NUM); \ + tlib_pass_if_str_equal("error.message", MESSAGE, \ + SEGMENT.error->error_message); \ + tlib_pass_if_str_equal("error.class", CLASS, SEGMENT.error->error_class); \ + tlib_pass_if_str_equal("error.file", ERRFILE, SEGMENT.error->error_file); \ + tlib_pass_if_int_equal("error.line", LINE, SEGMENT.error->error_line); \ + tlib_pass_if_str_equal("error.context", CONTEXT, \ + SEGMENT.error->error_context); \ + tlib_pass_if_int_equal("error.num", NUM, SEGMENT.error->error_no); + static void test_segment_set_error_attributes(void) { nr_segment_t segment = {.type = NR_SEGMENT_CUSTOM}; @@ -2853,6 +2866,12 @@ static void test_segment_set_error_attributes(void) { nr_segment_set_error(&segment, NULL, NULL); tlib_pass_if_null("Null segment error", segment.error); + nr_segment_set_error_with_additional_params(NULL, "error.message", "error.class", "rand1", 125, "rand3", 100); + tlib_pass_if_null("Null segment error", segment.error); + + nr_segment_set_error_with_additional_params(NULL, "error.message", NULL, "rand1", 125, NULL, 100); + tlib_pass_if_null("Null segment error", segment.error); + /* * Test : Normal operation. */ @@ -2868,6 +2887,11 @@ static void test_segment_set_error_attributes(void) { tlib_pass_if_str_equal("error.class", "error.class 1", segment.error->error_class); + test_segment_set_error_with_additional_params(segment, "error.message", "error.class", "error.file", 125, "error.context", 100); + test_segment_set_error_with_additional_params(segment, NULL, "error.class", "error.file", 125, "error.context", 100); + test_segment_set_error_with_additional_params(segment, "error.message", "error.class", NULL, 125, "error.context", 100); + test_segment_set_error_with_additional_params(segment, "error.message", "error.class", "error.file", 125, NULL, 100); + nr_segment_destroy_fields(&segment); } diff --git a/axiom/tests/test_txn.c b/axiom/tests/test_txn.c index 59b19f9b0..27e0ea37a 100644 --- a/axiom/tests/test_txn.c +++ b/axiom/tests/test_txn.c @@ -3902,7 +3902,10 @@ static void test_error_to_event(void) { "\"nr.transactionGuid\":\"abcd\"," "\"guid\":\"abcd\"" "}," - "{\"user_long\":1}," + "{" + "\"user_long\":1," + "\"user.error.message\":\"the_msg\"" + "}," "{\"agent_long\":2}" "]", nr_analytics_event_json(event)); @@ -3931,7 +3934,10 @@ static void test_error_to_event(void) { "\"nr.transactionGuid\":\"abcd\"," "\"guid\":\"abcd\"" "}," - "{\"user_long\":1}," + "{" + "\"user_long\":1," + "\"user.error.message\":\"the_msg\"" + "}," "{\"agent_long\":2}" "]"); nr_analytics_event_destroy(&event); @@ -3960,7 +3966,10 @@ static void test_error_to_event(void) { "\"nr.syntheticsJobId\":\"b\"," "\"nr.syntheticsMonitorId\":\"c\"" "}," - "{\"user_long\":1}," + "{" + "\"user_long\":1," + "\"user.error.message\":\"the_msg\"" + "}," "{\"agent_long\":2}" "]"); nr_analytics_event_destroy(&event); @@ -8134,6 +8143,130 @@ static void test_segment_record_error(void) { nr_txn_destroy(&txn); } +static void test_segment_record_error_with_additional_params(void) { + nrapp_t app = { + .state = NR_APP_OK, + .limits = { + .analytics_events = NR_MAX_ANALYTIC_EVENTS, + .span_events = NR_DEFAULT_SPAN_EVENTS_MAX_SAMPLES_STORED, + }, + }; + nrtxnopt_t opts; + nr_segment_t* segment; + nrtxn_t* txn; + + /* Setup transaction and segment */ + nr_memset(&opts, 0, sizeof(opts)); + opts.distributed_tracing_enabled = 1; + opts.span_events_enabled = 1; + txn = nr_txn_begin(&app, &opts, NULL); + segment = nr_segment_start(txn, NULL, NULL); + nr_distributed_trace_set_sampled(txn->distributed_trace, true); + txn->options.allow_raw_exception_messages = 1; + + /* No error attributes added if error collection isn't enabled */ + txn->options.err_enabled = 0; + nr_txn_record_error_with_additional_attributes( + txn, 1, true, "low priority message", "low priority class", "random.php", + 150, "random context", 256, "[\"A\",\"B\"]"); + tlib_pass_if_null("No segment error created", segment->error); + tlib_pass_if_null("No txn error created", txn->error); + + /* Enable error collection */ + txn->options.err_enabled = 1; + + /* Do not add to current segment */ + nr_txn_record_error_with_additional_attributes(txn, 0.5, false, + "low priority message", "low priority class", "random php", 150, "random context", 256, "[\"A\",\"B\"]"); + tlib_pass_if_not_null("Txn error event created", txn->error); + tlib_pass_if_null("Segment error NOT created", segment->error); + tlib_pass_if_str_equal("Correct txn error.message", "low priority message", + nr_error_get_message(txn->error)); + tlib_pass_if_str_equal("Correct txn error.class", "low priority class", + nr_error_get_klass(txn->error)); + + /* Normal operation: txn error prioritized over previous */ + nr_txn_record_error_with_additional_attributes( + txn, 1, true, "random message", "random class", "random.php", + 150, "random context", 256, "[\"A\",\"B\"]"); + tlib_pass_if_not_null("Txn error event created", txn->error); + tlib_pass_if_not_null("Segment error created", segment->error); + tlib_pass_if_str_equal("Correct segment error.message", "random message", + segment->error->error_message); + tlib_pass_if_str_equal("Correct segment error.class", "random class", + segment->error->error_class); + tlib_pass_if_str_equal("Correct segment error.file", "random.php", + segment->error->error_file); + tlib_pass_if_int_equal("Correct segment error.line", 150, + segment->error->error_line); + tlib_pass_if_str_equal("Correct segment error.context", "random context", + segment->error->error_context); + tlib_pass_if_int_equal("Correct segment error.no", 256, + segment->error->error_no); + tlib_pass_if_str_equal("txn error message matches segment error message", + segment->error->error_message, + nr_error_get_message(txn->error)); + tlib_pass_if_str_equal("txn error class matches segment error class", + segment->error->error_class, + nr_error_get_klass(txn->error)); + + /* Multiple errors on the same segment where the new error is + prioritized over the previous because of priority level*/ + nr_txn_record_error_with_additional_attributes( + txn, 1, true, "random message 2", "random class 2", "random.php2", + 150, "random context2", 256, "[\"A\",\"B\"]"); + + tlib_pass_if_str_equal("Segment error.message overwritten", "random message 2", + segment->error->error_message); + tlib_pass_if_str_equal("Segment error.class overwritten", "random class 2", + segment->error->error_class); + tlib_pass_if_str_equal("txn error message matches segment error message", + segment->error->error_message, + nr_error_get_message(txn->error)); + tlib_pass_if_str_equal("txn error class matches segment error class", + segment->error->error_class, + nr_error_get_klass(txn->error)); + + /* High_security */ + txn->high_security = 1; + nr_txn_record_error_with_additional_attributes( + txn, 1, true, "secure message", "random class", "random.php", + 150, "random context", 256, "[\"A\",\"B\"]"); + tlib_pass_if_not_null("Segment error created", segment->error); + tlib_pass_if_str_equal("Secure error.message", + NR_TXN_HIGH_SECURITY_ERROR_MESSAGE, + segment->error->error_message); + tlib_pass_if_str_equal("Correct segment error class", "random class", + segment->error->error_class); + tlib_pass_if_str_equal("txn error message matches segment error message", + segment->error->error_message, + nr_error_get_message(txn->error)); + tlib_pass_if_str_equal("txn error class matches segment error class", + segment->error->error_class, + nr_error_get_klass(txn->error)); + txn->high_security = 0; + + /* allow_raw_exception_messages */ + txn->options.allow_raw_exception_messages = 0; + nr_txn_record_error_with_additional_attributes( + txn, 1, true, "another secure message", "another random class", "random.php", + 150, "random context", 256, "[\"A\",\"B\"]"); + tlib_pass_if_not_null("Segment error created", segment->error); + tlib_pass_if_str_equal("Secure error message", + NR_TXN_ALLOW_RAW_EXCEPTION_MESSAGE, + segment->error->error_message); + tlib_pass_if_str_equal("Correct segment error.class", "another random class", + segment->error->error_class); + tlib_pass_if_str_equal("txn error message matches segment error message", + segment->error->error_message, + nr_error_get_message(txn->error)); + tlib_pass_if_str_equal("txn error class matches segment error class", + segment->error->error_class, + nr_error_get_klass(txn->error)); + + nr_txn_destroy(&txn); +} + static nrtxn_t* new_txn_for_record_log_event_test(char* entity_name) { nrapp_t app; nrtxnopt_t opts; @@ -8721,6 +8854,7 @@ void test_main(void* p NRUNUSED) { test_txn_accept_distributed_trace_payload_w3c_and_nr(); test_span_queue(); test_segment_record_error(); + test_segment_record_error_with_additional_params(); test_log_level_verify(); test_record_log_event(); test_txn_log_configuration(); diff --git a/tests/integration/api/notice_error/test_transaction_additional_params.php b/tests/integration/api/notice_error/test_transaction_additional_params.php new file mode 100644 index 000000000..05027fe1d --- /dev/null +++ b/tests/integration/api/notice_error/test_transaction_additional_params.php @@ -0,0 +1,48 @@ +