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/.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/README.md b/README.md
index 6a67babc9..e0bef27e9 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,4 @@
-[](https://opensource.newrelic.com/oss-category/#community-plus)
+
# New Relic PHP agent [](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).
diff --git a/VERSION b/VERSION
index db24ab967..f9fb144f9 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-10.13.0
+10.15.0
diff --git a/agent/Makefile.frag b/agent/Makefile.frag
index a6cec7adb..3633379cb 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 \
@@ -264,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/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_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_internal_instrument.c b/agent/php_internal_instrument.c
index 42b2a4ccb..15c43dcdd 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);
@@ -1343,9 +1399,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/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 */
diff --git a/agent/php_newrelic.h b/agent/php_newrelic.h
index 0f4ada5a7..71e13a354 100644
--- a/agent/php_newrelic.h
+++ b/agent/php_newrelic.h
@@ -356,6 +356,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 09f614783..4346e6f42 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);
@@ -797,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/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_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/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/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_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_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/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 bf2a40463..e2eff1a00 100644
--- a/axiom/nr_txn.c
+++ b/axiom/nr_txn.c
@@ -3277,6 +3277,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;
@@ -3372,6 +3384,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();
@@ -3381,6 +3394,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);
@@ -3391,6 +3405,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;
@@ -3412,7 +3427,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;
@@ -3449,12 +3464,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 b5a4356ef..04e0f2871 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
@@ -629,6 +629,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
*/
@@ -652,13 +657,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/nr_version.c b/axiom/nr_version.c
index da541a91d..703447ba3 100644
--- a/axiom/nr_version.c
+++ b/axiom/nr_version.c
@@ -40,8 +40,10 @@
* marigold 30May2023 (10.10)
* narcissus 20Jun2023 (10.11)
* orchid 20Sep2023 (10.12)
+ * poinsettia 03Oct2023 (10.13)
+ * quince 13Nov2023 (10.14)
*/
-#define NR_CODENAME "poinsettia"
+#define NR_CODENAME "rose"
const char* nr_version(void) {
return NR_STR2(NR_VERSION);
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_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_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/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 351b470fe..1e8c5fa7b 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/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"]
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/integration/parse.go b/src/newrelic/integration/parse.go
index 36745dfa7..577bd6294 100644
--- a/src/newrelic/integration/parse.go
+++ b/src/newrelic/integration/parse.go
@@ -33,6 +33,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,
@@ -254,6 +255,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 f5dfd9b8f..d4295342d 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
@@ -560,6 +561,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/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
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 {
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/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/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
}
]
- ]
]
*/
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',
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');
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();
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);
+