Skip to content

Commit 693e80c

Browse files
mfulbbduranleau-nrzsistlalavarouhahuja2
authored
feat: Implements APM Log Context Attributes (newrelic#758)
This PR adds the log context attribute feature for APM Log Forwarding. --------- Co-authored-by: bduranleau-nr <[email protected]> Co-authored-by: Amber Sistla <[email protected]> Co-authored-by: Michal Nowacki <[email protected]> Co-authored-by: Hitesh Ahuja <[email protected]>
1 parent 51759a5 commit 693e80c

File tree

82 files changed

+7959
-199
lines changed

Some content is hidden

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

82 files changed

+7959
-199
lines changed

agent/Makefile.frag

+1
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@ TEST_BINARIES = \
8989
tests/test_internal_instrument \
9090
tests/test_hash \
9191
tests/test_mongodb \
92+
tests/test_monolog \
9293
tests/test_mysql \
9394
tests/test_mysqli \
9495
tests/test_output \

agent/lib_monolog.c

+105-139
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,12 @@
1212
#include "php_wrapper.h"
1313
#include "fw_hooks.h"
1414
#include "fw_support.h"
15+
#include "lib_monolog_private.h"
1516
#include "nr_datastore_instance.h"
1617
#include "nr_segment_datastore.h"
18+
#include "nr_txn.h"
1719
#include "util_logging.h"
20+
#include "util_object.h"
1821
#include "util_memory.h"
1922
#include "util_strings.h"
2023
#include "util_sleep.h"
@@ -27,19 +30,6 @@
2730
#define LOG_DECORATE_PROC_FUNC_NAME \
2831
"newrelic_phpagent_monolog_decorating_processor"
2932

30-
// clang-format off
31-
/*
32-
* This macro affects how instrumentation $context argument of
33-
* Monolog\Logger::addRecord works:
34-
*
35-
* 0 - $context argument will not be instrumented: its existance and value
36-
* are ignored
37-
* 1 - the message of the log record forwarded by the agent will have the value
38-
* of $context appended to the value of $message.
39-
*/
40-
// clang-format on
41-
#define HAVE_CONTEXT_IN_MESSAGE 0
42-
4333
/*
4434
* Purpose : Convert Monolog\Logger::API to integer
4535
*
@@ -155,123 +145,78 @@ static char* nr_monolog_get_message(NR_EXECUTE_PROTO TSRMLS_DC) {
155145
return message;
156146
}
157147

158-
#if HAVE_CONTEXT_IN_MESSAGE
159148
/*
160-
* Purpose : Format key of $context array's element as string
149+
* Purpose : Convert a zval value from context data to a nrobj_t
161150
*
162-
* Params : zend_hash_key
163-
* *
164-
* Returns : A new string representing zval; caller must free
165-
*
166-
*/
167-
static char* nr_monolog_fmt_context_key(const zend_hash_key* hash_key) {
168-
char* key_str = NULL;
169-
zval* key = nr_php_zval_alloc();
170-
if (nr_php_zend_hash_key_is_string(hash_key)) {
171-
nr_php_zval_str(key, nr_php_zend_hash_key_string_value(hash_key));
172-
key_str = nr_formatf("%s", Z_STRVAL_P(key));
173-
} else if (nr_php_zend_hash_key_is_numeric(hash_key)) {
174-
ZVAL_LONG(key, (zend_long)nr_php_zend_hash_key_integer(hash_key));
175-
key_str = nr_formatf("%ld", (long)Z_LVAL_P(key));
176-
} else {
177-
/*
178-
* This is a warning because this really, really shouldn't ever happen.
179-
*/
180-
nrl_warning(NRL_INSTRUMENT, "%s: unexpected key type", __func__);
181-
key_str = nr_formatf("unsupported-key-type");
182-
}
183-
nr_php_zval_free(&key);
184-
return key_str;
185-
}
186-
187-
/*
188-
* Purpose : Format value of $context array's element as string
151+
* Params : zval
189152
*
190-
* Params : zval value
191-
* *
192-
* Returns : A new string representing zval; caller must free
153+
* Returns : nrobj_t* holding converted value
154+
* NULL otherwise
193155
*
156+
* Notes : Only scalar and string types are supported.
157+
* Nested arrays are not converted and are ignored.
158+
* Other zval types are also ignored.
194159
*/
195-
static char* nr_monolog_fmt_context_value(zval* zv) {
196-
char* val_str = NULL;
197-
zval* zv_str = NULL;
160+
nrobj_t* nr_monolog_context_data_zval_to_attribute_obj(
161+
const zval* z TSRMLS_DC) {
162+
nrobj_t* retobj = NULL;
198163

199-
if (NULL == zv) {
200-
return nr_strdup("");
164+
if (NULL == z) {
165+
return NULL;
201166
}
202167

203-
zv_str = nr_php_zval_alloc();
204-
if (NULL == zv_str) {
205-
return nr_strdup("");
206-
}
168+
nr_php_zval_unwrap(z);
207169

208-
ZVAL_DUP(zv_str, zv);
209-
convert_to_string(zv_str);
210-
val_str = nr_strdup(Z_STRVAL_P(zv_str));
211-
nr_php_zval_free(&zv_str);
170+
switch (Z_TYPE_P(z)) {
171+
case IS_NULL:
172+
retobj = NULL;
173+
break;
212174

213-
return val_str;
214-
}
175+
case IS_LONG:
176+
retobj = nro_new_long((long)Z_LVAL_P(z));
177+
break;
215178

216-
/*
217-
* Purpose : Format an element of $context array as "key => value" string
218-
*
219-
* Params : zval value, pointer to string buffer to store formatted output
220-
* and hash key
221-
*
222-
* Side effect : string buffer is reallocated with each call.
223-
*
224-
* Returns : ZEND_HASH_APPLY_KEEP to keep iteration
225-
*
226-
*/
227-
static int nr_monolog_fmt_context_item(zval* value,
228-
char** strbuf,
229-
zend_hash_key* hash_key TSRMLS_DC) {
230-
NR_UNUSED_TSRMLS;
231-
char* key = nr_monolog_fmt_context_key(hash_key);
232-
char* val = nr_monolog_fmt_context_value(value);
233-
234-
char* kv_str = nr_formatf("%s => %s", key, val);
235-
nr_free(key);
236-
nr_free(val);
237-
238-
char* sep = nr_strlen(*strbuf) > 1 ? ", " : "";
239-
*strbuf = nr_str_append(*strbuf, kv_str, sep);
240-
nr_free(kv_str);
241-
242-
return ZEND_HASH_APPLY_KEEP;
243-
}
179+
case IS_DOUBLE:
180+
retobj = nro_new_double(Z_DVAL_P(z));
181+
break;
244182

245-
/*
246-
* Purpose : Iterate over $context array and format each element
247-
*
248-
* Params : string buffer to store formatted output and
249-
* Monolog\Logger::addRecord argument list
250-
*
251-
* Returns : A new string with Monolog's log context
252-
*/
253-
static char* nr_monolog_fmt_context(char* strbuf,
254-
HashTable* context TSRMLS_DC) {
255-
strbuf = nr_str_append(strbuf, "[", "");
183+
case IS_TRUE:
184+
retobj = nro_new_boolean(true);
185+
break;
186+
187+
case IS_FALSE:
188+
retobj = nro_new_boolean(false);
189+
break;
256190

257-
nr_php_zend_hash_zval_apply(context,
258-
(nr_php_zval_apply_t)nr_monolog_fmt_context_item,
259-
(void*)&strbuf TSRMLS_CC);
191+
case IS_STRING:
192+
if (!nr_php_is_zval_valid_string(z)) {
193+
retobj = NULL;
194+
} else {
195+
retobj = nro_new_string(Z_STRVAL_P(z));
196+
}
197+
break;
198+
199+
default:
200+
/* any other type conversion to attribute not supported */
201+
retobj = NULL;
202+
break;
203+
}
260204

261-
return nr_str_append(strbuf, "]", "");
205+
return retobj;
262206
}
263207

264208
/*
265-
* Purpose : Convert $context argument of Monolog\Logger::addRecord to a string
209+
* Purpose : Get $context argument of Monolog\Logger::addRecord as `zval *`.
266210
*
267211
* Params : # of Monolog\Logger::addRecord arguments, and
268212
* Monolog\Logger::addRecord argument list
269213
*
270-
* Returns : A new string with Monolog's log context
214+
* Returns : zval* for context array on success (must be freed by caller)
215+
* NULL otherwise
216+
*
271217
*/
272-
static char* nr_monolog_get_context(const size_t argc,
273-
NR_EXECUTE_PROTO TSRMLS_DC) {
274-
char* context = nr_strdup("");
218+
static zval* nr_monolog_extract_context_data(const size_t argc,
219+
NR_EXECUTE_PROTO TSRMLS_DC) {
275220
zval* context_arg = NULL;
276221

277222
if (3 > argc) {
@@ -298,45 +243,57 @@ static char* nr_monolog_get_context(const size_t argc,
298243
goto return_context;
299244
}
300245

301-
context = nr_monolog_fmt_context(context, Z_ARRVAL_P(context_arg) TSRMLS_CC);
302-
303246
return_context:
304-
nr_php_arg_release(&context_arg);
305-
return context;
247+
return context_arg;
306248
}
307-
#endif
308249

309250
/*
310-
* Purpose : Combine $message and $context arguments of
311-
* Monolog\Logger::addRecord into a single string to be used as a message
312-
* property of the log event.
251+
* Purpose : Convert $context array of Monolog\Logger::addRecord to
252+
* attributes
313253
*
314-
* Params : # of Monolog\Logger::addRecord arguments, and
315-
* Monolog\Logger::addRecord argument list
254+
* Params : zval* for context array from Monolog
255+
*
256+
* Returns : nr_attributes representation of $context on success
257+
* NULL otherwise
316258
*
317-
* Returns : A new string with a log record message; caller must free
318259
*/
319-
static char* nr_monolog_build_message(const size_t argc,
320-
NR_EXECUTE_PROTO TSRMLS_DC) {
321-
#if !HAVE_CONTEXT_IN_MESSAGE
322-
/* Make the compiler happy - argc is not used when $context is ignored */
323-
(void)argc;
324-
#endif
325-
char* message_and_context = nr_strdup("");
326-
327-
char* message = nr_monolog_get_message(NR_EXECUTE_ORIG_ARGS TSRMLS_CC);
328-
message_and_context = nr_str_append(message_and_context, message, "");
329-
nr_free(message);
260+
nr_attributes_t* nr_monolog_convert_context_data_to_attributes(
261+
zval* context_data TSRMLS_DC) {
262+
zend_string* key;
263+
zval* val;
330264

331-
#if HAVE_CONTEXT_IN_MESSAGE
332-
char* context = nr_monolog_get_context(argc, NR_EXECUTE_ORIG_ARGS TSRMLS_CC);
333-
if (!nr_strempty(context)) {
334-
message_and_context = nr_str_append(message_and_context, context, " ");
265+
nr_attributes_t* attributes = NULL;
266+
267+
if (NULL == context_data || !nr_php_is_zval_valid_array(context_data)) {
268+
return NULL;
269+
}
270+
271+
attributes = nr_attributes_create(NRPRG(txn)->attribute_config);
272+
if (NULL == attributes) {
273+
return NULL;
274+
}
275+
276+
ZEND_HASH_FOREACH_STR_KEY_VAL(Z_ARR_P(context_data), key, val) {
277+
if (NULL == key) {
278+
continue;
279+
}
280+
281+
nrobj_t* obj = nr_monolog_context_data_zval_to_attribute_obj(val);
282+
283+
if (NULL != obj) {
284+
nr_attributes_user_add(attributes, NR_ATTRIBUTE_DESTINATION_LOG,
285+
ZSTR_VAL(key), obj);
286+
nro_delete(obj);
287+
} else {
288+
nrl_verbosedebug(NRL_INSTRUMENT,
289+
"%s: log context attribute '%s' dropped due to value "
290+
"being of unsupported type %d",
291+
__func__, ZSTR_VAL(key), Z_TYPE_P(val));
292+
}
335293
}
336-
nr_free(context);
337-
#endif
294+
ZEND_HASH_FOREACH_END();
338295

339-
return message_and_context;
296+
return attributes;
340297
}
341298

342299
/*
@@ -397,21 +354,30 @@ NR_PHP_WRAPPER(nr_monolog_logger_addrecord) {
397354
int api = 0;
398355
size_t argc = 0;
399356
char* message = NULL;
357+
nr_attributes_t* context_attributes = NULL;
400358
nrtime_t timestamp = nr_get_time();
401359

402360
/* Values of $message and $timestamp arguments are needed only if log
403361
* forwarding is enabled so agent will get them conditionally */
404362
if (nr_txn_log_forwarding_enabled(NRPRG(txn))) {
405363
argc = nr_php_get_user_func_arg_count(NR_EXECUTE_ORIG_ARGS TSRMLS_CC);
406-
message = nr_monolog_build_message(argc, NR_EXECUTE_ORIG_ARGS TSRMLS_CC);
364+
message = nr_monolog_get_message(NR_EXECUTE_ORIG_ARGS TSRMLS_CC);
365+
366+
if (nr_txn_log_forwarding_context_data_enabled(NRPRG(txn))) {
367+
zval* context_data = nr_monolog_extract_context_data(
368+
argc, NR_EXECUTE_ORIG_ARGS TSRMLS_CC);
369+
context_attributes
370+
= nr_monolog_convert_context_data_to_attributes(context_data);
371+
nr_php_arg_release(&context_data);
372+
}
407373
api = nr_monolog_version(this_var TSRMLS_CC);
408374
timestamp
409375
= nr_monolog_get_timestamp(api, argc, NR_EXECUTE_ORIG_ARGS TSRMLS_CC);
410376
}
411377

412378
/* Record the log event */
413379
nr_txn_record_log_event(NRPRG(txn), level_name, message, timestamp,
414-
NRPRG(app));
380+
context_attributes, NRPRG(app));
415381

416382
nr_free(level_name);
417383
nr_free(message);

agent/lib_monolog_private.h

+29
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
/*
2+
* Copyright 2020 New Relic Corporation. All rights reserved.
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
#ifndef LIB_MONOLOG_PRIVATE_HDR
7+
#define LIB_MONOLOG_PRIVATE_HDR
8+
9+
/*
10+
* Purpose : ONLY for testing to verify that the appropriate behavior of
11+
* the conversion of zvals to attribute via nro.
12+
*
13+
* Returns : Pointer to nr_object_t representation of zval or
14+
* NULL if zval is not a supported type for conversion
15+
* to an attribute
16+
*/
17+
extern nrobj_t* nr_monolog_context_data_zval_to_attribute_obj(
18+
const zval* z TSRMLS_DC);
19+
20+
/*
21+
* Purpose : ONLY for testing to verify that the appropriate behavior of
22+
* the conversion of a Monolog context array to attributes.
23+
*
24+
* Returns : Caller takes ownership of attributes struct
25+
*
26+
*/
27+
extern nr_attributes_t* nr_monolog_convert_context_data_to_attributes(
28+
zval* context_data TSRMLS_DC);
29+
#endif /* LIB_MONOLOG_PRIVATE_HDR */

agent/php_newrelic.h

+3
Original file line numberDiff line numberDiff line change
@@ -308,6 +308,9 @@ nr_php_ini_attribute_config_t
308308
nr_php_ini_attribute_config_t
309309
browser_monitoring_attributes; /* newrelic.browser_monitoring.attributes.*
310310
*/
311+
nr_php_ini_attribute_config_t
312+
log_context_data_attributes; /* newrelic.application_logging.forwarding.context_data.*
313+
*/
311314

312315
nrinibool_t custom_events_enabled; /* newrelic.custom_insights_events.enabled */
313316
nriniuint_t custom_events_max_samples_stored; /* newrelic.custom_events.max_samples_stored

0 commit comments

Comments
 (0)