Skip to content

Commit 558b1fb

Browse files
committed
feat(process-tags): emit svc.user/svc.auto per-span and via sidecar
Addresses senior review on the prior PR commit. Process tags are per-process (set once, propagated by the sidecar), but the active service name in PHP is request-local (mutable via `ini_set` and OTEL/RC fallbacks). Baking `svc.user`/`svc.auto` into the static process_tags string leaked the latest request's override into subsequent FPM requests. Two cooperating paths now: 1. **Per-span** (`ext/serializer.c::ddtrace_serialize_span_to_rust_span`): computes svc.user/svc.auto from `get_DD_SERVICE()` at serialization time and appends to that span's `_dd.tags.process`. Each span sees exactly its own request's state — no cross-request leak. 2. **Sidecar** (`ext/sidecar.c::ddtrace_sidecar_update_process_tags`): sends the process-level svc source to libdatadog via the new `ddog_sidecar_session_set_default_service_name` FFI. The sidecar injects svc.user/svc.auto into outgoing telemetry/RC/runtime_info payloads at emission time, eliminating the static-string conflict. The libdatadog half is in DataDog/libdatadog#2053; the submodule is bumped here to that commit. Reverts the static svc.* emission and `ddtrace_alter_dd_service` reload hook from 5a55f2d. Tests: - 5 new `.phpt` tests (CLI per-span correctness incl. ini_set + ini_restore) - New PHPUnit `testSvcTagDoesNotLeakBetweenRequests` against the FPM weblog: two sequential requests on the same worker prove svc.* reflects per-request state with no leak. Implements: RFC "Signal Service Name Source via Process Tags" https://docs.google.com/document/d/1c47iSTWxIOHMHfZTF2nT9xfyQaIBP9KJvI9sRn5SvpM
1 parent 5a55f2d commit 558b1fb

12 files changed

Lines changed: 129 additions & 50 deletions

File tree

