feat(svc-src): emit _dd.svc_src per Service Override Source Attribution RFC#3948
feat(svc-src): emit _dd.svc_src per Service Override Source Attribution RFC#3948Leiyks wants to merge 18 commits into
Conversation
|
…svc_src) Implements the RFC "Service Override Source Attribution": tags every span whose service was actively chosen (vs. tracer's default) with a `_dd.svc_src` meta value identifying the source. Cleared for the default case (DD_SERVICE-set or auto-resolved). Sources emitted, per RFC categories: - `<integration_name>` — when the integration sets the service (Curl, Guzzle, PDO, Mysqli, SQLSRV, MongoDB, Memcached, AMQP, Kafka, ... via `Integration::handleInternalSpanServiceName`) - `opt.<option>` — when a config-driven split changes the service: * `opt.http_client_split_by_domain` (Curl, Guzzle, Psr18, Ratchet) * `opt.db_client_split_by_instance` (PDO, Mysqli, SQLSRV) * `opt.redis_client_split_by_host` (PHPRedis) - Framework root-span overrides (Laravel, LaravelQueue, Drupal, Laminas, Lumen, Slim, SymfonyMessenger, Roadrunner, Ratchet web mode, CakePHP) use the new `Integration::tagFrameworkServiceSource` helper: cleared when user-set DD_SERVICE drove the value, else tagged with the integration name. - `m` — manual API override at serialization-time fallback in `ddtrace_serialize_span_to_rust_span` when the span's service differs from the root's and no other tag was set. Inheritance per RFC: `ddtrace_inherit_span_properties` copies `_dd.svc_src` from parent meta at span creation; no back-propagation. Tests: - 3 new `.phpt` covering default-cleared, manual `'m'`, and parent->child inheritance. - Existing `.phpt` expectations updated where manual service overrides now correctly emit `_dd.svc_src = 'm'`. Out of scope (follow-up): - Predis SPLIT_BY_HOST: stores service via meta override (`service.name`) rather than `$span->service`. Different mechanism; worth a separate discussion. Refs RFC "Service Override Source Attribution".
Mirrors the existing pattern for _dd.p.dm / _dd.p.ksr / _dd.p.tid / runtime-id / _dd.code_origin.*: when not explicitly asserted, drop _dd.svc_src from the strict "extra tags" check so per-integration test suites don't need every test updated. Fixes 14 Memcache (and similar) integration-test failures on CI.
This reverts commit 89d443e.
…lock For each `Tag::COMPONENT => '<name>'` entry across the PHPUnit integration suite, inject `'_dd.svc_src' => '<same value>'` so the RFC contract is verified explicitly per test (instead of being filtered out at the SpanChecker level). Mechanical, 576 sites across 94 test files. Catches drift between the integration's NAME constant and what shows up on spans. (Reverts the SpanChecker filter from 89d443e; this is the proper-per-test enforcement the senior asked for.)
The previous mechanical pass (31f8140) inserted `_dd.svc_src` next to every Tag::COMPONENT line, assuming svc_src always equals the component value. That assumption holds for plain integrations but is wrong in four cases that the runtime actually produces: * Split-by-X paths (Guzzle DD_TRACE_HTTP_CLIENT_SPLIT_BY_DOMAIN, PHPRedis DD_TRACE_REDIS_CLIENT_SPLIT_BY_HOST): svc_src is the option name (opt.<flag>), not the component. * Integrations with DEFAULT_SERVICE_NAME != NAME (Predis): svc_src is the fallback service name (redis), not the integration NAME (predis). * Frameworks that don't call handleInternalSpanServiceName / call tagFrameworkServiceSource with DD_SERVICE set (Symfony, Nette, CodeIgniter, ZendFramework, plus DD_SERVICE-configured CakePHP/Slim scenarios): no svc_src is emitted at all. * Inherited spans (Laravel V4/V5_7 Eloquent): svc_src is inherited from the framework root (laravel), not the eloquent component. * Symfony console commands (CLI scenarios + ConsoleCommandTest): \$span->service is set manually by the integration, so the serializer default-tags svc_src=m. Also extends SnapshotTestTrait::stopAndCompareSnapshotSession to always add 'meta._dd.svc_src' to the ignore list, so snapshot tests don't drift on this new internal field.
…losures trace_method/install_hook rebind closure scope to the target class at runtime, so `self::NAME` inside non-static closures resolves to the hooked class (e.g. Illuminate\View\View) rather than the lexical integration class. PHP 8.x throws "Undefined constant <hooked>::NAME" and the entire integration hook fails. This was breaking Laravel/Lumen/LaravelQueue scenarios across many test targets (Lumen 100/52/81, Laravel 9x/10x/11x/Latest/Octane, etc). Switches all self::NAME / Integration::tagFrameworkServiceSource calls in these integrations to the explicit <Integration>::NAME form, matching the pre-existing pattern in the same files (e.g. LaravelIntegration::getServiceName() on the line above the fix). Also drops two svc_src expected lines my earlier sed missed (Swoole CommonScenariosTest, Zend V1_21 TraceSearchConfigTest with double quotes).
RatchetIntegration calls handleInternalSpanServiceName/tagFrameworkServiceSource which now tags _dd.svc_src='ratchet', and the websocket send/receive/close spans inherit svc_src from their handshake span (see line 264-266 of RatchetIntegration). My earlier mechanical pass missed this file because its assertions use lowercase 'component' => 'ratchet' rather than Tag::COMPONENT constant.
48bb658 to
2e14e97
Compare
Critical: tagFrameworkServiceSource declared `: void`, which PHP 7.0 parses as a class reference in the current namespace. This raised TypeError "Return value must be an instance of DDTrace\Integrations\void" on every CakePHP V2_8 / V3_10 / Laravel 4/5_8 invocation, breaking all their integration tests. Drops the return type to match the codebase's PHP 7.0 baseline. Also drops the `string` parameter type for the same reason (consistency). Test expectation fixes: - Curl: split-by-domain svc_src='opt.http_client_split_by_domain' (was 'curl'), and drop svc_src in testNoFakeServices (DD_SERVICE set). - PHPRedis V3/V4 ClusterTest WithClusterName + ClusterNameAndSeeds: add svc_src='opt.redis_client_split_by_host' to the inline + baseTags() overrides. - Laravel V5_8/V8_x EloquentTest, V8_x InternalExceptionsTest / RouteCachingTest / TraceSearchConfigTest, V5_7 TraceSearchConfigTest, Lumen V5_2 TraceSearchConfigTest: add svc_src='laravel' or 'lumen' next to existing TAG::COMPONENT entries (uppercase TAG, missed by the earlier mechanical pass). - CLI CakePHP V3_10 CommonScenariosTest (inherited by V4_5): drop the two 'cakephp.console' svc_src expectations (DD_SERVICE clears them). - Laravel Octane Latest CommonScenariosTest: drop all 12 svc_src=laravel lines (DD_SERVICE='swoole_test_app' clears framework svc_src). - Ratchet V0_4: drop svc_src='ratchet' from the line-83 websocket.send with service='phpunit' (outgoingMessage doesn't set svc_src and the span doesn't inherit from a ratchet root). Add svc_src='ratchet' to testRatchetConnectFail's Connector.__invoke expectation (double-quote style missed by the earlier sweep).
Benchmarks [ tracer ]Benchmark execution time: 2026-06-10 11:50:42 Comparing candidate commit 3eb74bc in PR branch Found 4 performance improvements and 2 performance regressions! Performance is the same for 188 metrics, 0 unstable metrics.
|
- CakePHP V2_8/V3_10 (web): drop stray 'cakephp' svc_src lines my earlier sed missed (only touched V4_5 paths). - Yii Latest (4 files): drop \"yii\" double-quoted svc_src lines; Yii integration doesn't call tagFrameworkServiceSource so spans have no svc_src. - Laravel V8_x EloquentTest / InternalExceptionsTest / RouteCachingTest / TraceSearchConfigTest: change 'laravel' → 'Laravel' (V8 app config default APP_NAME is capitalized; getAppName() returns it as-is). - Laravel V8_x QueueTest: add '_dd.svc_src' to withExistingTagsNames in spanEventJobProcessing/Processed helpers (SpanChecker.php filters existing-tags out of the strict-extra check; without this svc_src triggers an 'extra' failure). - Mysqli: parameterize baseTags($svcSrc='mysqli') so testNoFakeServices can pass null (DD_SERVICE set, no svc_src on span); add '_dd.svc_src' to testConstructorConnectError's withExistingTagsNames.
Last iteration overcorrected — only EloquentTest goes through Eloquent's getAppName() (which returns the V8 app config name 'Laravel'). Every other V8_x span emits from Laravel's own loader via tagFrameworkServiceSource(\$span, LaravelIntegration::NAME), so svc_src='laravel' (lowercase NAME constant). - InternalExceptionsTest / RouteCachingTest / TraceSearchConfigTest: revert 'Laravel' → 'laravel'. - InternalExceptionsTest testNotImplemented / testUnauthorized: add the missing svc_src on the laravel.request and laravel.view.render spans that triggered \"extra\" failures. - ZendFramework V1 TraceSearchConfigTest: drop one stray svc_src line the earlier V1_21-focused sed missed.
Earlier sed loop only matched CommonScenariosTest / TraceSearchConfigTest; these slipped through: - Symfony V3_4 AutofinishedTracesSymfony34Test / TemplateEnginesTest: drop svc_src='symfony' (SymfonyIntegration doesn't tag svc_src). - Frankenphp CommonScenariosTest: drop svc_src='frankenphp' (FrankenphpIntegration sets \$rootSpan->service directly without calling tagFrameworkServiceSource).
…-instance Same pattern as the earlier Predis/Mysqli fix: parameterize baseTags() to accept an optional svc_src override (defaulting to integration name) so testNoFakeServices can pass null when DD_SERVICE is set + flat services strip svc_src from spans. Files: Memcache, Memcached, MongoDB Latest, PDO, PHPRedis V5 Cluster, SQLSRV. PDO split-by-instance: 4 tests (testPDOSplitByDomain, testPDOServiceMappedSplitByDomain, testPDOStatementSplitByDomain, testPDOStatementSplitByDomainAndServiceFlattening) — svc_src='pdo' → 'opt.db_client_split_by_instance' via the new baseTags param. Guzzle V5 testAppendHostnameToServiceName / testAppendHostnameToServiceNameInlineCredentials: svc_src='guzzle' → 'opt.http_client_split_by_domain' (matches the V6/Latest fix that was already in).
- PHPRedis V5 PHPRedisTest: add inline '_dd.svc_src' => 'phpredis' on 41 sites where the assertion uses \"Tag::COMPONENT => 'phpredis', Tag::DB_SYSTEM => 'redis', Tag::TARGET_HOST\" on a single line (the earlier mechanical pass only matched Tag::COMPONENT on its own line, so missed these). - PHPRedis V5 ClusterTest testSplitByDomainWithClusterName / testSplitByDomainWithClusterNameAndSeeds: pass 'opt.redis_client_split_by_host' as the new baseTags(\$svcSrc) param (runtime overrides svc_src when DD_TRACE_REDIS_CLIENT_SPLIT_BY_HOST is set). The PeerService variants do NOT set that env and keep the default 'phpredis'. - Laravel V5_8 EloquentTest: svc_src 'laravel' → 'Laravel' (V5_8 app config uses APP_NAME=Laravel, same as V8_x). - Laravel V5_8 QueueTest spanEventJobProcessing/Processed: add '_dd.svc_src' to withExistingTagsNames (SpanChecker filters existing-tags from the strict-extra check; same fix as V8_x). - CLI CakePHP V2_8 CommonScenariosTest: drop two stray svc_src='cakephp' lines (DD_SERVICE='cake_console_test_app' is set, framework spans carry no svc_src).
…ices - Laravel V5_7 EloquentTest: svc_src 'laravel' → 'Laravel'. Same APP_NAME capitalization as V5_8 / V8_x — Eloquent's getAppName() returns the configured app name, which is 'Laravel' in the test fixture app. - PHPRedis V5 PHPRedisTest::testNoFakeServices: drop the stray svc_src='phpredis' on the Redis.save assertion. My earlier 41-site mechanical replace caught this inline line too, but testNoFakeServices sets DD_SERVICE + flat services so the runtime emits no svc_src. Skipped (not svc_src related): - Elasticsearch V1 testConstructor: 'Missing or additional spans' from the integration emitting many sub-namespace __construct spans the test doesn't expect — same pre-existing issue seen in V7. - Laravel V5_7 PipelineTracingTest tests: PHPUnit markTestSkipped (\"Pipeline are not properly traced\"). - PHPRedis V5 ClusterTest::testScriptingFunctions: markTestSkipped (\"This is flaky in CI\").
Root cause of the 'Missing or additional spans' failure in
test_integrations_elasticsearch{1,7}:
withExactTags([Tag::COMPONENT => 'elasticsearch'])
is on a SINGLE line with the closing ']' immediately after the value,
so my earlier line-anchored regex (which required a trailing comma or
end-of-line) didn't add '_dd.svc_src'. At runtime the integration's
handleInternalSpanServiceName tags svc_src='elasticsearch' on the
Client.__construct span, so withExactTags fails on the FIRST run with
'unexpected extra _dd.svc_src'.
PHPUnit's RetryTrait then re-runs the test in the SAME PHP process. On
the first run, the Client.__construct posthook registered trace hooks
for every namespace ::__construct method (line 30 of
ElasticSearchIntegration's init posthook). Those hooks persist, so the
RETRY's call to ClientBuilder::build() now produces a span for every
namespace constructor — boom, 'Missing or additional spans' with 30+
NamespaceX.__construct spans the test never expected.
Adding '_dd.svc_src' to the expected tags lets run 1 pass, no retry,
no namespace-hook firing, test stays at the single expected span.
V7 (and Latest) extend V1 so this single fix covers them all.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 3eb74bc25d
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
- serializer.c inherit hook: use Z_TRY_ADDREF_P + zend_hash_str_update directly instead of copying the parent zval. - Add Integration::setComponentMetadata(\$span, \$component, \$service = null) helper consolidating service / _dd.svc_src / Tag::COMPONENT in one call. Refactored 19 adjacent 3-line patterns in Drupal, Laminas, Laravel, CakePHP V2/V3, Slim. tagFrameworkServiceSource kept for the remaining partial-pattern callers (root-span-only, etc.). - Move the _dd.svc_src='m' fallback out of ddtrace_serialize_span_to_rust_span into the ddtrace_span_data_readonly write hook (tracer/functions.c). Fires only when service is being changed on a non-root span to a value differing from the root's service and meta has no _dd.svc_src yet. Writes the tag to the PHP meta zval (instead of just the serialized rust span), so ddtrace_feed_span_to_concentrator now sees the source too.
3b0159b to
8010587
Compare
|
Actually, the failing tests raise a good point: |
| if (!is_top_level_root && span->root != NULL && Z_TYPE_P(value) == IS_STRING | ||
| && Z_TYPE(span->root->property_service) == IS_STRING | ||
| && !zend_string_equals(Z_STR_P(value), Z_STR(span->root->property_service))) { |
There was a problem hiding this comment.
why this check? What is the root span service important here for?
Per bwoebi's follow-up — the write hook's exists check was wrong: a manual \$span->service override would not be reflected on a span that already had a pre-existing integration-set svc_src. - Remove the !zend_hash_str_exists guard so the write hook always stamps _dd.svc_src='m' on a non-root manual service override. - Drop ddtrace_config_app_name in setComponentMetadata; the DD_SERVICE check is the same. Both service and svc_src are now assigned only inside the !DD_SERVICE branch, so when the user configured DD_SERVICE we don't touch the span's service or svc_src. - Reorder handleInternalSpanServiceName to assign \$span->service first and svc_src after, so the write hook's 'm' is immediately overwritten by the integration's intended source.
8010587 to
a370649
Compare
Implements Service Override Source Attribution for the PHP tracer.
Every span whose service was actively chosen (vs. the tracer's default) now carries a
_dd.svc_srcmeta tag identifying who chose it. The tag is cleared on default-service spans (DD_SERVICE-set or auto-resolved) per the RFC.Source values
<integration_name>Integration::handleInternalSpanServiceName(Curl, Guzzle, PDO, Mysqli, SQLSRV, MongoDB, Memcache(d), Predis, PHPRedis, Eloquent, AMQP, …)opt.http_client_split_by_domainopt.db_client_split_by_instanceopt.redis_client_split_by_host<integration_name>(only when DD_SERVICE not user-set)Integration::tagFrameworkServiceSourcehelper — Laravel, LaravelQueue, Drupal, Laminas, Lumen, Slim, SymfonyMessenger, Roadrunner, Ratchet (web), CakePHPmChild spans inherit
_dd.svc_srcfrom their parent at span-creation time (serializer.c).Tests
.phpttests covering the contract (svc_src_default_cleared,svc_src_inheritance,svc_src_manual_override)