From ce1673bdd106bcfc2f9c9d7982f7fc598dae0bb8 Mon Sep 17 00:00:00 2001 From: Hitesh Ahuja <108540135+hahuja2@users.noreply.github.com> Date: Tue, 3 Oct 2023 13:07:54 -0700 Subject: [PATCH 01/12] chore(agent): bump version to 10.14 (#740) --- VERSION | 2 +- axiom/nr_version.c | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/VERSION b/VERSION index db24ab967..0ca1348de 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -10.13.0 +10.14.0 diff --git a/axiom/nr_version.c b/axiom/nr_version.c index da541a91d..30fc06d36 100644 --- a/axiom/nr_version.c +++ b/axiom/nr_version.c @@ -40,8 +40,9 @@ * marigold 30May2023 (10.10) * narcissus 20Jun2023 (10.11) * orchid 20Sep2023 (10.12) + * poinsettia 03Oct2023 (10.13) */ -#define NR_CODENAME "poinsettia" +#define NR_CODENAME "quince" const char* nr_version(void) { return NR_STR2(NR_VERSION); From 41c3ba9e554d6c9170a6b78c465dcef750aac19a Mon Sep 17 00:00:00 2001 From: bduranleau-nr <106178551+bduranleau-nr@users.noreply.github.com> Date: Thu, 19 Oct 2023 16:25:12 -0500 Subject: [PATCH 02/12] chore: development environment enhancements (#747) Add a number of enhancements/fixes to the current development environment setup - add .vscode to .gitignore - remove broken mysqli install - programmatically determine PHP version for uopz/memcache docker install for compatibility with PHP < 8.0 - remove dead reference to mongo extension installations - add QOL aliases to the container to make a clean rebuild and integration tests easier to run --- .gitignore | 3 +++ files/Dockerfile | 34 ++++++++++++++++++++++------------ 2 files changed, 25 insertions(+), 12 deletions(-) diff --git a/.gitignore b/.gitignore index 52967292b..d850b2fd0 100644 --- a/.gitignore +++ b/.gitignore @@ -10,3 +10,6 @@ pkg/ integration-tests.log make/secrets.mk php_agent.log + +# Dev artifacts +.vscode diff --git a/files/Dockerfile b/files/Dockerfile index d699b6d2d..b0bec8bdd 100644 --- a/files/Dockerfile +++ b/files/Dockerfile @@ -125,28 +125,32 @@ RUN \ # the mysql tests a separate machine running mysql server 5.6 is required. RUN docker-php-ext-install pdo pdo_mysql -RUN docker-php-ext-install mysqli - # redis RUN pecl install redis && docker-php-ext-enable redis # memcache -RUN pecl install memcache && docker-php-ext-enable memcache +# Pre 8.0 requires 4.0.5.2 +RUN \ + php_cmp=$(php -r "echo version_compare(PHP_VERSION, '8.0.0', '>');"); \ + if [ "$php_cmp" = 1 ]; then \ + pecl install memcache && docker-php-ext-enable memcache; \ + else \ + pecl install memcache-4.0.5.2 && docker-php-ext-enable memcache; \ + fi # memcached RUN apt-get install -y libmemcached-dev RUN pecl install memcached && docker-php-ext-enable memcached -# mongo? -# this extension is no longer maintained and doesn't work with -# recent PHP releases -#RUN pecl install mongo && docker-php-ext-enable mongo - -# mongodb - need tests written for this maintained mongo extension -#RUN pecl install mongodb && docker-php-ext-enable mongodb - # uopz -RUN pecl install uopz && docker-php-ext-enable uopz +# Pre-8.0 requires 6.1.2 +RUN \ + php_cmp=$(php -r "echo version_compare(PHP_VERSION, '8.0.0', '>');"); \ + if [ "$php_cmp" = 1 ]; then \ + pecl install uopz && docker-php-ext-enable uopz; \ + else \ + pecl install uopz-6.1.2 && docker-php-ext-enable uopz; \ + fi # configure uopz to honor exit() and die() otherwise it just ignores these RUN echo "uopz.exit=1" > /usr/local/etc/php/conf.d/uopz-enable-exit.ini @@ -170,5 +174,11 @@ ENV PHP_VER=${PHP_VER} ARG PS1 ENV PS1="New Relic > " +# QOL aliases +# `rebuild` - make clean + make agent + make tests +# `integ` - run all integration tests +RUN echo 'alias integ="/usr/src/myapp/bin/integration_runner -agent /usr/src/myapp/agent/.libs/newrelic.so"' >> ~/.bashrc \ + && echo 'alias rebuild="make -C agent clean && rm agent/Makefile && make && make tests"' >> ~/.bashrc + WORKDIR /usr/src/myapp CMD ["bash"] From 9db916ed72822791e6f1a7a6272e161008ac06ec Mon Sep 17 00:00:00 2001 From: ZNeumann Date: Fri, 20 Oct 2023 08:30:15 -0600 Subject: [PATCH 03/12] fix(daemon): remove duplicate harvests (#745) The DefaultData harvest is only for data which can't/won't have its rate dictated by the Collector. All other data, which can have its own individual report rate, must be handled outside of the DefaultData block. SpanEvents and LogEvents already have their rates handled as such ([here](https://github.com/newrelic/newrelic-php-agent/pull/745/files#diff-d34a767f316aaeded794ac3a231f867d3ffb42de19365ed338eace17f5876d94R740) and [here](https://github.com/newrelic/newrelic-php-agent/pull/745/files#diff-d34a767f316aaeded794ac3a231f867d3ffb42de19365ed338eace17f5876d94R748)), and therefore should not also be reported with DefaultData. Because the DefaultData block was not resetting the above event containers, duplicate harvests were possible. --- src/newrelic/processor.go | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/newrelic/processor.go b/src/newrelic/processor.go index b3c472b98..08d2af55c 100644 --- a/src/newrelic/processor.go +++ b/src/newrelic/processor.go @@ -695,8 +695,6 @@ func harvestByType(ah *AppHarvest, args *harvestArgs, ht HarvestType, du_chan ch errors := harvest.Errors slowSQLs := harvest.SlowSQLs txnTraces := harvest.TxnTraces - spanEvents := harvest.SpanEvents - logEvents := harvest.LogEvents harvest.Metrics = NewMetricTable(limits.MaxMetrics, time.Now()) harvest.Errors = NewErrorHeap(limits.MaxErrors) @@ -709,8 +707,6 @@ func harvestByType(ah *AppHarvest, args *harvestArgs, ht HarvestType, du_chan ch considerHarvestPayload(errors, args, duc) considerHarvestPayload(slowSQLs, args, duc) considerHarvestPayload(txnTraces, args, duc) - considerHarvestPayload(spanEvents, args, duc) - considerHarvestPayload(logEvents, args, duc) } eventConfigs := ah.App.connectReply.EventHarvestConfig.EventConfigs From 51759a507e7b33dcf507d4460e9f99814e706c12 Mon Sep 17 00:00:00 2001 From: bduranleau-nr <106178551+bduranleau-nr@users.noreply.github.com> Date: Wed, 1 Nov 2023 09:27:43 -0500 Subject: [PATCH 04/12] fix(agent): Resolve mysqli deprecation warnings on PHP 8.1+ (#750) Certain wrapped mysqli functions allow null values to be passed for optional values on PHP 8.1+. This PR fixes the issue of the agent not properly expecting these null values in zpp resulting in deprecation warnings being thrown. --- agent/php_internal_instrument.c | 73 +++++++++++++++++-- .../mysqli/test_null_connect_proc.php | 36 +++++++++ .../mysqli/test_null_construct_oo.php | 36 +++++++++ tests/integration/mysqli/test_null_rc_oo.php | 38 ++++++++++ .../integration/mysqli/test_null_rc_proc.php | 39 ++++++++++ .../mysqli/test_null_stmt_construct.php | 60 +++++++++++++++ 6 files changed, 276 insertions(+), 6 deletions(-) create mode 100644 tests/integration/mysqli/test_null_connect_proc.php create mode 100644 tests/integration/mysqli/test_null_construct_oo.php create mode 100644 tests/integration/mysqli/test_null_rc_oo.php create mode 100644 tests/integration/mysqli/test_null_rc_proc.php create mode 100644 tests/integration/mysqli/test_null_stmt_construct.php diff --git a/agent/php_internal_instrument.c b/agent/php_internal_instrument.c index a2c898ff5..a314de027 100644 --- a/agent/php_internal_instrument.c +++ b/agent/php_internal_instrument.c @@ -643,14 +643,28 @@ NR_INNER_WRAPPER(mysqli_construct) { zval* mysqli_obj = NULL; int zcaught = 0; +#if ZEND_MODULE_API_NO >= ZEND_8_1_X_API_NO + bool port_is_null = 1; + const char *type_spec = "|s!s!s!s!l!s!"; if (FAILURE == zend_parse_parameters_ex( - ZEND_PARSE_PARAMS_QUIET, ZEND_NUM_ARGS() TSRMLS_CC, "|ssssls", &host, + ZEND_PARSE_PARAMS_QUIET, ZEND_NUM_ARGS() TSRMLS_CC, type_spec, &host, + &host_len, &username, &username_len, &password, &password_len, + &database, &database_len, &port, &port_is_null, &socket, &socket_len)) { + nr_wrapper->oldhandler(INTERNAL_FUNCTION_PARAM_PASSTHRU); + return; + } +#else + const char *type_spec = "|ssssls"; + if (FAILURE + == zend_parse_parameters_ex( + ZEND_PARSE_PARAMS_QUIET, ZEND_NUM_ARGS() TSRMLS_CC, type_spec, &host, &host_len, &username, &username_len, &password, &password_len, &database, &database_len, &port, &socket, &socket_len)) { nr_wrapper->oldhandler(INTERNAL_FUNCTION_PARAM_PASSTHRU); return; } +#endif zcaught = nr_zend_call_old_handler(nr_wrapper->oldhandler, INTERNAL_FUNCTION_PARAM_PASSTHRU); @@ -794,13 +808,21 @@ NR_INNER_WRAPPER(mysqli_commit) { zend_long flags = 0; nr_string_len_t name_len = 0; +#if ZEND_MODULE_API_NO >= ZEND_8_1_X_API_NO + const char *proc_type_spec = "o|ls!"; + const char *oo_type_spec = "|ls!"; +#else + const char *proc_type_spec = "o|ls"; + const char *oo_type_spec = "|ls"; +#endif + if (FAILURE == zend_parse_parameters_ex(ZEND_PARSE_PARAMS_QUIET, - ZEND_NUM_ARGS() TSRMLS_CC, "o|ls", + ZEND_NUM_ARGS() TSRMLS_CC, proc_type_spec, &mysqli_obj, &flags, &name, &name_len)) { if (FAILURE == zend_parse_parameters_ex(ZEND_PARSE_PARAMS_QUIET, - ZEND_NUM_ARGS() TSRMLS_CC, "|ls", &flags, + ZEND_NUM_ARGS() TSRMLS_CC, oo_type_spec, &flags, &name, &name_len)) { nr_wrapper->oldhandler(INTERNAL_FUNCTION_PARAM_PASSTHRU); return; @@ -808,6 +830,7 @@ NR_INNER_WRAPPER(mysqli_commit) { mysqli_obj = NR_PHP_INTERNAL_FN_THIS(); } } + nr_php_instrument_datastore_operation_call(nr_wrapper, NR_DATASTORE_MYSQL, "commit", instance, INTERNAL_FUNCTION_PARAM_PASSTHRU); @@ -849,15 +872,47 @@ NR_INNER_WRAPPER(mysqli_real_connect) { zval* mysqli_obj = NULL; int zcaught = 0; + /* PHP 8.1 and later will report a deprecation warning if null is sent where + * a non-null argument value is expected. For these PHP versions we use the + * same argument type specification as the mysqli::real_connect() extension + * uses to avoid creating this deprecation warning. + * For older PHPs continue to use the same specification string as previously + * to minimize any chances of introducing new problems. + */ +#if ZEND_MODULE_API_NO >= ZEND_8_1_X_API_NO + bool port_is_null = 1; + const char *proc_type_spec = "o|s!s!s!s!l!s!l"; + const char *oo_type_spec = "|s!s!s!s!l!s!l"; if (FAILURE == zend_parse_parameters_ex( - ZEND_PARSE_PARAMS_QUIET, ZEND_NUM_ARGS() TSRMLS_CC, "o|sssslsl", + ZEND_PARSE_PARAMS_QUIET, ZEND_NUM_ARGS() TSRMLS_CC, proc_type_spec, + &mysqli_obj, &host, &host_len, &username, &username_len, &password, + &password_len, &database, &database_len, &port, &port_is_null, &socket, &socket_len, + &flags)) { + if (FAILURE + == zend_parse_parameters_ex( + ZEND_PARSE_PARAMS_QUIET, ZEND_NUM_ARGS() TSRMLS_CC, oo_type_spec, + &host, &host_len, &username, &username_len, &password, + &password_len, &database, &database_len, &port, &port_is_null, &socket, + &socket_len, &flags)) { + nr_wrapper->oldhandler(INTERNAL_FUNCTION_PARAM_PASSTHRU); + return; + } else { + mysqli_obj = NR_PHP_INTERNAL_FN_THIS(); + } + } +#else + const char *proc_type_spec = "o|sssslsl"; + const char *oo_type_spec = "|sssslsl"; + if (FAILURE + == zend_parse_parameters_ex( + ZEND_PARSE_PARAMS_QUIET, ZEND_NUM_ARGS() TSRMLS_CC, proc_type_spec, &mysqli_obj, &host, &host_len, &username, &username_len, &password, &password_len, &database, &database_len, &port, &socket, &socket_len, &flags)) { if (FAILURE == zend_parse_parameters_ex( - ZEND_PARSE_PARAMS_QUIET, ZEND_NUM_ARGS() TSRMLS_CC, "|sssslsl", + ZEND_PARSE_PARAMS_QUIET, ZEND_NUM_ARGS() TSRMLS_CC, oo_type_spec, &host, &host_len, &username, &username_len, &password, &password_len, &database, &database_len, &port, &socket, &socket_len, &flags)) { @@ -867,6 +922,7 @@ NR_INNER_WRAPPER(mysqli_real_connect) { mysqli_obj = NR_PHP_INTERNAL_FN_THIS(); } } +#endif zcaught = nr_zend_call_old_handler(nr_wrapper->oldhandler, INTERNAL_FUNCTION_PARAM_PASSTHRU); @@ -1339,9 +1395,14 @@ NR_INNER_WRAPPER(mysqli_stmt_construct) { char* sqlstr = NULL; nr_string_len_t sqlstrlen = 0; +#if ZEND_MODULE_API_NO >= ZEND_8_1_X_API_NO + const char *type_spec = "o|s!"; +#else + const char *type_spec = "o|s"; +#endif if (FAILURE == zend_parse_parameters_ex(ZEND_PARSE_PARAMS_QUIET, - ZEND_NUM_ARGS() TSRMLS_CC, "o|s", &mysqli_obj, + ZEND_NUM_ARGS() TSRMLS_CC, type_spec, &mysqli_obj, &sqlstr, &sqlstrlen)) { nr_wrapper->oldhandler(INTERNAL_FUNCTION_PARAM_PASSTHRU); return; diff --git a/tests/integration/mysqli/test_null_connect_proc.php b/tests/integration/mysqli/test_null_connect_proc.php new file mode 100644 index 000000000..4981f3284 --- /dev/null +++ b/tests/integration/mysqli/test_null_connect_proc.php @@ -0,0 +1,36 @@ +options(MYSQLI_OPT_CONNECT_TIMEOUT, 10); +$link->real_connect(null, null, null, null, null, null); + +if (mysqli_connect_errno()) { + echo mysqli_connect_error() . "\n"; + exit(1); +} + +mysqli_close($link); diff --git a/tests/integration/mysqli/test_null_rc_proc.php b/tests/integration/mysqli/test_null_rc_proc.php new file mode 100644 index 000000000..74766ab53 --- /dev/null +++ b/tests/integration/mysqli/test_null_rc_proc.php @@ -0,0 +1,39 @@ +prepare($query) || + FALSE === $stmt->bind_param('s', $name) || + FALSE === $stmt->execute() || + FALSE === $stmt->bind_result($name)) { + echo mysqli_stmt_error($stmt) . "\n"; + mysqli_stmt_close($stmt); + return; + } + + while (mysqli_stmt_fetch($stmt)) { + echo $name . "\n"; + } + + mysqli_stmt_close($stmt); +} + +$link = mysqli_connect($MYSQL_HOST, $MYSQL_USER, $MYSQL_PASSWD, $MYSQL_DB, $MYSQL_PORT, $MYSQL_SOCKET); +if (mysqli_connect_errno()) { + echo mysqli_connect_error() . "\n"; + exit(1); +} + +test_stmt_prepare($link); +mysqli_close($link); + From 693e80ce2ee093fbdf7f3a6beb37cfaef19e11bc Mon Sep 17 00:00:00 2001 From: Michael Fulbright <89205663+mfulb@users.noreply.github.com> Date: Thu, 2 Nov 2023 12:18:53 -0400 Subject: [PATCH 05/12] feat: Implements APM Log Context Attributes (#758) This PR adds the log context attribute feature for APM Log Forwarding. --------- Co-authored-by: bduranleau-nr <106178551+bduranleau-nr@users.noreply.github.com> Co-authored-by: Amber Sistla Co-authored-by: Michal Nowacki Co-authored-by: Hitesh Ahuja <108540135+hahuja2@users.noreply.github.com> --- agent/Makefile.frag | 1 + agent/lib_monolog.c | 244 ++++++-------- agent/lib_monolog_private.h | 29 ++ agent/php_newrelic.h | 3 + agent/php_nrini.c | 24 ++ agent/php_txn.c | 14 + agent/scripts/newrelic.ini.template | 41 +++ agent/tests/test_monolog.c | 319 ++++++++++++++++++ axiom/nr_attributes.c | 234 ++++++++++++- axiom/nr_attributes.h | 37 +- axiom/nr_attributes_private.h | 2 + axiom/nr_log_event.c | 55 ++- axiom/nr_log_event.h | 5 +- axiom/nr_log_event_private.h | 4 +- axiom/nr_txn.c | 21 +- axiom/nr_txn.h | 11 +- axiom/tests/test_attributes.c | 67 +++- axiom/tests/test_cmd_txndata.c | 132 ++++++++ axiom/tests/test_log_event.c | 55 ++- axiom/tests/test_txn.c | 40 +-- .../attributes/test_filtering_1.php | 74 ++++ .../attributes/test_filtering_10.php | 74 ++++ .../attributes/test_filtering_11.php | 69 ++++ .../attributes/test_filtering_12.php | 74 ++++ .../attributes/test_filtering_2.php | 68 ++++ .../attributes/test_filtering_3.php | 73 ++++ .../attributes/test_filtering_4.php | 69 ++++ .../attributes/test_filtering_5.php | 76 +++++ .../attributes/test_filtering_6.php | 73 ++++ .../attributes/test_filtering_7.php | 74 ++++ .../attributes/test_filtering_8.php | 74 ++++ .../attributes/test_filtering_9.php | 74 ++++ .../monolog2/test_monolog_basic_clm.php | 160 +++++++++ .../monolog2/test_monolog_basic_clm_off.php | 154 +++++++++ .../monolog2/test_monolog_context_default.php | 103 ++++++ .../test_monolog_context_exception.php | 104 ++++++ .../test_monolog_context_filter_extra1.php | 115 +++++++ .../test_monolog_context_filter_extra2.php | 110 ++++++ .../test_monolog_context_filter_extra3.php | 115 +++++++ .../test_monolog_context_filter_extra4.php | 115 +++++++ .../test_monolog_context_filter_extra5.php | 114 +++++++ .../test_monolog_context_filter_rule1.php | 115 +++++++ .../test_monolog_context_filter_rule10.php | 113 +++++++ .../test_monolog_context_filter_rule11.php | 114 +++++++ .../test_monolog_context_filter_rule2.php | 113 +++++++ .../test_monolog_context_filter_rule3.php | 113 +++++++ .../test_monolog_context_filter_rule4.php | 114 +++++++ .../test_monolog_context_filter_rule5.php | 112 ++++++ .../test_monolog_context_filter_rule6.php | 110 ++++++ .../test_monolog_context_filter_rule7.php | 112 ++++++ .../test_monolog_context_filter_rule8.php | 114 +++++++ .../test_monolog_context_filter_rule9.php | 114 +++++++ ...monolog_context_hsm_disable_forwarding.php | 136 ++++++++ .../test_monolog_context_limits_1.php | 105 ++++++ .../test_monolog_context_limits_2.php | 108 ++++++ .../test_monolog_context_precedence_1.php | 106 ++++++ .../test_monolog_context_precedence_2.php | 106 ++++++ .../monolog2/test_monolog_context_simple.php | 236 +++++++++++++ .../monolog3/test_monolog_context_default.php | 103 ++++++ .../test_monolog_context_exception.php | 104 ++++++ .../test_monolog_context_filter_extra1.php | 115 +++++++ .../test_monolog_context_filter_extra2.php | 110 ++++++ .../test_monolog_context_filter_extra3.php | 115 +++++++ .../test_monolog_context_filter_extra4.php | 115 +++++++ .../test_monolog_context_filter_extra5.php | 114 +++++++ .../test_monolog_context_filter_rule1.php | 115 +++++++ .../test_monolog_context_filter_rule10.php | 113 +++++++ .../test_monolog_context_filter_rule11.php | 114 +++++++ .../test_monolog_context_filter_rule2.php | 113 +++++++ .../test_monolog_context_filter_rule3.php | 113 +++++++ .../test_monolog_context_filter_rule4.php | 114 +++++++ .../test_monolog_context_filter_rule5.php | 112 ++++++ .../test_monolog_context_filter_rule6.php | 110 ++++++ .../test_monolog_context_filter_rule7.php | 112 ++++++ .../test_monolog_context_filter_rule8.php | 114 +++++++ .../test_monolog_context_filter_rule9.php | 114 +++++++ ...monolog_context_hsm_disable_forwarding.php | 136 ++++++++ .../test_monolog_context_limits_1.php | 105 ++++++ .../test_monolog_context_limits_2.php | 108 ++++++ .../test_monolog_context_precedence_1.php | 106 ++++++ .../test_monolog_context_precedence_2.php | 106 ++++++ .../monolog3/test_monolog_context_simple.php | 236 +++++++++++++ 82 files changed, 7959 insertions(+), 199 deletions(-) create mode 100644 agent/lib_monolog_private.h create mode 100644 agent/tests/test_monolog.c create mode 100644 tests/integration/attributes/test_filtering_1.php create mode 100644 tests/integration/attributes/test_filtering_10.php create mode 100644 tests/integration/attributes/test_filtering_11.php create mode 100644 tests/integration/attributes/test_filtering_12.php create mode 100644 tests/integration/attributes/test_filtering_2.php create mode 100644 tests/integration/attributes/test_filtering_3.php create mode 100644 tests/integration/attributes/test_filtering_4.php create mode 100644 tests/integration/attributes/test_filtering_5.php create mode 100644 tests/integration/attributes/test_filtering_6.php create mode 100644 tests/integration/attributes/test_filtering_7.php create mode 100644 tests/integration/attributes/test_filtering_8.php create mode 100644 tests/integration/attributes/test_filtering_9.php create mode 100644 tests/integration/logging/monolog2/test_monolog_basic_clm.php create mode 100644 tests/integration/logging/monolog2/test_monolog_basic_clm_off.php create mode 100644 tests/integration/logging/monolog2/test_monolog_context_default.php create mode 100644 tests/integration/logging/monolog2/test_monolog_context_exception.php create mode 100644 tests/integration/logging/monolog2/test_monolog_context_filter_extra1.php create mode 100644 tests/integration/logging/monolog2/test_monolog_context_filter_extra2.php create mode 100644 tests/integration/logging/monolog2/test_monolog_context_filter_extra3.php create mode 100644 tests/integration/logging/monolog2/test_monolog_context_filter_extra4.php create mode 100644 tests/integration/logging/monolog2/test_monolog_context_filter_extra5.php create mode 100644 tests/integration/logging/monolog2/test_monolog_context_filter_rule1.php create mode 100644 tests/integration/logging/monolog2/test_monolog_context_filter_rule10.php create mode 100644 tests/integration/logging/monolog2/test_monolog_context_filter_rule11.php create mode 100644 tests/integration/logging/monolog2/test_monolog_context_filter_rule2.php create mode 100644 tests/integration/logging/monolog2/test_monolog_context_filter_rule3.php create mode 100644 tests/integration/logging/monolog2/test_monolog_context_filter_rule4.php create mode 100644 tests/integration/logging/monolog2/test_monolog_context_filter_rule5.php create mode 100644 tests/integration/logging/monolog2/test_monolog_context_filter_rule6.php create mode 100644 tests/integration/logging/monolog2/test_monolog_context_filter_rule7.php create mode 100644 tests/integration/logging/monolog2/test_monolog_context_filter_rule8.php create mode 100644 tests/integration/logging/monolog2/test_monolog_context_filter_rule9.php create mode 100644 tests/integration/logging/monolog2/test_monolog_context_hsm_disable_forwarding.php create mode 100644 tests/integration/logging/monolog2/test_monolog_context_limits_1.php create mode 100644 tests/integration/logging/monolog2/test_monolog_context_limits_2.php create mode 100644 tests/integration/logging/monolog2/test_monolog_context_precedence_1.php create mode 100644 tests/integration/logging/monolog2/test_monolog_context_precedence_2.php create mode 100644 tests/integration/logging/monolog2/test_monolog_context_simple.php create mode 100644 tests/integration/logging/monolog3/test_monolog_context_default.php create mode 100644 tests/integration/logging/monolog3/test_monolog_context_exception.php create mode 100644 tests/integration/logging/monolog3/test_monolog_context_filter_extra1.php create mode 100644 tests/integration/logging/monolog3/test_monolog_context_filter_extra2.php create mode 100644 tests/integration/logging/monolog3/test_monolog_context_filter_extra3.php create mode 100644 tests/integration/logging/monolog3/test_monolog_context_filter_extra4.php create mode 100644 tests/integration/logging/monolog3/test_monolog_context_filter_extra5.php create mode 100644 tests/integration/logging/monolog3/test_monolog_context_filter_rule1.php create mode 100644 tests/integration/logging/monolog3/test_monolog_context_filter_rule10.php create mode 100644 tests/integration/logging/monolog3/test_monolog_context_filter_rule11.php create mode 100644 tests/integration/logging/monolog3/test_monolog_context_filter_rule2.php create mode 100644 tests/integration/logging/monolog3/test_monolog_context_filter_rule3.php create mode 100644 tests/integration/logging/monolog3/test_monolog_context_filter_rule4.php create mode 100644 tests/integration/logging/monolog3/test_monolog_context_filter_rule5.php create mode 100644 tests/integration/logging/monolog3/test_monolog_context_filter_rule6.php create mode 100644 tests/integration/logging/monolog3/test_monolog_context_filter_rule7.php create mode 100644 tests/integration/logging/monolog3/test_monolog_context_filter_rule8.php create mode 100644 tests/integration/logging/monolog3/test_monolog_context_filter_rule9.php create mode 100644 tests/integration/logging/monolog3/test_monolog_context_hsm_disable_forwarding.php create mode 100644 tests/integration/logging/monolog3/test_monolog_context_limits_1.php create mode 100644 tests/integration/logging/monolog3/test_monolog_context_limits_2.php create mode 100644 tests/integration/logging/monolog3/test_monolog_context_precedence_1.php create mode 100644 tests/integration/logging/monolog3/test_monolog_context_precedence_2.php create mode 100644 tests/integration/logging/monolog3/test_monolog_context_simple.php diff --git a/agent/Makefile.frag b/agent/Makefile.frag index a6cec7adb..8a4b6705c 100644 --- a/agent/Makefile.frag +++ b/agent/Makefile.frag @@ -89,6 +89,7 @@ TEST_BINARIES = \ tests/test_internal_instrument \ tests/test_hash \ tests/test_mongodb \ + tests/test_monolog \ tests/test_mysql \ tests/test_mysqli \ tests/test_output \ diff --git a/agent/lib_monolog.c b/agent/lib_monolog.c index 3ea482b9a..f85848d4f 100644 --- a/agent/lib_monolog.c +++ b/agent/lib_monolog.c @@ -12,9 +12,12 @@ #include "php_wrapper.h" #include "fw_hooks.h" #include "fw_support.h" +#include "lib_monolog_private.h" #include "nr_datastore_instance.h" #include "nr_segment_datastore.h" +#include "nr_txn.h" #include "util_logging.h" +#include "util_object.h" #include "util_memory.h" #include "util_strings.h" #include "util_sleep.h" @@ -27,19 +30,6 @@ #define LOG_DECORATE_PROC_FUNC_NAME \ "newrelic_phpagent_monolog_decorating_processor" -// clang-format off -/* - * This macro affects how instrumentation $context argument of - * Monolog\Logger::addRecord works: - * - * 0 - $context argument will not be instrumented: its existance and value - * are ignored - * 1 - the message of the log record forwarded by the agent will have the value - * of $context appended to the value of $message. - */ -// clang-format on -#define HAVE_CONTEXT_IN_MESSAGE 0 - /* * Purpose : Convert Monolog\Logger::API to integer * @@ -155,123 +145,78 @@ static char* nr_monolog_get_message(NR_EXECUTE_PROTO TSRMLS_DC) { return message; } -#if HAVE_CONTEXT_IN_MESSAGE /* - * Purpose : Format key of $context array's element as string + * Purpose : Convert a zval value from context data to a nrobj_t * - * Params : zend_hash_key - * * - * Returns : A new string representing zval; caller must free - * - */ -static char* nr_monolog_fmt_context_key(const zend_hash_key* hash_key) { - char* key_str = NULL; - zval* key = nr_php_zval_alloc(); - if (nr_php_zend_hash_key_is_string(hash_key)) { - nr_php_zval_str(key, nr_php_zend_hash_key_string_value(hash_key)); - key_str = nr_formatf("%s", Z_STRVAL_P(key)); - } else if (nr_php_zend_hash_key_is_numeric(hash_key)) { - ZVAL_LONG(key, (zend_long)nr_php_zend_hash_key_integer(hash_key)); - key_str = nr_formatf("%ld", (long)Z_LVAL_P(key)); - } else { - /* - * This is a warning because this really, really shouldn't ever happen. - */ - nrl_warning(NRL_INSTRUMENT, "%s: unexpected key type", __func__); - key_str = nr_formatf("unsupported-key-type"); - } - nr_php_zval_free(&key); - return key_str; -} - -/* - * Purpose : Format value of $context array's element as string + * Params : zval * - * Params : zval value - * * - * Returns : A new string representing zval; caller must free + * Returns : nrobj_t* holding converted value + * NULL otherwise * + * Notes : Only scalar and string types are supported. + * Nested arrays are not converted and are ignored. + * Other zval types are also ignored. */ -static char* nr_monolog_fmt_context_value(zval* zv) { - char* val_str = NULL; - zval* zv_str = NULL; +nrobj_t* nr_monolog_context_data_zval_to_attribute_obj( + const zval* z TSRMLS_DC) { + nrobj_t* retobj = NULL; - if (NULL == zv) { - return nr_strdup(""); + if (NULL == z) { + return NULL; } - zv_str = nr_php_zval_alloc(); - if (NULL == zv_str) { - return nr_strdup(""); - } + nr_php_zval_unwrap(z); - ZVAL_DUP(zv_str, zv); - convert_to_string(zv_str); - val_str = nr_strdup(Z_STRVAL_P(zv_str)); - nr_php_zval_free(&zv_str); + switch (Z_TYPE_P(z)) { + case IS_NULL: + retobj = NULL; + break; - return val_str; -} + case IS_LONG: + retobj = nro_new_long((long)Z_LVAL_P(z)); + break; -/* - * Purpose : Format an element of $context array as "key => value" string - * - * Params : zval value, pointer to string buffer to store formatted output - * and hash key - * - * Side effect : string buffer is reallocated with each call. - * - * Returns : ZEND_HASH_APPLY_KEEP to keep iteration - * - */ -static int nr_monolog_fmt_context_item(zval* value, - char** strbuf, - zend_hash_key* hash_key TSRMLS_DC) { - NR_UNUSED_TSRMLS; - char* key = nr_monolog_fmt_context_key(hash_key); - char* val = nr_monolog_fmt_context_value(value); - - char* kv_str = nr_formatf("%s => %s", key, val); - nr_free(key); - nr_free(val); - - char* sep = nr_strlen(*strbuf) > 1 ? ", " : ""; - *strbuf = nr_str_append(*strbuf, kv_str, sep); - nr_free(kv_str); - - return ZEND_HASH_APPLY_KEEP; -} + case IS_DOUBLE: + retobj = nro_new_double(Z_DVAL_P(z)); + break; -/* - * Purpose : Iterate over $context array and format each element - * - * Params : string buffer to store formatted output and - * Monolog\Logger::addRecord argument list - * - * Returns : A new string with Monolog's log context - */ -static char* nr_monolog_fmt_context(char* strbuf, - HashTable* context TSRMLS_DC) { - strbuf = nr_str_append(strbuf, "[", ""); + case IS_TRUE: + retobj = nro_new_boolean(true); + break; + + case IS_FALSE: + retobj = nro_new_boolean(false); + break; - nr_php_zend_hash_zval_apply(context, - (nr_php_zval_apply_t)nr_monolog_fmt_context_item, - (void*)&strbuf TSRMLS_CC); + case IS_STRING: + if (!nr_php_is_zval_valid_string(z)) { + retobj = NULL; + } else { + retobj = nro_new_string(Z_STRVAL_P(z)); + } + break; + + default: + /* any other type conversion to attribute not supported */ + retobj = NULL; + break; + } - return nr_str_append(strbuf, "]", ""); + return retobj; } /* - * Purpose : Convert $context argument of Monolog\Logger::addRecord to a string + * Purpose : Get $context argument of Monolog\Logger::addRecord as `zval *`. * * Params : # of Monolog\Logger::addRecord arguments, and * Monolog\Logger::addRecord argument list * - * Returns : A new string with Monolog's log context + * Returns : zval* for context array on success (must be freed by caller) + * NULL otherwise + * */ -static char* nr_monolog_get_context(const size_t argc, - NR_EXECUTE_PROTO TSRMLS_DC) { - char* context = nr_strdup(""); +static zval* nr_monolog_extract_context_data(const size_t argc, + NR_EXECUTE_PROTO TSRMLS_DC) { zval* context_arg = NULL; if (3 > argc) { @@ -298,45 +243,57 @@ static char* nr_monolog_get_context(const size_t argc, goto return_context; } - context = nr_monolog_fmt_context(context, Z_ARRVAL_P(context_arg) TSRMLS_CC); - return_context: - nr_php_arg_release(&context_arg); - return context; + return context_arg; } -#endif /* - * Purpose : Combine $message and $context arguments of - * Monolog\Logger::addRecord into a single string to be used as a message - * property of the log event. + * Purpose : Convert $context array of Monolog\Logger::addRecord to + * attributes * - * Params : # of Monolog\Logger::addRecord arguments, and - * Monolog\Logger::addRecord argument list + * Params : zval* for context array from Monolog + * + * Returns : nr_attributes representation of $context on success + * NULL otherwise * - * Returns : A new string with a log record message; caller must free */ -static char* nr_monolog_build_message(const size_t argc, - NR_EXECUTE_PROTO TSRMLS_DC) { -#if !HAVE_CONTEXT_IN_MESSAGE - /* Make the compiler happy - argc is not used when $context is ignored */ - (void)argc; -#endif - char* message_and_context = nr_strdup(""); - - char* message = nr_monolog_get_message(NR_EXECUTE_ORIG_ARGS TSRMLS_CC); - message_and_context = nr_str_append(message_and_context, message, ""); - nr_free(message); +nr_attributes_t* nr_monolog_convert_context_data_to_attributes( + zval* context_data TSRMLS_DC) { + zend_string* key; + zval* val; -#if HAVE_CONTEXT_IN_MESSAGE - char* context = nr_monolog_get_context(argc, NR_EXECUTE_ORIG_ARGS TSRMLS_CC); - if (!nr_strempty(context)) { - message_and_context = nr_str_append(message_and_context, context, " "); + nr_attributes_t* attributes = NULL; + + if (NULL == context_data || !nr_php_is_zval_valid_array(context_data)) { + return NULL; + } + + attributes = nr_attributes_create(NRPRG(txn)->attribute_config); + if (NULL == attributes) { + return NULL; + } + + ZEND_HASH_FOREACH_STR_KEY_VAL(Z_ARR_P(context_data), key, val) { + if (NULL == key) { + continue; + } + + nrobj_t* obj = nr_monolog_context_data_zval_to_attribute_obj(val); + + if (NULL != obj) { + nr_attributes_user_add(attributes, NR_ATTRIBUTE_DESTINATION_LOG, + ZSTR_VAL(key), obj); + nro_delete(obj); + } else { + nrl_verbosedebug(NRL_INSTRUMENT, + "%s: log context attribute '%s' dropped due to value " + "being of unsupported type %d", + __func__, ZSTR_VAL(key), Z_TYPE_P(val)); + } } - nr_free(context); -#endif + ZEND_HASH_FOREACH_END(); - return message_and_context; + return attributes; } /* @@ -397,13 +354,22 @@ NR_PHP_WRAPPER(nr_monolog_logger_addrecord) { int api = 0; size_t argc = 0; char* message = NULL; + nr_attributes_t* context_attributes = NULL; nrtime_t timestamp = nr_get_time(); /* Values of $message and $timestamp arguments are needed only if log * forwarding is enabled so agent will get them conditionally */ if (nr_txn_log_forwarding_enabled(NRPRG(txn))) { argc = nr_php_get_user_func_arg_count(NR_EXECUTE_ORIG_ARGS TSRMLS_CC); - message = nr_monolog_build_message(argc, NR_EXECUTE_ORIG_ARGS TSRMLS_CC); + message = nr_monolog_get_message(NR_EXECUTE_ORIG_ARGS TSRMLS_CC); + + if (nr_txn_log_forwarding_context_data_enabled(NRPRG(txn))) { + zval* context_data = nr_monolog_extract_context_data( + argc, NR_EXECUTE_ORIG_ARGS TSRMLS_CC); + context_attributes + = nr_monolog_convert_context_data_to_attributes(context_data); + nr_php_arg_release(&context_data); + } api = nr_monolog_version(this_var TSRMLS_CC); timestamp = nr_monolog_get_timestamp(api, argc, NR_EXECUTE_ORIG_ARGS TSRMLS_CC); @@ -411,7 +377,7 @@ NR_PHP_WRAPPER(nr_monolog_logger_addrecord) { /* Record the log event */ nr_txn_record_log_event(NRPRG(txn), level_name, message, timestamp, - NRPRG(app)); + context_attributes, NRPRG(app)); nr_free(level_name); nr_free(message); diff --git a/agent/lib_monolog_private.h b/agent/lib_monolog_private.h new file mode 100644 index 000000000..de6f9c97b --- /dev/null +++ b/agent/lib_monolog_private.h @@ -0,0 +1,29 @@ +/* + * Copyright 2020 New Relic Corporation. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef LIB_MONOLOG_PRIVATE_HDR +#define LIB_MONOLOG_PRIVATE_HDR + +/* + * Purpose : ONLY for testing to verify that the appropriate behavior of + * the conversion of zvals to attribute via nro. + * + * Returns : Pointer to nr_object_t representation of zval or + * NULL if zval is not a supported type for conversion + * to an attribute + */ +extern nrobj_t* nr_monolog_context_data_zval_to_attribute_obj( + const zval* z TSRMLS_DC); + +/* + * Purpose : ONLY for testing to verify that the appropriate behavior of + * the conversion of a Monolog context array to attributes. + * + * Returns : Caller takes ownership of attributes struct + * + */ +extern nr_attributes_t* nr_monolog_convert_context_data_to_attributes( + zval* context_data TSRMLS_DC); +#endif /* LIB_MONOLOG_PRIVATE_HDR */ diff --git a/agent/php_newrelic.h b/agent/php_newrelic.h index 3f1f4b9b5..d7edaba65 100644 --- a/agent/php_newrelic.h +++ b/agent/php_newrelic.h @@ -308,6 +308,9 @@ nr_php_ini_attribute_config_t nr_php_ini_attribute_config_t browser_monitoring_attributes; /* newrelic.browser_monitoring.attributes.* */ +nr_php_ini_attribute_config_t + log_context_data_attributes; /* newrelic.application_logging.forwarding.context_data.* + */ nrinibool_t custom_events_enabled; /* newrelic.custom_insights_events.enabled */ nriniuint_t custom_events_max_samples_stored; /* newrelic.custom_events.max_samples_stored diff --git a/agent/php_nrini.c b/agent/php_nrini.c index ef1be6694..cea0f216f 100644 --- a/agent/php_nrini.c +++ b/agent/php_nrini.c @@ -2984,6 +2984,30 @@ STD_PHP_INI_ENTRY_EX("newrelic.application_logging.metrics.enabled", zend_newrelic_globals, newrelic_globals, nr_enabled_disabled_dh) +STD_PHP_INI_ENTRY_EX("newrelic.application_logging.forwarding.context_data.enabled", + "0", + NR_PHP_REQUEST, + nr_boolean_mh, + log_context_data_attributes.enabled, + zend_newrelic_globals, + newrelic_globals, + nr_enabled_disabled_dh) +STD_PHP_INI_ENTRY_EX("newrelic.application_logging.forwarding.context_data.include", + "", + NR_PHP_REQUEST, + nr_string_mh, + log_context_data_attributes.include, + zend_newrelic_globals, + newrelic_globals, + 0) +STD_PHP_INI_ENTRY_EX("newrelic.application_logging.forwarding.context_data.exclude", + "", + NR_PHP_REQUEST, + nr_string_mh, + log_context_data_attributes.exclude, + zend_newrelic_globals, + newrelic_globals, + 0) PHP_INI_END() /* } */ diff --git a/agent/php_txn.c b/agent/php_txn.c index 61a32a872..0ebb8cd00 100644 --- a/agent/php_txn.c +++ b/agent/php_txn.c @@ -447,6 +447,11 @@ static nr_attribute_config_t* nr_php_create_attribute_config(TSRMLS_D) { NRINI(browser_monitoring_capture_attributes), NR_ATTRIBUTE_DESTINATION_BROWSER); + disabled_destinations |= nr_php_attribute_disable_destination_helper( + "newrelic.application_logging.forwarding.context_data.enabled", + NRINI(log_context_data_attributes.enabled), 0, + NR_ATTRIBUTE_DESTINATION_LOG); + if (0 == NRINI(attributes.enabled)) { disabled_destinations |= NR_ATTRIBUTE_DESTINATION_ALL; } @@ -491,6 +496,13 @@ static nr_attribute_config_t* nr_php_create_attribute_config(TSRMLS_D) { config, 0, NRINI(browser_monitoring_attributes.exclude), 0, NR_ATTRIBUTE_DESTINATION_BROWSER); + nr_php_modify_attribute_destinations( + config, 0, NRINI(log_context_data_attributes.include), + NR_ATTRIBUTE_DESTINATION_LOG, 0); + nr_php_modify_attribute_destinations( + config, 0, NRINI(log_context_data_attributes.exclude), 0, + NR_ATTRIBUTE_DESTINATION_LOG); + nr_php_modify_attribute_destinations(config, 0, NRINI(attributes.include), NR_ATTRIBUTE_DESTINATION_ALL, 0); nr_php_modify_attribute_destinations(config, 0, NRINI(attributes.exclude), 0, @@ -747,6 +759,8 @@ nr_status_t nr_php_txn_begin(const char* appnames, opts.logging_enabled = NRINI(logging_enabled); opts.log_decorating_enabled = NRINI(log_decorating_enabled); opts.log_forwarding_enabled = NRINI(log_forwarding_enabled); + opts.log_forwarding_context_data_enabled + = NRINI(log_context_data_attributes.enabled); opts.log_forwarding_log_level = NRINI(log_forwarding_log_level); opts.log_events_max_samples_stored = NRINI(log_events_max_samples_stored); opts.log_metrics_enabled = NRINI(log_metrics_enabled); diff --git a/agent/scripts/newrelic.ini.template b/agent/scripts/newrelic.ini.template index 29b125083..e55296cd4 100644 --- a/agent/scripts/newrelic.ini.template +++ b/agent/scripts/newrelic.ini.template @@ -1239,6 +1239,47 @@ newrelic.daemon.logfile = "/var/log/newrelic/newrelic-daemon.log" ; ;newrelic.application_logging.metrics.enabled = true +; Setting: newrelic.application_logging.forwarding.context_data.enabled +; Type : boolean +; Scope : per-directory +; Default: false +; Info : Control if context data associated with log messages is +; converted to log event attributes which are forwarded to New Relic. +; +;newrelic.application_logging.forwarding.context_data.enabled = false + +; Setting: newrelic.application_logging.forwarding.context_data.include +; newrelic.application_logging.forwarding.context_data.exclude +; Type : string +; Scope : per-directory +; Default: none +; Info : This configuration options allow complete control over the +; context data array keys which are converted to log event attributes. +; +; To include context data whose key is 'alpha', the configuration is: +; newrelic.application_logging.forwarding.context_data.include = alpha +; +; To exclude context data whose key is 'alpha', the configuration is: +; newrelic.application_logging.forwarding.context_data.exclude = alpha +; +; The newrelic.attributes.exclude and newrelic.attributes.include +; settings affect the conversion of custom data as well. +; +; To exclude the attributes 'beta' and 'gamma' from all destinations, +; including log events, the configuration is: +; newrelic.attributes.exclude = beta,gamma +; +; If one of the values in the comma separated list ends in a '*', +; it will match any suffix. For example, to exclude any attribute +; which begin with 'psi', the configuration is: +; newrelic.attributes.exclude = psi* +; +; For more information, please refer to: +; https://docs.newrelic.com/docs/agents/manage-apm-agents/agent-metrics/agent-attributes +; +;newrelic.application_logging.forwarding.context_data.include = "" +;newrelic.application_logging.forwarding.context_data.exclude = "" + ; Setting: newrelic.code_level_metrics.enabled ; Type : boolean ; Scope : per-directory diff --git a/agent/tests/test_monolog.c b/agent/tests/test_monolog.c new file mode 100644 index 000000000..c1d415628 --- /dev/null +++ b/agent/tests/test_monolog.c @@ -0,0 +1,319 @@ +/* + * Copyright 2020 New Relic Corporation. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + */ +#include "tlib_php.h" +#include "tlib_datastore.h" + +#include "php_agent.h" +#include "nr_attributes.h" +#include "lib_monolog_private.h" + +tlib_parallel_info_t parallel_info + = {.suggested_nthreads = -1, .state_size = 0}; + +static void test_convert_zval_to_attribute_obj(TSRMLS_D) { + zval* obj; + nrobj_t* nrobj; + nr_status_t err; + + tlib_php_request_start(); + + /* test null zval */ + obj = nr_php_zval_alloc(); + nrobj = nr_monolog_context_data_zval_to_attribute_obj(obj); + tlib_pass_if_null("NULL zval", nrobj); + nr_php_zval_free(&obj); + nro_delete(nrobj); + + /* test boolean */ + obj = tlib_php_request_eval_expr("True;" TSRMLS_CC); + nrobj = nr_monolog_context_data_zval_to_attribute_obj(obj); + tlib_pass_if_not_null("Boolean converted", nrobj); + tlib_pass_if_equal("Boolean type correct", NR_OBJECT_BOOLEAN, nro_type(nrobj), + int, "%d"); + tlib_pass_if_true("Boolean value correct", nro_get_boolean(nrobj, &err), + "expected true"); + tlib_pass_if_equal("Boolean GET successful", NR_SUCCESS, err, int, "%d"); + nr_php_zval_free(&obj); + nro_delete(nrobj); + + /* long */ + obj = tlib_php_request_eval_expr("1234567;" TSRMLS_CC); + nrobj = nr_monolog_context_data_zval_to_attribute_obj(obj); + tlib_pass_if_not_null("Long converted", nrobj); + tlib_pass_if_equal("Long type correct", NR_OBJECT_LONG, nro_type(nrobj), int, + "%d"); + tlib_pass_if_equal("Long value correct", 1234567, nro_get_long(nrobj, &err), + int, "%d"); + tlib_pass_if_equal("Long GET successful", NR_SUCCESS, err, int, "%d"); + nr_php_zval_free(&obj); + nro_delete(nrobj); + + /* double */ + obj = tlib_php_request_eval_expr("1.234567;" TSRMLS_CC); + nrobj = nr_monolog_context_data_zval_to_attribute_obj(obj); + tlib_pass_if_not_null("Double converted", nrobj); + tlib_pass_if_equal("Double type correct", NR_OBJECT_DOUBLE, nro_type(nrobj), + int, "%d"); + tlib_pass_if_equal("Double value correct", 1.234567, + nro_get_double(nrobj, &err), int, "%d"); + tlib_pass_if_equal("Double GET successful", NR_SUCCESS, err, int, "%d"); + nr_php_zval_free(&obj); + nro_delete(nrobj); + + /* string */ + obj = tlib_php_request_eval_expr("\"A\";" TSRMLS_CC); + nrobj = nr_monolog_context_data_zval_to_attribute_obj(obj); + tlib_pass_if_not_null("String converted", nrobj); + tlib_pass_if_equal("String type correct", NR_OBJECT_STRING, nro_type(nrobj), + int, "%d"); + tlib_pass_if_str_equal("String value correct", "A", + nro_get_string(nrobj, &err)); + tlib_pass_if_equal("String GET successful", NR_SUCCESS, err, int, "%d"); + nr_php_zval_free(&obj); + nro_delete(nrobj); + + /* constant boolean */ + tlib_php_request_eval("define(\"CONSTANT_DEFINE_BOOLEAN\", True);" TSRMLS_CC); + obj = tlib_php_request_eval_expr("CONSTANT_DEFINE_BOOLEAN;" TSRMLS_CC); + nrobj = nr_monolog_context_data_zval_to_attribute_obj(obj); + tlib_pass_if_not_null("Constant Boolean converted", nrobj); + tlib_pass_if_equal("Constant Boolean type correct", NR_OBJECT_BOOLEAN, + nro_type(nrobj), int, "%d"); + tlib_pass_if_true("Constant Boolean value correct", + nro_get_boolean(nrobj, &err), "expected true"); + tlib_pass_if_equal("Constant Boolean GET successful", NR_SUCCESS, err, int, + "%d"); + nr_php_zval_free(&obj); + nro_delete(nrobj); + + /* constant long */ + tlib_php_request_eval("define(\"CONSTANT_DEFINE_LONG\",1234567);" TSRMLS_CC); + obj = tlib_php_request_eval_expr("CONSTANT_DEFINE_LONG;" TSRMLS_CC); + nrobj = nr_monolog_context_data_zval_to_attribute_obj(obj); + tlib_pass_if_not_null("Constant Long converted", nrobj); + tlib_pass_if_equal("Constant Long type correct", NR_OBJECT_LONG, + nro_type(nrobj), int, "%d"); + tlib_pass_if_equal("Constant Long value correct", 1234567, + nro_get_long(nrobj, &err), int, "%d"); + tlib_pass_if_equal("Constant Long GET successful", NR_SUCCESS, err, int, + "%d"); + nr_php_zval_free(&obj); + nro_delete(nrobj); + + /* double */ + tlib_php_request_eval( + "define(\"CONSTANT_DEFINE_DOUBLE\",1.234567);" TSRMLS_CC); + obj = tlib_php_request_eval_expr("CONSTANT_DEFINE_DOUBLE;" TSRMLS_CC); + nrobj = nr_monolog_context_data_zval_to_attribute_obj(obj); + tlib_pass_if_not_null("Constant Double converted", nrobj); + tlib_pass_if_equal("Constant Double type correct", NR_OBJECT_DOUBLE, + nro_type(nrobj), int, "%d"); + tlib_pass_if_equal("Constant Double value correct", 1.234567, + nro_get_double(nrobj, &err), int, "%d"); + tlib_pass_if_equal("Constant Double GET successful", NR_SUCCESS, err, int, + "%d"); + nr_php_zval_free(&obj); + nro_delete(nrobj); + + /* test constant string */ + tlib_php_request_eval("define(\"CONSTANT_DEFINE_STRING\", \"A\");" TSRMLS_CC); + obj = tlib_php_request_eval_expr("CONSTANT_DEFINE_STRING;" TSRMLS_CC); + nrobj = nr_monolog_context_data_zval_to_attribute_obj(obj); + tlib_pass_if_not_null("Constant String converted", nrobj); + tlib_pass_if_equal("Constant tring type correct", NR_OBJECT_STRING, + nro_type(nrobj), int, "%d"); + tlib_pass_if_str_equal("Constant String value correct", "A", + nro_get_string(nrobj, &err)); + tlib_pass_if_equal("Constant String GET successful", NR_SUCCESS, err, int, + "%d"); + nr_php_zval_free(&obj); + nro_delete(nrobj); + + /* test array */ + obj = tlib_php_request_eval_expr("array(1, 2, 3);" TSRMLS_CC); + nrobj = nr_monolog_context_data_zval_to_attribute_obj(obj); + tlib_pass_if_null("Array not converted", nrobj); + nr_php_zval_free(&obj); + nro_delete(nrobj); + + /* test object */ + obj = tlib_php_request_eval_expr("new stdClass();" TSRMLS_CC); + nrobj = nr_monolog_context_data_zval_to_attribute_obj(obj); + tlib_pass_if_null("Object not converted", nrobj); + nr_php_zval_free(&obj); + nro_delete(nrobj); + + tlib_php_request_end(); +} + +#define TEST_ATTRIBUTES_CREATION(CONTEXT_DATA, EXPECTED_JSON) \ + do { \ + char* actual_json; \ + nr_attributes_t* attributes \ + = nr_monolog_convert_context_data_to_attributes(context_data); \ + \ + tlib_fail_if_null("attributes is not NULL", attributes); \ + \ + nrobj_t* log_attributes = nr_attributes_logcontext_to_obj( \ + attributes, NR_ATTRIBUTE_DESTINATION_LOG); \ + \ + tlib_fail_if_null("log_attributes is not NULL", log_attributes); \ + tlib_fail_if_bool_equal("At least one attribute created", 1, \ + 0 > nro_getsize(log_attributes)); \ + \ + if (0 < nro_getsize(log_attributes)) { \ + actual_json = nro_to_json(log_attributes); \ + } \ + \ + tlib_pass_if_str_equal("Converted array", expected_json, actual_json); \ + nr_free(actual_json); \ + nro_delete(log_attributes); \ + nr_attributes_destroy(&attributes); \ + } while (0) + +static void test_convert_context_data_to_attributes(TSRMLS_D) { + zval* context_data; + + tlib_php_request_start(); + nrtxn_t* txn = NRPRG(txn); + + /* enable context data filtering */ + nr_attribute_config_t* orig_config + = nr_attribute_config_copy(NRPRG(txn)->attribute_config); + txn->options.log_forwarding_context_data_enabled = 1; + nr_attribute_config_enable_destinations(txn->attribute_config, + NR_ATTRIBUTE_DESTINATION_LOG); + + context_data = tlib_php_request_eval_expr( + "array(" + "1=>\"one\"," + "\"null_attr\"=>null," + "\"string_attr\"=>\"string_value\"," + "\"double_attr\"=>3.1," + "\"int_attr\"=>1234," + "\"true_bool_attr\"=>True," + "\"false_bool_attr\"=>False," + "\"array_attr\"=>array(\"nested_string\"=>\"nested_string_value\")," + "\"object_attr\"=>new StdClass())" TSRMLS_CC); + + /* test without any filters and all attributes allowed */ + char* expected_json + = "{" + "\"context.false_bool_attr\":false," + "\"context.true_bool_attr\":true," + "\"context.int_attr\":1234," + "\"context.double_attr\":3.10000," + "\"context.string_attr\":\"string_value\"" + "}"; + + TEST_ATTRIBUTES_CREATION(context_data, expected_json); + + /* add filtering rules and try again */ + nr_attribute_config_modify_destinations(NRPRG(txn)->attribute_config, + "string_attr", + NR_ATTRIBUTE_DESTINATION_LOG, 0); + nr_attribute_config_modify_destinations(NRPRG(txn)->attribute_config, "i*", + NR_ATTRIBUTE_DESTINATION_LOG, 0); + nr_attribute_config_modify_destinations(NRPRG(txn)->attribute_config, "f*", 0, + NR_ATTRIBUTE_DESTINATION_LOG); + nr_attribute_config_modify_destinations(NRPRG(txn)->attribute_config, "t*", 0, + NR_ATTRIBUTE_DESTINATION_LOG); + expected_json + = "{" + "\"context.int_attr\":1234," + "\"context.string_attr\":\"string_value\"" + "}"; + + TEST_ATTRIBUTES_CREATION(context_data, expected_json); + + /* another case to add filtering rules and try again */ + nr_attribute_config_destroy(&(NRPRG(txn)->attribute_config)); + NRPRG(txn)->attribute_config = nr_attribute_config_copy(orig_config); + nr_attribute_config_enable_destinations(txn->attribute_config, + NR_ATTRIBUTE_DESTINATION_LOG); + nr_attribute_config_modify_destinations(NRPRG(txn)->attribute_config, "d*", + NR_ATTRIBUTE_DESTINATION_LOG, 0); + nr_attribute_config_modify_destinations(NRPRG(txn)->attribute_config, "i*", + NR_ATTRIBUTE_DESTINATION_LOG, 0); + nr_attribute_config_modify_destinations(NRPRG(txn)->attribute_config, "*", 0, + NR_ATTRIBUTE_DESTINATION_LOG); + expected_json + = "{" + "\"context.int_attr\":1234," + "\"context.double_attr\":3.10000" + "}"; + + TEST_ATTRIBUTES_CREATION(context_data, expected_json); + + /* test global and context_data include/exclude rules */ + nr_attribute_config_destroy(&(NRPRG(txn)->attribute_config)); + NRPRG(txn)->attribute_config = nr_attribute_config_copy(orig_config); + nr_attribute_config_enable_destinations(txn->attribute_config, + NR_ATTRIBUTE_DESTINATION_LOG); + nr_attribute_config_modify_destinations(NRPRG(txn)->attribute_config, "d*", + NR_ATTRIBUTE_DESTINATION_LOG, 0); + nr_attribute_config_modify_destinations(NRPRG(txn)->attribute_config, "i*", + NR_ATTRIBUTE_DESTINATION_LOG, 0); + nr_attribute_config_modify_destinations(NRPRG(txn)->attribute_config, + "true_bool_attr", + NR_ATTRIBUTE_DESTINATION_LOG, 0); + nr_attribute_config_modify_destinations(NRPRG(txn)->attribute_config, "t*", 0, + NR_ATTRIBUTE_DESTINATION_ALL); + nr_attribute_config_modify_destinations(NRPRG(txn)->attribute_config, + "false_bool_attr", 0, + NR_ATTRIBUTE_DESTINATION_ALL); + expected_json + = "{" + "\"context.true_bool_attr\":true," + "\"context.int_attr\":1234," + "\"context.double_attr\":3.10000" + "}"; + + TEST_ATTRIBUTES_CREATION(context_data, expected_json); + + nr_attribute_config_destroy(&orig_config); + nr_php_zval_free(&context_data); + + tlib_php_request_end(); +} + +static void test_convert_context_data_to_attributes_bad_params(TSRMLS_D) { + tlib_php_request_start(); + + /* enable context data destination */ + nrtxn_t* txn = NRPRG(txn); + txn->options.log_forwarding_context_data_enabled = 1; + nr_attribute_config_enable_destinations(txn->attribute_config, + NR_ATTRIBUTE_DESTINATION_LOG); + + nr_attributes_t* attributes + = nr_monolog_convert_context_data_to_attributes(NULL); + + tlib_pass_if_null("NULL context yields attributes is NULL", attributes); + + // create an undefined zval - nr_php_zval_alloc() returns undefined + zval* z = nr_php_zval_alloc(); + + tlib_pass_if_equal("zval is undefined type", IS_UNDEF, Z_TYPE_P(z), int, "%d"); + + attributes = nr_monolog_convert_context_data_to_attributes(z); + + tlib_pass_if_null("zval of undefined type yields attributes is NULL", + attributes); + nr_php_zval_free(&z); + + tlib_php_request_end(); +} + +void test_main(void* p NRUNUSED) { + + tlib_php_engine_create(""); + + test_convert_zval_to_attribute_obj(); + test_convert_context_data_to_attributes(); + test_convert_context_data_to_attributes_bad_params(); + + tlib_php_engine_destroy(TSRMLS_C); +} diff --git a/axiom/nr_attributes.c b/axiom/nr_attributes.c index 68d0d8c73..54cee95ca 100644 --- a/axiom/nr_attributes.c +++ b/axiom/nr_attributes.c @@ -17,6 +17,13 @@ #include "util_memory.h" #include "util_strings.h" +static void nr_attribute_config_modify_destinations_internal( + nr_attribute_config_t* config, + const char* match, + uint32_t include_destinations, + uint32_t exclude_destinations, + bool finalize_destinations); + /* * Returns : 1 if there is a match and 0 otherwise. */ @@ -97,10 +104,20 @@ void nr_attribute_config_disable_destinations(nr_attribute_config_t* config, config->disabled_destinations |= disabled_destinations; } -nr_attribute_destination_modifier_t* nr_attribute_destination_modifier_create( - const char* match, - uint32_t include_destinations, - uint32_t exclude_destinations) { +void nr_attribute_config_enable_destinations(nr_attribute_config_t* config, + uint32_t enabled_destinations) { + if (0 == config) { + return; + } + + config->disabled_destinations &= ~enabled_destinations; +} + +static nr_attribute_destination_modifier_t* +nr_attribute_destination_modifier_create_internal(const char* match, + uint32_t include_destinations, + uint32_t exclude_destinations, + int is_finalize_rule) { nr_attribute_destination_modifier_t* new_entry; int match_len; int has_wildcard_suffix; @@ -121,6 +138,8 @@ nr_attribute_destination_modifier_t* nr_attribute_destination_modifier_create( new_entry = (nr_attribute_destination_modifier_t*)nr_zalloc( sizeof(nr_attribute_destination_modifier_t)); new_entry->has_wildcard_suffix = has_wildcard_suffix; + new_entry->is_finalize_rule = is_finalize_rule; + /* Use nr_strndup with match_len to avoid copying '*' suffix */ new_entry->match = nr_strndup(match, match_len); new_entry->match_len = match_len; @@ -132,6 +151,14 @@ nr_attribute_destination_modifier_t* nr_attribute_destination_modifier_create( return new_entry; } +nr_attribute_destination_modifier_t* nr_attribute_destination_modifier_create( + const char* match, + uint32_t include_destinations, + uint32_t exclude_destinations) { + return nr_attribute_destination_modifier_create_internal( + match, include_destinations, exclude_destinations, false); +} + /* * Purpose : Determine the precedence order of two destination modifiers. * @@ -162,10 +189,132 @@ static int nr_attribute_destination_modifier_compare( return 1; } -void nr_attribute_config_modify_destinations(nr_attribute_config_t* config, - const char* match, - uint32_t include_destinations, - uint32_t exclude_destinations) { +/* + * Purpose : Inspects current modifier list and adds finalize rules as needed. + * + * Params : 1. Attribute configuration + * + * Notes : Certain attributes like log context attributes expect the "include" + * rules to act exclusively. + * For example: + * include = "A" + * exclude = "B" + * input = "A" "B" "C" + * expected = "A" + * + * Note that "C" was excluded because it was not in the include rules. + * Also note an empty include rule means include everything and + * and to exclude nothing. + * + * All other attributes besides log context attributes do NOT have + * this exclusive behavior for the include rules. This function only + * considers rules of the NR_ATTRIBUTE_DESTINATION_LOG destination. + * + * The algorithm examines all input rules for the + * NR_ATTRIBUTE_DESTINATION_LOG destination, and will create a new + * rule of "exclude=*" with "is_finalize_rule=true" if any input + * rules exist. The exception is if there is an input rule of "*" + * for destination NR_ATTRIBUTE_DESTINATION_LOG. In this case no + * finalize rule is added. + * + * The net resulting effect is the exclusion of any attributes not + * contained in the set of input rules for + * NR_ATTRIBUTE_DESTINATION_LOG. The exception for if an "input=*" + * rule is necessary since this input rule excludes nothing, making + * the finalize rule of "exclude=*" unnecessary. + * + */ +static void nr_attribute_config_finalize_log_destination( + nr_attribute_config_t* config) { + nr_attribute_destination_modifier_t* cur = NULL; + nr_attribute_destination_modifier_t* prev = NULL; + nr_attribute_destination_modifier_t* next = NULL; + bool add_finalize_rule = false; + + if (NULL == config || NULL == config->modifier_list) { + /* Since there is no configuration, no work to do. */ + return; + } + + /* remove any existing rules with is_finalize_rule = true */ + cur = config->modifier_list; + while (NULL != cur) { + next = cur->next; + + /* currently only finalize rules being created are for the + * NR_ATTRIBUTE_DESTINATION_LOG destination but check to + * be thorough and in case other finalize rules are created + * in the future. + */ + if (cur->is_finalize_rule + && (cur->include_destinations & NR_ATTRIBUTE_DESTINATION_LOG)) { + nr_attribute_destination_modifier_destroy(&cur); + if (NULL != prev) { + prev->next = next; + } else { + config->modifier_list = next; + } + } else { + prev = cur; + } + cur = next; + } + + /* unlikely but if all rules were finalize rules then no more work to do */ + if (NULL == config->modifier_list) { + return; + } + + /* now look for any include rules with a destination of + * NR_ATTRIBUTE_DESTINATION_LOG and evaluate if any + * finalize rules need to be added. + */ + for (cur = config->modifier_list; cur; cur = cur->next) { + if (cur->include_destinations & NR_ATTRIBUTE_DESTINATION_LOG) { + if (cur->has_wildcard_suffix && 0 == cur->match_len) { + /* there is an include rule of "*" so no finalize is needed + * since all attributes are being explicitely included */ + return; + } + add_finalize_rule = true; + } + } + + /* a finalize rule is needed + * add an exclude rule of "*" which will remove any attributes which passed + * through the include rules and therefore are excluded implicitely + */ + if (add_finalize_rule) { + nr_attribute_config_modify_destinations_internal( + config, "*", 0, NR_ATTRIBUTE_DESTINATION_LOG, true); + } +} + +/* + * Purpose : Inserts a modifier rule into an attribute configuration. + * + * Params : 1. Attribute config to add modifier to + * 2. String containing matching text for modifier + * 3. Destinations to apply match to as include rule + * 4. Destinations to apply match to as exclude rule + * 5. If true this is a finalize rule used to cause + * include rules (on log destinations only currently) + * to be exclusive to anything not in the set of all + * include rules. + * For normal modifiers (from user rules) this can be false. + * + * Notes : The is_finalize_rule option is currently only used in + * nr_attribute_config_finalize_log_destination() as it + * is called while adding an attribute and without this + * option there would be an infinite loop of adding and + * finalizing. + */ +static void nr_attribute_config_modify_destinations_internal( + nr_attribute_config_t* config, + const char* match, + uint32_t include_destinations, + uint32_t exclude_destinations, + bool is_finalize_rule) { nr_attribute_destination_modifier_t* entry; nr_attribute_destination_modifier_t* new_entry; nr_attribute_destination_modifier_t** entry_ptr; @@ -174,8 +323,8 @@ void nr_attribute_config_modify_destinations(nr_attribute_config_t* config, return; } - new_entry = nr_attribute_destination_modifier_create( - match, include_destinations, exclude_destinations); + new_entry = nr_attribute_destination_modifier_create_internal( + match, include_destinations, exclude_destinations, is_finalize_rule); if (0 == new_entry) { return; } @@ -190,11 +339,14 @@ void nr_attribute_config_modify_destinations(nr_attribute_config_t* config, break; } - if (0 == cmp) { + /* if a finalize rule with the same name as a user rule is added (or vice + * verse), do not remove the existing one or else the user rule could be + * lost when finalizing */ + if (0 == cmp && new_entry->is_finalize_rule == entry->is_finalize_rule) { entry->include_destinations |= new_entry->include_destinations; entry->exclude_destinations |= new_entry->exclude_destinations; nr_attribute_destination_modifier_destroy(&new_entry); - return; + goto finalize_modifier; } entry_ptr = &entry->next; @@ -203,6 +355,24 @@ void nr_attribute_config_modify_destinations(nr_attribute_config_t* config, new_entry->next = entry; *entry_ptr = new_entry; + +finalize_modifier: + /* if an include modifier was added need to also add an exclude rule of "*" + * to have include rule act to exclude anything not included. Exception is + * if include rule was simply "*" which would allow everything so no + * exclude=* is required + */ + if (!is_finalize_rule) { + nr_attribute_config_finalize_log_destination(config); + } +} + +void nr_attribute_config_modify_destinations(nr_attribute_config_t* config, + const char* match, + uint32_t include_destinations, + uint32_t exclude_destinations) { + nr_attribute_config_modify_destinations_internal( + config, match, include_destinations, exclude_destinations, false); } static nr_attribute_destination_modifier_t* @@ -213,6 +383,7 @@ nr_attribute_destination_modifier_copy( new_entry = (nr_attribute_destination_modifier_t*)nr_zalloc(sizeof(*new_entry)); new_entry->has_wildcard_suffix = entry->has_wildcard_suffix; + new_entry->is_finalize_rule = entry->is_finalize_rule; new_entry->match = nr_strdup(entry->match); new_entry->match_len = entry->match_len; new_entry->match_hash = entry->match_hash; @@ -650,14 +821,23 @@ nr_status_t nr_attributes_agent_add_string(nr_attributes_t* ats, return rv; } +/* + * Purpose : Internal function to convert list of attributes to nro + * + * Params : 1. List of attributes + * 2. Prefix to prepend to all attribute names + * NULL indicates to use no prefix and is more efficient than "" + * 3. Attribute destinations + */ static nrobj_t* nr_attributes_to_obj_internal( const nr_attribute_t* attribute_list, + const char* attribute_prefix, uint32_t destination) { nrobj_t* obj; const nr_attribute_t* attribute; - if (0 == attribute_list) { - return 0; + if (NULL == attribute_list) { + return NULL; } obj = nro_new_hash(); @@ -666,7 +846,14 @@ static nrobj_t* nr_attributes_to_obj_internal( if (0 == (attribute->destinations & destination)) { continue; } - nro_set_hash(obj, attribute->key, attribute->value); + + if (nrlikely(NULL == attribute_prefix)) { + nro_set_hash(obj, attribute->key, attribute->value); + } else { + char* key = nr_formatf("%s%s", attribute_prefix, attribute->key); + nro_set_hash(obj, key, attribute->value); + nr_free(key); + } } return obj; @@ -677,7 +864,7 @@ nrobj_t* nr_attributes_user_to_obj(const nr_attributes_t* attributes, if (0 == attributes) { return 0; } - return nr_attributes_to_obj_internal(attributes->user_attribute_list, + return nr_attributes_to_obj_internal(attributes->user_attribute_list, NULL, destination); } @@ -686,7 +873,17 @@ nrobj_t* nr_attributes_agent_to_obj(const nr_attributes_t* attributes, if (0 == attributes) { return 0; } - return nr_attributes_to_obj_internal(attributes->agent_attribute_list, + return nr_attributes_to_obj_internal(attributes->agent_attribute_list, NULL, + destination); +} + +nrobj_t* nr_attributes_logcontext_to_obj(const nr_attributes_t* attributes, + uint32_t destination) { + if (NULL == attributes) { + return NULL; + } + return nr_attributes_to_obj_internal(attributes->user_attribute_list, + NR_LOG_CONTEXT_DATA_ATTRIBUTE_PREFIX, destination); } @@ -714,6 +911,9 @@ static char* nr_attribute_debug_json(const nr_attribute_t* attribute) { if (NR_ATTRIBUTE_DESTINATION_BROWSER & attribute->destinations) { nro_set_array_string(dests, 0, "browser"); } + if (NR_ATTRIBUTE_DESTINATION_LOG & attribute->destinations) { + nro_set_array_string(dests, 0, "log"); + } nro_set_hash(obj, "dests", dests); nro_delete(dests); diff --git a/axiom/nr_attributes.h b/axiom/nr_attributes.h index f324354b8..7c8774b00 100644 --- a/axiom/nr_attributes.h +++ b/axiom/nr_attributes.h @@ -38,10 +38,11 @@ typedef struct _nr_attributes_t nr_attributes_t; #define NR_ATTRIBUTE_DESTINATION_ERROR 4 #define NR_ATTRIBUTE_DESTINATION_BROWSER 8 #define NR_ATTRIBUTE_DESTINATION_SPAN 16 +#define NR_ATTRIBUTE_DESTINATION_LOG 32 #define NR_ATTRIBUTE_DESTINATION_ALL \ (NR_ATTRIBUTE_DESTINATION_TXN_EVENT | NR_ATTRIBUTE_DESTINATION_TXN_TRACE \ | NR_ATTRIBUTE_DESTINATION_ERROR | NR_ATTRIBUTE_DESTINATION_BROWSER \ - | NR_ATTRIBUTE_DESTINATION_SPAN) + | NR_ATTRIBUTE_DESTINATION_SPAN | NR_ATTRIBUTE_DESTINATION_LOG) /* * Attribute keys and value string lengths are limited. If a string exceeds @@ -56,6 +57,11 @@ typedef struct _nr_attributes_t nr_attributes_t; */ #define NR_ATTRIBUTE_USER_LIMIT 64 +/* + * APM log forwarding context attributes SHOULD have a prefix of "context." + */ +#define NR_LOG_CONTEXT_DATA_ATTRIBUTE_PREFIX "context." + /* * Configuration * @@ -99,6 +105,22 @@ extern void nr_attribute_config_disable_destinations( nr_attribute_config_t* config, uint32_t disabled_destinations); +/* + * Purpose : Enable attribute destinations. + * + * Params : 1. The configuration to modify. + * 2. The set of destinations to enable. + * + * Note : Destinations are enabled by default when a config + * is created. This function can be used to enable + * a previously disabled destination. Current + * use is to enable destinations disabled by default + * for testing purposes. + */ +extern void nr_attribute_config_enable_destinations( + nr_attribute_config_t* config, + uint32_t enabled_destinations); + /* * Purpose : Modify the destinations of a particular attribute, or all * attributes matching a certain prefix. @@ -185,6 +207,19 @@ extern nrobj_t* nr_attributes_user_to_obj(const nr_attributes_t* attributes, extern nrobj_t* nr_attributes_agent_to_obj(const nr_attributes_t* attributes, uint32_t destination); +/* + * Purpose : Specialized conversion function for attributes created on + * log events. These SHOULD have a prefix of "context." + * to avoid name collision with attributes from other + * destinations. + * + * Params : 1. Pointer to list of log context attributes + * 2. Attribute destinations for attribute + */ +extern nrobj_t* nr_attributes_logcontext_to_obj( + const nr_attributes_t* attributes, + uint32_t destination); + /* * Purpose : Check if an attribute with the given key exists. */ diff --git a/axiom/nr_attributes_private.h b/axiom/nr_attributes_private.h index 74edbfac4..3df7973a2 100644 --- a/axiom/nr_attributes_private.h +++ b/axiom/nr_attributes_private.h @@ -13,6 +13,8 @@ typedef struct _nr_attribute_destination_modifier_t { int has_wildcard_suffix; /* Whether 'match' is exact or a prefix. */ + int is_finalize_rule; /* if true this rule was added by finalize step + and not by a user rule */ char* match; /* The string to match against. This will not contain a trailing '*'. */ int match_len; /* The length of 'match'. */ diff --git a/axiom/nr_log_event.c b/axiom/nr_log_event.c index 0fbd90bef..dd3675868 100644 --- a/axiom/nr_log_event.c +++ b/axiom/nr_log_event.c @@ -33,6 +33,9 @@ void nr_log_event_destroy(nr_log_event_t** ptr) { nr_free(event->entity_guid); nr_free(event->entity_name); nr_free(event->hostname); + if (NULL != event->context_attributes) { + nr_attributes_destroy(&(event->context_attributes)); + } nr_realfree((void**)ptr); } @@ -46,6 +49,8 @@ void nr_log_event_destroy(nr_log_event_t** ptr) { * 3. Value of the field (JSON value) * 4. Boolean indicating if this is the first field * 5. Boolean indicating if this field is required + * 6. Boolean indicating if this field should be quoted + * (use false if including a JSON string, for example) * * Returns : True is data was added to buf. */ @@ -53,7 +58,8 @@ static bool add_log_field_to_buf(nrbuf_t* buf, const char* field_name, const char* field_value, const bool first, - const bool required) { + const bool required, + const bool quoted) { const char* final_value = field_value; if (NULL == buf || nr_strempty(field_name)) { @@ -75,7 +81,11 @@ static bool add_log_field_to_buf(nrbuf_t* buf, nr_buffer_add(buf, field_name, nr_strlen(field_name)); nr_buffer_add(buf, NR_PSTR("\"")); nr_buffer_add(buf, NR_PSTR(":")); - nr_buffer_add_escape_json(buf, final_value); + if (quoted) { + nr_buffer_add_escape_json(buf, final_value); + } else { + nr_buffer_add(buf, final_value, nr_strlen(final_value)); + } return true; } @@ -99,6 +109,9 @@ char* nr_log_event_to_json(const nr_log_event_t* event) { } bool nr_log_event_to_json_buffer(const nr_log_event_t* event, nrbuf_t* buf) { + char* json = NULL; + nrobj_t* log_attributes = NULL; + if (NULL == event || NULL == buf) { return false; } @@ -107,18 +120,34 @@ bool nr_log_event_to_json_buffer(const nr_log_event_t* event, nrbuf_t* buf) { nr_buffer_add(buf, NR_PSTR("{")); // only add non-empty fields - add_log_field_to_buf(buf, "message", event->message, true, true); - add_log_field_to_buf(buf, "level", event->log_level, false, true); - add_log_field_to_buf(buf, "trace.id", event->trace_id, false, false); - add_log_field_to_buf(buf, "span.id", event->span_id, false, false); - add_log_field_to_buf(buf, "entity.guid", event->entity_guid, false, false); - add_log_field_to_buf(buf, "entity.name", event->entity_name, false, false); - add_log_field_to_buf(buf, "hostname", event->hostname, false, false); + add_log_field_to_buf(buf, "message", event->message, true, true, true); + add_log_field_to_buf(buf, "level", event->log_level, false, true, true); + add_log_field_to_buf(buf, "trace.id", event->trace_id, false, false, true); + add_log_field_to_buf(buf, "span.id", event->span_id, false, false, true); + add_log_field_to_buf(buf, "entity.guid", event->entity_guid, false, false, + true); + add_log_field_to_buf(buf, "entity.name", event->entity_name, false, false, + true); + add_log_field_to_buf(buf, "hostname", event->hostname, false, false, true); // timestamp always present nr_buffer_add(buf, NR_PSTR(",\"timestamp\":")); nr_buffer_write_uint64_t_as_text(buf, event->timestamp); + // add attributes if present + if (NULL != event->context_attributes) { + log_attributes = nr_attributes_logcontext_to_obj( + event->context_attributes, NR_ATTRIBUTE_DESTINATION_LOG); + + if (0 < nro_getsize(log_attributes)) { + json = nro_to_json(log_attributes); + add_log_field_to_buf(buf, "attributes", json, false, false, false); + nr_free(json); + } + + nro_delete(log_attributes); + } + nr_buffer_add(buf, NR_PSTR("}")); return true; @@ -153,6 +182,14 @@ void nr_log_event_set_timestamp(nr_log_event_t* event, const nrtime_t time) { event->timestamp = time / NR_TIME_DIVISOR_MS; } +void nr_log_event_set_context_attributes(nr_log_event_t* event, + nr_attributes_t* context_attributes) { + if (NULL == event) { + return; + } + event->context_attributes = context_attributes; +} + void nr_log_event_set_trace_id(nr_log_event_t* event, const char* trace_id) { if (NULL == event || NULL == trace_id) { return; diff --git a/axiom/nr_log_event.h b/axiom/nr_log_event.h index f3010b4a3..32d7581dc 100644 --- a/axiom/nr_log_event.h +++ b/axiom/nr_log_event.h @@ -8,6 +8,7 @@ #include "util_buffer.h" #include "util_time.h" +#include "nr_attributes.h" #include #include @@ -99,5 +100,7 @@ extern void nr_log_event_set_entity_name(nr_log_event_t* event, extern void nr_log_event_set_hostname(nr_log_event_t* event, const char* name); extern void nr_log_event_set_timestamp(nr_log_event_t* event, nrtime_t time); extern void nr_log_event_set_priority(nr_log_event_t* event, int priority); - +extern void nr_log_event_set_context_attributes( + nr_log_event_t* event, + nr_attributes_t* context_attributes); #endif /* NR_LOG_EVENT_HDR */ diff --git a/axiom/nr_log_event_private.h b/axiom/nr_log_event_private.h index ded8af50a..8e059e0dd 100644 --- a/axiom/nr_log_event_private.h +++ b/axiom/nr_log_event_private.h @@ -6,12 +6,12 @@ #ifndef NR_LOG_EVENT_PRIVATE_H #define NR_LOG_EVENT_PRIVATE_H -#include "util_object.h" - +#include "nr_attributes.h" struct _nr_log_event_t { char* message; char* log_level; nrtime_t timestamp; + nr_attributes_t* context_attributes; char* trace_id; char* span_id; char* entity_guid; diff --git a/axiom/nr_txn.c b/axiom/nr_txn.c index aa0c91c92..27f326d16 100644 --- a/axiom/nr_txn.c +++ b/axiom/nr_txn.c @@ -3276,6 +3276,18 @@ bool nr_txn_log_forwarding_enabled(nrtxn_t* txn) { return true; } +bool nr_txn_log_forwarding_context_data_enabled(nrtxn_t* txn) { + if (!nr_txn_log_forwarding_enabled(txn)) { + return false; + } + + if (!txn->options.log_forwarding_context_data_enabled) { + return false; + } + + return true; +} + bool nr_txn_log_forwarding_log_level_verify(nrtxn_t* txn, const char* log_level_name) { int log_level; @@ -3371,6 +3383,7 @@ static void log_event_set_linking_metadata(nr_log_event_t* e, static nr_log_event_t* log_event_create(const char* log_level_name, const char* log_message, nrtime_t timestamp, + nr_attributes_t* context_attributes, nrtxn_t* txn, nrapp_t* app) { nr_log_event_t* e = nr_log_event_create(); @@ -3380,6 +3393,7 @@ static nr_log_event_t* log_event_create(const char* log_level_name, nr_log_event_set_log_level(e, ENSURE_LOG_LEVEL_NAME(log_level_name)); nr_log_event_set_message(e, log_message); nr_log_event_set_timestamp(e, timestamp); + nr_log_event_set_context_attributes(e, context_attributes); log_event_set_linking_metadata(e, txn, app); @@ -3390,6 +3404,7 @@ static void nr_txn_add_log_event(nrtxn_t* txn, const char* log_level_name, const char* log_message, nrtime_t timestamp, + nr_attributes_t* context_attributes, nrapp_t* app) { nr_log_event_t* e = NULL; bool event_dropped = false; @@ -3411,7 +3426,7 @@ static void nr_txn_add_log_event(nrtxn_t* txn, event_dropped = true; } else { /* event passed log level filter so add it */ - e = log_event_create(log_level_name, log_message, timestamp, txn, app); + e = log_event_create(log_level_name, log_message, timestamp, context_attributes, txn, app); if (NULL == e) { nrl_debug(NRL_TXN, "%s: failed to create log event", __func__); event_dropped = true; @@ -3448,12 +3463,14 @@ void nr_txn_record_log_event(nrtxn_t* txn, const char* log_level_name, const char* log_message, nrtime_t timestamp, + nr_attributes_t* context_attributes, nrapp_t* app) { if (nrunlikely(NULL == txn)) { return; } - nr_txn_add_log_event(txn, log_level_name, log_message, timestamp, app); + nr_txn_add_log_event(txn, log_level_name, log_message, timestamp, + context_attributes, app); nr_txn_add_logging_metrics(txn, log_level_name); } diff --git a/axiom/nr_txn.h b/axiom/nr_txn.h index a51424b6a..e3e063214 100644 --- a/axiom/nr_txn.h +++ b/axiom/nr_txn.h @@ -41,7 +41,6 @@ #include "util_string_pool.h" #define NR_TXN_REQUEST_PARAMETER_ATTRIBUTE_PREFIX "request.parameters." - typedef enum _nr_tt_recordsql_t { NR_SQL_NONE = 0, NR_SQL_RAW = 1, @@ -122,6 +121,7 @@ typedef struct _nrtxnopt_t { application logging features */ bool log_decorating_enabled; /* Whether log decorating is enabled */ bool log_forwarding_enabled; /* Whether log forwarding is enabled */ + bool log_forwarding_context_data_enabled; /* Whether context data is forwarded with logs */ int log_forwarding_log_level; /* minimum log level to forward to the collector */ size_t log_events_max_samples_stored; /* The maximum number of log events per @@ -628,6 +628,11 @@ extern void nr_txn_record_custom_event(nrtxn_t* txn, */ extern bool nr_txn_log_forwarding_enabled(nrtxn_t* txn); +/* + * Purpose : Check log forwarding context data configuration + */ +extern bool nr_txn_log_forwarding_context_data_enabled(nrtxn_t* txn); + /* * Purpose : Check log forwarding log level configuration */ @@ -651,13 +656,15 @@ extern bool nr_txn_log_decorating_enabled(nrtxn_t* txn); * 2. Log record level name * 3. Log record message * 4. Log record timestamp - * 5. The application (to get linking meta data) + * 5. Attribute data for Monolog context data (can be NULL) + * 6. The application (to get linking meta data) * */ extern void nr_txn_record_log_event(nrtxn_t* txn, const char* level_name, const char* message, nrtime_t timestamp, + nr_attributes_t* context_attributes, nrapp_t* app); /* diff --git a/axiom/tests/test_attributes.c b/axiom/tests/test_attributes.c index e6aa81ff2..e1d9d3b5e 100644 --- a/axiom/tests/test_attributes.c +++ b/axiom/tests/test_attributes.c @@ -32,6 +32,7 @@ static char* nr_attribute_destination_modifier_to_json( obj = nro_new_hash(); nro_set_hash_boolean(obj, "has_wildcard_suffix", modifier->has_wildcard_suffix); + nro_set_hash_boolean(obj, "is_finalize_rule", modifier->is_finalize_rule); nro_set_hash_string(obj, "match", modifier->match); nro_set_hash_int(obj, "match_len", modifier->match_len); nro_set_hash_long(obj, "match_hash", modifier->match_hash); @@ -241,6 +242,7 @@ static void test_destination_modifier_create(void) { test_modifier_as_json("exact match modifier created", modifier, "{" "\"has_wildcard_suffix\":false," + "\"is_finalize_rule\":false," "\"match\":\"alpha\"," "\"match_len\":5," "\"match_hash\":2000440672," @@ -253,6 +255,7 @@ static void test_destination_modifier_create(void) { test_modifier_as_json("wildcard modifier created", modifier, "{" "\"has_wildcard_suffix\":true," + "\"is_finalize_rule\":false," "\"match\":\"alpha\"," "\"match_len\":5," "\"match_hash\":2000440672," @@ -268,7 +271,7 @@ static void test_config_modify_destinations(void) { uint32_t trace = NR_ATTRIBUTE_DESTINATION_TXN_TRACE; uint32_t error = NR_ATTRIBUTE_DESTINATION_ERROR; uint32_t browser = NR_ATTRIBUTE_DESTINATION_BROWSER; - + uint32_t log = NR_ATTRIBUTE_DESTINATION_LOG; /* NULL config: Don't blow up! */ nr_attribute_config_modify_destinations(0, "alpha", event, error); @@ -283,6 +286,10 @@ static void test_config_modify_destinations(void) { nr_attribute_config_modify_destinations(config, "beta.*", 0, trace); nr_attribute_config_modify_destinations(config, "beta.alpha", 0, browser); + nr_attribute_config_modify_destinations(config, "beta.al*", 0, log); + + nr_attribute_config_modify_destinations(config, "beta.alp", log, 0); + nr_attribute_config_modify_destinations(config, "beta.alph", 0, browser); test_config_as_json("modifiers created and in correct order", config, "{" @@ -291,6 +298,16 @@ static void test_config_modify_destinations(void) { "[" "{" "\"has_wildcard_suffix\":true," + "\"is_finalize_rule\":true," + "\"match\":\"\"," + "\"match_len\":0," + "\"match_hash\":0," + "\"include_destinations\":0," + "\"exclude_destinations\":32" + "}," + "{" + "\"has_wildcard_suffix\":true," + "\"is_finalize_rule\":false," "\"match\":\"beta.\"," "\"match_len\":5," "\"match_hash\":1419915658," @@ -299,6 +316,7 @@ static void test_config_modify_destinations(void) { "}," "{" "\"has_wildcard_suffix\":false," + "\"is_finalize_rule\":false," "\"match\":\"beta.\"," "\"match_len\":5," "\"match_hash\":1419915658," @@ -307,6 +325,7 @@ static void test_config_modify_destinations(void) { "}," "{" "\"has_wildcard_suffix\":false," + "\"is_finalize_rule\":false," "\"match\":\"beta.a\"," "\"match_len\":6," "\"match_hash\":4222617845," @@ -314,7 +333,17 @@ static void test_config_modify_destinations(void) { "\"exclude_destinations\":0" "}," "{" + "\"has_wildcard_suffix\":true," + "\"is_finalize_rule\":false," + "\"match\":\"beta.al\"," + "\"match_len\":7," + "\"match_hash\":3041978671," + "\"include_destinations\":0," + "\"exclude_destinations\":32" + "}," + "{" "\"has_wildcard_suffix\":false," + "\"is_finalize_rule\":false," "\"match\":\"beta.al\"," "\"match_len\":7," "\"match_hash\":3041978671," @@ -323,6 +352,25 @@ static void test_config_modify_destinations(void) { "}," "{" "\"has_wildcard_suffix\":false," + "\"is_finalize_rule\":false," + "\"match\":\"beta.alp\"," + "\"match_len\":8," + "\"match_hash\":4236011699," + "\"include_destinations\":32," + "\"exclude_destinations\":0" + "}," + "{" + "\"has_wildcard_suffix\":false," + "\"is_finalize_rule\":false," + "\"match\":\"beta.alph\"," + "\"match_len\":9," + "\"match_hash\":2625680436," + "\"include_destinations\":0," + "\"exclude_destinations\":8" + "}," + "{" + "\"has_wildcard_suffix\":false," + "\"is_finalize_rule\":false," "\"match\":\"beta.alpha\"," "\"match_len\":10," "\"match_hash\":2601622409," @@ -344,6 +392,7 @@ static void test_config_copy(void) { uint32_t trace = NR_ATTRIBUTE_DESTINATION_TXN_TRACE; uint32_t error = NR_ATTRIBUTE_DESTINATION_ERROR; uint32_t browser = NR_ATTRIBUTE_DESTINATION_BROWSER; + uint32_t log = NR_ATTRIBUTE_DESTINATION_LOG; config_copy = nr_attribute_config_copy(0); tlib_pass_if_true("copy NULL config", 0 == config_copy, "config_copy=%p", @@ -374,6 +423,9 @@ static void test_config_copy(void) { nr_attribute_config_modify_destinations(config, "beta.", browser, 0); nr_attribute_config_modify_destinations(config, "beta.*", 0, trace); + nr_attribute_config_modify_destinations(config, "gamma.", log, 0); + nr_attribute_config_modify_destinations(config, "gamma.*", 0, trace); + config_copy = nr_attribute_config_copy(config); config_copy_json = nr_attribute_config_to_json(config_copy); config_json = nr_attribute_config_to_json(config); @@ -394,6 +446,7 @@ static void test_config_apply(void) { uint32_t trace = NR_ATTRIBUTE_DESTINATION_TXN_TRACE; uint32_t error = NR_ATTRIBUTE_DESTINATION_ERROR; uint32_t browser = NR_ATTRIBUTE_DESTINATION_BROWSER; + uint32_t log = NR_ATTRIBUTE_DESTINATION_LOG; config = nr_attribute_config_create(); @@ -414,9 +467,10 @@ static void test_config_apply(void) { /* * Test that the destination modifier are applied in the correct order. */ - nr_attribute_config_modify_destinations(config, "alpha.*", browser | trace, + nr_attribute_config_modify_destinations(config, "alpha.*", browser | trace | log, 0); nr_attribute_config_modify_destinations(config, "alpha.beta", error, browser); + nr_attribute_config_modify_destinations(config, "alpha.b*", 0, log); destinations = nr_attribute_config_apply(config, "alpha.beta", nr_mkhash("alpha.beta", 0), event); @@ -568,6 +622,7 @@ static void test_add(void) { uint32_t trace = NR_ATTRIBUTE_DESTINATION_TXN_TRACE; uint32_t error = NR_ATTRIBUTE_DESTINATION_ERROR; uint32_t browser = NR_ATTRIBUTE_DESTINATION_BROWSER; + uint32_t log = NR_ATTRIBUTE_DESTINATION_LOG; uint32_t all = NR_ATTRIBUTE_DESTINATION_ALL; nr_status_t st; nrobj_t* obj; @@ -605,6 +660,8 @@ static void test_add(void) { tlib_pass_if_true("add success", NR_SUCCESS == st, "st=%d", (int)st); st = nr_attributes_user_add_string(attributes, error, "gamma", "789"); tlib_pass_if_true("add success", NR_SUCCESS == st, "st=%d", (int)st); + st = nr_attributes_user_add_string(attributes, log, "delta", "abc"); + tlib_pass_if_true("add success", NR_SUCCESS == st, "st=%d", (int)st); st = nr_attributes_agent_add_long(attributes, browser | event, "psi", 123); tlib_pass_if_true("add success", NR_SUCCESS == st, "st=%d", (int)st); @@ -613,6 +670,8 @@ static void test_add(void) { st = nr_attributes_agent_add_string(attributes, browser | error, "theta", "789"); tlib_pass_if_true("add success", NR_SUCCESS == st, "st=%d", (int)st); + st = nr_attributes_agent_add_string(attributes, log, "chi", "abc"); + tlib_pass_if_true("add success", NR_SUCCESS == st, "st=%d", (int)st); st = nr_attributes_agent_add_string(attributes, 0, "no_destinations_ignore_me", "789"); @@ -625,10 +684,10 @@ static void test_add(void) { test_user_attributes_as_json( "user attributes: all", attributes, all, - "{\"gamma\":\"789\",\"beta\":456,\"alpha\":123}"); + "{\"delta\":\"abc\",\"gamma\":\"789\",\"beta\":456,\"alpha\":123}"); test_agent_attributes_as_json( "agent attributes: all", attributes, all, - "{\"theta\":\"789\",\"omega\":456,\"psi\":123}"); + "{\"chi\":\"abc\",\"theta\":\"789\",\"omega\":456,\"psi\":123}"); test_user_attributes_as_json("user attributes: event", attributes, event, "{\"alpha\":123}"); diff --git a/axiom/tests/test_cmd_txndata.c b/axiom/tests/test_cmd_txndata.c index e840e0614..0871d7b30 100644 --- a/axiom/tests/test_cmd_txndata.c +++ b/axiom/tests/test_cmd_txndata.c @@ -761,6 +761,137 @@ static void test_encode_custom_events(void) { nro_delete(params); } +static void test_encode_log_events(void) { + nrtxn_t txn; + nr_flatbuffers_table_t tbl; + nr_flatbuffer_t* fb; + nr_aoffset_t events; + nrtime_t now; + uint32_t count; + int data_type; + int did_pass; + nr_log_event_t* log1 = NULL; + nr_log_event_t* log2 = NULL; + nr_attributes_t* attributes = NULL; + nr_attribute_config_t* config = NULL; + + now = 123 * NR_TIME_DIVISOR; + + // create 2 log event events + log1 = nr_log_event_create(); + nr_log_event_set_log_level(log1, "LOG_LEVEL_TEST_ERROR"); + nr_log_event_set_message(log1, "\" \\ / \b \f \n \r \t GBP sign \xc2\xa3xxx"); + nr_log_event_set_timestamp(log1, now); + nr_log_event_set_trace_id(log1, "test id 1"); + nr_log_event_set_span_id(log1, "test id 2"); + nr_log_event_set_guid(log1, "test id 3"); + nr_log_event_set_entity_name(log1, "entity name here"); + nr_log_event_set_hostname(log1, "host name here"); + config = nr_attribute_config_create(); + attributes = nr_attributes_create(config); + nr_attribute_config_destroy(&config); + nr_attributes_user_add_string(attributes, NR_ATTRIBUTE_DESTINATION_LOG, + "string_attr", "string_attr_value"); + nr_attributes_user_add_long(attributes, NR_ATTRIBUTE_DESTINATION_LOG, + "long_attr", 12345); + nr_log_event_set_context_attributes(log1, attributes); + + log2 = nr_log_event_create(); + nr_log_event_set_log_level(log2, "LOG_LEVEL_TEST_WARN"); + nr_log_event_set_message(log2, "\" \\ / \b \f \n \r \t GBP sign \xc2\xa3xxx"); + nr_log_event_set_timestamp(log2, now); + nr_log_event_set_trace_id(log2, "test id 3"); + nr_log_event_set_span_id(log2, "test id 4"); + nr_log_event_set_guid(log2, "test id 5"); + nr_log_event_set_entity_name(log2, "entity name here 2"); + nr_log_event_set_hostname(log2, "host name here 2"); + + // add event2 + nr_memset(&txn, 0, sizeof(txn)); + txn.status.recording = 1; + txn.app_limits.log_events = 100; + txn.log_events = nr_log_events_create(100); + nr_log_events_add_event(txn.log_events, log1); + nr_log_events_add_event(txn.log_events, log2); + + fb = nr_txndata_encode(&txn); + nr_flatbuffers_table_init_root(&tbl, nr_flatbuffers_data(fb), + nr_flatbuffers_len(fb)); + + data_type = nr_flatbuffers_table_read_i8(&tbl, MESSAGE_FIELD_DATA_TYPE, + MESSAGE_BODY_NONE); + did_pass = tlib_pass_if_true(__func__, MESSAGE_BODY_TXN == data_type, + "data_type=%d", data_type); + if (0 != did_pass) { + goto done; + } + + did_pass = tlib_pass_if_true( + __func__, + 0 != nr_flatbuffers_table_read_union(&tbl, &tbl, MESSAGE_FIELD_DATA), + "transaction data missing"); + if (0 != did_pass) { + goto done; + } + + count = nr_flatbuffers_table_read_vector_len(&tbl, + TRANSACTION_FIELD_LOG_EVENTS); + if (0 != tlib_pass_if_true(__func__, 2 == count, "count=%d", count)) { + goto done; + } + + events = nr_flatbuffers_table_read_vector(&tbl, TRANSACTION_FIELD_LOG_EVENTS); + + nr_flatbuffers_table_init( + &tbl, tbl.data, tbl.length, + nr_flatbuffers_read_indirect(tbl.data, events).offset); + tlib_pass_if_bytes_equal_f( + __func__, + NR_PSTR("{" + "\"message\":\"\\\" \\\\ \\/ \\b \\f \\n \\r \\t GBP sign " + "\\u00a3xxx\"," + "\"level\":\"LOG_LEVEL_TEST_WARN\"," + "\"trace.id\":\"test id 3\"," + "\"span.id\":\"test id 4\"," + "\"entity.guid\":\"test id 5\"," + "\"entity.name\":\"entity name here 2\"," + "\"hostname\":\"host name here 2\"," + "\"timestamp\":123000" + "}"), + nr_flatbuffers_table_read_bytes(&tbl, EVENT_FIELD_DATA), + nr_flatbuffers_table_read_vector_len(&tbl, EVENT_FIELD_DATA), __FILE__, + __LINE__); + + events.offset += sizeof(uint32_t); + nr_flatbuffers_table_init( + &tbl, tbl.data, tbl.length, + nr_flatbuffers_read_indirect(tbl.data, events).offset); + tlib_pass_if_bytes_equal_f( + __func__, + NR_PSTR("{" + "\"message\":\"\\\" \\\\ \\/ \\b \\f \\n \\r \\t GBP sign " + "\\u00a3xxx\"," + "\"level\":\"LOG_LEVEL_TEST_ERROR\"," + "\"trace.id\":\"test id 1\"," + "\"span.id\":\"test id 2\"," + "\"entity.guid\":\"test id 3\"," + "\"entity.name\":\"entity name here\"," + "\"hostname\":\"host name here\"," + "\"timestamp\":123000," + "\"attributes\":{" + "\"context.long_attr\":12345," + "\"context.string_attr\":\"string_attr_value\"" + "}" + "}"), + nr_flatbuffers_table_read_bytes(&tbl, EVENT_FIELD_DATA), + nr_flatbuffers_table_read_vector_len(&tbl, EVENT_FIELD_DATA), __FILE__, + __LINE__); + +done: + nr_flatbuffers_destroy(&fb); + nr_txn_destroy_fields(&txn); +} + static void test_encode_trace(void) { nrtxn_t txn; nr_flatbuffers_table_t tbl; @@ -1081,6 +1212,7 @@ void test_main(void* p NRUNUSED) { test_encode_span_events(); test_encode_trace(); test_encode_txn_event(); + test_encode_log_events(); test_bad_daemon_fd(); test_null_txn(); diff --git a/axiom/tests/test_log_event.c b/axiom/tests/test_log_event.c index 38468892e..a5554411a 100644 --- a/axiom/tests/test_log_event.c +++ b/axiom/tests/test_log_event.c @@ -29,6 +29,8 @@ static void test_log_event_create_destroy(void) { static void test_log_event_to_json(void) { char* json; nr_log_event_t* log; + nr_attributes_t* attributes = NULL; + nr_attribute_config_t* config = NULL; /* * Test : Bad parameters. @@ -99,6 +101,14 @@ static void test_log_event_to_json(void) { nr_log_event_set_guid(log, "test id 3"); nr_log_event_set_entity_name(log, "entity name here"); nr_log_event_set_hostname(log, "host name here"); + config = nr_attribute_config_create(); + attributes = nr_attributes_create(config); + nr_attribute_config_destroy(&config); + nr_attributes_user_add_string(attributes, NR_ATTRIBUTE_DESTINATION_LOG, + "string_attr", "string_attr_value"); + nr_attributes_user_add_long(attributes, NR_ATTRIBUTE_DESTINATION_LOG, + "long_attr", 12345); + nr_log_event_set_context_attributes(log, attributes); json = nr_log_event_to_json(log); tlib_pass_if_str_equal( "requires escaping for JSON event", @@ -110,7 +120,11 @@ static void test_log_event_to_json(void) { "\"entity.guid\":\"test id 3\"," "\"entity.name\":\"entity name here\"," "\"hostname\":\"host name here\"," - "\"timestamp\":12345" + "\"timestamp\":12345," + "\"attributes\":{" + "\"context.long_attr\":12345," + "\"context.string_attr\":\"string_attr_value\"" + "}" "}", json); nr_free(json); @@ -120,6 +134,8 @@ static void test_log_event_to_json(void) { static void test_log_event_to_json_buffer(void) { nrbuf_t* buf = nr_buffer_create(0, 0); nr_log_event_t* log; + nr_attributes_t* attributes = NULL; + nr_attribute_config_t* config = NULL; /* * Test : Bad parameters. @@ -163,6 +179,14 @@ static void test_log_event_to_json_buffer(void) { nr_log_event_set_guid(log, "test id 3"); nr_log_event_set_entity_name(log, "entity name here"); nr_log_event_set_hostname(log, "host name here"); + config = nr_attribute_config_create(); + attributes = nr_attributes_create(config); + nr_attribute_config_destroy(&config); + nr_attributes_user_add_string(attributes, NR_ATTRIBUTE_DESTINATION_LOG, + "string_attr", "string_attr_value"); + nr_attributes_user_add_long(attributes, NR_ATTRIBUTE_DESTINATION_LOG, + "long_attr", 12345); + nr_log_event_set_context_attributes(log, attributes); tlib_pass_if_bool_equal("full log event", true, nr_log_event_to_json_buffer(log, buf)); nr_buffer_add(buf, NR_PSTR("\0")); @@ -175,7 +199,11 @@ static void test_log_event_to_json_buffer(void) { "\"entity.guid\":\"test id 3\"," "\"entity.name\":\"entity name here\"," "\"hostname\":\"host name here\"," - "\"timestamp\":12345" + "\"timestamp\":12345," + "\"attributes\":{" + "\"context.long_attr\":12345," + "\"context.string_attr\":\"string_attr_value\"" + "}" "}", nr_buffer_cptr(buf)); nr_log_event_destroy(&log); @@ -370,6 +398,28 @@ static void test_log_event_priority(void) { nr_log_event_set_priority(NULL, 0xFFFF); } +static void test_log_event_context_attributes(void) { + nr_log_event_t* event = nr_log_event_create(); + nr_attributes_t* attributes = NULL; + + // Test : Get context attributes with a NULL event + tlib_pass_if_null("Initialize event should have NULL context", + event->context_attributes); + + // Test: Setting context data on NULL event ptr should not crash + attributes = nr_attributes_create(NULL); + nr_log_event_set_context_attributes(NULL, attributes); + nr_attributes_destroy(&attributes); + + // Test: Setting a NULL context data ptr should not crash + nr_log_event_set_context_attributes(event, NULL); + + // Test: All NULL parameters should not crash + nr_log_event_set_context_attributes(NULL, NULL); + + nr_log_event_destroy(&event); +} + tlib_parallel_info_t parallel_info = {.suggested_nthreads = 1, .state_size = 0}; void test_main(void* p NRUNUSED) { @@ -385,4 +435,5 @@ void test_main(void* p NRUNUSED) { test_log_event_timestamp(); test_log_event_priority(); test_log_event_span_id(); + test_log_event_context_attributes(); } diff --git a/axiom/tests/test_txn.c b/axiom/tests/test_txn.c index d3571d6b1..37d9a8784 100644 --- a/axiom/tests/test_txn.c +++ b/axiom/tests/test_txn.c @@ -8180,7 +8180,7 @@ static void test_record_log_event(void) { */ txn = new_txn_for_record_log_event_test(APP_ENTITY_NAME); - nr_txn_record_log_event(NULL, NULL, NULL, 0, NULL); + nr_txn_record_log_event(NULL, NULL, NULL, 0, NULL, NULL); tlib_pass_if_int_equal("all params null, no crash, event not recorded", 0, nr_log_events_number_seen(txn->log_events)); tlib_pass_if_int_equal("all params null, no crash, event not recorded", 0, @@ -8188,7 +8188,7 @@ static void test_record_log_event(void) { nr_txn_destroy(&txn); txn = new_txn_for_record_log_event_test(APP_ENTITY_NAME); - nr_txn_record_log_event(NULL, LOG_EVENT_PARAMS, NULL); + nr_txn_record_log_event(NULL, LOG_EVENT_PARAMS, NULL, NULL); tlib_pass_if_int_equal("null txn, no crash, event not recorded", 0, nr_log_events_number_seen(txn->log_events)); tlib_pass_if_int_equal("null txn, no crash, event not recorded", 0, @@ -8200,7 +8200,7 @@ static void test_record_log_event(void) { * don't blow up! */ txn = new_txn_for_record_log_event_test(APP_ENTITY_NAME); - nr_txn_record_log_event(txn, NULL, NULL, 0, NULL); + nr_txn_record_log_event(txn, NULL, NULL, 0, NULL, NULL); tlib_pass_if_int_equal("null log params, event not recorded", 0, nr_log_events_number_seen(txn->log_events)); tlib_pass_if_int_equal("null log params, event not recorded", 0, @@ -8214,7 +8214,7 @@ static void test_record_log_event(void) { nr_txn_destroy(&txn); txn = new_txn_for_record_log_event_test(APP_ENTITY_NAME); - nr_txn_record_log_event(txn, NULL, LOG_MESSAGE, 0, NULL); + nr_txn_record_log_event(txn, NULL, LOG_MESSAGE, 0, NULL, NULL); tlib_pass_if_int_equal("null log level, event seen", 1, nr_log_events_number_seen(txn->log_events)); tlib_pass_if_int_equal("null log level, event saved", 1, @@ -8253,7 +8253,7 @@ static void test_record_log_event(void) { /* Happy path - everything initialized: record! */ txn = new_txn_for_record_log_event_test(APP_ENTITY_NAME); - nr_txn_record_log_event(txn, LOG_EVENT_PARAMS, &appv); + nr_txn_record_log_event(txn, LOG_EVENT_PARAMS, NULL, &appv); tlib_pass_if_int_equal("happy path, event seen", 1, nr_log_events_number_seen(txn->log_events)); tlib_pass_if_int_equal("happy path, event saved", 1, @@ -8303,11 +8303,11 @@ static void test_record_log_event(void) { /* fill up events pool to force sampling */ for (int i = 0, max_events = nr_log_events_max_events(txn->log_events); i < max_events; i++) { - nr_txn_record_log_event(txn, LOG_EVENT_PARAMS, &appv); + nr_txn_record_log_event(txn, LOG_EVENT_PARAMS, NULL, &appv); } /* force sampling */ - nr_txn_record_log_event(txn, LOG_EVENT_PARAMS, &appv); - nr_txn_record_log_event(txn, LOG_EVENT_PARAMS, &appv); + nr_txn_record_log_event(txn, LOG_EVENT_PARAMS, NULL, &appv); + nr_txn_record_log_event(txn, LOG_EVENT_PARAMS, NULL, &appv); test_txn_metric_is("happy path with sampling, events recorded and dropped", txn->unscoped_metrics, MET_FORCED, "Logging/Forwarding/Dropped", 2, 0, 0, 0, 0, 0); @@ -8321,8 +8321,8 @@ static void test_record_log_event(void) { tlib_pass_if_not_null("empty log events pool created", txn->log_events); tlib_pass_if_int_equal("empty log events pool stores 0 events", 0, nr_log_events_max_events(txn->log_events)); - nr_txn_record_log_event(txn, LOG_EVENT_PARAMS, &appv); - nr_txn_record_log_event(txn, LOG_EVENT_PARAMS, &appv); + nr_txn_record_log_event(txn, LOG_EVENT_PARAMS, NULL, &appv); + nr_txn_record_log_event(txn, LOG_EVENT_PARAMS, NULL, &appv); /* Events are seen because log forwarding is enabled and txn->options.log_events_max_samples_stored > 0 */ tlib_pass_if_int_equal("happy path, event seen", 2, @@ -8337,7 +8337,7 @@ static void test_record_log_event(void) { /* High_security */ txn = new_txn_for_record_log_event_test(APP_ENTITY_NAME); txn->high_security = 1; - nr_txn_record_log_event(txn, LOG_EVENT_PARAMS, &appv); + nr_txn_record_log_event(txn, LOG_EVENT_PARAMS, NULL, &appv); tlib_pass_if_int_equal("happy path, hsm, event seen", 0, nr_log_events_number_seen(txn->log_events)); tlib_pass_if_int_equal("happy path, hsm, event saved", 0, @@ -8355,17 +8355,17 @@ static void test_record_log_event(void) { /* default filter log level is LOG_LEVEL_WARNING */ /* these messages should be accepted */ - nr_txn_record_log_event(txn, LL_ALER_STR, LOG_MESSAGE, 0, NULL); - nr_txn_record_log_event(txn, LL_CRIT_STR, LOG_MESSAGE, 0, NULL); - nr_txn_record_log_event(txn, LL_WARN_STR, LOG_MESSAGE, 0, NULL); - nr_txn_record_log_event(txn, LL_EMER_STR, LOG_MESSAGE, 0, NULL); - nr_txn_record_log_event(txn, LL_UNKN_STR, LOG_MESSAGE, 0, NULL); - nr_txn_record_log_event(txn, "APPLES", LOG_MESSAGE, 0, NULL); + nr_txn_record_log_event(txn, LL_ALER_STR, LOG_MESSAGE, 0, NULL, NULL); + nr_txn_record_log_event(txn, LL_CRIT_STR, LOG_MESSAGE, 0, NULL, NULL); + nr_txn_record_log_event(txn, LL_WARN_STR, LOG_MESSAGE, 0, NULL, NULL); + nr_txn_record_log_event(txn, LL_EMER_STR, LOG_MESSAGE, 0, NULL, NULL); + nr_txn_record_log_event(txn, LL_UNKN_STR, LOG_MESSAGE, 0, NULL, NULL); + nr_txn_record_log_event(txn, "APPLES", LOG_MESSAGE, 0, NULL, NULL); /* these messages will be dropped */ - nr_txn_record_log_event(txn, LL_INFO_STR, LOG_MESSAGE, 0, NULL); - nr_txn_record_log_event(txn, LL_DEBU_STR, LOG_MESSAGE, 0, NULL); - nr_txn_record_log_event(txn, LL_NOTI_STR, LOG_MESSAGE, 0, NULL); + nr_txn_record_log_event(txn, LL_INFO_STR, LOG_MESSAGE, 0, NULL, NULL); + nr_txn_record_log_event(txn, LL_DEBU_STR, LOG_MESSAGE, 0, NULL, NULL); + nr_txn_record_log_event(txn, LL_NOTI_STR, LOG_MESSAGE, 0, NULL, NULL); /* events seen and saved are both 6 because the filtering occurs before * log forwarding handles the messages. diff --git a/tests/integration/attributes/test_filtering_1.php b/tests/integration/attributes/test_filtering_1.php new file mode 100644 index 000000000..fb8b01aa0 --- /dev/null +++ b/tests/integration/attributes/test_filtering_1.php @@ -0,0 +1,74 @@ +", + [ + [ + 0, {}, {}, + "?? trace details", + { + "userAttributes": { + "A": "A", + "B": "B", + "C": "C" + }, + "intrinsics": "??" + } + ], + [ + "OtherTransaction/php__FILE__", + "Custom/force_transaction_trace" + ] + ], + "?? txn guid", + "?? reserved", + "?? force persist", + "?? x-ray sessions", + "?? synthetics resource id" + ] + ] +] +*/ + +require_once(realpath (dirname ( __FILE__ )) . '/../../include/helpers.php'); + +force_transaction_trace(); + +newrelic_add_custom_parameter("A", "A"); +newrelic_add_custom_parameter("B", "B"); +newrelic_add_custom_parameter("C", "C"); + + diff --git a/tests/integration/attributes/test_filtering_10.php b/tests/integration/attributes/test_filtering_10.php new file mode 100644 index 000000000..3f321732e --- /dev/null +++ b/tests/integration/attributes/test_filtering_10.php @@ -0,0 +1,74 @@ +", + [ + [ + 0, {}, {}, + "?? trace details", + { + "userAttributes": { + "AC": "AC", + "AB": "AB", + "AA": "AA" + }, + "intrinsics": "??" + } + ], + [ + "OtherTransaction/php__FILE__", + "Custom/force_transaction_trace" + ] + ], + "?? txn guid", + "?? reserved", + "?? force persist", + "?? x-ray sessions", + "?? synthetics resource id" + ] + ] +] +*/ + +require_once(realpath (dirname ( __FILE__ )) . '/../../include/helpers.php'); + +force_transaction_trace(); + +newrelic_add_custom_parameter("AA", "AA"); +newrelic_add_custom_parameter("AB", "AB"); +newrelic_add_custom_parameter("AC", "AC"); +newrelic_add_custom_parameter("BB", "BB"); + diff --git a/tests/integration/attributes/test_filtering_11.php b/tests/integration/attributes/test_filtering_11.php new file mode 100644 index 000000000..60a149cb1 --- /dev/null +++ b/tests/integration/attributes/test_filtering_11.php @@ -0,0 +1,69 @@ +", + [ + [ + 0, {}, {}, + "?? trace details", + { + "intrinsics": "??" + } + ], + [ + "OtherTransaction/php__FILE__", + "Custom/force_transaction_trace" + ] + ], + "?? txn guid", + "?? reserved", + "?? force persist", + "?? x-ray sessions", + "?? synthetics resource id" + ] + ] +] +*/ + +require_once(realpath (dirname ( __FILE__ )) . '/../../include/helpers.php'); + +force_transaction_trace(); + +newrelic_add_custom_parameter("AA", "AA"); +newrelic_add_custom_parameter("AB", "AB"); +newrelic_add_custom_parameter("AC", "AC"); +newrelic_add_custom_parameter("BB", "BB"); + diff --git a/tests/integration/attributes/test_filtering_12.php b/tests/integration/attributes/test_filtering_12.php new file mode 100644 index 000000000..440a979a3 --- /dev/null +++ b/tests/integration/attributes/test_filtering_12.php @@ -0,0 +1,74 @@ +", + [ + [ + 0, {}, {}, + "?? trace details", + { + "userAttributes": { + "AC": "AC", + "AB": "AB", + "AA": "AA" + }, + "intrinsics": "??" + } + ], + [ + "OtherTransaction/php__FILE__", + "Custom/force_transaction_trace" + ] + ], + "?? txn guid", + "?? reserved", + "?? force persist", + "?? x-ray sessions", + "?? synthetics resource id" + ] + ] +] +*/ + +require_once(realpath (dirname ( __FILE__ )) . '/../../include/helpers.php'); + +force_transaction_trace(); + +newrelic_add_custom_parameter("AA", "AA"); +newrelic_add_custom_parameter("AB", "AB"); +newrelic_add_custom_parameter("AC", "AC"); +newrelic_add_custom_parameter("BB", "BB"); + diff --git a/tests/integration/attributes/test_filtering_2.php b/tests/integration/attributes/test_filtering_2.php new file mode 100644 index 000000000..a424d9129 --- /dev/null +++ b/tests/integration/attributes/test_filtering_2.php @@ -0,0 +1,68 @@ +", + [ + [ + 0, {}, {}, + "?? trace details", + { + "userAttributes": { + "C": "C", + "B": "B" + }, + "intrinsics": "??" + } + ], + [ + "OtherTransaction/php__FILE__", + "Custom/force_transaction_trace" + ] + ], + "?? txn guid", + "?? reserved", + "?? force persist", + "?? x-ray sessions", + "?? synthetics resource id" + ] + ] +] +*/ + +require_once(realpath (dirname ( __FILE__ )) . '/../../include/helpers.php'); + +force_transaction_trace(); + +newrelic_add_custom_parameter("A", "A"); +newrelic_add_custom_parameter("B", "B"); +newrelic_add_custom_parameter("C", "C"); + + diff --git a/tests/integration/attributes/test_filtering_3.php b/tests/integration/attributes/test_filtering_3.php new file mode 100644 index 000000000..74850761e --- /dev/null +++ b/tests/integration/attributes/test_filtering_3.php @@ -0,0 +1,73 @@ +", + [ + [ + 0, {}, {}, + "?? trace details", + { + "userAttributes": { + "A": "A", + "B": "B" + }, + "intrinsics": "??" + } + ], + [ + "OtherTransaction/php__FILE__", + "Custom/force_transaction_trace" + ] + ], + "?? txn guid", + "?? reserved", + "?? force persist", + "?? x-ray sessions", + "?? synthetics resource id" + ] + ] +] +*/ + +require_once(realpath (dirname ( __FILE__ )) . '/../../include/helpers.php'); + +force_transaction_trace(); + +newrelic_add_custom_parameter("A", "A"); +newrelic_add_custom_parameter("B", "B"); +newrelic_add_custom_parameter("C", "C"); + + diff --git a/tests/integration/attributes/test_filtering_4.php b/tests/integration/attributes/test_filtering_4.php new file mode 100644 index 000000000..22904355c --- /dev/null +++ b/tests/integration/attributes/test_filtering_4.php @@ -0,0 +1,69 @@ +", + [ + [ + 0, {}, {}, + "?? trace details", + { + "intrinsics": "??" + } + ], + [ + "OtherTransaction/php__FILE__", + "Custom/force_transaction_trace" + ] + ], + "?? txn guid", + "?? reserved", + "?? force persist", + "?? x-ray sessions", + "?? synthetics resource id" + ] + ] +] +*/ + +require_once(realpath (dirname ( __FILE__ )) . '/../../include/helpers.php'); + +force_transaction_trace(); + +newrelic_add_custom_parameter("A", "A"); + + + diff --git a/tests/integration/attributes/test_filtering_5.php b/tests/integration/attributes/test_filtering_5.php new file mode 100644 index 000000000..a0ccfecfa --- /dev/null +++ b/tests/integration/attributes/test_filtering_5.php @@ -0,0 +1,76 @@ +", + [ + [ + 0, {}, {}, + "?? trace details", + { + "userAttributes": { + "A": "A", + "B": "B", + "C": "C", + "D": "D" + }, + "intrinsics": "??" + } + ], + [ + "OtherTransaction/php__FILE__", + "Custom/force_transaction_trace" + ] + ], + "?? txn guid", + "?? reserved", + "?? force persist", + "?? x-ray sessions", + "?? synthetics resource id" + ] + ] +] +*/ + +require_once(realpath (dirname ( __FILE__ )) . '/../../include/helpers.php'); + +force_transaction_trace(); + +newrelic_add_custom_parameter("A", "A"); +newrelic_add_custom_parameter("B", "B"); +newrelic_add_custom_parameter("D", "D"); +newrelic_add_custom_parameter("C", "C"); + + diff --git a/tests/integration/attributes/test_filtering_6.php b/tests/integration/attributes/test_filtering_6.php new file mode 100644 index 000000000..8399327e6 --- /dev/null +++ b/tests/integration/attributes/test_filtering_6.php @@ -0,0 +1,73 @@ +", + [ + [ + 0, {}, {}, + "?? trace details", + { + "userAttributes": { + "B": "B", + "C": "C" + }, + "intrinsics": "??" + } + ], + [ + "OtherTransaction/php__FILE__", + "Custom/force_transaction_trace" + ] + ], + "?? txn guid", + "?? reserved", + "?? force persist", + "?? x-ray sessions", + "?? synthetics resource id" + ] + ] +] +*/ + +require_once(realpath (dirname ( __FILE__ )) . '/../../include/helpers.php'); + +force_transaction_trace(); + +newrelic_add_custom_parameter("A", "A"); +newrelic_add_custom_parameter("B", "B"); +newrelic_add_custom_parameter("C", "C"); + + diff --git a/tests/integration/attributes/test_filtering_7.php b/tests/integration/attributes/test_filtering_7.php new file mode 100644 index 000000000..ea4c484dc --- /dev/null +++ b/tests/integration/attributes/test_filtering_7.php @@ -0,0 +1,74 @@ +", + [ + [ + 0, {}, {}, + "?? trace details", + { + "userAttributes": { + "AC": "AC", + "AA": "AA", + "BB": "BB" + }, + "intrinsics": "??" + } + ], + [ + "OtherTransaction/php__FILE__", + "Custom/force_transaction_trace" + ] + ], + "?? txn guid", + "?? reserved", + "?? force persist", + "?? x-ray sessions", + "?? synthetics resource id" + ] + ] +] +*/ + +require_once(realpath (dirname ( __FILE__ )) . '/../../include/helpers.php'); + +force_transaction_trace(); + +newrelic_add_custom_parameter("AA", "AA"); +newrelic_add_custom_parameter("AB", "AB"); +newrelic_add_custom_parameter("AC", "AC"); +newrelic_add_custom_parameter("BB", "BB"); + diff --git a/tests/integration/attributes/test_filtering_8.php b/tests/integration/attributes/test_filtering_8.php new file mode 100644 index 000000000..9f53607b4 --- /dev/null +++ b/tests/integration/attributes/test_filtering_8.php @@ -0,0 +1,74 @@ +", + [ + [ + 0, {}, {}, + "?? trace details", + { + "userAttributes": { + "AB": "AB", + "BB": "BB" + }, + "intrinsics": "??" + } + ], + [ + "OtherTransaction/php__FILE__", + "Custom/force_transaction_trace" + ] + ], + "?? txn guid", + "?? reserved", + "?? force persist", + "?? x-ray sessions", + "?? synthetics resource id" + ] + ] +] +*/ + +require_once(realpath (dirname ( __FILE__ )) . '/../../include/helpers.php'); + +force_transaction_trace(); + +newrelic_add_custom_parameter("AA", "AA"); +newrelic_add_custom_parameter("AB", "AB"); +newrelic_add_custom_parameter("AC", "AC"); +newrelic_add_custom_parameter("BB", "BB"); + + diff --git a/tests/integration/attributes/test_filtering_9.php b/tests/integration/attributes/test_filtering_9.php new file mode 100644 index 000000000..b0b821321 --- /dev/null +++ b/tests/integration/attributes/test_filtering_9.php @@ -0,0 +1,74 @@ +", + [ + [ + 0, {}, {}, + "?? trace details", + { + "userAttributes": { + "AC": "AC", + "AA": "AA", + "BB": "BB" + }, + "intrinsics": "??" + } + ], + [ + "OtherTransaction/php__FILE__", + "Custom/force_transaction_trace" + ] + ], + "?? txn guid", + "?? reserved", + "?? force persist", + "?? x-ray sessions", + "?? synthetics resource id" + ] + ] +] +*/ + +require_once(realpath (dirname ( __FILE__ )) . '/../../include/helpers.php'); + +force_transaction_trace(); + +newrelic_add_custom_parameter("AA", "AA"); +newrelic_add_custom_parameter("AB", "AB"); +newrelic_add_custom_parameter("AC", "AC"); +newrelic_add_custom_parameter("BB", "BB"); + diff --git a/tests/integration/logging/monolog2/test_monolog_basic_clm.php b/tests/integration/logging/monolog2/test_monolog_basic_clm.php new file mode 100644 index 000000000..50f234e57 --- /dev/null +++ b/tests/integration/logging/monolog2/test_monolog_basic_clm.php @@ -0,0 +1,160 @@ +setFormatter($formatter); + + $logger->pushHandler($stdoutHandler); + + // insert delays between log messages to allow priority sampling + // to resolve that later messages have higher precedence + // since timestamps are only millisecond resolution + // without delays sometimes order in output will reflect + // all having the same timestamp. + $logger->debug("debug"); + usleep(10000); + +} + +test_logging(); \ No newline at end of file diff --git a/tests/integration/logging/monolog2/test_monolog_basic_clm_off.php b/tests/integration/logging/monolog2/test_monolog_basic_clm_off.php new file mode 100644 index 000000000..c997317e7 --- /dev/null +++ b/tests/integration/logging/monolog2/test_monolog_basic_clm_off.php @@ -0,0 +1,154 @@ +setFormatter($formatter); + + $logger->pushHandler($stdoutHandler); + + // insert delays between log messages to allow priority sampling + // to resolve that later messages have higher precedence + // since timestamps are only millisecond resolution + // without delays sometimes order in output will reflect + // all having the same timestamp. + $logger->debug("debug"); + usleep(10000); + +} + +test_logging(); diff --git a/tests/integration/logging/monolog2/test_monolog_context_default.php b/tests/integration/logging/monolog2/test_monolog_context_default.php new file mode 100644 index 000000000..5fbc32cf0 --- /dev/null +++ b/tests/integration/logging/monolog2/test_monolog_context_default.php @@ -0,0 +1,103 @@ +setFormatter($formatter); + + $logger->pushHandler($stdoutHandler); + + $context = array("A" => "A value", "B" => "B value", "C" => "C value"); + $logger->debug("None converted", $context); +} + +test_logging(); \ No newline at end of file diff --git a/tests/integration/logging/monolog2/test_monolog_context_exception.php b/tests/integration/logging/monolog2/test_monolog_context_exception.php new file mode 100644 index 000000000..3cbd4c3bc --- /dev/null +++ b/tests/integration/logging/monolog2/test_monolog_context_exception.php @@ -0,0 +1,104 @@ +setFormatter($formatter); + + $logger->pushHandler($stdoutHandler); + $context = ['exception' => new \RuntimeException('Foo')]; + $logger->alert("context is nested array", $context); +} + +test_logging(); diff --git a/tests/integration/logging/monolog2/test_monolog_context_filter_extra1.php b/tests/integration/logging/monolog2/test_monolog_context_filter_extra1.php new file mode 100644 index 000000000..fb318cdc3 --- /dev/null +++ b/tests/integration/logging/monolog2/test_monolog_context_filter_extra1.php @@ -0,0 +1,115 @@ +setFormatter($formatter); + + $logger->pushHandler($stdoutHandler); + + $context = array("AA" => "AA value", "AB" => "AB value", "AC" => "AC value", "BB" => "BB value"); + $logger->debug("AA AB AC converted", $context); +} + +test_logging(); \ No newline at end of file diff --git a/tests/integration/logging/monolog2/test_monolog_context_filter_extra2.php b/tests/integration/logging/monolog2/test_monolog_context_filter_extra2.php new file mode 100644 index 000000000..5867f935c --- /dev/null +++ b/tests/integration/logging/monolog2/test_monolog_context_filter_extra2.php @@ -0,0 +1,110 @@ +setFormatter($formatter); + + $logger->pushHandler($stdoutHandler); + + $context = array("AA" => "AA value", "AB" => "AB value", "AC" => "AC value", "BB" => "BB value"); + $logger->debug("None converted", $context); +} + +test_logging(); \ No newline at end of file diff --git a/tests/integration/logging/monolog2/test_monolog_context_filter_extra3.php b/tests/integration/logging/monolog2/test_monolog_context_filter_extra3.php new file mode 100644 index 000000000..92812d6bd --- /dev/null +++ b/tests/integration/logging/monolog2/test_monolog_context_filter_extra3.php @@ -0,0 +1,115 @@ +setFormatter($formatter); + + $logger->pushHandler($stdoutHandler); + + $context = array("AA" => "AA value", "AB" => "AB value", "AC" => "AC value", "BB" => "BB value"); + $logger->debug("AA AB AC converted", $context); +} + +test_logging(); \ No newline at end of file diff --git a/tests/integration/logging/monolog2/test_monolog_context_filter_extra4.php b/tests/integration/logging/monolog2/test_monolog_context_filter_extra4.php new file mode 100644 index 000000000..84c0fdfcc --- /dev/null +++ b/tests/integration/logging/monolog2/test_monolog_context_filter_extra4.php @@ -0,0 +1,115 @@ +setFormatter($formatter); + + $logger->pushHandler($stdoutHandler); + + $context = array("AA" => "AA value", "AB" => "AB value", "AC" => "AC value", "BB" => "BB value"); + $logger->debug("AA AB AC converted", $context); +} + +test_logging(); \ No newline at end of file diff --git a/tests/integration/logging/monolog2/test_monolog_context_filter_extra5.php b/tests/integration/logging/monolog2/test_monolog_context_filter_extra5.php new file mode 100644 index 000000000..e29b78368 --- /dev/null +++ b/tests/integration/logging/monolog2/test_monolog_context_filter_extra5.php @@ -0,0 +1,114 @@ +setFormatter($formatter); + + $logger->pushHandler($stdoutHandler); + + $context = array("AA" => "AA value", "AB" => "AB value", "AC" => "AC value", "BB" => "BB value"); + $logger->debug("AA AC converted", $context); +} + +test_logging(); \ No newline at end of file diff --git a/tests/integration/logging/monolog2/test_monolog_context_filter_rule1.php b/tests/integration/logging/monolog2/test_monolog_context_filter_rule1.php new file mode 100644 index 000000000..bebe8489b --- /dev/null +++ b/tests/integration/logging/monolog2/test_monolog_context_filter_rule1.php @@ -0,0 +1,115 @@ +setFormatter($formatter); + + $logger->pushHandler($stdoutHandler); + + $context = array("A" => "A value", "B" => "B value", "C" => "C value"); + $logger->debug("A B C converted", $context); +} + +test_logging(); \ No newline at end of file diff --git a/tests/integration/logging/monolog2/test_monolog_context_filter_rule10.php b/tests/integration/logging/monolog2/test_monolog_context_filter_rule10.php new file mode 100644 index 000000000..3a817bb7e --- /dev/null +++ b/tests/integration/logging/monolog2/test_monolog_context_filter_rule10.php @@ -0,0 +1,113 @@ +setFormatter($formatter); + + $logger->pushHandler($stdoutHandler); + + $context = array("AA" => "AA value", "AB" => "AB value", "AC" => "AC value", "BB" => "BB value"); + $logger->debug("AB converted", $context); +} + +test_logging(); \ No newline at end of file diff --git a/tests/integration/logging/monolog2/test_monolog_context_filter_rule11.php b/tests/integration/logging/monolog2/test_monolog_context_filter_rule11.php new file mode 100644 index 000000000..71e268443 --- /dev/null +++ b/tests/integration/logging/monolog2/test_monolog_context_filter_rule11.php @@ -0,0 +1,114 @@ +setFormatter($formatter); + + $logger->pushHandler($stdoutHandler); + + $context = array("AA" => "AA value", "AB" => "AB value", "AC" => "AC value", "BB" => "BB value"); + $logger->debug("AA AC converted", $context); +} + +test_logging(); \ No newline at end of file diff --git a/tests/integration/logging/monolog2/test_monolog_context_filter_rule2.php b/tests/integration/logging/monolog2/test_monolog_context_filter_rule2.php new file mode 100644 index 000000000..87f4f3968 --- /dev/null +++ b/tests/integration/logging/monolog2/test_monolog_context_filter_rule2.php @@ -0,0 +1,113 @@ +setFormatter($formatter); + + $logger->pushHandler($stdoutHandler); + + $context = array("A" => "A value", "B" => "B value", "C" => "C value"); + $logger->debug("B C converted", $context); +} + +test_logging(); \ No newline at end of file diff --git a/tests/integration/logging/monolog2/test_monolog_context_filter_rule3.php b/tests/integration/logging/monolog2/test_monolog_context_filter_rule3.php new file mode 100644 index 000000000..bb65ea6b7 --- /dev/null +++ b/tests/integration/logging/monolog2/test_monolog_context_filter_rule3.php @@ -0,0 +1,113 @@ +setFormatter($formatter); + + $logger->pushHandler($stdoutHandler); + + $context = array("A" => "A value", "B" => "B value", "C" => "C value"); + $logger->debug("A B converted", $context); +} + +test_logging(); \ No newline at end of file diff --git a/tests/integration/logging/monolog2/test_monolog_context_filter_rule4.php b/tests/integration/logging/monolog2/test_monolog_context_filter_rule4.php new file mode 100644 index 000000000..09f13b10c --- /dev/null +++ b/tests/integration/logging/monolog2/test_monolog_context_filter_rule4.php @@ -0,0 +1,114 @@ +setFormatter($formatter); + + $logger->pushHandler($stdoutHandler); + + $context = array("A" => "A value", "B" => "B value", "C" => "C value", "D" => "D value"); + $logger->debug("A B converted", $context); +} + +test_logging(); diff --git a/tests/integration/logging/monolog2/test_monolog_context_filter_rule5.php b/tests/integration/logging/monolog2/test_monolog_context_filter_rule5.php new file mode 100644 index 000000000..252180a7b --- /dev/null +++ b/tests/integration/logging/monolog2/test_monolog_context_filter_rule5.php @@ -0,0 +1,112 @@ +setFormatter($formatter); + + $logger->pushHandler($stdoutHandler); + + $context = array("A" => "A value", "B" => "B value", "C" => "C value", "D" => "D value"); + $logger->debug("A converted", $context); +} + +test_logging(); \ No newline at end of file diff --git a/tests/integration/logging/monolog2/test_monolog_context_filter_rule6.php b/tests/integration/logging/monolog2/test_monolog_context_filter_rule6.php new file mode 100644 index 000000000..799336ee5 --- /dev/null +++ b/tests/integration/logging/monolog2/test_monolog_context_filter_rule6.php @@ -0,0 +1,110 @@ +setFormatter($formatter); + + $logger->pushHandler($stdoutHandler); + + $context = array("A" => "A value", "B" => "B value", "C" => "C value"); + $logger->debug("None converted", $context); +} + +test_logging(); \ No newline at end of file diff --git a/tests/integration/logging/monolog2/test_monolog_context_filter_rule7.php b/tests/integration/logging/monolog2/test_monolog_context_filter_rule7.php new file mode 100644 index 000000000..55bc44caa --- /dev/null +++ b/tests/integration/logging/monolog2/test_monolog_context_filter_rule7.php @@ -0,0 +1,112 @@ +setFormatter($formatter); + + $logger->pushHandler($stdoutHandler); + + $context = array("A" => "A value", "B" => "B value", "C" => "C value"); + $logger->debug("A converted", $context); +} + +test_logging(); \ No newline at end of file diff --git a/tests/integration/logging/monolog2/test_monolog_context_filter_rule8.php b/tests/integration/logging/monolog2/test_monolog_context_filter_rule8.php new file mode 100644 index 000000000..d63c67184 --- /dev/null +++ b/tests/integration/logging/monolog2/test_monolog_context_filter_rule8.php @@ -0,0 +1,114 @@ +setFormatter($formatter); + + $logger->pushHandler($stdoutHandler); + + $context = array("A" => "A value", "B" => "B value", "C" => "C value"); + $logger->debug("B C converted", $context); +} + +test_logging(); \ No newline at end of file diff --git a/tests/integration/logging/monolog2/test_monolog_context_filter_rule9.php b/tests/integration/logging/monolog2/test_monolog_context_filter_rule9.php new file mode 100644 index 000000000..b1be23aec --- /dev/null +++ b/tests/integration/logging/monolog2/test_monolog_context_filter_rule9.php @@ -0,0 +1,114 @@ +setFormatter($formatter); + + $logger->pushHandler($stdoutHandler); + + $context = array("AA" => "AA value", "AB" => "AB value", "AC" => "AC value","BB" => "BB value"); + $logger->debug("AA AC converted", $context); +} + +test_logging(); \ No newline at end of file diff --git a/tests/integration/logging/monolog2/test_monolog_context_hsm_disable_forwarding.php b/tests/integration/logging/monolog2/test_monolog_context_hsm_disable_forwarding.php new file mode 100644 index 000000000..bd10ed610 --- /dev/null +++ b/tests/integration/logging/monolog2/test_monolog_context_hsm_disable_forwarding.php @@ -0,0 +1,136 @@ +setFormatter($formatter); + + $logger->pushHandler($stdoutHandler); + + // insert delays between log messages to allow priority sampling + // to resolve that later messages have higher precedence + // since timestamps are only millisecond resolution + // without delays sometimes order in output will reflect + // all having the same timestamp. + $context = ["testkey_string" => "value"]; + $logger->debug("key is string converted", $context); + usleep(10000); + + $context = [1 => "value"]; + $logger->info("key is int not converted", $context); + usleep(10000); + + $context = ["int" => 1]; + $logger->notice("int value converted", $context); + usleep(10000); + + $context = ["dbl" => 3.1415926]; + $logger->warning("dbl value converted", $context); + usleep(10000); + + $context = ["TRUE" => TRUE]; + $logger->error("TRUE value converted", $context); + usleep(10000); + + $context = array("FALSE" => FALSE); + $logger->critical("FALSE value converted", $context); + usleep(10000); + + $context = ["array" => array('foo' => 'bar', 'baz' => 'long')]; + $logger->alert("array value not converted", $context); + usleep(10000); + + $context = ["object" => $logger]; + $logger->emergency("object value not converted", $context); +} + +test_logging(); diff --git a/tests/integration/logging/monolog2/test_monolog_context_limits_1.php b/tests/integration/logging/monolog2/test_monolog_context_limits_1.php new file mode 100644 index 000000000..72245794f --- /dev/null +++ b/tests/integration/logging/monolog2/test_monolog_context_limits_1.php @@ -0,0 +1,105 @@ +setFormatter($formatter); + + $logger->pushHandler($stdoutHandler); + + $key = str_repeat("A", 300); + $context = array($key => "value"); + $logger->debug("None converted", $context); +} + +test_logging(); \ No newline at end of file diff --git a/tests/integration/logging/monolog2/test_monolog_context_limits_2.php b/tests/integration/logging/monolog2/test_monolog_context_limits_2.php new file mode 100644 index 000000000..300365203 --- /dev/null +++ b/tests/integration/logging/monolog2/test_monolog_context_limits_2.php @@ -0,0 +1,108 @@ +setFormatter($formatter); + + $logger->pushHandler($stdoutHandler); + + $value = str_repeat("A", 300); + $context = array("key" => $value); + $logger->debug("Value truncated", $context); +} + +test_logging(); \ No newline at end of file diff --git a/tests/integration/logging/monolog2/test_monolog_context_precedence_1.php b/tests/integration/logging/monolog2/test_monolog_context_precedence_1.php new file mode 100644 index 000000000..94b6b9069 --- /dev/null +++ b/tests/integration/logging/monolog2/test_monolog_context_precedence_1.php @@ -0,0 +1,106 @@ +setFormatter($formatter); + + $logger->pushHandler($stdoutHandler); + $context = ["testkey_string" => "value"]; + $logger->debug("key is string converted", $context); +} + +test_logging(); diff --git a/tests/integration/logging/monolog2/test_monolog_context_precedence_2.php b/tests/integration/logging/monolog2/test_monolog_context_precedence_2.php new file mode 100644 index 000000000..1401e6571 --- /dev/null +++ b/tests/integration/logging/monolog2/test_monolog_context_precedence_2.php @@ -0,0 +1,106 @@ +setFormatter($formatter); + + $logger->pushHandler($stdoutHandler); + $context = ["testkey_string" => "value"]; + $logger->debug("key is string converted", $context); +} + +test_logging(); diff --git a/tests/integration/logging/monolog2/test_monolog_context_simple.php b/tests/integration/logging/monolog2/test_monolog_context_simple.php new file mode 100644 index 000000000..f29ab92d8 --- /dev/null +++ b/tests/integration/logging/monolog2/test_monolog_context_simple.php @@ -0,0 +1,236 @@ +setFormatter($formatter); + + $logger->pushHandler($stdoutHandler); + + // insert delays between log messages to allow priority sampling + // to resolve that later messages have higher precedence + // since timestamps are only millisecond resolution + // without delays sometimes order in output will reflect + // all having the same timestamp. + $context = ["testkey_string" => "value"]; + $logger->debug("key is string converted", $context); + usleep(10000); + + $context = [1 => "value"]; + $logger->info("key is int not converted", $context); + usleep(10000); + + $context = ["int" => 1]; + $logger->notice("int value converted", $context); + usleep(10000); + + $context = ["dbl" => 3.1415926]; + $logger->warning("dbl value converted", $context); + usleep(10000); + + $context = ["TRUE" => TRUE]; + $logger->error("TRUE value converted", $context); + usleep(10000); + + $context = array("FALSE" => FALSE); + $logger->critical("FALSE value converted", $context); + usleep(10000); + + $context = ["array" => array('foo' => 'bar', 'baz' => 'long')]; + $logger->alert("array value not converted", $context); + usleep(10000); + + $context = ["object" => $logger]; + $logger->emergency("object value not converted", $context); +} + +test_logging(); diff --git a/tests/integration/logging/monolog3/test_monolog_context_default.php b/tests/integration/logging/monolog3/test_monolog_context_default.php new file mode 100644 index 000000000..f5dde42ad --- /dev/null +++ b/tests/integration/logging/monolog3/test_monolog_context_default.php @@ -0,0 +1,103 @@ +setFormatter($formatter); + + $logger->pushHandler($stdoutHandler); + + $context = array("A" => "A value", "B" => "B value", "C" => "C value"); + $logger->debug("None converted", $context); +} + +test_logging(); \ No newline at end of file diff --git a/tests/integration/logging/monolog3/test_monolog_context_exception.php b/tests/integration/logging/monolog3/test_monolog_context_exception.php new file mode 100644 index 000000000..23d7c28f0 --- /dev/null +++ b/tests/integration/logging/monolog3/test_monolog_context_exception.php @@ -0,0 +1,104 @@ +setFormatter($formatter); + + $logger->pushHandler($stdoutHandler); + $context = ['exception' => new \RuntimeException('Foo')]; + $logger->alert("context is nested array", $context); +} + +test_logging(); diff --git a/tests/integration/logging/monolog3/test_monolog_context_filter_extra1.php b/tests/integration/logging/monolog3/test_monolog_context_filter_extra1.php new file mode 100644 index 000000000..443dc5248 --- /dev/null +++ b/tests/integration/logging/monolog3/test_monolog_context_filter_extra1.php @@ -0,0 +1,115 @@ +setFormatter($formatter); + + $logger->pushHandler($stdoutHandler); + + $context = array("AA" => "AA value", "AB" => "AB value", "AC" => "AC value", "BB" => "BB value"); + $logger->debug("AA AB AC converted", $context); +} + +test_logging(); \ No newline at end of file diff --git a/tests/integration/logging/monolog3/test_monolog_context_filter_extra2.php b/tests/integration/logging/monolog3/test_monolog_context_filter_extra2.php new file mode 100644 index 000000000..ce5d41251 --- /dev/null +++ b/tests/integration/logging/monolog3/test_monolog_context_filter_extra2.php @@ -0,0 +1,110 @@ +setFormatter($formatter); + + $logger->pushHandler($stdoutHandler); + + $context = array("AA" => "AA value", "AB" => "AB value", "AC" => "AC value", "BB" => "BB value"); + $logger->debug("None converted", $context); +} + +test_logging(); \ No newline at end of file diff --git a/tests/integration/logging/monolog3/test_monolog_context_filter_extra3.php b/tests/integration/logging/monolog3/test_monolog_context_filter_extra3.php new file mode 100644 index 000000000..3abd81c6a --- /dev/null +++ b/tests/integration/logging/monolog3/test_monolog_context_filter_extra3.php @@ -0,0 +1,115 @@ +setFormatter($formatter); + + $logger->pushHandler($stdoutHandler); + + $context = array("AA" => "AA value", "AB" => "AB value", "AC" => "AC value", "BB" => "BB value"); + $logger->debug("AA AB AC converted", $context); +} + +test_logging(); \ No newline at end of file diff --git a/tests/integration/logging/monolog3/test_monolog_context_filter_extra4.php b/tests/integration/logging/monolog3/test_monolog_context_filter_extra4.php new file mode 100644 index 000000000..2d7dcac60 --- /dev/null +++ b/tests/integration/logging/monolog3/test_monolog_context_filter_extra4.php @@ -0,0 +1,115 @@ +setFormatter($formatter); + + $logger->pushHandler($stdoutHandler); + + $context = array("AA" => "AA value", "AB" => "AB value", "AC" => "AC value", "BB" => "BB value"); + $logger->debug("AA AB AC converted", $context); +} + +test_logging(); \ No newline at end of file diff --git a/tests/integration/logging/monolog3/test_monolog_context_filter_extra5.php b/tests/integration/logging/monolog3/test_monolog_context_filter_extra5.php new file mode 100644 index 000000000..0496fa444 --- /dev/null +++ b/tests/integration/logging/monolog3/test_monolog_context_filter_extra5.php @@ -0,0 +1,114 @@ +setFormatter($formatter); + + $logger->pushHandler($stdoutHandler); + + $context = array("AA" => "AA value", "AB" => "AB value", "AC" => "AC value", "BB" => "BB value"); + $logger->debug("AA AC converted", $context); +} + +test_logging(); \ No newline at end of file diff --git a/tests/integration/logging/monolog3/test_monolog_context_filter_rule1.php b/tests/integration/logging/monolog3/test_monolog_context_filter_rule1.php new file mode 100644 index 000000000..27297c832 --- /dev/null +++ b/tests/integration/logging/monolog3/test_monolog_context_filter_rule1.php @@ -0,0 +1,115 @@ +setFormatter($formatter); + + $logger->pushHandler($stdoutHandler); + + $context = array("A" => "A value", "B" => "B value", "C" => "C value"); + $logger->debug("A B C converted", $context); +} + +test_logging(); \ No newline at end of file diff --git a/tests/integration/logging/monolog3/test_monolog_context_filter_rule10.php b/tests/integration/logging/monolog3/test_monolog_context_filter_rule10.php new file mode 100644 index 000000000..6c5a0c94b --- /dev/null +++ b/tests/integration/logging/monolog3/test_monolog_context_filter_rule10.php @@ -0,0 +1,113 @@ +setFormatter($formatter); + + $logger->pushHandler($stdoutHandler); + + $context = array("AA" => "AA value", "AB" => "AB value", "AC" => "AC value", "BB" => "BB value"); + $logger->debug("AB converted", $context); +} + +test_logging(); \ No newline at end of file diff --git a/tests/integration/logging/monolog3/test_monolog_context_filter_rule11.php b/tests/integration/logging/monolog3/test_monolog_context_filter_rule11.php new file mode 100644 index 000000000..eb3b8f4ad --- /dev/null +++ b/tests/integration/logging/monolog3/test_monolog_context_filter_rule11.php @@ -0,0 +1,114 @@ +setFormatter($formatter); + + $logger->pushHandler($stdoutHandler); + + $context = array("AA" => "AA value", "AB" => "AB value", "AC" => "AC value", "BB" => "BB value"); + $logger->debug("AA AC converted", $context); +} + +test_logging(); \ No newline at end of file diff --git a/tests/integration/logging/monolog3/test_monolog_context_filter_rule2.php b/tests/integration/logging/monolog3/test_monolog_context_filter_rule2.php new file mode 100644 index 000000000..648d24aa3 --- /dev/null +++ b/tests/integration/logging/monolog3/test_monolog_context_filter_rule2.php @@ -0,0 +1,113 @@ +setFormatter($formatter); + + $logger->pushHandler($stdoutHandler); + + $context = array("A" => "A value", "B" => "B value", "C" => "C value"); + $logger->debug("B C converted", $context); +} + +test_logging(); \ No newline at end of file diff --git a/tests/integration/logging/monolog3/test_monolog_context_filter_rule3.php b/tests/integration/logging/monolog3/test_monolog_context_filter_rule3.php new file mode 100644 index 000000000..3b3954a17 --- /dev/null +++ b/tests/integration/logging/monolog3/test_monolog_context_filter_rule3.php @@ -0,0 +1,113 @@ +setFormatter($formatter); + + $logger->pushHandler($stdoutHandler); + + $context = array("A" => "A value", "B" => "B value", "C" => "C value"); + $logger->debug("A B converted", $context); +} + +test_logging(); \ No newline at end of file diff --git a/tests/integration/logging/monolog3/test_monolog_context_filter_rule4.php b/tests/integration/logging/monolog3/test_monolog_context_filter_rule4.php new file mode 100644 index 000000000..6e07994f6 --- /dev/null +++ b/tests/integration/logging/monolog3/test_monolog_context_filter_rule4.php @@ -0,0 +1,114 @@ +setFormatter($formatter); + + $logger->pushHandler($stdoutHandler); + + $context = array("A" => "A value", "B" => "B value", "C" => "C value", "D" => "D value"); + $logger->debug("A B converted", $context); +} + +test_logging(); diff --git a/tests/integration/logging/monolog3/test_monolog_context_filter_rule5.php b/tests/integration/logging/monolog3/test_monolog_context_filter_rule5.php new file mode 100644 index 000000000..446120f46 --- /dev/null +++ b/tests/integration/logging/monolog3/test_monolog_context_filter_rule5.php @@ -0,0 +1,112 @@ +setFormatter($formatter); + + $logger->pushHandler($stdoutHandler); + + $context = array("A" => "A value", "B" => "B value", "C" => "C value", "D" => "D value"); + $logger->debug("A converted", $context); +} + +test_logging(); \ No newline at end of file diff --git a/tests/integration/logging/monolog3/test_monolog_context_filter_rule6.php b/tests/integration/logging/monolog3/test_monolog_context_filter_rule6.php new file mode 100644 index 000000000..5544883ff --- /dev/null +++ b/tests/integration/logging/monolog3/test_monolog_context_filter_rule6.php @@ -0,0 +1,110 @@ +setFormatter($formatter); + + $logger->pushHandler($stdoutHandler); + + $context = array("A" => "A value", "B" => "B value", "C" => "C value"); + $logger->debug("None converted", $context); +} + +test_logging(); \ No newline at end of file diff --git a/tests/integration/logging/monolog3/test_monolog_context_filter_rule7.php b/tests/integration/logging/monolog3/test_monolog_context_filter_rule7.php new file mode 100644 index 000000000..d4c4f85a6 --- /dev/null +++ b/tests/integration/logging/monolog3/test_monolog_context_filter_rule7.php @@ -0,0 +1,112 @@ +setFormatter($formatter); + + $logger->pushHandler($stdoutHandler); + + $context = array("A" => "A value", "B" => "B value", "C" => "C value"); + $logger->debug("A converted", $context); +} + +test_logging(); \ No newline at end of file diff --git a/tests/integration/logging/monolog3/test_monolog_context_filter_rule8.php b/tests/integration/logging/monolog3/test_monolog_context_filter_rule8.php new file mode 100644 index 000000000..abf12928f --- /dev/null +++ b/tests/integration/logging/monolog3/test_monolog_context_filter_rule8.php @@ -0,0 +1,114 @@ +setFormatter($formatter); + + $logger->pushHandler($stdoutHandler); + + $context = array("A" => "A value", "B" => "B value", "C" => "C value"); + $logger->debug("B C converted", $context); +} + +test_logging(); \ No newline at end of file diff --git a/tests/integration/logging/monolog3/test_monolog_context_filter_rule9.php b/tests/integration/logging/monolog3/test_monolog_context_filter_rule9.php new file mode 100644 index 000000000..61c8f1a0c --- /dev/null +++ b/tests/integration/logging/monolog3/test_monolog_context_filter_rule9.php @@ -0,0 +1,114 @@ +setFormatter($formatter); + + $logger->pushHandler($stdoutHandler); + + $context = array("AA" => "AA value", "AB" => "AB value", "AC" => "AC value","BB" => "BB value"); + $logger->debug("AA AC converted", $context); +} + +test_logging(); \ No newline at end of file diff --git a/tests/integration/logging/monolog3/test_monolog_context_hsm_disable_forwarding.php b/tests/integration/logging/monolog3/test_monolog_context_hsm_disable_forwarding.php new file mode 100644 index 000000000..18d211b35 --- /dev/null +++ b/tests/integration/logging/monolog3/test_monolog_context_hsm_disable_forwarding.php @@ -0,0 +1,136 @@ +setFormatter($formatter); + + $logger->pushHandler($stdoutHandler); + + // insert delays between log messages to allow priority sampling + // to resolve that later messages have higher precedence + // since timestamps are only millisecond resolution + // without delays sometimes order in output will reflect + // all having the same timestamp. + $context = ["testkey_string" => "value"]; + $logger->debug("key is string converted", $context); + usleep(10000); + + $context = [1 => "value"]; + $logger->info("key is int not converted", $context); + usleep(10000); + + $context = ["int" => 1]; + $logger->notice("int value converted", $context); + usleep(10000); + + $context = ["dbl" => 3.1415926]; + $logger->warning("dbl value converted", $context); + usleep(10000); + + $context = ["TRUE" => TRUE]; + $logger->error("TRUE value converted", $context); + usleep(10000); + + $context = array("FALSE" => FALSE); + $logger->critical("FALSE value converted", $context); + usleep(10000); + + $context = ["array" => array('foo' => 'bar', 'baz' => 'long')]; + $logger->alert("array value not converted", $context); + usleep(10000); + + $context = ["object" => $logger]; + $logger->emergency("object value not converted", $context); +} + +test_logging(); diff --git a/tests/integration/logging/monolog3/test_monolog_context_limits_1.php b/tests/integration/logging/monolog3/test_monolog_context_limits_1.php new file mode 100644 index 000000000..0eb98376f --- /dev/null +++ b/tests/integration/logging/monolog3/test_monolog_context_limits_1.php @@ -0,0 +1,105 @@ +setFormatter($formatter); + + $logger->pushHandler($stdoutHandler); + + $key = str_repeat("A", 300); + $context = array($key => "value"); + $logger->debug("None converted", $context); +} + +test_logging(); \ No newline at end of file diff --git a/tests/integration/logging/monolog3/test_monolog_context_limits_2.php b/tests/integration/logging/monolog3/test_monolog_context_limits_2.php new file mode 100644 index 000000000..6efed9ff5 --- /dev/null +++ b/tests/integration/logging/monolog3/test_monolog_context_limits_2.php @@ -0,0 +1,108 @@ +setFormatter($formatter); + + $logger->pushHandler($stdoutHandler); + + $value = str_repeat("A", 300); + $context = array("key" => $value); + $logger->debug("Value truncated", $context); +} + +test_logging(); \ No newline at end of file diff --git a/tests/integration/logging/monolog3/test_monolog_context_precedence_1.php b/tests/integration/logging/monolog3/test_monolog_context_precedence_1.php new file mode 100644 index 000000000..6c66317b6 --- /dev/null +++ b/tests/integration/logging/monolog3/test_monolog_context_precedence_1.php @@ -0,0 +1,106 @@ +setFormatter($formatter); + + $logger->pushHandler($stdoutHandler); + $context = ["testkey_string" => "value"]; + $logger->debug("key is string converted", $context); +} + +test_logging(); diff --git a/tests/integration/logging/monolog3/test_monolog_context_precedence_2.php b/tests/integration/logging/monolog3/test_monolog_context_precedence_2.php new file mode 100644 index 000000000..451e5f49b --- /dev/null +++ b/tests/integration/logging/monolog3/test_monolog_context_precedence_2.php @@ -0,0 +1,106 @@ +setFormatter($formatter); + + $logger->pushHandler($stdoutHandler); + $context = ["testkey_string" => "value"]; + $logger->debug("key is string converted", $context); +} + +test_logging(); diff --git a/tests/integration/logging/monolog3/test_monolog_context_simple.php b/tests/integration/logging/monolog3/test_monolog_context_simple.php new file mode 100644 index 000000000..3c6fd51ef --- /dev/null +++ b/tests/integration/logging/monolog3/test_monolog_context_simple.php @@ -0,0 +1,236 @@ +setFormatter($formatter); + + $logger->pushHandler($stdoutHandler); + + // insert delays between log messages to allow priority sampling + // to resolve that later messages have higher precedence + // since timestamps are only millisecond resolution + // without delays sometimes order in output will reflect + // all having the same timestamp. + $context = ["testkey_string" => "value"]; + $logger->debug("key is string converted", $context); + usleep(10000); + + $context = [1 => "value"]; + $logger->info("key is int not converted", $context); + usleep(10000); + + $context = ["int" => 1]; + $logger->notice("int value converted", $context); + usleep(10000); + + $context = ["dbl" => 3.1415926]; + $logger->warning("dbl value converted", $context); + usleep(10000); + + $context = ["TRUE" => TRUE]; + $logger->error("TRUE value converted", $context); + usleep(10000); + + $context = array("FALSE" => FALSE); + $logger->critical("FALSE value converted", $context); + usleep(10000); + + $context = ["array" => array('foo' => 'bar', 'baz' => 'long')]; + $logger->alert("array value not converted", $context); + usleep(10000); + + $context = ["object" => $logger]; + $logger->emergency("object value not converted", $context); +} + +test_logging(); From cbe830350a059cea47cfe0f2683b03a979af5d57 Mon Sep 17 00:00:00 2001 From: Amber Sistla Date: Tue, 7 Nov 2023 12:04:39 -0700 Subject: [PATCH 06/12] fix(agent): Addressed zpp/arginfo mismatches (#755) Aligned zpp and arginfo. When PHP8+ is built in debug mode, zpp/arginfo mismatches are now Fatal Errors. Fixed the mismatches that existed in the code base. For reference: https://www.phpinternalsbook.com/php7/extensions_design/php_functions.html --- agent/php_newrelic.c | 28 +++++++++++++++++++--------- 1 file changed, 19 insertions(+), 9 deletions(-) diff --git a/agent/php_newrelic.c b/agent/php_newrelic.c index 625377d08..b34d6d518 100644 --- a/agent/php_newrelic.c +++ b/agent/php_newrelic.c @@ -120,6 +120,10 @@ ZEND_BEGIN_ARG_INFO_EX(newrelic_arginfo_void, 0, 0, 0) ZEND_END_ARG_INFO() #endif /* PHP 8.0+ */ +ZEND_BEGIN_ARG_INFO_EX(newrelic_get_request_metadata_arginfo, 0, 0, 0) +ZEND_ARG_INFO(0, transport) +ZEND_END_ARG_INFO() + ZEND_BEGIN_ARG_INFO_EX(newrelic_add_custom_parameter_arginfo, 0, 0, 2) ZEND_ARG_INFO(0, parameter) ZEND_ARG_INFO(0, value) @@ -180,7 +184,7 @@ ZEND_ARG_INFO(0, event_type) ZEND_ARG_ARRAY_INFO(0, parameters, 0) ZEND_END_ARG_INFO() -ZEND_BEGIN_ARG_INFO_EX(newrelic_add_custom_span_parameter_arginfo, 0, 0, 3) +ZEND_BEGIN_ARG_INFO_EX(newrelic_add_custom_span_parameter_arginfo, 0, 0, 2) ZEND_ARG_INFO(0, key) ZEND_ARG_INFO(0, value) ZEND_END_ARG_INFO() @@ -236,23 +240,23 @@ ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_INFO_EX(newrelic_insert_distributed_trace_headers_arginfo, 0, 0, - 3) + 1) ZEND_ARG_INFO(1, headers) ZEND_END_ARG_INFO() /* * Other New Relic Functions */ -ZEND_BEGIN_ARG_INFO_EX(newrelic_curl_header_callback_arginfo, 0, 0, 3) +ZEND_BEGIN_ARG_INFO_EX(newrelic_curl_header_callback_arginfo, 0, 0, 2) ZEND_ARG_INFO(0, curl_resource) ZEND_ARG_INFO(0, header_data) ZEND_END_ARG_INFO() -ZEND_BEGIN_ARG_INFO_EX(newrelic_add_headers_to_context_arginfo, 0, 0, 3) +ZEND_BEGIN_ARG_INFO_EX(newrelic_add_headers_to_context_arginfo, 0, 0, 1) ZEND_ARG_INFO(0, stream_context) ZEND_END_ARG_INFO() -ZEND_BEGIN_ARG_INFO_EX(newrelic_remove_headers_from_context_arginfo, 0, 0, 3) +ZEND_BEGIN_ARG_INFO_EX(newrelic_remove_headers_from_context_arginfo, 0, 0, 1) ZEND_ARG_INFO(0, stream_context) ZEND_END_ARG_INFO() @@ -260,6 +264,14 @@ ZEND_BEGIN_ARG_INFO_EX(newrelic_exception_handler_arginfo, 0, 0, 1) ZEND_ARG_INFO(0, exception) ZEND_END_ARG_INFO() +ZEND_BEGIN_ARG_INFO_EX(newrelic_notice_error_arginfo, 0, 0, 1) +ZEND_ARG_INFO(0, exception) +ZEND_ARG_INFO(0, errstr) +ZEND_ARG_INFO(0, fname) +ZEND_ARG_INFO(0, line_nr) +ZEND_ARG_INFO(0, ctx) +ZEND_END_ARG_INFO() + /* * Integration test helpers */ @@ -299,19 +311,18 @@ static zend_function_entry newrelic_functions[] = { PHP_FE(newrelic_add_custom_span_parameter, newrelic_add_custom_span_parameter_arginfo) PHP_FE(newrelic_set_user_id, newrelic_set_user_id_arginfo) PHP_FE(newrelic_set_error_group_callback, newrelic_set_error_group_callback_arginfo) + PHP_FE(newrelic_notice_error, newrelic_notice_error_arginfo) #if ZEND_MODULE_API_NO >= ZEND_8_0_X_API_NO /* PHP 8.0+ */ PHP_FE(newrelic_ignore_transaction, newrelic_arginfo_void) PHP_FE(newrelic_ignore_apdex, newrelic_arginfo_void) PHP_FE(newrelic_end_of_transaction, newrelic_arginfo_void) - PHP_FE(newrelic_notice_error, newrelic_arginfo_void) PHP_FE(newrelic_disable_autorum, newrelic_arginfo_void) PHP_FE(newrelic_is_sampled, newrelic_arginfo_void) #else PHP_FE(newrelic_ignore_transaction, 0) PHP_FE(newrelic_ignore_apdex, 0) PHP_FE(newrelic_end_of_transaction,0) - PHP_FE(newrelic_notice_error,0) PHP_FE(newrelic_disable_autorum, 0) PHP_FE(newrelic_is_sampled, 0) #endif /* PHP8+ */ @@ -327,13 +338,12 @@ static zend_function_entry newrelic_functions[] = { PHP_FE(newrelic_accept_distributed_trace_headers, newrelic_accept_distributed_trace_headers_arginfo) PHP_FE(newrelic_accept_distributed_trace_payload, newrelic_accept_distributed_trace_payload_arginfo) PHP_FE(newrelic_accept_distributed_trace_payload_httpsafe, newrelic_accept_distributed_trace_payload_httpsafe_arginfo) + PHP_FE(newrelic_get_request_metadata, newrelic_get_request_metadata_arginfo) #ifdef PHP8 - PHP_FE(newrelic_get_request_metadata, newrelic_arginfo_void) PHP_FE(newrelic_get_linking_metadata, newrelic_arginfo_void) PHP_FE(newrelic_get_trace_metadata, newrelic_arginfo_void) #else - PHP_FE(newrelic_get_request_metadata, 0) PHP_FE(newrelic_get_linking_metadata, 0) PHP_FE(newrelic_get_trace_metadata, 0) #endif /* PHP 8 */ From 37532b3a07646235e1b3767819b1f1aafc4a5de8 Mon Sep 17 00:00:00 2001 From: bduranleau-nr <106178551+bduranleau-nr@users.noreply.github.com> Date: Thu, 9 Nov 2023 11:42:19 -0600 Subject: [PATCH 07/12] feat: Send Docker cgroup V2 ID (#756) Implements the following: - Parse the Docker cgroup V2 ID from the Agent container contained in the file `/proc/self/mountinfo` - Send the Docker ID from agent to Daemon - Send Agent Docker ID from Daemon to Collector in the connect message --------- Co-authored-by: Michael Fulbright <89205663+mfulb@users.noreply.github.com> --- agent/Makefile.frag | 2 + agent/php_environment.c | 104 ++++++++++++++++++ agent/php_environment.h | 18 +++ agent/php_globals.c | 1 + agent/php_globals.h | 1 + agent/php_txn.c | 1 + agent/tests/test_environment.c | 70 +++++++++++- axiom/cmd_appinfo_transmit.c | 4 + axiom/nr_app.c | 4 +- axiom/nr_app.h | 1 + axiom/nr_commands_private.h | 3 +- .../docker_container_id_v2/README.md | 6 + .../docker_container_id_v2/cases.json | 38 +++++++ .../docker-20.10.16.txt | 24 ++++ .../docker_container_id_v2/docker-24.0.2.txt | 21 ++++ .../docker-too-long.txt | 21 ++++ .../docker_container_id_v2/empty.txt | 0 .../invalid-characters.txt | 21 ++++ .../docker_container_id_v2/invalid-length.txt | 21 ++++ axiom/tests/test_cmd_appinfo.c | 7 ++ protocol/flatbuffers/protocol.fbs | 1 + src/flatbuffersdata/data.go | 2 + src/newrelic/app.go | 11 ++ src/newrelic/app_test.go | 67 +++++++++++ src/newrelic/commands.go | 1 + src/newrelic/protocol/App.go | 13 ++- src/newrelic/utilization/utilization_hash.go | 43 +++++++- 27 files changed, 492 insertions(+), 14 deletions(-) create mode 100644 axiom/tests/cross_agent_tests/docker_container_id_v2/README.md create mode 100644 axiom/tests/cross_agent_tests/docker_container_id_v2/cases.json create mode 100644 axiom/tests/cross_agent_tests/docker_container_id_v2/docker-20.10.16.txt create mode 100644 axiom/tests/cross_agent_tests/docker_container_id_v2/docker-24.0.2.txt create mode 100644 axiom/tests/cross_agent_tests/docker_container_id_v2/docker-too-long.txt create mode 100644 axiom/tests/cross_agent_tests/docker_container_id_v2/empty.txt create mode 100644 axiom/tests/cross_agent_tests/docker_container_id_v2/invalid-characters.txt create mode 100644 axiom/tests/cross_agent_tests/docker_container_id_v2/invalid-length.txt diff --git a/agent/Makefile.frag b/agent/Makefile.frag index 8a4b6705c..3633379cb 100644 --- a/agent/Makefile.frag +++ b/agent/Makefile.frag @@ -265,6 +265,8 @@ endif TEST_LIBS := $(PHP_EMBED_LIBRARY) $(shell $(PHP_CONFIG) --libs) TEST_LDFLAGS := $(shell $(PHP_CONFIG) --ldflags) $(EXPORT_DYNAMIC) TEST_LDFLAGS += $(USER_LDFLAGS) +CROSS_AGENT_DIR := $(CURDIR)/../axiom/tests/cross_agent_tests +EXTRA_CFLAGS += -DCROSS_AGENT_TESTS_DIR="\"$(CROSS_AGENT_DIR)\"" # # Implicit rule to build test object files with the appropriate flags. diff --git a/agent/php_environment.c b/agent/php_environment.c index 1bb5cf3e1..60f2a2c74 100644 --- a/agent/php_environment.c +++ b/agent/php_environment.c @@ -511,6 +511,109 @@ static void nr_php_get_environment_variables(TSRMLS_D) { __func__, NR_PHP_PROCESS_GLOBALS(env_labels)); } +#define MAX_LINE_COUNT (1000) // Upper bound for number of lines to read +/* + * Purpose: + * Extract the 64-byte hexadecimal Docker cgroup ID from + * /proc/self/mountinfo + */ +char* nr_php_parse_v2_docker_id(const char* cgroup_fname) { + char* line_ptr = NULL; + char* retval = NULL; + bool found = false; + int line_count = 0; + FILE* fd = NULL; + size_t len = 0; + nr_regex_t* line_regex = NULL; + nr_regex_substrings_t* ss = NULL; + + if (NULL == cgroup_fname) { + return NULL; + } + + // check if file exists + if (SUCCESS != access(cgroup_fname, F_OK)) { + nrl_verbosedebug(NRL_AGENT, "%s: File not found: %s", __func__, + cgroup_fname); + return NULL; + } + + // open file + fd = fopen(cgroup_fname, "r"); + if (NULL == fd) { + nrl_warning(NRL_AGENT, "%s: Failed to open %s", __func__, cgroup_fname); + return NULL; + } + + // compile regex to extract target string from file line + line_regex = nr_regex_create("/docker/containers/([a-fA-F0-9]{64})/", 0, 0); + + if (NULL == line_regex) { + nrl_error(NRL_AGENT, "%s: Error: line regex creation failed", __func__); + fclose(fd); + return NULL; + } + + // clang-format off + /* + * Example /proc/self/mountinfo file structure: + * ... + * 795 787 254:1 /docker/containers/ec807d5258c06c355c07e2acb700f9029d820afe5836d6a7e19764773dc790f5/resolv.conf /etc/resolv.conf rw,relatime - ext4 /dev/vda1 rw + * 796 787 254:1 /docker/containers/ec807d5258c06c355c07e2acb700f9029d820afe5836d6a7e19764773dc790f5/hostname /etc/hostname rw,relatime - ext4 /dev/vda1 rw + * 797 787 254:1 /docker/containers/ec807d5258c06c355c07e2acb700f9029d820afe5836d6a7e19764773dc790f5/hosts /etc/hosts rw,relatime - ext4 /dev/vda1 rw + * ... + */ + + /* + * File parsing logic: + * 1. scan file line-by-line + * 2. regex search each line for '/docker/containers/' string followed by a string + * a. 64 bytes long (not including null terminator) + * b. comprised of only hexadecimal characters + * 3. extract the 64 byte substring following "/docker/containers/" + * 4. Assign the extracted & verified ID to the retval + * a. Example ID: ec807d5258c06c355c07e2acb700f9029d820afe5836d6a7e19764773dc790f5 + * 5. Set found = true and exit the loops + */ + // clang-format on + + while (FAILURE != getline(&line_ptr, &len, fd) && !found + && line_count++ < MAX_LINE_COUNT) { + ss = nr_regex_match_capture(line_regex, line_ptr, nr_strlen(line_ptr)); + if (NULL == ss) { + continue; + } + retval = nr_regex_substrings_get(ss, 1); + nr_regex_substrings_destroy(&ss); + found = true; + } + + nr_regex_destroy(&line_regex); + nr_free(line_ptr); + fclose(fd); + return retval; +} +#undef MAX_LINE_COUNT + +void nr_php_gather_v2_docker_id() { + char* dockerId = NULL; + + // check if docker_id global already set + if (NULL != NR_PHP_PROCESS_GLOBALS(docker_id)) { + nrl_verbosedebug(NRL_AGENT, "%s: Docker ID already set.", __func__); + return; + } + + dockerId = nr_php_parse_v2_docker_id("/proc/self/mountinfo"); + if (NULL != dockerId) { + NR_PHP_PROCESS_GLOBALS(docker_id) = dockerId; + nrl_verbosedebug(NRL_AGENT, "%s: Docker v2 ID: %s", __func__, dockerId); + } else { + nrl_warning(NRL_AGENT, "%s: Unable to read docker v2 container id", + __func__); + } +} + nrobj_t* nr_php_get_environment(TSRMLS_D) { nrobj_t* env; @@ -520,6 +623,7 @@ nrobj_t* nr_php_get_environment(TSRMLS_D) { nr_php_gather_dynamic_modules(env TSRMLS_CC); nr_php_gather_dispatcher_information(env); nr_php_get_environment_variables(TSRMLS_C); + nr_php_gather_v2_docker_id(); return env; } diff --git a/agent/php_environment.h b/agent/php_environment.h index 333e2b171..c219a8982 100644 --- a/agent/php_environment.h +++ b/agent/php_environment.h @@ -97,4 +97,22 @@ char* nr_php_process_environment_variable_to_string(const char* prefix, const char* kv_delimeter, const char* delimeter); +/* + * Purpose : Parse the /proc/self/mountinfo file for the Docker cgroup v2 ID. + * Assign the value (if found) to the docker_id global. + * + * Params : 1. The filepath of the mountinfo file to parse + * + * Returns : String with v2 ID or NULL if not detected. + * Caller takes ownership of the string. + */ +char* nr_php_parse_v2_docker_id(const char* cgroup_fname); + +/* + * Purpose : Attempt to detect Docker cgroup v2 ID and set the global + * environment variable if successful + * */ +void nr_php_gather_v2_docker_id(); + + #endif /* PHP_ENVIRONMENT_HDR */ diff --git a/agent/php_globals.c b/agent/php_globals.c index 68f64ef04..5bb372d33 100644 --- a/agent/php_globals.c +++ b/agent/php_globals.c @@ -41,6 +41,7 @@ static void nr_php_per_process_globals_dispose(void) { nro_delete(nr_php_per_process_globals.metadata); nr_free(nr_php_per_process_globals.env_labels); nr_free(nr_php_per_process_globals.apache_add); + nr_free(nr_php_per_process_globals.docker_id); nr_memset(&nr_php_per_process_globals, 0, sizeof(nr_php_per_process_globals)); } diff --git a/agent/php_globals.h b/agent/php_globals.h index 8ae65995e..2f5ba9a41 100644 --- a/agent/php_globals.h +++ b/agent/php_globals.h @@ -75,6 +75,7 @@ typedef struct _nrphpglobals_t { int apache_threaded; /* 1 if a threaded MPM is in use, 0 otherwise */ int preload_framework_library_detection; /* Enables preloading framework and library detection */ + char* docker_id; /* 64 byte hex docker ID parsed from /proc/self/mountinfo */ /* Original PHP callback pointer contents */ nrphperrfn_t orig_error_cb; diff --git a/agent/php_txn.c b/agent/php_txn.c index 0ebb8cd00..5cd0f75de 100644 --- a/agent/php_txn.c +++ b/agent/php_txn.c @@ -811,6 +811,7 @@ nr_status_t nr_php_txn_begin(const char* appnames, info.log_events_max_samples_stored = NRINI(log_events_max_samples_stored); info.custom_events_max_samples_stored = NRINI(custom_events_max_samples_stored); + info.docker_id = nr_strdup(NR_PHP_PROCESS_GLOBALS(docker_id)); NRPRG(app) = nr_agent_find_or_add_app( nr_agent_applist, &info, diff --git a/agent/tests/test_environment.c b/agent/tests/test_environment.c index f5cfe32c6..feb77a1a8 100644 --- a/agent/tests/test_environment.c +++ b/agent/tests/test_environment.c @@ -7,6 +7,7 @@ #include "php_agent.h" #include "php_environment.h" +#include "util_text.h" tlib_parallel_info_t parallel_info = {.suggested_nthreads = -1, .state_size = 0}; @@ -420,12 +421,65 @@ static void test_nr_php_process_environment_variables_to_string(void) { test_multi_nr_php_process_environment_variable_to_string(); } -void test_main(void* p NRUNUSED) { -#if defined(ZTS) && !defined(PHP7) - void*** tsrm_ls = NULL; -#endif /* ZTS && !PHP7 */ +static void test_cross_agent_docker_v2(void) { + int i; + char* json; + nrobj_t* tests; + +#define DOCKER_V2_TESTS_PATH CROSS_AGENT_TESTS_DIR "/docker_container_id_v2/" + + json = nr_read_file_contents(DOCKER_V2_TESTS_PATH "cases.json", + 10 * 1000 * 1000); + tlib_pass_if_not_null(DOCKER_V2_TESTS_PATH "cases.json readable", json); + tests = nro_create_from_json(json); + nr_free(json); + + for (i = 1; i <= nro_getsize(tests); i++) { + const nrobj_t* test = NULL; + const char* filename = NULL; + const char* expectedID = NULL; + const nrobj_t* expectedMetrics = NULL; + char* full_filename = NULL; + char* detectedID = NULL; + + test = nro_get_array_hash(tests, i, NULL); + tlib_pass_if_true("test valid", NULL != test, "test=%p", test); + filename = nro_get_hash_string(test, "filename", NULL); + expectedID = nro_get_hash_string(test, "containerId", NULL); + expectedMetrics = nro_get_hash_array(test, "expectedMetrics", NULL); + /* not currently inspected so this avoids a compiler error */ + (void)expectedMetrics; + + tlib_pass_if_true("filname valid", NULL != filename, "filename=%p", + filename); + + full_filename + = nr_str_append(nr_strdup(DOCKER_V2_TESTS_PATH), filename, ""); + detectedID = nr_php_parse_v2_docker_id(full_filename); + nr_free(full_filename); + tlib_pass_if_str_equal("Match Docker cgroup v2 ID", expectedID, detectedID); + nr_free(detectedID); + } + + nro_delete(tests); +} + +static void test_docker_v2(void) { + char* detectedID = NULL; + + // handles bad values without problems + detectedID = nr_php_parse_v2_docker_id(NULL); + tlib_pass_if_null("NULL filename returns NULL", detectedID); - tlib_php_engine_create("" PTSRMLS_CC); + detectedID = nr_php_parse_v2_docker_id(""); + tlib_pass_if_null("Empty filename returns NULL", detectedID); + + detectedID = nr_php_parse_v2_docker_id("/dev/null"); + tlib_pass_if_null("/dev/null returns NULL", detectedID); +} + +void test_main(void* p NRUNUSED) { + tlib_php_engine_create(""); test_rocket_assignments(); @@ -433,5 +487,9 @@ void test_main(void* p NRUNUSED) { test_nr_php_process_environment_variables_to_string(); - tlib_php_engine_destroy(TSRMLS_C); + tlib_php_engine_destroy(); + + test_cross_agent_docker_v2(); + + test_docker_v2(); } diff --git a/axiom/cmd_appinfo_transmit.c b/axiom/cmd_appinfo_transmit.c index 1333e2eff..806a66f63 100644 --- a/axiom/cmd_appinfo_transmit.c +++ b/axiom/cmd_appinfo_transmit.c @@ -147,6 +147,7 @@ nr_flatbuffer_t* nr_appinfo_create_query(const char* agent_run_id, uint32_t host_name; uint32_t trace_observer_host; uint32_t metadata; + uint32_t docker_id; char* json_supported_security_policies; fb = nr_flatbuffers_create(0); @@ -171,6 +172,8 @@ nr_flatbuffer_t* nr_appinfo_create_query(const char* agent_run_id, supported_security_policies = nr_flatbuffers_prepend_string(fb, json_supported_security_policies); + docker_id = nr_flatbuffers_prepend_string(fb, info->docker_id); + metadata = nr_appinfo_prepend_metadata(info, fb); nr_flatbuffers_object_begin(fb, APP_NUM_FIELDS); @@ -206,6 +209,7 @@ nr_flatbuffer_t* nr_appinfo_create_query(const char* agent_run_id, agent_lang, 0); nr_flatbuffers_object_prepend_uoffset(fb, APP_FIELD_APPNAME, appname, 0); nr_flatbuffers_object_prepend_uoffset(fb, APP_FIELD_LICENSE, license, 0); + nr_flatbuffers_object_prepend_uoffset(fb, APP_DOCKER_ID, docker_id, 0); appinfo = nr_flatbuffers_object_end(fb); if (agent_run_id && *agent_run_id) { diff --git a/axiom/nr_app.c b/axiom/nr_app.c index bc5660ac0..d20a56f65 100644 --- a/axiom/nr_app.c +++ b/axiom/nr_app.c @@ -93,6 +93,7 @@ void nr_app_info_destroy_fields(nr_app_info_t* info) { nr_free(info->security_policies_token); nro_delete(info->supported_security_policies); nr_free(info->trace_observer_host); + nr_free(info->docker_id); } /* @@ -277,8 +278,9 @@ static nrapp_t* create_new_app(const nr_app_info_t* info) { app->info.span_events_max_samples_stored = info->span_events_max_samples_stored; app->info.log_events_max_samples_stored = info->log_events_max_samples_stored; - app->info.custom_events_max_samples_stored + app->info.custom_events_max_samples_stored = info->custom_events_max_samples_stored; + app->info.docker_id = nr_strdup(info->docker_id); app->rnd = nr_random_create(); nr_random_seed_from_time(app->rnd); diff --git a/axiom/nr_app.h b/axiom/nr_app.h index 1fb780b7a..d05a7f364 100644 --- a/axiom/nr_app.h +++ b/axiom/nr_app.h @@ -81,6 +81,7 @@ typedef struct _nr_app_info_t { the daemon) */ uint64_t custom_events_max_samples_stored; /* maximum number of custom events per min (for the daemon) */ + char* docker_id; /* Docker container ID */ } nr_app_info_t; /* diff --git a/axiom/nr_commands_private.h b/axiom/nr_commands_private.h index b605b84db..0c60bf503 100644 --- a/axiom/nr_commands_private.h +++ b/axiom/nr_commands_private.h @@ -83,7 +83,8 @@ enum { APP_METADATA = 17, APP_LOG_EVENTS_MAX_SAMPLES_STORED = 18, APP_CUSTOM_EVENTS_MAX_SAMPLES_STORED = 19, - APP_NUM_FIELDS = 20, + APP_DOCKER_ID = 20, + APP_NUM_FIELDS = 21, }; /* Generated from: table AppReply */ diff --git a/axiom/tests/cross_agent_tests/docker_container_id_v2/README.md b/axiom/tests/cross_agent_tests/docker_container_id_v2/README.md new file mode 100644 index 000000000..ea6cc2503 --- /dev/null +++ b/axiom/tests/cross_agent_tests/docker_container_id_v2/README.md @@ -0,0 +1,6 @@ +These tests cover parsing of Docker container IDs on Linux hosts out of +`/proc/self/mountinfo` (or `/proc//mountinfo` more generally). + +The `cases.json` file lists each filename in this directory containing +example `/proc/self/mountinfo` content, and the expected Docker container ID that +should be parsed from that file. diff --git a/axiom/tests/cross_agent_tests/docker_container_id_v2/cases.json b/axiom/tests/cross_agent_tests/docker_container_id_v2/cases.json new file mode 100644 index 000000000..1f47d0c06 --- /dev/null +++ b/axiom/tests/cross_agent_tests/docker_container_id_v2/cases.json @@ -0,0 +1,38 @@ +[ + { + "filename": "docker-20.10.16.txt", + "containerId": "84cf3472a20d1bfb4b50e48b6ff50d96dfcd812652d76dd907951e6f98997bce", + "expectedMetrics": null + }, + { + "filename": "docker-24.0.2.txt", + "containerId": "b0a24eed1b031271d8ba0784b8f354b3da892dfd08bbcf14dd7e8a1cf9292f65", + "expectedMetrics": null + }, + { + "filename": "empty.txt", + "containerId": null, + "expectedMetrics": null + }, + { + "filename": "invalid-characters.txt", + "containerId": null, + "expectedMetrics": null + }, + { + "filename": "docker-too-long.txt", + "containerId": null, + "expectedMetrics": null + }, + { + "filename": "invalid-length.txt", + "containerId": null, + "expectedMetrics": [ + { + "Supportability/utilization/docker/error": { + "callCount": 1 + } + } + ] + } +] \ No newline at end of file diff --git a/axiom/tests/cross_agent_tests/docker_container_id_v2/docker-20.10.16.txt b/axiom/tests/cross_agent_tests/docker_container_id_v2/docker-20.10.16.txt new file mode 100644 index 000000000..ce2b1bedf --- /dev/null +++ b/axiom/tests/cross_agent_tests/docker_container_id_v2/docker-20.10.16.txt @@ -0,0 +1,24 @@ +519 413 0:152 / / rw,relatime master:180 - overlay overlay rw,lowerdir=/var/lib/docker/overlay2/l/YCID3333O5VYPYDNTQRZX4GI67:/var/lib/docker/overlay2/l/G7H4TULAFM2UBFRL7QFQPUNXY5:/var/lib/docker/overlay2/l/RLC4GCL75VGXXXYJJO57STHIYN:/var/lib/docker/overlay2/l/YOZKNWFAP6YX74XEKPHX4KG4UN:/var/lib/docker/overlay2/l/46EQ6YX5PQQZ4Z3WCSMQ6Z4YWI:/var/lib/docker/overlay2/l/KGKX3Z5ZMOCDWOFKBS2FSHMQMQ:/var/lib/docker/overlay2/l/CKFYAF4TXZD4RCE6RG6UNL5WVI,upperdir=/var/lib/docker/overlay2/358c429f7b04ee5a228b94efaebe3413a98fcc676b726f078fe875727e3bddd2/diff,workdir=/var/lib/docker/overlay2/358c429f7b04ee5a228b94efaebe3413a98fcc676b726f078fe875727e3bddd2/work +520 519 0:155 / /proc rw,nosuid,nodev,noexec,relatime - proc proc rw +521 519 0:156 / /dev rw,nosuid - tmpfs tmpfs rw,size=65536k,mode=755 +522 521 0:157 / /dev/pts rw,nosuid,noexec,relatime - devpts devpts rw,gid=5,mode=620,ptmxmode=666 +523 519 0:158 / /sys ro,nosuid,nodev,noexec,relatime - sysfs sysfs ro +524 523 0:30 / /sys/fs/cgroup ro,nosuid,nodev,noexec,relatime - cgroup2 cgroup rw +525 521 0:154 / /dev/mqueue rw,nosuid,nodev,noexec,relatime - mqueue mqueue rw +526 521 0:159 / /dev/shm rw,nosuid,nodev,noexec,relatime - tmpfs shm rw,size=65536k +527 519 254:1 /docker/volumes/3237dea4f8022f1addd7b6f072a9c847eb3e5b8df0d599f462ba7040884d4618/_data /data rw,relatime master:28 - ext4 /dev/vda1 rw +528 519 254:1 /docker/containers/84cf3472a20d1bfb4b50e48b6ff50d96dfcd812652d76dd907951e6f98997bce/resolv.conf /etc/resolv.conf rw,relatime - ext4 /dev/vda1 rw +529 519 254:1 /docker/containers/84cf3472a20d1bfb4b50e48b6ff50d96dfcd812652d76dd907951e6f98997bce/hostname /etc/hostname rw,relatime - ext4 /dev/vda1 rw +530 519 254:1 /docker/containers/84cf3472a20d1bfb4b50e48b6ff50d96dfcd812652d76dd907951e6f98997bce/hosts /etc/hosts rw,relatime - ext4 /dev/vda1 rw +414 521 0:157 /0 /dev/console rw,nosuid,noexec,relatime - devpts devpts rw,gid=5,mode=620,ptmxmode=666 +415 520 0:155 /bus /proc/bus ro,nosuid,nodev,noexec,relatime - proc proc rw +416 520 0:155 /fs /proc/fs ro,nosuid,nodev,noexec,relatime - proc proc rw +417 520 0:155 /irq /proc/irq ro,nosuid,nodev,noexec,relatime - proc proc rw +418 520 0:155 /sys /proc/sys ro,nosuid,nodev,noexec,relatime - proc proc rw +419 520 0:155 /sysrq-trigger /proc/sysrq-trigger ro,nosuid,nodev,noexec,relatime - proc proc rw +420 520 0:160 / /proc/acpi ro,relatime - tmpfs tmpfs ro +421 520 0:156 /null /proc/kcore rw,nosuid - tmpfs tmpfs rw,size=65536k,mode=755 +422 520 0:156 /null /proc/keys rw,nosuid - tmpfs tmpfs rw,size=65536k,mode=755 +423 520 0:156 /null /proc/timer_list rw,nosuid - tmpfs tmpfs rw,size=65536k,mode=755 +424 520 0:156 /null /proc/sched_debug rw,nosuid - tmpfs tmpfs rw,size=65536k,mode=755 +425 523 0:161 / /sys/firmware ro,relatime - tmpfs tmpfs ro diff --git a/axiom/tests/cross_agent_tests/docker_container_id_v2/docker-24.0.2.txt b/axiom/tests/cross_agent_tests/docker_container_id_v2/docker-24.0.2.txt new file mode 100644 index 000000000..1725e7726 --- /dev/null +++ b/axiom/tests/cross_agent_tests/docker_container_id_v2/docker-24.0.2.txt @@ -0,0 +1,21 @@ +1014 1013 0:269 / /proc rw,nosuid,nodev,noexec,relatime - proc proc rw +1019 1013 0:270 / /dev rw,nosuid - tmpfs tmpfs rw,size=65536k,mode=755 +1020 1019 0:271 / /dev/pts rw,nosuid,noexec,relatime - devpts devpts rw,gid=5,mode=620,ptmxmode=666 +1021 1013 0:272 / /sys ro,nosuid,nodev,noexec,relatime - sysfs sysfs ro +1022 1021 0:30 / /sys/fs/cgroup ro,nosuid,nodev,noexec,relatime - cgroup2 cgroup rw +1023 1019 0:268 / /dev/mqueue rw,nosuid,nodev,noexec,relatime - mqueue mqueue rw +1024 1019 0:273 / /dev/shm rw,nosuid,nodev,noexec,relatime - tmpfs shm rw,size=65536k +1025 1013 254:1 /docker/containers/b0a24eed1b031271d8ba0784b8f354b3da892dfd08bbcf14dd7e8a1cf9292f65/resolv.conf /etc/resolv.conf rw,relatime - ext4 /dev/vda1 rw,discard +1026 1013 254:1 /docker/containers/b0a24eed1b031271d8ba0784b8f354b3da892dfd08bbcf14dd7e8a1cf9292f65/hostname /etc/hostname rw,relatime - ext4 /dev/vda1 rw,discard +1027 1013 254:1 /docker/containers/b0a24eed1b031271d8ba0784b8f354b3da892dfd08bbcf14dd7e8a1cf9292f65/hosts /etc/hosts rw,relatime - ext4 /dev/vda1 rw,discard +717 1019 0:271 /0 /dev/console rw,nosuid,noexec,relatime - devpts devpts rw,gid=5,mode=620,ptmxmode=666 +718 1014 0:269 /bus /proc/bus ro,nosuid,nodev,noexec,relatime - proc proc rw +719 1014 0:269 /fs /proc/fs ro,nosuid,nodev,noexec,relatime - proc proc rw +720 1014 0:269 /irq /proc/irq ro,nosuid,nodev,noexec,relatime - proc proc rw +721 1014 0:269 /sys /proc/sys ro,nosuid,nodev,noexec,relatime - proc proc rw +723 1014 0:269 /sysrq-trigger /proc/sysrq-trigger ro,nosuid,nodev,noexec,relatime - proc proc rw +726 1014 0:274 / /proc/acpi ro,relatime - tmpfs tmpfs ro +727 1014 0:270 /null /proc/kcore rw,nosuid - tmpfs tmpfs rw,size=65536k,mode=755 +728 1014 0:270 /null /proc/keys rw,nosuid - tmpfs tmpfs rw,size=65536k,mode=755 +729 1014 0:270 /null /proc/timer_list rw,nosuid - tmpfs tmpfs rw,size=65536k,mode=755 +730 1021 0:275 / /sys/firmware ro,relatime - tmpfs tmpfs ro diff --git a/axiom/tests/cross_agent_tests/docker_container_id_v2/docker-too-long.txt b/axiom/tests/cross_agent_tests/docker_container_id_v2/docker-too-long.txt new file mode 100644 index 000000000..608eaf7a4 --- /dev/null +++ b/axiom/tests/cross_agent_tests/docker_container_id_v2/docker-too-long.txt @@ -0,0 +1,21 @@ +1014 1013 0:269 / /proc rw,nosuid,nodev,noexec,relatime - proc proc rw +1019 1013 0:270 / /dev rw,nosuid - tmpfs tmpfs rw,size=65536k,mode=755 +1020 1019 0:271 / /dev/pts rw,nosuid,noexec,relatime - devpts devpts rw,gid=5,mode=620,ptmxmode=666 +1021 1013 0:272 / /sys ro,nosuid,nodev,noexec,relatime - sysfs sysfs ro +1022 1021 0:30 / /sys/fs/cgroup ro,nosuid,nodev,noexec,relatime - cgroup2 cgroup rw +1023 1019 0:268 / /dev/mqueue rw,nosuid,nodev,noexec,relatime - mqueue mqueue rw +1024 1019 0:273 / /dev/shm rw,nosuid,nodev,noexec,relatime - tmpfs shm rw,size=65536k +1025 1013 254:1 /docker/containers/3ccfa00432798ff38f85839de1e396f771b4acbe9f4ddea0a761c39b9790a7821/resolv.conf /etc/resolv.conf rw,relatime - ext4 /dev/vda1 rw,discard +1026 1013 254:1 /docker/containers/3ccfa00432798ff38f85839de1e396f771b4acbe9f4ddea0a761c39b9790a7821/hostname /etc/hostname rw,relatime - ext4 /dev/vda1 rw,discard +1027 1013 254:1 /docker/containers/3ccfa00432798ff38f85839de1e396f771b4acbe9f4ddea0a761c39b9790a7821/hosts /etc/hosts rw,relatime - ext4 /dev/vda1 rw,discard +717 1019 0:271 /0 /dev/console rw,nosuid,noexec,relatime - devpts devpts rw,gid=5,mode=620,ptmxmode=666 +718 1014 0:269 /bus /proc/bus ro,nosuid,nodev,noexec,relatime - proc proc rw +719 1014 0:269 /fs /proc/fs ro,nosuid,nodev,noexec,relatime - proc proc rw +720 1014 0:269 /irq /proc/irq ro,nosuid,nodev,noexec,relatime - proc proc rw +721 1014 0:269 /sys /proc/sys ro,nosuid,nodev,noexec,relatime - proc proc rw +723 1014 0:269 /sysrq-trigger /proc/sysrq-trigger ro,nosuid,nodev,noexec,relatime - proc proc rw +726 1014 0:274 / /proc/acpi ro,relatime - tmpfs tmpfs ro +727 1014 0:270 /null /proc/kcore rw,nosuid - tmpfs tmpfs rw,size=65536k,mode=755 +728 1014 0:270 /null /proc/keys rw,nosuid - tmpfs tmpfs rw,size=65536k,mode=755 +729 1014 0:270 /null /proc/timer_list rw,nosuid - tmpfs tmpfs rw,size=65536k,mode=755 +730 1021 0:275 / /sys/firmware ro,relatime - tmpfs tmpfs ro diff --git a/axiom/tests/cross_agent_tests/docker_container_id_v2/empty.txt b/axiom/tests/cross_agent_tests/docker_container_id_v2/empty.txt new file mode 100644 index 000000000..e69de29bb diff --git a/axiom/tests/cross_agent_tests/docker_container_id_v2/invalid-characters.txt b/axiom/tests/cross_agent_tests/docker_container_id_v2/invalid-characters.txt new file mode 100644 index 000000000..b561475ac --- /dev/null +++ b/axiom/tests/cross_agent_tests/docker_container_id_v2/invalid-characters.txt @@ -0,0 +1,21 @@ +1014 1013 0:269 / /proc rw,nosuid,nodev,noexec,relatime - proc proc rw +1019 1013 0:270 / /dev rw,nosuid - tmpfs tmpfs rw,size=65536k,mode=755 +1020 1019 0:271 / /dev/pts rw,nosuid,noexec,relatime - devpts devpts rw,gid=5,mode=620,ptmxmode=666 +1021 1013 0:272 / /sys ro,nosuid,nodev,noexec,relatime - sysfs sysfs ro +1022 1021 0:30 / /sys/fs/cgroup ro,nosuid,nodev,noexec,relatime - cgroup2 cgroup rw +1023 1019 0:268 / /dev/mqueue rw,nosuid,nodev,noexec,relatime - mqueue mqueue rw +1024 1019 0:273 / /dev/shm rw,nosuid,nodev,noexec,relatime - tmpfs shm rw,size=65536k +1025 1013 254:1 /docker/containers/WRONGINCORRECTINVALIDCHARSERRONEOUSBADPHONYBROKEN2TERRIBLENOPE55/resolv.conf /etc/resolv.conf rw,relatime - ext4 /dev/vda1 rw,discard +1026 1013 254:1 /docker/containers/WRONGINCORRECTINVALIDCHARSERRONEOUSBADPHONYBROKEN2TERRIBLENOPE55/hostname /etc/hostname rw,relatime - ext4 /dev/vda1 rw,discard +1027 1013 254:1 /docker/containers/WRONGINCORRECTINVALIDCHARSERRONEOUSBADPHONYBROKEN2TERRIBLENOPE55/hosts /etc/hosts rw,relatime - ext4 /dev/vda1 rw,discard +717 1019 0:271 /0 /dev/console rw,nosuid,noexec,relatime - devpts devpts rw,gid=5,mode=620,ptmxmode=666 +718 1014 0:269 /bus /proc/bus ro,nosuid,nodev,noexec,relatime - proc proc rw +719 1014 0:269 /fs /proc/fs ro,nosuid,nodev,noexec,relatime - proc proc rw +720 1014 0:269 /irq /proc/irq ro,nosuid,nodev,noexec,relatime - proc proc rw +721 1014 0:269 /sys /proc/sys ro,nosuid,nodev,noexec,relatime - proc proc rw +723 1014 0:269 /sysrq-trigger /proc/sysrq-trigger ro,nosuid,nodev,noexec,relatime - proc proc rw +726 1014 0:274 / /proc/acpi ro,relatime - tmpfs tmpfs ro +727 1014 0:270 /null /proc/kcore rw,nosuid - tmpfs tmpfs rw,size=65536k,mode=755 +728 1014 0:270 /null /proc/keys rw,nosuid - tmpfs tmpfs rw,size=65536k,mode=755 +729 1014 0:270 /null /proc/timer_list rw,nosuid - tmpfs tmpfs rw,size=65536k,mode=755 +730 1021 0:275 / /sys/firmware ro,relatime - tmpfs tmpfs ro diff --git a/axiom/tests/cross_agent_tests/docker_container_id_v2/invalid-length.txt b/axiom/tests/cross_agent_tests/docker_container_id_v2/invalid-length.txt new file mode 100644 index 000000000..a8987df70 --- /dev/null +++ b/axiom/tests/cross_agent_tests/docker_container_id_v2/invalid-length.txt @@ -0,0 +1,21 @@ +1014 1013 0:269 / /proc rw,nosuid,nodev,noexec,relatime - proc proc rw +1019 1013 0:270 / /dev rw,nosuid - tmpfs tmpfs rw,size=65536k,mode=755 +1020 1019 0:271 / /dev/pts rw,nosuid,noexec,relatime - devpts devpts rw,gid=5,mode=620,ptmxmode=666 +1021 1013 0:272 / /sys ro,nosuid,nodev,noexec,relatime - sysfs sysfs ro +1022 1021 0:30 / /sys/fs/cgroup ro,nosuid,nodev,noexec,relatime - cgroup2 cgroup rw +1023 1019 0:268 / /dev/mqueue rw,nosuid,nodev,noexec,relatime - mqueue mqueue rw +1024 1019 0:273 / /dev/shm rw,nosuid,nodev,noexec,relatime - tmpfs shm rw,size=65536k +1025 1013 254:1 /docker/containers/47cbd16b77c5/resolv.conf /etc/resolv.conf rw,relatime - ext4 /dev/vda1 rw,discard +1026 1013 254:1 /docker/containers/47cbd16b77c5/hostname /etc/hostname rw,relatime - ext4 /dev/vda1 rw,discard +1027 1013 254:1 /docker/containers/47cbd16b77c5/hosts /etc/hosts rw,relatime - ext4 /dev/vda1 rw,discard +717 1019 0:271 /0 /dev/console rw,nosuid,noexec,relatime - devpts devpts rw,gid=5,mode=620,ptmxmode=666 +718 1014 0:269 /bus /proc/bus ro,nosuid,nodev,noexec,relatime - proc proc rw +719 1014 0:269 /fs /proc/fs ro,nosuid,nodev,noexec,relatime - proc proc rw +720 1014 0:269 /irq /proc/irq ro,nosuid,nodev,noexec,relatime - proc proc rw +721 1014 0:269 /sys /proc/sys ro,nosuid,nodev,noexec,relatime - proc proc rw +723 1014 0:269 /sysrq-trigger /proc/sysrq-trigger ro,nosuid,nodev,noexec,relatime - proc proc rw +726 1014 0:274 / /proc/acpi ro,relatime - tmpfs tmpfs ro +727 1014 0:270 /null /proc/kcore rw,nosuid - tmpfs tmpfs rw,size=65536k,mode=755 +728 1014 0:270 /null /proc/keys rw,nosuid - tmpfs tmpfs rw,size=65536k,mode=755 +729 1014 0:270 /null /proc/timer_list rw,nosuid - tmpfs tmpfs rw,size=65536k,mode=755 +730 1021 0:275 / /sys/firmware ro,relatime - tmpfs tmpfs ro diff --git a/axiom/tests/test_cmd_appinfo.c b/axiom/tests/test_cmd_appinfo.c index bd30dda15..59f277bc9 100644 --- a/axiom/tests/test_cmd_appinfo.c +++ b/axiom/tests/test_cmd_appinfo.c @@ -54,6 +54,7 @@ static void test_create_empty_query(void) { test_pass_if_empty_vector(&app, APP_TRACE_OBSERVER_HOST); test_pass_if_empty_vector(&app, APP_FIELD_LABELS); test_pass_if_empty_vector(&app, APP_METADATA); + test_pass_if_empty_vector(&app, APP_DOCKER_ID); high_security = nr_flatbuffers_table_read_i8(&app, APP_FIELD_HIGH_SECURITY, 42); @@ -108,6 +109,8 @@ static void test_create_query(void) { info.span_events_max_samples_stored = 1234; info.log_events_max_samples_stored = 2345; info.custom_events_max_samples_stored = 345; + info.docker_id = nr_strdup( + "1056761e1f44969c959364a8e26e9345b37ccb91aef09a8173c90cf1d1d99156"); query = nr_appinfo_create_query("12345", "this_host", &info); @@ -172,6 +175,10 @@ static void test_create_query(void) { = nr_flatbuffers_table_read_i8(&app, APP_FIELD_HIGH_SECURITY, 0); tlib_pass_if_true(__func__, 1 == high_security, "high_security=%d", high_security); + tlib_pass_if_str_equal( + __func__, + "1056761e1f44969c959364a8e26e9345b37ccb91aef09a8173c90cf1d1d99156", + (const char*)nr_flatbuffers_table_read_bytes(&app, APP_DOCKER_ID)); nr_app_info_destroy_fields(&info); nr_flatbuffers_destroy(&query); diff --git a/protocol/flatbuffers/protocol.fbs b/protocol/flatbuffers/protocol.fbs index b04b624d2..b34081d84 100644 --- a/protocol/flatbuffers/protocol.fbs +++ b/protocol/flatbuffers/protocol.fbs @@ -35,6 +35,7 @@ table App { metadata: string; // pre-computed json, added for PHP agent release 10.0 log_events_max_samples_stored: uint64; // added for PHP agent release 10.1 custom_events_max_samples_stored: uint64; // added for PHP agent release 10.4 + docker_id: string; // added for PHP agent release 10.14 } enum AppStatus : byte { Unknown = 0, Disconnected = 1, InvalidLicense = 2, diff --git a/src/flatbuffersdata/data.go b/src/flatbuffersdata/data.go index d05b15155..fd32ca4b1 100644 --- a/src/flatbuffersdata/data.go +++ b/src/flatbuffersdata/data.go @@ -34,6 +34,7 @@ func MarshalAppInfo(info *newrelic.AppInfo) ([]byte, error) { metadata := buf.CreateString(string(metadataJSON)) host := buf.CreateString(string(info.Hostname)) traceObserverHost := buf.CreateString(info.TraceObserverHost) + dockerId := buf.CreateString(info.DockerId) protocol.AppStart(buf) protocol.AppAddAgentLanguage(buf, lang) @@ -50,6 +51,7 @@ func MarshalAppInfo(info *newrelic.AppInfo) ([]byte, error) { protocol.AppAddTraceObserverPort(buf, info.TraceObserverPort) protocol.AppAddHighSecurity(buf, info.HighSecurity) + protocol.AppAddDockerId(buf, dockerId) appInfo := protocol.AppEnd(buf) diff --git a/src/newrelic/app.go b/src/newrelic/app.go index a29c919d1..9ba90d577 100644 --- a/src/newrelic/app.go +++ b/src/newrelic/app.go @@ -74,6 +74,7 @@ type AppInfo struct { TraceObserverPort uint16 SpanQueueSize uint64 AgentEventLimits collector.EventConfigs + DockerId string } func (info *AppInfo) String() string { @@ -244,6 +245,12 @@ func (info *AppInfo) ConnectPayloadInternal(pid int, util *utilization.Data) *Ra utilCopy := *util data.Util = &utilCopy data.Util.Hostname = data.Host + if info.DockerId != "" { + err := utilization.OverrideDockerId(data.Util, info.DockerId) + if err != nil { + log.Errorf("Failed to set Agent Docker ID: %s", err) + } + } } if len(info.Labels) > 0 { @@ -257,6 +264,10 @@ func (info *AppInfo) ConnectPayloadInternal(pid int, util *utilization.Data) *Ra data.Metadata = JSONString("{}") } + if data.Util != nil { + utilization.OverrideVendors(data.Util) + } + return data } diff --git a/src/newrelic/app_test.go b/src/newrelic/app_test.go index dd5a0749f..f2b49e35c 100644 --- a/src/newrelic/app_test.go +++ b/src/newrelic/app_test.go @@ -38,6 +38,7 @@ func TestConnectPayloadInternal(t *testing.T) { Metadata: JSONString(`{"NEW_RELIC_METADATA_ONE":"one","NEW_RELIC_METADATA_TWO":"two"}`), RedirectCollector: "collector.newrelic.com", Hostname: "some_host", + DockerId: "1056761e1f44969c959364a8e26e9345b37ccb91aef09a8173c90cf1d1d99156", } info.AgentEventLimits.SpanEventConfig.Limit = 2323 @@ -63,6 +64,8 @@ func TestConnectPayloadInternal(t *testing.T) { pid := 123 b := info.ConnectPayloadInternal(pid, util) + actualDockerId, _ := utilization.GetDockerId(b.Util) + // Compare the string integer and string portions of the structs // TestConnectEncodedJSON will do a full comparison after being encoded to bytes if b == nil { @@ -92,6 +95,9 @@ func TestConnectPayloadInternal(t *testing.T) { if b.Util.Hostname != expected.Util.Hostname { t.Errorf("expected: %s\nactual: %s", expected.Util.Hostname, b.Util.Hostname) } + if actualDockerId != info.DockerId { + t.Errorf("expected: %s\nactual: %s", info.DockerId, actualDockerId) + } } func TestConnectPayloadInternalHostname(t *testing.T) { @@ -184,6 +190,67 @@ func TestConnectPayloadInternalHostname(t *testing.T) { } } +func TestConnectPayloadInternalDocker(t *testing.T) { + util := &utilization.Data{} + info := &AppInfo{} + + // No docker ID, nil utilization data + info.DockerId = "" + + b := info.ConnectPayloadInternal(1, nil) + + result, err := utilization.GetDockerId(b.Util) + + if nil != b.Util { + t.Errorf("expected: %v\nactual: %v", nil, b.Util) + } + if "Util is nil" != err.Error() { + t.Errorf("expected: %s\nactual: %s", "Util is nil", err.Error()) + } + + // No Docker ID, utilization data + info.DockerId = "" + + b = info.ConnectPayloadInternal(1, util) + + result, err = utilization.GetDockerId(b.Util) + + if result != info.DockerId { + t.Errorf("expected: %s\nactual: %v", info.DockerId, result) + } + if "Vendors structure is empty" != err.Error() { + t.Errorf("expected: %s\nactual: %s", "Vendors structure is empty", err.Error()) + } + + // Docker ID, nil utilization data + info.DockerId = "1056761e1f44969c959364a8e26e9345b37ccb91aef09a8173c90cf1d1d99156" + + b = info.ConnectPayloadInternal(1, nil) + result, err = utilization.GetDockerId(b.Util) + + if nil != b.Util { + t.Errorf("expected: %v\nactual: %v", nil, b.Util) + } + if "Util is nil" != err.Error() { + t.Errorf("expected: %s\nactual: %s", "Util is nil", err.Error()) + } + + // Docker ID, utilization data + info.DockerId = "1056761e1f44969c959364a8e26e9345b37ccb91aef09a8173c90cf1d1d99156" + + b = info.ConnectPayloadInternal(1, util) + + result, err = utilization.GetDockerId(b.Util) + + if result != info.DockerId { + t.Errorf("expected: %s\nactual: %v", info.DockerId, result) + } + if nil != err { + t.Errorf("expected: %v\nactual: %v", nil, err) + } + +} + func TestPreconnectPayloadEncoded(t *testing.T) { preconnectPayload := &RawPreconnectPayload{SecurityPolicyToken: "ffff-eeee-eeee-dddd", HighSecurity: false} diff --git a/src/newrelic/commands.go b/src/newrelic/commands.go index a357def65..5e67d71e4 100644 --- a/src/newrelic/commands.go +++ b/src/newrelic/commands.go @@ -275,6 +275,7 @@ func UnmarshalAppInfo(tbl flatbuffers.Table) *AppInfo { TraceObserverPort: app.TraceObserverPort(), SpanQueueSize: app.SpanQueueSize(), HighSecurity: app.HighSecurity(), + DockerId: string(app.DockerId()), } info.initSettings(app.Settings()) diff --git a/src/newrelic/protocol/App.go b/src/newrelic/protocol/App.go index eccafeb81..ecb8689fb 100644 --- a/src/newrelic/protocol/App.go +++ b/src/newrelic/protocol/App.go @@ -221,8 +221,16 @@ func (rcv *App) MutateCustomEventsMaxSamplesStored(n uint64) bool { return rcv._tab.MutateUint64Slot(42, n) } +func (rcv *App) DockerId() []byte { + o := flatbuffers.UOffsetT(rcv._tab.Offset(44)) + if o != 0 { + return rcv._tab.ByteVector(o + rcv._tab.Pos) + } + return nil +} + func AppStart(builder *flatbuffers.Builder) { - builder.StartObject(20) + builder.StartObject(21) } func AppAddLicense(builder *flatbuffers.Builder, license flatbuffers.UOffsetT) { builder.PrependUOffsetTSlot(0, flatbuffers.UOffsetT(license), 0) @@ -284,6 +292,9 @@ func AppAddLogEventsMaxSamplesStored(builder *flatbuffers.Builder, logEventsMaxS func AppAddCustomEventsMaxSamplesStored(builder *flatbuffers.Builder, customEventsMaxSamplesStored uint64) { builder.PrependUint64Slot(19, customEventsMaxSamplesStored, 0) } +func AppAddDockerId(builder *flatbuffers.Builder, dockerId flatbuffers.UOffsetT) { + builder.PrependUOffsetTSlot(20, flatbuffers.UOffsetT(dockerId), 0) +} func AppEnd(builder *flatbuffers.Builder) flatbuffers.UOffsetT { return builder.EndObject() } diff --git a/src/newrelic/utilization/utilization_hash.go b/src/newrelic/utilization/utilization_hash.go index 2f28c6235..c357175cb 100644 --- a/src/newrelic/utilization/utilization_hash.go +++ b/src/newrelic/utilization/utilization_hash.go @@ -164,11 +164,6 @@ func Gather(config Config) *Data { // Override whatever needs to be overridden. uDat.Config = overrideFromConfig(config) - if uDat.Vendors.isEmpty() { - // Per spec, we MUST NOT send any vendors hash if it's empty. - uDat.Vendors = nil - } - return uDat } @@ -204,6 +199,44 @@ func GatherDockerID(util *Data) error { return nil } +func OverrideDockerId(util *Data, id string) error { + if nil == util { + return fmt.Errorf("util is nil") + } + if nil == util.Vendors { + util.Vendors = &vendors{} + } + util.Vendors.Docker = &docker{ID: id} + return nil +} + +func OverrideVendors(util *Data) { + if nil == util { + return + } + if util.Vendors.isEmpty() { + // Per spec, we MUST NOT send any vendors hash if it's empty. + util.Vendors = nil + } +} + +func GetDockerId(util *Data) (string, error) { + id := "" + if nil == util { + return id, fmt.Errorf("Util is nil") + } + if util.Vendors.isEmpty() { + return id, fmt.Errorf("Vendors structure is empty") + } + if nil == util.Vendors.Docker { + return id, fmt.Errorf("Docker structure is empty") + } + + id = util.Vendors.Docker.ID + + return id, nil +} + func GatherMemory(util *Data) error { ram, err := sysinfo.PhysicalMemoryBytes() if nil == err { From 42e401d07ddae84f393add9ea6e8c383137fa8dd Mon Sep 17 00:00:00 2001 From: Michael Fulbright <89205663+mfulb@users.noreply.github.com> Date: Tue, 14 Nov 2023 12:02:17 -0500 Subject: [PATCH 08/12] chore(agent): Bump to 10.15.0 (#764) This bumps the dev branch to the next release version/code name. --- VERSION | 2 +- axiom/nr_version.c | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/VERSION b/VERSION index 0ca1348de..f9fb144f9 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -10.14.0 +10.15.0 diff --git a/axiom/nr_version.c b/axiom/nr_version.c index 30fc06d36..703447ba3 100644 --- a/axiom/nr_version.c +++ b/axiom/nr_version.c @@ -41,8 +41,9 @@ * narcissus 20Jun2023 (10.11) * orchid 20Sep2023 (10.12) * poinsettia 03Oct2023 (10.13) + * quince 13Nov2023 (10.14) */ -#define NR_CODENAME "quince" +#define NR_CODENAME "rose" const char* nr_version(void) { return NR_STR2(NR_VERSION); From 78c55dcfffa0e74f927a371a835baa8ba288afef Mon Sep 17 00:00:00 2001 From: Michal Nowacki Date: Wed, 15 Nov 2023 16:42:22 -0500 Subject: [PATCH 09/12] tests: fix guzzle integration tests (#772) Guzzle integration tests are flaky - they expect a span for a call to `new GuzzleHttp\Client();`. However, when `transaction_tracer.detail` is set to 0, the agent sometimes drops it. On the other hand when `transaction_tracer.detail` is set to 1, the agent generates many more spans and adding them to `EXPECT_SPAN_EVENTS` seems tedious. Therefore `EXPECT_SPAN_EVENTS_LIKE` is used in test expectations to check if the required spans - root and external - are present. --- .../external/guzzle5/test_spans_external.php | 31 +------------------ .../guzzle5/test_spans_external.php5.php | 26 +--------------- .../test_spans_are_created_correctly.php | 31 +------------------ .../test_spans_are_created_correctly.php5.php | 26 +--------------- .../test_spans_are_created_correctly.php | 31 +------------------ 5 files changed, 5 insertions(+), 140 deletions(-) diff --git a/tests/integration/external/guzzle5/test_spans_external.php b/tests/integration/external/guzzle5/test_spans_external.php index c32e4ec34..c03af59cf 100644 --- a/tests/integration/external/guzzle5/test_spans_external.php +++ b/tests/integration/external/guzzle5/test_spans_external.php @@ -26,14 +26,8 @@ /*EXPECT_RESPONSE_HEADERS */ -/*EXPECT_SPAN_EVENTS +/*EXPECT_SPAN_EVENTS_LIKE [ - "?? agent run id", - { - "reservoir_size": 10000, - "events_seen": 4 - }, - [ [ { "traceId": "??", @@ -52,28 +46,6 @@ {}, {} ], - [ - { - "traceId": "??", - "duration": "??", - "transactionId": "??", - "name": "Custom\/GuzzleHttp\\Client::__construct", - "guid": "??", - "type": "Span", - "category": "generic", - "priority": "??", - "sampled": true, - "parentId": "??", - "timestamp": "??" - }, - {}, - { - "code.lineno": "??", - "code.namespace": "GuzzleHttp\\Client", - "code.filepath": "??", - "code.function": "__construct" - } - ], [ { "traceId": "??", @@ -120,7 +92,6 @@ "http.statusCode": 200 } ] - ] ] */ diff --git a/tests/integration/external/guzzle5/test_spans_external.php5.php b/tests/integration/external/guzzle5/test_spans_external.php5.php index 6d8f0b1fa..509e96964 100644 --- a/tests/integration/external/guzzle5/test_spans_external.php5.php +++ b/tests/integration/external/guzzle5/test_spans_external.php5.php @@ -23,14 +23,8 @@ /*EXPECT_RESPONSE_HEADERS */ -/*EXPECT_SPAN_EVENTS +/*EXPECT_SPAN_EVENTS_LIKE [ - "?? agent run id", - { - "reservoir_size": 10000, - "events_seen": 4 - }, - [ [ { "traceId": "??", @@ -49,23 +43,6 @@ {}, {} ], - [ - { - "traceId": "??", - "duration": "??", - "transactionId": "??", - "name": "Custom\/GuzzleHttp\\Client::__construct", - "guid": "??", - "type": "Span", - "category": "generic", - "priority": "??", - "sampled": true, - "parentId": "??", - "timestamp": "??" - }, - {}, - {} - ], [ { "traceId": "??", @@ -112,7 +89,6 @@ "http.statusCode": 200 } ] - ] ] */ diff --git a/tests/integration/external/guzzle6/test_spans_are_created_correctly.php b/tests/integration/external/guzzle6/test_spans_are_created_correctly.php index 2692781a4..0c9d0994f 100644 --- a/tests/integration/external/guzzle6/test_spans_are_created_correctly.php +++ b/tests/integration/external/guzzle6/test_spans_are_created_correctly.php @@ -24,14 +24,8 @@ */ -/*EXPECT_SPAN_EVENTS +/*EXPECT_SPAN_EVENTS_LIKE [ - "?? agent run id", - { - "reservoir_size": 10000, - "events_seen": 3 - }, - [ [ { "traceId": "??", @@ -50,28 +44,6 @@ {}, {} ], - [ - { - "traceId": "??", - "duration": "??", - "transactionId": "??", - "name": "Custom\/GuzzleHttp\\Client::__construct", - "guid": "??", - "type": "Span", - "category": "generic", - "priority": "??", - "sampled": true, - "parentId": "??", - "timestamp": "??" - }, - {}, - { - "code.lineno": "??", - "code.namespace": "GuzzleHttp\\Client", - "code.filepath": "??", - "code.function": "__construct" - } - ], [ { "traceId": "??", @@ -95,7 +67,6 @@ "http.statusCode": 200 } ] - ] ] */ diff --git a/tests/integration/external/guzzle6/test_spans_are_created_correctly.php5.php b/tests/integration/external/guzzle6/test_spans_are_created_correctly.php5.php index 1520144f6..82b9b9911 100644 --- a/tests/integration/external/guzzle6/test_spans_are_created_correctly.php5.php +++ b/tests/integration/external/guzzle6/test_spans_are_created_correctly.php5.php @@ -21,14 +21,8 @@ */ -/*EXPECT_SPAN_EVENTS +/*EXPECT_SPAN_EVENTS_LIKE [ - "?? agent run id", - { - "reservoir_size": 10000, - "events_seen": 3 - }, - [ [ { "traceId": "??", @@ -47,23 +41,6 @@ {}, {} ], - [ - { - "traceId": "??", - "duration": "??", - "transactionId": "??", - "name": "Custom\/GuzzleHttp\\Client::__construct", - "guid": "??", - "type": "Span", - "category": "generic", - "priority": "??", - "sampled": true, - "parentId": "??", - "timestamp": "??" - }, - {}, - {} - ], [ { "traceId": "??", @@ -87,7 +64,6 @@ "http.statusCode": 200 } ] - ] ] */ diff --git a/tests/integration/external/guzzle7/test_spans_are_created_correctly.php b/tests/integration/external/guzzle7/test_spans_are_created_correctly.php index 58d3825e0..87388da64 100644 --- a/tests/integration/external/guzzle7/test_spans_are_created_correctly.php +++ b/tests/integration/external/guzzle7/test_spans_are_created_correctly.php @@ -20,14 +20,8 @@ */ -/*EXPECT_SPAN_EVENTS +/*EXPECT_SPAN_EVENTS_LIKE [ - "?? agent run id", - { - "reservoir_size": 10000, - "events_seen": 3 - }, - [ [ { "traceId": "??", @@ -46,28 +40,6 @@ {}, {} ], - [ - { - "traceId": "??", - "duration": "??", - "transactionId": "??", - "name": "Custom\/GuzzleHttp\\Client::__construct", - "guid": "??", - "type": "Span", - "category": "generic", - "priority": "??", - "sampled": true, - "parentId": "??", - "timestamp": "??" - }, - {}, - { - "code.lineno": "??", - "code.namespace": "GuzzleHttp\\Client", - "code.filepath": "??", - "code.function": "__construct" - } - ], [ { "traceId": "??", @@ -91,7 +63,6 @@ "http.statusCode": 200 } ] - ] ] */ From 9d3708265379869534a1521a9ddea630ca109c09 Mon Sep 17 00:00:00 2001 From: Amber Sistla Date: Thu, 16 Nov 2023 06:29:48 -0700 Subject: [PATCH 10/12] chore: Update repolinter related items (#762) * Update README.md community plus header with the latest snippet * Update README.md link to point directly to the forums * Update repolinter.yml to elevate permissions needed to create/modify/close issues. Resolves https://github.com/newrelic/newrelic-php-agent/issues/573 --------- Co-authored-by: Michael Fulbright <89205663+mfulb@users.noreply.github.com> --- .github/workflows/repolinter.yml | 3 +++ README.md | 4 ++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/.github/workflows/repolinter.yml b/.github/workflows/repolinter.yml index acb921f30..e1e3c1cc1 100644 --- a/.github/workflows/repolinter.yml +++ b/.github/workflows/repolinter.yml @@ -8,6 +8,9 @@ name: Repolinter Action # filtered in the "Test Default Branch" step. on: [push, workflow_dispatch] +permissions: + issues: write + jobs: repolint: name: Run Repolinter diff --git a/README.md b/README.md index 6a67babc9..e0bef27e9 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -[![Community Plus header](https://github.com/newrelic/opensource-website/raw/master/src/images/categories/Community_Plus.png)](https://opensource.newrelic.com/oss-category/#community-plus) +New Relic Open Source community plus project banner. # New Relic PHP agent [![agent-build status](https://github.com/newrelic/newrelic-php-agent/workflows/agent-build/badge.svg)](https://github.com/newrelic/newrelic-php-agent/actions) @@ -35,7 +35,7 @@ If the issue is confirmed as a bug or is a feature request, please file a GitHub **Support Channels** * [New Relic Documentation](https://docs.newrelic.com/docs/agents/php-agent/getting-started/introduction-new-relic-php): Comprehensive guidance for using our platform -* [New Relic Community](https://discuss.newrelic.com/tags/phpagent): The best place to engage in troubleshooting questions +* [New Relic Community](https://forum.newrelic.com/): The best place to engage in troubleshooting questions * [New Relic Developer](https://developer.newrelic.com/): Resources for building a custom observability applications * [New Relic University](https://learn.newrelic.com/): A range of online training for New Relic users of every level * [New Relic Technical Support](https://support.newrelic.com/) 24/7/365 ticketed support. Read more about our [Technical Support Offerings](https://docs.newrelic.com/docs/licenses/license-information/general-usage-licenses/global-technical-support-offerings). From 7b3a076340edc65e50e5914133db7db1b579b3a8 Mon Sep 17 00:00:00 2001 From: Michal Nowacki Date: Fri, 17 Nov 2023 16:12:40 -0500 Subject: [PATCH 11/12] tests: add `EXPECT_METRICS_EXIST` to integration tests (#771) Integration tests sometimes don't care about the exact set of metrics that are being sent (particularly around things like datastore metrics); what is really interesting is that the framework is detected, the transaction is named correctly, and that there aren't errors. This adds a new `EXPECT_METRICS_EXIST` section which is simply a newline separated list of metric names that will be searched for: if any don't exist, the test will fail. For example: ```php ``` This asserts that the test will generate `Supportability/framework/WordPress/detected` metric, but doesn't assert anything else about the metric table. --------- Co-authored-by: Adam Harvey --- src/newrelic/integration/parse.go | 5 ++++ src/newrelic/integration/test.go | 10 ++++++++ src/newrelic/metrics.go | 7 ++++++ src/newrelic/metrics_test.go | 18 +++++++++++++++ .../analog/test_supportability_metric.php | 22 +++--------------- .../test_supportability_metric.php | 23 +++---------------- .../test_supportability_metric.php | 23 +++---------------- .../test_supportability_metric.php | 23 +++---------------- 8 files changed, 52 insertions(+), 79 deletions(-) diff --git a/src/newrelic/integration/parse.go b/src/newrelic/integration/parse.go index 554972556..f44c8d22a 100644 --- a/src/newrelic/integration/parse.go +++ b/src/newrelic/integration/parse.go @@ -32,6 +32,7 @@ var ( "EXPECT_SPAN_EVENTS_LIKE": parseSpanEventsLike, "EXPECT_LOG_EVENTS": parseLogEvents, "EXPECT_METRICS": parseMetrics, + "EXPECT_METRICS_EXIST": parseMetricsExist, "EXPECT": parseExpect, "EXPECT_REGEX": parseExpectRegex, "EXPECT_SCRUBBED": parseExpectScrubbed, @@ -224,6 +225,10 @@ func parseMetrics(test *Test, content []byte) error { test.metrics = content return nil } +func parseMetricsExist(test *Test, content []byte) error { + test.metricsExist = content + return nil +} func parseSlowSQLs(test *Test, content []byte) error { test.slowSQLs = content return nil diff --git a/src/newrelic/integration/test.go b/src/newrelic/integration/test.go index 4ce126a33..25945964d 100644 --- a/src/newrelic/integration/test.go +++ b/src/newrelic/integration/test.go @@ -36,6 +36,7 @@ type Test struct { spanEventsLike []byte logEvents []byte metrics []byte + metricsExist []byte slowSQLs []byte tracedErrors []byte txnTraces []byte @@ -557,6 +558,15 @@ func (t *Test) Compare(harvest *newrelic.Harvest) { return } + if nil != t.metricsExist { + for _, name := range strings.Split(strings.TrimSpace(string(t.metricsExist)), "\n") { + name = strings.TrimSpace(name) + if !harvest.Metrics.Has(name) { + t.Fail(fmt.Errorf("metric does not exist: %s\n\nactual metric table: %s", name, harvest.Metrics.DebugJSON())) + } + } + } + // if we expect a harvest and these is not, then we run our tests as per normal t.compareResponseHeaders() diff --git a/src/newrelic/metrics.go b/src/newrelic/metrics.go index 3a5e7cee0..28a78620c 100644 --- a/src/newrelic/metrics.go +++ b/src/newrelic/metrics.go @@ -340,6 +340,13 @@ func (mt *MetricTable) Empty() bool { return 0 == mt.count } +// Has returns true if the given metric exists in the metric table (regardless +// of scope). +func (mt *MetricTable) Has(name string) bool { + _, ok := mt.metrics[name] + return ok +} + // Data marshals the collection to JSON according to the schema expected // by the collector. func (mt *MetricTable) Data(id AgentRunID, harvestStart time.Time) ([]byte, diff --git a/src/newrelic/metrics_test.go b/src/newrelic/metrics_test.go index 1aee056dc..1c29e6eaa 100644 --- a/src/newrelic/metrics_test.go +++ b/src/newrelic/metrics_test.go @@ -301,3 +301,21 @@ func BenchmarkMetricTableCollectorJSON(b *testing.B) { mt.CollectorJSON(AgentRunID("12345"), now) } } + +func TestMetricTableHas(t *testing.T) { + mt := NewMetricTable(20, start) + mt.AddCount("foo", "", 1, 0) + mt.AddCount("bar", "quux", 1, 0) + + if mt.Has("quux") { + t.Fatal("non-existent metric is reported as existing") + } + + if !mt.Has("foo") { + t.Fatal("unscoped metric is reported as missing") + } + + if !mt.Has("bar") { + t.Fatal("scoped metric is reported as missing") + } +} diff --git a/tests/integration/logging/analog/test_supportability_metric.php b/tests/integration/logging/analog/test_supportability_metric.php index c0c5ce644..f3cfb4477 100644 --- a/tests/integration/logging/analog/test_supportability_metric.php +++ b/tests/integration/logging/analog/test_supportability_metric.php @@ -16,25 +16,9 @@ newrelic.application_logging.enabled = true */ -/*EXPECT_METRICS -[ - "?? agent run id", - "?? timeframe start", - "?? timeframe stop", - [ - [{"name":"DurationByCaller/Unknown/Unknown/Unknown/Unknown/all"}, [1, "??", "??", "??", "??", "??"]], - [{"name":"DurationByCaller/Unknown/Unknown/Unknown/Unknown/allOther"}, [1, "??", "??", "??", "??", "??"]], - [{"name":"OtherTransaction/all"}, [1, "??", "??", "??", "??", "??"]], - [{"name":"OtherTransaction/php__FILE__"}, [1, "??", "??", "??", "??", "??"]], - [{"name":"OtherTransactionTotalTime"}, [1, "??", "??", "??", "??", "??"]], - [{"name":"OtherTransactionTotalTime/php__FILE__"}, [1, "??", "??", "??", "??", "??"]], - [{"name":"Supportability/Logging/PHP/Analog/disabled"}, [1, "??", "??", "??", "??", "??"]], - [{"name":"Supportability/library/Analog/detected"}, [1, "??", "??", "??", "??", "??"]], - [{"name":"Supportability/Logging/Forwarding/PHP/enabled"}, [1, "??", "??", "??", "??", "??"]], - [{"name":"Supportability/Logging/Metrics/PHP/enabled"}, [1, "??", "??", "??", "??", "??"]], - [{"name":"Supportability/Logging/LocalDecorating/PHP/disabled"}, [1, "??", "??", "??", "??", "??"]] - ] -] +/*EXPECT_METRICS_EXIST +Supportability/library/Analog/detected +Supportability/Logging/PHP/Analog/disabled */ diff --git a/tests/integration/logging/cakephp-log/test_supportability_metric.php b/tests/integration/logging/cakephp-log/test_supportability_metric.php index 777dae825..93bc9e01e 100644 --- a/tests/integration/logging/cakephp-log/test_supportability_metric.php +++ b/tests/integration/logging/cakephp-log/test_supportability_metric.php @@ -16,26 +16,9 @@ newrelic.application_logging.enabled = true */ -/*EXPECT_METRICS -[ - "?? agent run id", - "?? timeframe start", - "?? timeframe stop", - [ - [{"name":"DurationByCaller/Unknown/Unknown/Unknown/Unknown/all"}, [1, "??", "??", "??", "??", "??"]], - [{"name":"DurationByCaller/Unknown/Unknown/Unknown/Unknown/allOther"}, [1, "??", "??", "??", "??", "??"]], - [{"name":"OtherTransaction/all"}, [1, "??", "??", "??", "??", "??"]], - [{"name":"OtherTransaction/php__FILE__"}, [1, "??", "??", "??", "??", "??"]], - [{"name":"OtherTransactionTotalTime"}, [1, "??", "??", "??", "??", "??"]], - [{"name":"OtherTransactionTotalTime/php__FILE__"}, [1, "??", "??", "??", "??", "??"]], - [{"name":"Supportability/Logging/PHP/cakephp-log/disabled"}, [1, "??", "??", "??", "??", "??"]], - [{"name":"Supportability/library/cakephp-log/detected"}, [1, "??", "??", "??", "??", "??"]], - [{"name":"Supportability/Logging/Forwarding/PHP/enabled"}, [1, "??", "??", "??", "??", "??"]], - [{"name":"Supportability/Logging/Metrics/PHP/enabled"}, [1, "??", "??", "??", "??", "??"]], - [{"name":"Supportability/Logging/LocalDecorating/PHP/disabled"}, [1, "??", "??", "??", "??", "??"]] - ] -] +/*EXPECT_METRICS_EXIST +Supportability/library/cakephp-log/detected +Supportability/Logging/PHP/cakephp-log/disabled */ - require_once(realpath(dirname(__FILE__)) . '/vendor/cakephp/log/Log.php'); diff --git a/tests/integration/logging/consolidation-log/test_supportability_metric.php b/tests/integration/logging/consolidation-log/test_supportability_metric.php index e41529604..0eac6dfe2 100644 --- a/tests/integration/logging/consolidation-log/test_supportability_metric.php +++ b/tests/integration/logging/consolidation-log/test_supportability_metric.php @@ -16,26 +16,9 @@ newrelic.application_logging.enabled = true */ -/*EXPECT_METRICS -[ - "?? agent run id", - "?? timeframe start", - "?? timeframe stop", - [ - [{"name":"DurationByCaller/Unknown/Unknown/Unknown/Unknown/all"}, [1, "??", "??", "??", "??", "??"]], - [{"name":"DurationByCaller/Unknown/Unknown/Unknown/Unknown/allOther"}, [1, "??", "??", "??", "??", "??"]], - [{"name":"OtherTransaction/all"}, [1, "??", "??", "??", "??", "??"]], - [{"name":"OtherTransaction/php__FILE__"}, [1, "??", "??", "??", "??", "??"]], - [{"name":"OtherTransactionTotalTime"}, [1, "??", "??", "??", "??", "??"]], - [{"name":"OtherTransactionTotalTime/php__FILE__"}, [1, "??", "??", "??", "??", "??"]], - [{"name":"Supportability/Logging/PHP/Consolidation/Log/disabled"}, [1, "??", "??", "??", "??", "??"]], - [{"name":"Supportability/library/Consolidation/Log/detected"}, [1, "??", "??", "??", "??", "??"]], - [{"name":"Supportability/Logging/Forwarding/PHP/enabled"}, [1, "??", "??", "??", "??", "??"]], - [{"name":"Supportability/Logging/Metrics/PHP/enabled"}, [1, "??", "??", "??", "??", "??"]], - [{"name":"Supportability/Logging/LocalDecorating/PHP/disabled"}, [1, "??", "??", "??", "??", "??"]] - ] -] +/*EXPECT_METRICS_EXIST +Supportability/library/Consolidation/Log/detected +Supportability/Logging/PHP/Consolidation/Log/disabled */ - require_once(realpath(dirname(__FILE__)) . '/vendor/consolidation/log/src/Logger.php'); diff --git a/tests/integration/logging/laminas-log/test_supportability_metric.php b/tests/integration/logging/laminas-log/test_supportability_metric.php index 104f5d274..ddf7d276a 100644 --- a/tests/integration/logging/laminas-log/test_supportability_metric.php +++ b/tests/integration/logging/laminas-log/test_supportability_metric.php @@ -16,26 +16,9 @@ newrelic.application_logging.enabled = true */ -/*EXPECT_METRICS -[ - "?? agent run id", - "?? timeframe start", - "?? timeframe stop", - [ - [{"name":"DurationByCaller/Unknown/Unknown/Unknown/Unknown/all"}, [1, "??", "??", "??", "??", "??"]], - [{"name":"DurationByCaller/Unknown/Unknown/Unknown/Unknown/allOther"}, [1, "??", "??", "??", "??", "??"]], - [{"name":"OtherTransaction/all"}, [1, "??", "??", "??", "??", "??"]], - [{"name":"OtherTransaction/php__FILE__"}, [1, "??", "??", "??", "??", "??"]], - [{"name":"OtherTransactionTotalTime"}, [1, "??", "??", "??", "??", "??"]], - [{"name":"OtherTransactionTotalTime/php__FILE__"}, [1, "??", "??", "??", "??", "??"]], - [{"name":"Supportability/Logging/PHP/laminas-log/disabled"}, [1, "??", "??", "??", "??", "??"]], - [{"name":"Supportability/library/laminas-log/detected"}, [1, "??", "??", "??", "??", "??"]], - [{"name":"Supportability/Logging/Forwarding/PHP/enabled"}, [1, "??", "??", "??", "??", "??"]], - [{"name":"Supportability/Logging/Metrics/PHP/enabled"}, [1, "??", "??", "??", "??", "??"]], - [{"name":"Supportability/Logging/LocalDecorating/PHP/disabled"}, [1, "??", "??", "??", "??", "??"]] - ] -] +/*EXPECT_METRICS_EXIST +Supportability/library/laminas-log/detected +Supportability/Logging/PHP/laminas-log/disabled */ - require_once(realpath(dirname(__FILE__)) . '/vendor/laminas/laminas-log/src/Logger.php'); From 4bb62ba8f1e5e276e544ce7a3f31195b0fa20c7a Mon Sep 17 00:00:00 2001 From: Michal Nowacki Date: Mon, 20 Nov 2023 15:09:11 -0500 Subject: [PATCH 12/12] tests: make tests deterministic (#781) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. Force non-zero duration of helper user function When tests execute fast enough, the helper function's segment start time and end time are equal, and this causes the segment to be dropped. However, some integration tests have inconsistent test expectation in expected transaction traces. On one hand, wildcard match is used for trace details but on the other hand segment names are expected to match exactly. On faster hardware this discrepancy will cause such tests to fail. To address this test instability, non-zero duration is forced on test helper user function `force_transaction_trace`. 2. Adjust forced duration to ensure expected sampling `tests/integration/ini/test_transaction_tracer_max_segments_with_datastore.php` expects datastore segment, created with `newrelic_record_datastore_segment`, to have longer duration than the at least one execution of custom instrumented user function `my_function`, and therefore appear in the transaction trace. There is, however, absolutely no apparent reason for that to happen. `tests/integration/ini/test_transaction_tracer_max_segments_with_datastore.php` test limits the number of recorded segments to 3 with this ini setting: `newrelic.transaction_tracer.max_segments_cli=3`. This has direct impact on the size of the heap of segments kept. The test creates more than 3 segments - 3 for custom traced `my_function` and 1 datastore segment. However, neither of the segments have a well defined, deterministic duration. Duration is used to determine which segment gets kept and which one gets dropped at the time the segment gets ended. Note when the heap of segments kept heap is created, `nr_segment_wrapped_span_priority_comparator` is set to determine min and max values of elements in the heap. However, in this particular test, both segments have the same, default priority of 0. They’re neither root segments, neither segment’s id is used in distributed trace, nor any of the segments has logs or user attributes associated with it. This causes `nr_segment_wrapped_span_priority_comparator` to fall back to `nr_segment_wrapped_duration_comparator`, which leaves things to being more or less random as sometimes the execution of `my_function` is shorter than execution of `newrelic_record_datastore_segment` but other times it is the other way around. --- tests/include/helpers.php | 4 ++-- .../integration/ini/test_transaction_tracer_max_segments.php | 2 +- .../ini/test_transaction_tracer_max_segments_nested.php | 2 +- .../ini/test_transaction_tracer_max_segments_nested.php5.php | 2 +- .../ini/test_transaction_tracer_max_segments_no_cap.php | 2 +- .../test_transaction_tracer_max_segments_with_datastore.php | 4 ++-- ...st_transaction_tracer_max_segments_with_datastore.php5.php | 4 ++-- 7 files changed, 10 insertions(+), 10 deletions(-) diff --git a/tests/include/helpers.php b/tests/include/helpers.php index d528ececb..92e28a0f4 100644 --- a/tests/include/helpers.php +++ b/tests/include/helpers.php @@ -31,8 +31,8 @@ function force_error() { * A user function has to be called to force a transaction trace. PHP 7.1 * includes dead code elimination, which means that we have to actually do * something that can't be eliminated: re-setting the error reporting level to - * what it already is will do the job. + * what it already is will do the job. Additionally force non-zero duration for the segment not to be dropped. */ function force_transaction_trace() { - error_reporting(error_reporting()); + error_reporting(error_reporting()); time_nanosleep(0, 50000); // 50usec should be enough } diff --git a/tests/integration/ini/test_transaction_tracer_max_segments.php b/tests/integration/ini/test_transaction_tracer_max_segments.php index 6b33d1f92..758b570e1 100644 --- a/tests/integration/ini/test_transaction_tracer_max_segments.php +++ b/tests/integration/ini/test_transaction_tracer_max_segments.php @@ -82,7 +82,7 @@ function my_function(){ - printf(''); + time_nanosleep(0, 50000); // force non-zero duration for the segment not to be dropped, 50usec should be enough } newrelic_add_custom_tracer("my_function"); diff --git a/tests/integration/ini/test_transaction_tracer_max_segments_nested.php b/tests/integration/ini/test_transaction_tracer_max_segments_nested.php index 5495defc7..7a97a6448 100644 --- a/tests/integration/ini/test_transaction_tracer_max_segments_nested.php +++ b/tests/integration/ini/test_transaction_tracer_max_segments_nested.php @@ -177,7 +177,7 @@ function my_function() { - printf(''); + time_nanosleep(0, 50000); // force non-zero duration for the segment not to be dropped, 50usec should be enough } function grandmother(){ for ($i = 0; $i < 1000; $i++) { diff --git a/tests/integration/ini/test_transaction_tracer_max_segments_nested.php5.php b/tests/integration/ini/test_transaction_tracer_max_segments_nested.php5.php index 3b55503b2..f5fb391c9 100644 --- a/tests/integration/ini/test_transaction_tracer_max_segments_nested.php5.php +++ b/tests/integration/ini/test_transaction_tracer_max_segments_nested.php5.php @@ -126,7 +126,7 @@ function my_function() { - printf(''); + time_nanosleep(0, 50000); // force non-zero duration for the segment not to be dropped, 50usec should be enough } function grandmother(){ for ($i = 0; $i < 1000; $i++) { diff --git a/tests/integration/ini/test_transaction_tracer_max_segments_no_cap.php b/tests/integration/ini/test_transaction_tracer_max_segments_no_cap.php index 4da948d22..fe3f05356 100644 --- a/tests/integration/ini/test_transaction_tracer_max_segments_no_cap.php +++ b/tests/integration/ini/test_transaction_tracer_max_segments_no_cap.php @@ -82,7 +82,7 @@ function my_function(){ - printf(''); + time_nanosleep(0, 50000); // force non-zero duration for the segment not to be dropped, 50usec should be enough } newrelic_add_custom_tracer("my_function"); diff --git a/tests/integration/ini/test_transaction_tracer_max_segments_with_datastore.php b/tests/integration/ini/test_transaction_tracer_max_segments_with_datastore.php index a0e8ac8b1..f6e2b0171 100644 --- a/tests/integration/ini/test_transaction_tracer_max_segments_with_datastore.php +++ b/tests/integration/ini/test_transaction_tracer_max_segments_with_datastore.php @@ -123,7 +123,7 @@ function my_function(){ - printf(''); + time_nanosleep(0, 50000); // force non-zero duration for the segment not to be dropped; duration needs to be shorter than datastore segment } newrelic_add_custom_tracer("my_function"); @@ -133,7 +133,7 @@ function my_function(){ my_function(); newrelic_record_datastore_segment(function () { - return 42; + time_nanosleep(0, 70000); return 42; // force non-zero duration for the segment not to be dropped; duration needs to be longer than user func segment }, array( 'product' => 'mysql', 'collection' => 'table', diff --git a/tests/integration/ini/test_transaction_tracer_max_segments_with_datastore.php5.php b/tests/integration/ini/test_transaction_tracer_max_segments_with_datastore.php5.php index fb7b1e706..c27e5d3be 100644 --- a/tests/integration/ini/test_transaction_tracer_max_segments_with_datastore.php5.php +++ b/tests/integration/ini/test_transaction_tracer_max_segments_with_datastore.php5.php @@ -112,7 +112,7 @@ function my_function(){ - printf(''); + time_nanosleep(0, 50000); // force non-zero duration for the segment not to be dropped; duration needs to be shorter than datastore segment } newrelic_add_custom_tracer("my_function"); @@ -122,7 +122,7 @@ function my_function(){ my_function(); newrelic_record_datastore_segment(function () { - return 42; + time_nanosleep(0, 70000); return 42; // force non-zero duration for the segment not to be dropped; duration needs to be longer than user func segment }, array( 'product' => 'mysql', 'collection' => 'table',