-
Notifications
You must be signed in to change notification settings - Fork 180
feat(tracing): add DD_TRACE_PROPAGATION_BEHAVIOR_EXTRACT #3997
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
0ca6b04
5a63578
6914d02
68ddc93
3ca8428
f5db737
6374dad
602e2be
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,45 @@ | ||
| --TEST-- | ||
| DD_TRACE_PROPAGATION_BEHAVIOR_EXTRACT config parsing: case-insensitive, invalid falls back to continue | ||
| --ENV-- | ||
| DD_TRACE_GENERATE_ROOT_SPAN=0 | ||
| --FILE-- | ||
| <?php | ||
|
|
||
| // Helper: return trace_id after consuming upstream context with a given config value | ||
| function check_behavior(string $config_value): string { | ||
| ini_set('datadog.trace.propagation_behavior_extract', $config_value); | ||
|
|
||
| DDTrace\consume_distributed_tracing_headers([ | ||
| "x-datadog-trace-id" => 42, | ||
| "x-datadog-parent-id" => 10, | ||
| ]); | ||
|
|
||
| $span = DDTrace\start_span(); | ||
| $result = DDTrace\root_span()->traceId === "0000000000000000000000000000002a" ? "continue" : "restart_or_ignore"; | ||
| DDTrace\close_span(); | ||
|
|
||
| return $result; | ||
| } | ||
|
|
||
| // Lowercase values | ||
| echo "continue: " . check_behavior("continue") . "\n"; | ||
| echo "restart: " . check_behavior("restart") . "\n"; | ||
| echo "ignore: " . check_behavior("ignore") . "\n"; | ||
|
|
||
| // Case-insensitive | ||
| echo "CONTINUE: " . check_behavior("CONTINUE") . "\n"; | ||
| echo "RESTART: " . check_behavior("RESTART") . "\n"; | ||
| echo "Ignore: " . check_behavior("Ignore") . "\n"; | ||
|
|
||
| // Invalid value falls back to default (continue) | ||
| echo "invalid: " . check_behavior("invalid_value") . "\n"; | ||
|
|
||
| ?> | ||
| --EXPECT-- | ||
| continue: continue | ||
| restart: restart_or_ignore | ||
| ignore: restart_or_ignore | ||
| CONTINUE: continue | ||
| RESTART: restart_or_ignore | ||
| Ignore: restart_or_ignore | ||
| invalid: continue |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,32 @@ | ||
| --TEST-- | ||
| DD_TRACE_PROPAGATION_BEHAVIOR_EXTRACT=continue inherits upstream context | ||
| --ENV-- | ||
| DD_TRACE_GENERATE_ROOT_SPAN=0 | ||
| DD_TRACE_PROPAGATION_BEHAVIOR_EXTRACT=continue | ||
| --FILE-- | ||
| <?php | ||
|
|
||
| DDTrace\consume_distributed_tracing_headers([ | ||
| "x-datadog-trace-id" => 42, | ||
| "x-datadog-parent-id" => 10, | ||
| "x-datadog-sampling-priority" => 1, | ||
| "baggage" => "user.id=123", | ||
| ]); | ||
|
|
||
| $span = DDTrace\start_span(); | ||
| $root = DDTrace\root_span(); | ||
|
|
||
| echo "trace_id: " . $root->traceId . "\n"; | ||
| echo "parent_id: " . $root->parentId . "\n"; | ||
| echo "links_count: " . count($root->links) . "\n"; | ||
|
|
||
| $headers = DDTrace\generate_distributed_tracing_headers(['baggage']); | ||
| echo "baggage: " . ($headers['baggage'] ?? 'none') . "\n"; | ||
|
|
||
| DDTrace\close_span(); | ||
| ?> | ||
| --EXPECT-- | ||
| trace_id: 0000000000000000000000000000002a | ||
| parent_id: 10 | ||
| links_count: 0 | ||
| baggage: user.id=123 |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,42 @@ | ||
| --TEST-- | ||
| DD_TRACE_PROPAGATION_BEHAVIOR_EXTRACT=ignore drops all incoming context including baggage | ||
| --ENV-- | ||
| DD_TRACE_GENERATE_ROOT_SPAN=0 | ||
| DD_TRACE_PROPAGATION_BEHAVIOR_EXTRACT=ignore | ||
| --FILE-- | ||
| <?php | ||
|
|
||
| DDTrace\consume_distributed_tracing_headers([ | ||
| "x-datadog-trace-id" => 42, | ||
| "x-datadog-parent-id" => 10, | ||
| "x-datadog-sampling-priority" => 2, | ||
| "x-datadog-tags" => "_dd.p.dm=-4", | ||
| "baggage" => "user.id=123", | ||
| ]); | ||
|
|
||
| $span = DDTrace\start_span(); | ||
| $root = DDTrace\root_span(); | ||
|
|
||
| // fresh trace: not from upstream | ||
| echo "same_as_upstream: " . ($root->traceId === "0000000000000000000000000000002a" ? "yes" : "no") . "\n"; | ||
| echo "parent_id: " . $root->parentId . "\n"; | ||
|
|
||
| // no span link (context discarded entirely) | ||
| echo "links_count: " . count($root->links) . "\n"; | ||
|
|
||
| // baggage dropped | ||
| $headers = DDTrace\generate_distributed_tracing_headers(['baggage']); | ||
| echo "baggage: " . ($headers['baggage'] ?? 'none') . "\n"; | ||
|
|
||
| // upstream sampling priority not carried over | ||
| $dd_headers = DDTrace\generate_distributed_tracing_headers(['datadog']); | ||
| echo "sampling_priority: " . ($dd_headers['x-datadog-sampling-priority'] ?? 'none') . "\n"; | ||
|
|
||
| DDTrace\close_span(); | ||
| ?> | ||
| --EXPECT-- | ||
| same_as_upstream: no | ||
| parent_id: 0 | ||
| links_count: 0 | ||
| baggage: none | ||
| sampling_priority: none |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,57 @@ | ||
| --TEST-- | ||
| DD_TRACE_PROPAGATION_BEHAVIOR_EXTRACT=restart starts fresh trace with span link | ||
| --ENV-- | ||
| DD_TRACE_GENERATE_ROOT_SPAN=0 | ||
| DD_TRACE_PROPAGATION_BEHAVIOR_EXTRACT=restart | ||
| DD_TRACE_128_BIT_TRACEID_GENERATION_ENABLED=0 | ||
| DD_TRACE_DEBUG_PRNG_SEED=42 | ||
| --FILE-- | ||
| <?php | ||
|
|
||
| DDTrace\consume_distributed_tracing_headers([ | ||
| "x-datadog-trace-id" => 42, | ||
| "x-datadog-parent-id" => 10, | ||
| "x-datadog-sampling-priority" => 1, | ||
| "x-datadog-tags" => "_dd.p.foo=bar", | ||
| "baggage" => "user.id=123", | ||
| ]); | ||
|
|
||
| $span = DDTrace\start_span(); | ||
| $root = DDTrace\root_span(); | ||
|
|
||
| // fresh trace: different from upstream trace_id 42 | ||
| echo "same_as_upstream: " . ($root->traceId === "0000000000000000000000000000002a" ? "yes" : "no") . "\n"; | ||
|
|
||
| // span link attached to root span | ||
| echo "links_count: " . count($root->links) . "\n"; | ||
|
|
||
| $link = $root->links[0] ?? null; | ||
| if ($link !== null) { | ||
| // link captures upstream trace/span ids | ||
| echo "link_trace_id: " . $link->traceId . "\n"; | ||
| echo "link_span_id: " . $link->spanId . "\n"; | ||
| echo "link_reason: " . ($link->attributes['reason'] ?? 'missing') . "\n"; | ||
| // _dd.p.foo not included (was in upstream _dd.p.* tags, now dropped) | ||
| echo "link_has_foo: " . (isset($link->attributes['_dd.p.foo']) ? "yes" : "no") . "\n"; | ||
| } | ||
|
|
||
| // baggage preserved | ||
| $headers = DDTrace\generate_distributed_tracing_headers(['baggage']); | ||
| echo "baggage: " . ($headers['baggage'] ?? 'none') . "\n"; | ||
|
|
||
| // upstream _dd.p.foo not in outbound tags | ||
| $dd_headers = DDTrace\generate_distributed_tracing_headers(['datadog']); | ||
| $tags = $dd_headers['x-datadog-tags'] ?? ''; | ||
| echo "foo_in_tags: " . (str_contains($tags, '_dd.p.foo') ? "yes" : "no") . "\n"; | ||
|
|
||
| DDTrace\close_span(); | ||
| ?> | ||
| --EXPECT-- | ||
| same_as_upstream: no | ||
| links_count: 1 | ||
| link_trace_id: 0000000000000000000000000000002a | ||
| link_span_id: 000000000000000a | ||
| link_reason: propagation_behavior_extract | ||
| link_has_foo: no | ||
| baggage: user.id=123 | ||
| foo_in_tags: no |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -617,6 +617,57 @@ void apply_baggage_span_tags(zend_string *key, zval *val, zend_array *meta) { | |
| void ddtrace_apply_distributed_tracing_result(ddtrace_distributed_tracing_result *result, ddtrace_root_span_data *span) { | ||
| zval zv; | ||
|
|
||
| int behavior = get_DD_TRACE_PROPAGATION_BEHAVIOR_EXTRACT(); | ||
|
|
||
| if (behavior == DD_TRACE_PROPAGATION_BEHAVIOR_EXTRACT_IGNORE) { | ||
| // behavior=ignore: drop all extracted context including baggage | ||
| zend_hash_destroy(&result->propagated_tags); | ||
| zend_hash_destroy(&result->meta_tags); | ||
| zend_hash_destroy(&result->tracestate_unknown_dd_keys); | ||
| zend_hash_destroy(&result->baggage); | ||
| if (result->origin) { zend_string_release(result->origin); } | ||
| if (result->tracestate) { zend_string_release(result->tracestate); } | ||
| return; | ||
| } | ||
|
|
||
| if (behavior == DD_TRACE_PROPAGATION_BEHAVIOR_EXTRACT_RESTART && (result->trace_id.low || result->trace_id.high)) { | ||
| // behavior=restart: zero trace_id so a fresh trace starts; upstream captured as span link | ||
| // drop _dd.p.* first so the builder does not include them in link attributes | ||
| zend_string *mk; | ||
| zend_string *to_delete[64]; | ||
| int to_delete_count = 0; | ||
| ZEND_HASH_FOREACH_STR_KEY(&result->meta_tags, mk) { | ||
| if (mk && ZSTR_LEN(mk) > 6 && strncmp(ZSTR_VAL(mk), "_dd.p.", 6) == 0) { | ||
| to_delete[to_delete_count++] = mk; | ||
| } | ||
| } ZEND_HASH_FOREACH_END(); | ||
| for (int i = 0; i < to_delete_count; i++) { | ||
| zend_hash_del(&result->meta_tags, to_delete[i]); | ||
| } | ||
|
Comment on lines
+636
to
+646
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There's no need to split collection (to a fixed array?!) and iteration with PHP hashmaps. You can call zend_hash_del during iteration.
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I agree that the fixed array was stupid, but I didn't know |
||
| zend_hash_clean(&result->propagated_tags); | ||
|
|
||
| zval link_zv; | ||
| object_init_ex(&link_zv, ddtrace_ce_span_link); | ||
| ddtrace_span_link *link = (ddtrace_span_link *)Z_OBJ(link_zv); | ||
| ddtrace_build_span_link_from_result(result, link); | ||
|
|
||
| zval reason_str; | ||
| ZVAL_STR(&reason_str, zend_string_init(ZEND_STRL("propagation_behavior_extract"), 0)); | ||
| zend_hash_str_update(Z_ARR(link->property_attributes), ZEND_STRL("reason"), &reason_str); | ||
|
|
||
| result->trace_id = (datadog_trace_id){0}; | ||
| result->parent_id = 0; | ||
| result->priority_sampling = DDTRACE_PRIORITY_SAMPLING_UNKNOWN; | ||
|
|
||
| if (span) { | ||
| zend_array *links = ddtrace_property_array(&span->property_links); | ||
| zend_hash_next_index_insert(links, &link_zv); | ||
| } else { | ||
| zval_ptr_dtor(&DDTRACE_G(pending_upstream_span_link)); | ||
| ZVAL_COPY_VALUE(&DDTRACE_G(pending_upstream_span_link), &link_zv); | ||
| } | ||
| } | ||
|
|
||
| zend_array *root_meta = span ? ddtrace_property_array(&span->property_meta) : &DDTRACE_G(root_span_tags_preset); | ||
| if (span) { | ||
| zend_string *tagname; | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -231,6 +231,25 @@ PHP_METHOD(DDTrace_SpanLink, jsonSerialize) { | |
| RETURN_ARR(array); | ||
| } | ||
|
|
||
| void ddtrace_build_span_link_from_result(ddtrace_distributed_tracing_result *result, ddtrace_span_link *link) { | ||
| ZVAL_STR(&link->property_trace_id, datadog_trace_id_as_hex_string(result->trace_id)); | ||
| ZVAL_STR(&link->property_span_id, ddtrace_span_id_as_hex_string(result->parent_id)); | ||
| array_init(&link->property_attributes); | ||
| // The span link owns its own references to the copied values, independent of the | ||
| // result->meta_tags lifetime: callers may keep meta_tags alive and hand it to other | ||
| // consumers (e.g. the root span's meta) or destroy it right after. | ||
| zend_hash_copy(Z_ARR(link->property_attributes), &result->meta_tags, (copy_ctor_func_t)zval_add_ref); | ||
|
|
||
| zend_string *propagated_tags = ddtrace_format_propagated_tags(&result->propagated_tags, &result->meta_tags); | ||
| zend_string *full_tracestate = ddtrace_format_tracestate(result->tracestate, 0, result->origin, result->priority_sampling, propagated_tags, &result->tracestate_unknown_dd_keys); | ||
| if (propagated_tags) { | ||
| zend_string_release(propagated_tags); | ||
| } | ||
| if (full_tracestate) { | ||
| ZVAL_STR(&link->property_trace_state, full_tracestate); | ||
| } | ||
| } | ||
|
|
||
| static ddtrace_distributed_tracing_result dd_parse_distributed_tracing_headers_function(INTERNAL_FUNCTION_PARAMETERS, bool *success); | ||
| ZEND_METHOD(DDTrace_SpanLink, fromHeaders) { | ||
| bool success; | ||
|
|
@@ -245,21 +264,10 @@ ZEND_METHOD(DDTrace_SpanLink, fromHeaders) { | |
| return; | ||
| } | ||
|
|
||
| ZVAL_STR(&link->property_trace_id, datadog_trace_id_as_hex_string(result.trace_id)); | ||
| ZVAL_STR(&link->property_span_id, ddtrace_span_id_as_hex_string(result.parent_id)); | ||
| array_init(&link->property_attributes); | ||
| zend_hash_copy(Z_ARR(link->property_attributes), &result.meta_tags, NULL); | ||
|
|
||
| zend_string *propagated_tags = ddtrace_format_propagated_tags(&result.propagated_tags, &result.meta_tags); | ||
| zend_string *full_tracestate = ddtrace_format_tracestate(result.tracestate, 0, result.origin, result.priority_sampling, propagated_tags, &result.tracestate_unknown_dd_keys); | ||
| if (propagated_tags) { | ||
| zend_string_release(propagated_tags); | ||
| } | ||
| if (full_tracestate) { | ||
| ZVAL_STR(&link->property_trace_state, full_tracestate); | ||
| } | ||
| ddtrace_build_span_link_from_result(&result, link); | ||
|
|
||
| result.meta_tags.pDestructor = NULL; // we moved values directly | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You did not like the do-not-touch-the-refcount approach? :-) Seeing you susbtituted it for zval_add_ref copying. |
||
| // The span link took its own references (zval_add_ref) on the copied values, so destroy | ||
| // meta_tags normally to release the references it still owns. | ||
| zend_hash_destroy(&result.meta_tags); | ||
| zend_hash_destroy(&result.propagated_tags); | ||
| zend_hash_destroy(&result.tracestate_unknown_dd_keys); | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why would we want to drop e.g.
_dd.p.usr.id? Shouldn't that be an useful attribute to see?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As far as I understand it, the role of this feature is to explicitly discard all incoming tags other than baggage. That way, let's say it is a service extracting a trace coming from outside it can decide to restart a trace in its own terms (own sampling decision and everything else). That being said, I am new on the subject so I'll look again in other tracers