Skip to content

Commit 0e666b9

Browse files
mfulblavarou
andauthored
feat(agent): Adds label forwarding to log events (#1027)
This PR adds the ability for labels to be forwarded with any log messages forwarded by the PHP agent. --------- Co-authored-by: Michal Nowacki <[email protected]>
1 parent 57f7f12 commit 0e666b9

File tree

624 files changed

+13184
-814
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

624 files changed

+13184
-814
lines changed

agent/php_newrelic.h

+6
Original file line numberDiff line numberDiff line change
@@ -587,6 +587,12 @@ nrinibool_t
587587
nriniuint_t
588588
log_forwarding_log_level; /* newrelic.application_logging.forwarding.log_level
589589
*/
590+
nrinibool_t
591+
log_forwarding_labels_enabled; /* newrelic.application_logging.forwarding.labels.enabled */
592+
593+
nrinistr_t
594+
log_forwarding_labels_exclude; /* newrelic.application_logging.forwarding.labels.exclude */
595+
590596

591597
/*
592598
* Configuration option to toggle code level metrics collection.

agent/php_nrini.c

+16
Original file line numberDiff line numberDiff line change
@@ -3105,6 +3105,22 @@ STD_PHP_INI_ENTRY_EX("newrelic.application_logging.forwarding.context_data.exclu
31053105
zend_newrelic_globals,
31063106
newrelic_globals,
31073107
0)
3108+
STD_PHP_INI_ENTRY_EX("newrelic.application_logging.forwarding.labels.enabled",
3109+
"0",
3110+
NR_PHP_REQUEST,
3111+
nr_boolean_mh,
3112+
log_forwarding_labels_enabled,
3113+
zend_newrelic_globals,
3114+
newrelic_globals,
3115+
nr_enabled_disabled_dh)
3116+
STD_PHP_INI_ENTRY_EX("newrelic.application_logging.forwarding.labels.exclude",
3117+
"",
3118+
NR_PHP_REQUEST,
3119+
nr_string_mh,
3120+
log_forwarding_labels_exclude,
3121+
zend_newrelic_globals,
3122+
newrelic_globals,
3123+
0)
31083124

31093125
/*
31103126
* Vulnerability Management

agent/php_txn.c

+126-2
Original file line numberDiff line numberDiff line change
@@ -536,6 +536,119 @@ static nrobj_t* nr_php_txn_get_labels() {
536536
return nr_labels_parse(NR_PHP_PROCESS_GLOBALS(env_labels));
537537
}
538538

539+
static nr_status_t nr_php_txn_collect_label_keys_iter(const char* key,
540+
const nrobj_t* value,
541+
void* ptr) {
542+
nrobj_t* user_data = (nrobj_t*)ptr;
543+
544+
(void)value;
545+
546+
if (NULL == key || NULL == user_data) {
547+
return NR_FAILURE;
548+
}
549+
550+
nro_set_array_string(user_data, 0, key);
551+
552+
return NR_SUCCESS;
553+
}
554+
555+
/*
556+
* Purpose : Filter the labels hash to exclude any labels that are in the
557+
* newrelic.application_logging.forwarding.labels.exclude list.
558+
*
559+
* Params : 1. The labels hash to filter.
560+
*
561+
* Returns : A new hash containing the filtered labels.
562+
* If no labels exist or all labels are excluded, then return NULL.
563+
*
564+
*/
565+
566+
nrobj_t* nr_php_txn_get_log_forwarding_labels(nrobj_t* labels) {
567+
nrobj_t* label_keys = NULL;
568+
nrobj_t* exclude_labels_list = NULL;
569+
nrobj_t* exclude_labels_hash = NULL;
570+
nrobj_t* log_labels = NULL;
571+
572+
if (NULL == labels || 0 == nro_getsize(labels)) {
573+
nrl_verbosedebug(NRL_TXN, "%s: No labels defined", __FUNCTION__);
574+
return NULL;
575+
}
576+
577+
/* if logging labels are disabled then nothing to do */
578+
if (0 == NRINI(log_forwarding_labels_enabled)) {
579+
nrl_verbosedebug(NRL_TXN, "%s: Log forwarding labels disabled",
580+
__FUNCTION__);
581+
return NULL;
582+
}
583+
584+
/* split exclude string on commas - nr_strsplit() will trim leading
585+
* and trailing whitespace from each string extracted, as well as
586+
* ignoring empty strings after whitespace trimming
587+
*/
588+
exclude_labels_list
589+
= nr_strsplit(NRINI(log_forwarding_labels_exclude), ",", 0);
590+
591+
/* convert to lowercase to support case insensitive search below
592+
* will store lowercase version in a hash for more convenient lookup
593+
*/
594+
exclude_labels_hash = nro_new(NR_OBJECT_HASH);
595+
for (int i = 0; i < nro_getsize(exclude_labels_list); i++) {
596+
char* label = nr_string_to_lowercase(
597+
nro_get_array_string(exclude_labels_list, i + 1, NULL));
598+
599+
if (!nr_strempty(label)) {
600+
nro_set_hash_boolean(exclude_labels_hash, label, 1);
601+
}
602+
nr_free(label);
603+
}
604+
605+
/* original parsed exclude list is no longer needed */
606+
nro_delete(exclude_labels_list);
607+
608+
/* collect label keys from existing labels */
609+
label_keys = nro_new(NR_OBJECT_ARRAY);
610+
nro_iteratehash(labels, nr_php_txn_collect_label_keys_iter,
611+
(void*)label_keys);
612+
613+
/* filter by going over the list of label keys, seeing if it exists in the
614+
* exclude hash, and if it does skip it otherwise copy key/value for label
615+
* to the log labels
616+
*/
617+
log_labels = nro_new(NR_OBJECT_HASH);
618+
for (int i = 0; i < nro_getsize(label_keys); i++) {
619+
const char* key = NULL;
620+
char* lower_key = NULL;
621+
int exclude = false;
622+
623+
key = nro_get_array_string(label_keys, i + 1, NULL);
624+
if (NULL == key) {
625+
continue;
626+
}
627+
628+
lower_key = nr_string_to_lowercase(key);
629+
630+
if (1 != nro_get_hash_boolean(exclude_labels_hash, lower_key, NULL)) {
631+
nro_set_hash_string(log_labels, key,
632+
nro_get_hash_string(labels, key, NULL));
633+
} else {
634+
nrl_verbosedebug(NRL_TXN, "%s: Excluding label %s", __FUNCTION__,
635+
NRSAFESTR(key));
636+
}
637+
nr_free(lower_key);
638+
}
639+
640+
nro_delete(exclude_labels_hash);
641+
nro_delete(label_keys);
642+
643+
/* return NULL if all labels were excluded */
644+
if (0 == nro_getsize(log_labels)) {
645+
nro_delete(log_labels);
646+
log_labels = NULL;
647+
}
648+
649+
return log_labels;
650+
}
651+
539652
static void nr_php_txn_prepared_statement_destroy(void* sql) {
540653
nr_free(sql);
541654
}
@@ -666,6 +779,11 @@ static void nr_php_txn_send_metrics_once(nrtxn_t* txn TSRMLS_DC) {
666779
nrm_force_add(NRTXN(unscoped_metrics), metname, 0);
667780
nr_free(metname);
668781

782+
metname = nr_formatf("Supportability/Logging/Labels/PHP/%s",
783+
FMT_BOOL(nr_txn_log_forwarding_labels_enabled(txn)));
784+
nrm_force_add(NRTXN(unscoped_metrics), metname, 0);
785+
nr_free(metname);
786+
669787
txn->created_logging_onetime_metrics = true;
670788

671789
#undef FMT_BOOL
@@ -762,6 +880,7 @@ nr_status_t nr_php_txn_begin(const char* appnames,
762880
nrtxnopt_t opts;
763881
const char* lic_to_use;
764882
int pfd;
883+
nrobj_t* log_forwarding_labels = NULL;
765884
nr_attribute_config_t* attribute_config;
766885
nr_app_info_t info;
767886
bool is_cli = (0 != NR_PHP_PROCESS_GLOBALS(cli));
@@ -854,6 +973,7 @@ nr_status_t nr_php_txn_begin(const char* appnames,
854973
opts.log_forwarding_log_level = NRINI(log_forwarding_log_level);
855974
opts.log_events_max_samples_stored = NRINI(log_events_max_samples_stored);
856975
opts.log_metrics_enabled = NRINI(log_metrics_enabled);
976+
opts.log_forwarding_labels_enabled = NRINI(log_forwarding_labels_enabled);
857977
opts.message_tracer_segment_parameters_enabled
858978
= NRINI(message_tracer_segment_parameters_enabled);
859979

@@ -917,17 +1037,21 @@ nr_status_t nr_php_txn_begin(const char* appnames,
9171037
&nr_php_app_settings, NR_PHP_PROCESS_GLOBALS(daemon_app_connect_timeout));
9181038
nr_app_info_destroy_fields(&info);
9191039

920-
if (0 == NRPRG(app)) {
1040+
if (NULL == NRPRG(app)) {
9211041
nrl_debug(NRL_INIT, "unable to begin transaction: app '%.128s' is unknown",
9221042
appnames ? appnames : "");
9231043
return NR_FAILURE;
9241044
}
9251045

9261046
attribute_config = nr_php_create_attribute_config(TSRMLS_C);
927-
NRPRG(txn) = nr_txn_begin(NRPRG(app), &opts, attribute_config);
1047+
log_forwarding_labels
1048+
= nr_php_txn_get_log_forwarding_labels(NRPRG(app)->info.labels);
1049+
NRPRG(txn) = nr_txn_begin(NRPRG(app), &opts, attribute_config,
1050+
log_forwarding_labels);
9281051
nrt_mutex_unlock(&(NRPRG(app)->app_lock));
9291052

9301053
nr_attribute_config_destroy(&attribute_config);
1054+
nro_delete(log_forwarding_labels);
9311055

9321056
if (0 == NRPRG(txn)) {
9331057
nrl_debug(NRL_INIT, "no Axiom transaction this time around");

agent/php_txn_private.h

+13
Original file line numberDiff line numberDiff line change
@@ -86,3 +86,16 @@ extern void nr_php_txn_php_package_create_major_metric(void* value,
8686
* Params : 1. The current transaction.
8787
*/
8888
extern void nr_php_txn_create_packages_major_metrics(nrtxn_t* txn);
89+
90+
/*
91+
* Purpose : Filter the labels hash to exclude any labels that are in the
92+
* newrelic.application_logging.forwarding.labels.exclude list.
93+
*
94+
* Params : 1. The labels hash to filter.
95+
*
96+
* Returns : A new hash containing the filtered labels.
97+
* If no labels exist or all labels are excluded, then return NULL.
98+
*
99+
*/
100+
101+
extern nrobj_t* nr_php_txn_get_log_forwarding_labels(nrobj_t* labels);

agent/scripts/newrelic.ini.template

+28-1
Original file line numberDiff line numberDiff line change
@@ -1242,7 +1242,6 @@ newrelic.daemon.logfile = "/var/log/newrelic/newrelic-daemon.log"
12421242
;
12431243
;newrelic.application_logging.forwarding.log_level = "WARNING"
12441244

1245-
12461245
; Setting: newrelic.application_logging.local_decorating.enabled
12471246
; Type : boolean
12481247
; Scope : per-directory
@@ -1305,6 +1304,34 @@ newrelic.daemon.logfile = "/var/log/newrelic/newrelic-daemon.log"
13051304
;newrelic.application_logging.forwarding.context_data.include = ""
13061305
;newrelic.application_logging.forwarding.context_data.exclude = ""
13071306

1307+
; Setting: newrelic.application_logging.forwarding.labels.enabled
1308+
; Type : boolean
1309+
; Scope : per-directory
1310+
; Default: false
1311+
; Info : Toggles whether the agent adds labels as attributes to log records which
1312+
; are sent to New Relic. The labels can be defined by the newrelic.labels
1313+
; configuration value, and filtered using the
1314+
; newrelic.application_logging.forwarding.labels.exclude configuration value.
1315+
;
1316+
;newrelic.application_logging.forwarding.labels.enabled = false
1317+
1318+
; Setting: newrelic.application_logging.forwarding.labels.exclude
1319+
; Type : string
1320+
; Scope : per-directory
1321+
; Default: ""
1322+
; Info : A list of labels to NOT add as attributes to logs which are forwarded
1323+
; to New Relic. The values in the list must be separated by commas.
1324+
;
1325+
; NOTE: The values in this list are compared to label key names in a case
1326+
; insensitive fashion. So if an exclude rule for "server" is specified
1327+
; here then it would exclude keys like "server", "Server", "SERVER", and
1328+
; any other combination of lower and upper case letters that spell out "server".
1329+
;
1330+
; Ex:
1331+
; newrelic.application_logging.forwarding.labels.exclude = "label1, label2"
1332+
;
1333+
;newrelic.application_logging.forwarding.labels.exclude = ""
1334+
13081335
; Setting: newrelic.code_level_metrics.enabled
13091336
; Type : boolean
13101337
; Scope : per-directory

agent/tests/test_txn.c

+67-2
Original file line numberDiff line numberDiff line change
@@ -288,6 +288,69 @@ static void test_create_agent_php_version_metrics() {
288288
#undef PHP_VERSION_METRIC_BASE
289289
#undef AGENT_VERSION_METRIC_BASE
290290

291+
static void test_create_log_forwarding_labels(TSRMLS_D) {
292+
nrobj_t* labels = NULL;
293+
nrobj_t* log_labels = NULL;
294+
char* json = NULL;
295+
296+
/* Test : Create log forwarding labels with valid key/value pairs */
297+
labels = nro_new_hash();
298+
nro_set_hash_string(labels, "key1", "value1");
299+
nro_set_hash_string(labels, "key2", "value2");
300+
nro_set_hash_string(labels, "key3", "value3");
301+
302+
log_labels = nr_php_txn_get_log_forwarding_labels(labels);
303+
304+
json = nro_to_json(labels);
305+
tlib_pass_if_str_equal(
306+
"valid log label creation test",
307+
"{\"key1\":\"value1\",\"key2\":\"value2\",\"key3\":\"value3\"}", json);
308+
309+
nr_free(json);
310+
nro_delete(labels);
311+
nro_delete(log_labels);
312+
313+
/* Test : Create log forwarding labels with empty key/value pairs */
314+
labels = nro_new_hash();
315+
nro_set_hash_string(labels, "", "");
316+
nro_set_hash_string(labels, "key", "");
317+
nro_set_hash_string(labels, "", "value");
318+
319+
log_labels = nr_php_txn_get_log_forwarding_labels(labels);
320+
321+
json = nro_to_json(labels);
322+
tlib_pass_if_str_equal("empty string log label creation test",
323+
"{\"key\":\"\"}", json);
324+
325+
nr_free(json);
326+
nro_delete(labels);
327+
nro_delete(log_labels);
328+
329+
/* Test : Create log forwarding labels with NULL key/value pairs */
330+
labels = nro_new_hash();
331+
nro_set_hash_string(labels, NULL, NULL);
332+
nro_set_hash_string(labels, "key", NULL);
333+
nro_set_hash_string(labels, NULL, "value");
334+
335+
log_labels = nr_php_txn_get_log_forwarding_labels(labels);
336+
337+
json = nro_to_json(labels);
338+
tlib_pass_if_str_equal("NULL value log label creation test", "{\"key\":\"\"}",
339+
json);
340+
341+
nr_free(json);
342+
nro_delete(labels);
343+
nro_delete(log_labels);
344+
345+
/* Test : Create log forwarding labels NULL labels object */
346+
log_labels = nr_php_txn_get_log_forwarding_labels(NULL);
347+
json = nro_to_json(labels);
348+
tlib_pass_if_str_equal("NULL object log label creation test", "null", json);
349+
350+
nr_free(json);
351+
nro_delete(log_labels);
352+
}
353+
291354
tlib_parallel_info_t parallel_info = {.suggested_nthreads = 1, .state_size = 0};
292355

293356
void test_main(void* p NRUNUSED) {
@@ -299,14 +362,16 @@ void test_main(void* p NRUNUSED) {
299362
* We're setting up our own engine instance because we need to control the
300363
* attribute configuration.
301364
*/
302-
tlib_php_engine_create(
303-
"newrelic.transaction_events.attributes.include=request.uri" PTSRMLS_CC);
365+
// clang-format off
366+
tlib_php_engine_create("newrelic.transaction_events.attributes.include=request.uri" PTSRMLS_CC);
367+
// clang-format on
304368

305369
test_handle_fpm_error();
306370
test_max_segments_config_values();
307371
test_create_php_version_metric();
308372
test_create_agent_version_metric();
309373
test_create_agent_php_version_metrics();
374+
test_create_log_forwarding_labels();
310375

311376
tlib_php_engine_destroy();
312377
}

0 commit comments

Comments
 (0)