components-rs/sidecar.h

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -225,6 +225,17 @@ ddog_MaybeError ddog_sidecar_session_set_config(struct ddog_SidecarTransport **t
225225
ddog_MaybeError ddog_sidecar_session_set_process_tags(struct ddog_SidecarTransport **transport,
226226
const struct ddog_Vec_Tag *process_tags);
227227

228+
/**
229+
* Records the source of the default service name for the session so the
230+
* sidecar can inject svc.user:true or svc.auto:<default_service_name> into
231+
* outgoing process tag payloads. Pass an empty `default_service_name` to
232+
* signal the user explicitly set DD_SERVICE; pass a non-empty value (already
233+
* normalized via `ddog_normalize_process_tag_value`) to signal the tracer
234+
* auto-resolved that name.
235+
*/
236+
ddog_MaybeError ddog_sidecar_session_set_default_service_name(struct ddog_SidecarTransport **transport,
237+
ddog_CharSlice default_service_name);
238+
228239
/**
229240
* Enqueues a telemetry log action to be processed internally.
230241
* Non-blocking. Logs might be dropped if the internal queue is full.

ext/ddtrace.c

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -416,10 +416,6 @@ bool ddtrace_alter_dd_service(zval *old_value, zval *new_value, zend_string *new
416416
dd_alter_prop(XtOffsetOf(ddtrace_span_properties, property_service), old_value, new_value, new_str);
417417
if (DDTRACE_G(request_initialized)) {
418418
ddtrace_sidecar_submit_root_span_data_direct(&DDTRACE_G(sidecar), NULL, new_str, get_DD_ENV(), get_DD_VERSION());
419-
if (ddtrace_process_tags_enabled()) {
420-
ddtrace_process_tags_reload_with_service(new_str);
421-
ddtrace_sidecar_update_process_tags();
422-
}
423419
}
424420
return true;
425421
}

ext/process_tags.c

Lines changed: 0 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@
1111
#include <string.h>
1212
#include "process_tags.h"
1313
#include "configuration.h"
14-
#include "serializer.h"
1514
#include "Zend/zend_smart_str.h"
1615
#include "components-rs/ddtrace.h"
1716
#include "SAPI.h"
@@ -25,8 +24,6 @@
2524
#define TAG_ENTRYPOINT_WORKDIR "entrypoint.workdir"
2625
#define TAG_ENTRYPOINT_TYPE "entrypoint.type"
2726
#define TAG_RUNTIME_SAPI "runtime.sapi"
28-
#define TAG_SVC_USER "svc.user"
29-
#define TAG_SVC_AUTO "svc.auto"
3027

3128
#define TYPE_SCRIPT "script"
3229
#define TYPE_EXECUTABLE "executable"
@@ -47,7 +44,6 @@ typedef struct {
4744
} process_tags_t;
4845

4946
static process_tags_t process_tags = {0};
50-
static zend_string *pending_service_override = NULL;
5147

5248
static void clear_process_tags(void) {
5349
for (size_t i = 0; i < process_tags.count; i++) {
@@ -172,17 +168,6 @@ static void collect_process_tags(void) {
172168

173169
add_process_tag(TAG_RUNTIME_SAPI, sapi_module.name);
174170

175-
zend_string *dd_service = pending_service_override ? pending_service_override : get_DD_SERVICE();
176-
if (dd_service && ZSTR_LEN(dd_service) > 0) {
177-
add_process_tag(TAG_SVC_USER, "true");
178-
} else {
179-
zend_string *default_svc = ddtrace_default_service_name();
180-
if (default_svc) {
181-
add_process_tag(TAG_SVC_AUTO, ZSTR_VAL(default_svc));
182-
zend_string_release(default_svc);
183-
}
184-
}
185-
186171
const char *script = NULL;
187172
if (SG(request_info).path_translated && *SG(request_info).path_translated) {
188173
script = SG(request_info).path_translated;
@@ -351,13 +336,6 @@ void ddtrace_process_tags_reload(void) {
351336
clear_process_tags();
352337
init_process_tags();
353338
}
354-
355-
void ddtrace_process_tags_reload_with_service(zend_string *service_override) {
356-
pending_service_override = service_override;
357-
clear_process_tags();
358-
init_process_tags();
359-
pending_service_override = NULL;
360-
}
361339
void ddtrace_process_tags_mshutdown(void) {
362340
clear_process_tags();
363341
}

ext/process_tags.h

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,6 @@
1111
void ddtrace_process_tags_first_rinit(void);
1212
// Reload process tags in current request
1313
void ddtrace_process_tags_reload(void);
14-
// Reload process tags using the supplied service-name override. Use when the
15-
// DD_SERVICE INI is being changed but the new value has not yet been committed
16-
// to the ZAI config runtime (e.g. inside an `ini_change` callback).
17-
// Pass NULL to fall back to get_DD_SERVICE().
18-
void ddtrace_process_tags_reload_with_service(zend_string *service_override);
1914

2015
// Called at MSHUTDOWN to free resources
2116
void ddtrace_process_tags_mshutdown(void);

ext/serializer.c

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1577,7 +1577,44 @@ ddog_SpanBytes *ddtrace_serialize_span_to_rust_span(ddtrace_span_data *span, ddo
15771577
if (is_first_span) {
15781578
zend_string *process_tags = ddtrace_process_tags_get_serialized();
15791579
if (ZSTR_LEN(process_tags)) {
1580-
ddog_add_str_span_meta_zstr(rust_span, "_dd.tags.process", process_tags);
1580+
// Per-RFC "Signal Service Name Source via Process Tags", append a
1581+
// svc.user:true or svc.auto:<default_service_name> tag computed
1582+
// from the request-active state. Static process_tags are
1583+
// process-global; service name is request-local in PHP, so the
1584+
// svc.* signal must be derived per span.
1585+
zend_string *dd_service = get_DD_SERVICE();
1586+
bool has_user_service = dd_service && ZSTR_LEN(dd_service) > 0;
1587+
zend_string *default_svc = NULL;
1588+
const char *svc_auto_normalized = NULL;
1589+
if (!has_user_service) {
1590+
default_svc = ddtrace_default_service_name();
1591+
if (default_svc) {
1592+
svc_auto_normalized = ddog_normalize_process_tag_value((ddog_CharSlice){
1593+
.ptr = ZSTR_VAL(default_svc), .len = ZSTR_LEN(default_svc)
1594+
});
1595+
}
1596+
}
1597+
if (has_user_service || svc_auto_normalized) {
1598+
smart_str combined = {0};
1599+
smart_str_appendl(&combined, ZSTR_VAL(process_tags), ZSTR_LEN(process_tags));
1600+
if (has_user_service) {
1601+
smart_str_appends(&combined, ",svc.user:true");
1602+
} else {
1603+
smart_str_appends(&combined, ",svc.auto:");
1604+
smart_str_appends(&combined, svc_auto_normalized);
1605+
}
1606+
smart_str_0(&combined);
1607+
ddog_add_str_span_meta_zstr(rust_span, "_dd.tags.process", combined.s);
1608+
smart_str_free(&combined);
1609+
} else {
1610+
ddog_add_str_span_meta_zstr(rust_span, "_dd.tags.process", process_tags);
1611+
}
1612+
if (svc_auto_normalized) {
1613+
ddog_free_normalized_tag_value(svc_auto_normalized);
1614+
}
1615+
if (default_svc) {
1616+
zend_string_release(default_svc);
1617+
}
15811618
}
15821619
}
15831620

ext/sidecar.c

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
#include "ddtrace_export.h"
88
#include "dogstatsd.h"
99
#include "logging.h"
10+
#include "serializer.h"
1011
#include <components-rs/common.h>
1112
#include <components-rs/ddtrace.h>
1213
#include <components-rs/sidecar.h>
@@ -149,6 +150,25 @@ void ddtrace_sidecar_update_process_tags(void) {
149150
}
150151

151152
ddog_sidecar_session_set_process_tags(&DDTRACE_G(sidecar), process_tags);
153+
154+
zend_string *dd_service = get_DD_SERVICE();
155+
if (dd_service && ZSTR_LEN(dd_service) > 0) {
156+
ddog_sidecar_session_set_default_service_name(&DDTRACE_G(sidecar),
157+
(ddog_CharSlice){ .ptr = "", .len = 0 });
158+
} else {
159+
zend_string *default_svc = ddtrace_default_service_name();
160+
if (default_svc) {
161+
const char *normalized = ddog_normalize_process_tag_value((ddog_CharSlice){
162+
.ptr = ZSTR_VAL(default_svc), .len = ZSTR_LEN(default_svc)
163+
});
164+
if (normalized) {
165+
ddog_sidecar_session_set_default_service_name(&DDTRACE_G(sidecar),
166+
(ddog_CharSlice){ .ptr = normalized, .len = strlen(normalized) });
167+
ddog_free_normalized_tag_value(normalized);
168+
}
169+
zend_string_release(default_svc);
170+
}
171+
}
152172
}
153173

154174
static ddog_SidecarTransport *dd_sidecar_connection_factory_ex(bool is_fork);
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<?php
2+
3+
namespace App;
4+
5+
class SetServiceController
6+
{
7+
public function render()
8+
{
9+
ini_set('datadog.service', 'request-svc');
10+
header('Content-type: text/plain; charset=utf-8');
11+
echo 'service set';
12+
}
13+
}

tests/Frameworks/Custom/Version_Autoloaded/src/routes.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
return function(FastRoute\RouteCollector $r) {
44
$r->addRoute('GET', '/simple', '\App\SimpleController');
5+
$r->addRoute('GET', '/set_service', '\App\SetServiceController');
56
$r->addRoute('GET', '/simple_view', '\App\SimpleViewController');
67
$r->addRoute('GET', '/error', '\App\ErrorController');
78
$r->addRoute('GET', '/telemetry', '\App\TelemetryFlushController');

tests/Integrations/Custom/Autoloaded/ProcessTagsWebTest.php

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,4 +65,28 @@ public function testProcessTagsEnabledForWebSapi()
6565
);
6666
$this->assertEquals($tags['entrypoint.type'], 'script');
6767
}
68+
69+
/**
70+
* Proves the per-span svc.* design has no static-state leak between
71+
* requests served by the same FPM worker: request 1 calls
72+
* ini_set('datadog.service', ...) and must report svc.user:true; request 2
73+
* reuses the same worker but does not override the service, and must
74+
* report svc.auto:<default> (not the leaked svc.user from request 1).
75+
*/
76+
public function testSvcTagDoesNotLeakBetweenRequests()
77+
{
78+
$tracesUser = $this->tracesFromWebRequest(function () {
79+
return $this->call(new RequestSpec(__FUNCTION__ . '_user', 'GET', '/set_service', []));
80+
});
81+
$userTags = $tracesUser[0][0]['meta']['_dd.tags.process'];
82+
$this->assertStringContainsString('svc.user:true', $userTags);
83+
$this->assertStringNotContainsString('svc.auto:', $userTags);
84+
85+
$tracesAuto = $this->tracesFromWebRequest(function () {
86+
return $this->call(new RequestSpec(__FUNCTION__ . '_auto', 'GET', '/simple', []));
87+
});
88+
$autoTags = $tracesAuto[0][0]['meta']['_dd.tags.process'];
89+
$this->assertStringNotContainsString('svc.user', $autoTags);
90+
$this->assertStringContainsString('svc.auto:', $autoTags);
91+
}
6892
}

0 commit comments

Comments
 (0)