Skip to content

Commit b928df9

Browse files
[loader] Assign an appropriate tag to each type of error (#2985)
* Assign an appropriate tag to each type of error * Update loader/dd_library_loader.c Co-authored-by: Florian Engelhardt <[email protected]> --------- Co-authored-by: Florian Engelhardt <[email protected]>
1 parent b0da77f commit b928df9

File tree

4 files changed

+126
-27
lines changed

4 files changed

+126
-27
lines changed

loader/dd_library_loader.c

Lines changed: 47 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ static bool already_done = false;
3232
# define OS_PATH "linux-gnu/"
3333
#endif
3434

35-
static void ddloader_telemetryf(telemetry_reason reason, const char *format, ...);
35+
static void ddloader_telemetryf(telemetry_reason reason, const char *error, const char *format, ...);
3636

3737
static char *ddtrace_pre_load_hook(void) {
3838
char *libddtrace_php;
@@ -218,7 +218,11 @@ void ddloader_logf(log_level level, const char *format, ...) {
218218
va_end(va);
219219
}
220220

221-
static void ddloader_telemetryf(telemetry_reason reason, const char *format, ...) {
221+
/**
222+
* @param error The c-string this is pointing to must not exceed 150 bytes
223+
*/
224+
static void ddloader_telemetryf(telemetry_reason reason, const char *error, const char *format, ...) {
225+
log_level level = ERROR;
222226
switch (reason) {
223227
case REASON_ERROR:
224228
LOG(ERROR, "Error during instrumentation of application. Aborting.");
@@ -229,13 +233,17 @@ static void ddloader_telemetryf(telemetry_reason reason, const char *format, ...
229233
case REASON_INCOMPATIBLE_RUNTIME:
230234
LOG(ERROR, "Aborting application instrumentation due to an incompatible runtime");
231235
break;
236+
case REASON_COMPLETE:
237+
case REASON_ALREADY_LOADED:
238+
level = INFO;
239+
break;
232240
default:
233241
break;
234242
}
235243

236244
va_list va;
237245
va_start(va, format);
238-
ddloader_logv(reason == REASON_COMPLETE ? INFO : ERROR, format, va);
246+
ddloader_logv(level, format, va);
239247
va_end(va);
240248

241249
if (!telemetry_forwarder_path) {
@@ -257,13 +265,15 @@ static void ddloader_telemetryf(telemetry_reason reason, const char *format, ...
257265
return; // parent
258266
}
259267

260-
char *points = "";
268+
char points_buf[256] = {0};
269+
char *points = points_buf;
261270
switch (reason) {
262271
case REASON_ERROR:
263-
points =
264-
"\
265-
{\"name\": \"library_entrypoint.error\"}, \"tags\": [\"error_type:NA\"]}\
266-
";
272+
snprintf(points_buf, sizeof(points_buf), "\
273+
{\"name\": \"library_entrypoint.error\", \"tags\": [\"error_type:%s\"]}\
274+
",
275+
error ? error : "NA"
276+
);
267277
break;
268278

269279
case REASON_EOL_RUNTIME:
@@ -282,6 +292,13 @@ static void ddloader_telemetryf(telemetry_reason reason, const char *format, ...
282292
";
283293
break;
284294

295+
case REASON_ALREADY_LOADED:
296+
points =
297+
"\
298+
{\"name\": \"library_entrypoint.abort\", \"tags\": [\"reason:already_loaded\"]}\
299+
";
300+
break;
301+
285302
case REASON_COMPLETE:
286303
if (injection_forced) {
287304
points =
@@ -417,13 +434,13 @@ static PHP_MINIT_FUNCTION(ddloader_injected_extension_minit) {
417434
}
418435
}
419436
if (!config) {
420-
TELEMETRY(REASON_ERROR, "Unable to find the configuration for the injected extension. Something went wrong");
437+
TELEMETRY(REASON_ERROR, "ext_not_found", "Unable to find the configuration for the injected extension. Something went wrong");
421438
return SUCCESS;
422439
}
423440

424441
zend_module_entry *module = ddloader_zend_hash_str_find_ptr(php_api_no, &module_registry, config->ext_name, strlen(config->ext_name));
425442
if (module) {
426-
LOG(INFO, "Extension '%s' is already loaded, unregister the injected extension", config->ext_name);
443+
TELEMETRY(REASON_ALREADY_LOADED, NULL, "Extension '%s' is already loaded, unregister the injected extension", config->ext_name);
427444
ddloader_unregister_module(config->tmp_name);
428445

429446
return SUCCESS;
@@ -433,7 +450,7 @@ static PHP_MINIT_FUNCTION(ddloader_injected_extension_minit) {
433450

434451
// Normally done by zend_startup_module_ex, but we temporarily replaced these to skip potential errors. Check it ourselves here.
435452
if (!ddloader_check_deps(config->orig_module_deps)) {
436-
TELEMETRY(REASON_INCOMPATIBLE_RUNTIME, "Extension '%s' dependencies are not met, unregister the injected extension", config->ext_name);
453+
TELEMETRY(REASON_INCOMPATIBLE_RUNTIME, NULL, "Extension '%s' dependencies are not met, unregister the injected extension", config->ext_name);
437454
ddloader_unregister_module(config->tmp_name);
438455

439456
return SUCCESS;
@@ -455,7 +472,7 @@ static PHP_MINIT_FUNCTION(ddloader_injected_extension_minit) {
455472

456473
module = ddloader_zend_hash_str_find_ptr(php_api_no, &module_registry, config->ext_name, strlen(config->ext_name));
457474
if (!module) {
458-
TELEMETRY(REASON_ERROR, "Extension '%s' not found after renaming. Something wrong happened", config->ext_name);
475+
TELEMETRY(REASON_ERROR, "renamed_ext_not_found", "Extension '%s' not found after renaming. Something wrong happened", config->ext_name);
459476
return SUCCESS;
460477
}
461478

@@ -465,7 +482,7 @@ static PHP_MINIT_FUNCTION(ddloader_injected_extension_minit) {
465482
module->deps = config->orig_module_deps;
466483
module->functions = config->orig_module_functions;
467484
if (module->functions && zend_register_functions(NULL, module->functions, NULL, module->type) == FAILURE) {
468-
TELEMETRY(REASON_ERROR, "Unable to register extension's functions");
485+
TELEMETRY(REASON_ERROR, "cannot_register_functions", "Unable to register extension's functions");
469486
return SUCCESS;
470487
}
471488

@@ -475,9 +492,9 @@ static PHP_MINIT_FUNCTION(ddloader_injected_extension_minit) {
475492

476493
zend_result ret = module->module_startup_func(INIT_FUNC_ARGS_PASSTHRU);
477494
if (ret == FAILURE) {
478-
TELEMETRY(REASON_ERROR, "'%s' MINIT function failed", config->ext_name);
495+
TELEMETRY(REASON_ERROR, "error_minit", "'%s' MINIT function failed", config->ext_name);
479496
} else {
480-
TELEMETRY(REASON_COMPLETE, "Application instrumentation bootstrapping complete ('%s')", config->ext_name)
497+
TELEMETRY(REASON_COMPLETE, NULL, "Application instrumentation bootstrapping complete ('%s')", config->ext_name)
481498
}
482499

483500
return ret;
@@ -486,7 +503,12 @@ static PHP_MINIT_FUNCTION(ddloader_injected_extension_minit) {
486503
static int ddloader_load_extension(unsigned int php_api_no, char *module_build_id, bool is_zts, bool is_debug, injected_ext *config) {
487504
char *ext_path = ddloader_find_ext_path(config->ext_dir, config->ext_name, php_api_no, is_zts, is_debug);
488505
if (!ext_path) {
489-
TELEMETRY(REASON_INCOMPATIBLE_RUNTIME, "'%s' extension file not found", config->ext_name);
506+
if (is_debug) {
507+
TELEMETRY(REASON_INCOMPATIBLE_RUNTIME, NULL, "'%s' extension file not found (debug build)", config->ext_name);
508+
} else {
509+
TELEMETRY(REASON_ERROR, "so_not_found", "'%s' extension file not found", config->ext_name);
510+
}
511+
490512
return SUCCESS;
491513
}
492514

@@ -499,32 +521,32 @@ static int ddloader_load_extension(unsigned int php_api_no, char *module_build_i
499521
LOG(INFO, "Running '%s' pre-load hook", config->ext_name);
500522
char *err = config->pre_load_hook();
501523
if (err) {
502-
TELEMETRY(REASON_ERROR, "An error occurred while running '%s' pre-load hook: %s", config->ext_name, err);
524+
TELEMETRY(REASON_ERROR, "error_ext_pre_load", "An error occurred while running '%s' pre-load hook: %s", config->ext_name, err);
503525
goto abort;
504526
}
505527
}
506528

507529
void *handle = DL_LOAD(ext_path);
508530
if (!handle) {
509-
TELEMETRY(REASON_ERROR, "Cannot load '%s' extension file: %s", config->ext_name, dlerror());
531+
TELEMETRY(REASON_ERROR, "cannot_load_file", "Cannot load '%s' extension file: %s", config->ext_name, dlerror());
510532
goto abort;
511533
}
512534

513535
zend_module_entry *(*get_module)(void) = (zend_module_entry * (*)(void)) ddloader_dl_fetch_symbol(handle, "_get_module");
514536
if (!get_module) {
515-
TELEMETRY(REASON_ERROR, "Cannot fetch '%s' module entry", config->ext_name);
537+
TELEMETRY(REASON_ERROR, "cannot_fetch_mod_entry", "Cannot fetch '%s' module entry", config->ext_name);
516538
goto abort_and_unload;
517539
}
518540

519541
zend_module_entry *module_entry = get_module();
520542

521543
if (module_entry->zend_api != php_api_no) {
522-
TELEMETRY(REASON_ERROR, "'%s' API number mismatch between module (%d) and runtime (%d)", config->ext_name, module_entry->zend_api,
544+
TELEMETRY(REASON_ERROR, "api_mismatch", "'%s' API number mismatch between module (%d) and runtime (%d)", config->ext_name, module_entry->zend_api,
523545
php_api_no);
524546
goto abort_and_unload;
525547
}
526548
if (strcmp(module_entry->build_id, module_build_id)) {
527-
TELEMETRY(REASON_ERROR, "'%s' Build ID mismatch between module (%s) and runtime (%s)", config->ext_name, module_entry->build_id,
549+
TELEMETRY(REASON_ERROR, "build_id_mismatch", "'%s' Build ID mismatch between module (%s) and runtime (%s)", config->ext_name, module_entry->build_id,
528550
module_build_id);
529551
goto abort_and_unload;
530552
}
@@ -554,7 +576,7 @@ static int ddloader_load_extension(unsigned int php_api_no, char *module_build_i
554576
ddloader_restore_zend_error_cb();
555577

556578
if (module_entry == NULL) {
557-
TELEMETRY(REASON_ERROR, "Cannot register '%s' module", config->ext_name);
579+
TELEMETRY(REASON_ERROR, "cannot_register_ext", "Cannot register '%s' module", config->ext_name);
558580
goto abort_and_unload;
559581
}
560582

@@ -660,7 +682,7 @@ static int ddloader_api_no_check(int api_no) {
660682
}
661683

662684
if (!package_path) {
663-
TELEMETRY(REASON_ERROR, "DD_LOADER_PACKAGE_PATH environment variable is not set");
685+
TELEMETRY(REASON_ERROR, "path_env_var_not_set", "DD_LOADER_PACKAGE_PATH environment variable is not set");
664686
return SUCCESS;
665687
}
666688

@@ -680,7 +702,7 @@ static int ddloader_api_no_check(int api_no) {
680702
default:
681703
if (!force_load || api_no < 320151012) {
682704
telemetry_reason reason = api_no < 320151012 ? REASON_EOL_RUNTIME : REASON_INCOMPATIBLE_RUNTIME;
683-
TELEMETRY(reason, "Found incompatible runtime (api no: %d). Supported runtimes: PHP 7.0 to 8.3", api_no);
705+
TELEMETRY(reason, NULL, "Found incompatible runtime (api no: %d). Supported runtimes: PHP 7.0 to 8.3", api_no);
684706

685707
// If we return FAILURE, this Zend extension would be unload, BUT it would produce an error
686708
// similar to "The Zend Engine API version 220100525 which is installed, is newer."
@@ -720,7 +742,7 @@ static int ddloader_build_id_check(const char *build_id) {
720742

721743
// Guardrail
722744
if (*module_build_id == '\0') {
723-
TELEMETRY(REASON_ERROR, "Invalid build id");
745+
TELEMETRY(REASON_ERROR, "invalid_build_id", "Invalid build id");
724746
return SUCCESS;
725747
}
726748

loader/php_dd_library_loader.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,11 @@ typedef enum {
2424
REASON_ERROR,
2525
REASON_EOL_RUNTIME,
2626
REASON_INCOMPATIBLE_RUNTIME,
27+
REASON_ALREADY_LOADED,
2728
REASON_COMPLETE,
2829
} telemetry_reason;
2930

30-
#define TELEMETRY(reason, format, ...) ddloader_telemetryf(reason, format, ##__VA_ARGS__);
31+
#define TELEMETRY(reason, error, format, ...) ddloader_telemetryf(reason, error, format, ##__VA_ARGS__);
3132

3233
#define DECLARE_INJECTED_EXT(name, dir, _pre_load_hook, _pre_minit_hook, deps) \
3334
{ \

loader/tests/functional/test_ddtrace_already_loaded.php

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,43 @@
1212
$tmp = tempnam(sys_get_temp_dir(), 'test_loader_');
1313
copy($ext, $tmp);
1414

15-
$output = runCLI('-dextension='.$tmp.' -v', true, ['DD_TRACE_DEBUG=1']);
15+
$telemetryLogPath = tempnam(sys_get_temp_dir(), 'test_loader_');
16+
17+
$output = runCLI('-dextension='.$tmp.' -v', true, [
18+
'DD_TRACE_DEBUG=1',
19+
'FAKE_FORWARDER_LOG_PATH='.$telemetryLogPath,
20+
'DD_TELEMETRY_FORWARDER_PATH='.__DIR__.'/../../bin/fake_forwarder.sh',
21+
]);
1622
assertContains($output, 'Found extension file');
1723
assertContains($output, 'Extension \'ddtrace\' is already loaded, unregister the injected extension');
1824
assertContains($output, 'with ddtrace v');
1925
assertContains($output, 'with dd_library_loader v');
26+
27+
// Let time to the fork to write the telemetry log
28+
usleep(5000);
29+
30+
$raw = file_get_contents($telemetryLogPath);
31+
$payload = json_decode($raw, true);
32+
33+
$format = <<<EOS
34+
{
35+
"metadata": {
36+
"runtime_name": "php",
37+
"runtime_version": "%d.%d.%d%S",
38+
"language_name": "php",
39+
"language_version": "%d.%d.%d%S",
40+
"tracer_version": "%s",
41+
"pid": %d
42+
},
43+
"points": [
44+
{
45+
"name": "library_entrypoint.abort",
46+
"tags": [
47+
"reason:already_loaded"
48+
]
49+
}
50+
]
51+
}
52+
EOS;
53+
54+
assertMatchesFormat(json_encode($payload, JSON_PRETTY_PRINT), $format);
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
<?php
2+
3+
require_once __DIR__."/includes/autoload.php";
4+
skip_if_php5();
5+
6+
$telemetryLogPath = tempnam(sys_get_temp_dir(), 'test_loader_');
7+
8+
$output = runCLI('-v', true, [
9+
'FAKE_FORWARDER_LOG_PATH='.$telemetryLogPath,
10+
'DD_TELEMETRY_FORWARDER_PATH='.__DIR__.'/../../bin/fake_forwarder.sh',
11+
'DD_LOADER_PACKAGE_PATH=/foo/bar'
12+
]);
13+
14+
// Let time to the fork to write the telemetry log
15+
usleep(5000);
16+
17+
$raw = file_get_contents($telemetryLogPath);
18+
$payload = json_decode($raw, true);
19+
20+
$format = <<<EOS
21+
{
22+
"metadata": {
23+
"runtime_name": "php",
24+
"runtime_version": "%d.%d.%d%S",
25+
"language_name": "php",
26+
"language_version": "%d.%d.%d%S",
27+
"tracer_version": "%s",
28+
"pid": %d
29+
},
30+
"points": [
31+
{
32+
"name": "library_entrypoint.error",
33+
"tags": [
34+
"error_type:so_not_found"
35+
]
36+
}
37+
]
38+
}
39+
EOS;
40+
41+
assertMatchesFormat(json_encode($payload, JSON_PRETTY_PRINT), $format);

0 commit comments

Comments
 (0)