Skip to content

feat(process-tags): Service Override Source Attribution#3948

Draft
Leiyks wants to merge 12 commits into
masterfrom
leiyks/svc-override-source-attribution
Draft

feat(process-tags): Service Override Source Attribution#3948
Leiyks wants to merge 12 commits into
masterfrom
leiyks/svc-override-source-attribution

Conversation

@Leiyks

@Leiyks Leiyks commented Jun 3, 2026

Copy link
Copy Markdown
Contributor

Summary

Implements the RFC "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_src meta tag identifying the source. Cleared for the default case (DD_SERVICE-set or auto-resolved).

Sources emitted (per RFC categories)

Source Value Where
Integration override <integration_name> Integration::handleInternalSpanServiceName (auto-covers Curl, Guzzle, PDO, Mysqli, SQLSRV, MongoDB, Memcached, AMQP, Kafka, …)
Config-driven (split-by) 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 <integration_name> (only when DD_SERVICE is not user-set) Laravel, LaravelQueue, Drupal, Laminas, Lumen, Slim, SymfonyMessenger, Roadrunner, Ratchet web mode, CakePHP — via new Integration::tagFrameworkServiceSource helper
Manual API m C-side fallback in ddtrace_serialize_span_to_rust_span when span's service differs from root's and no other source tag was set
Default (DD_SERVICE or auto) not set Per RFC — cleared

@Leiyks Leiyks changed the title feat(process-tags): per-RFC Service Override Source Attribution feat(process-tags): Service Override Source Attribution Jun 3, 2026
@datadog-datadog-prod-us1-2

datadog-datadog-prod-us1-2 Bot commented Jun 3, 2026

Copy link
Copy Markdown

Pipelines  Tests

Fix all issues with BitsAI

⚠️ Warnings

🚦 56 Pipeline jobs failed

DataDog/apm-reliability/dd-trace-php | test_integrations_elasticsearch1: [7.0]   View in Datadog   GitLab

🧪 1 Test failed

testConstructor from tests/Integrations/Elasticsearch/V1.DDTrace\Tests\Integrations\Elasticsearch\V1\ElasticSearchIntegrationTest   View in Datadog (Fix with Cursor)
DDTrace\Tests\Integrations\Elasticsearch\V1\ElasticSearchIntegrationTest::testConstructor
Missing or additional spans. Expected: Array
(
    [0] =&gt; Elasticsearch.Client.__construct - __construct
)

 Found: Array
(
    [0] =&gt; Elasticsearch.IndicesNamespace.__construct - __construct
    [1] =&gt; Elasticsearch.ClusterNamespace.__construct - __construct
...

DataDog/apm-reliability/dd-trace-php | test_integrations_phpredis5: [8.1]   View in Datadog   GitLab

🧪 2 Tests failed

testSplitByDomainWithClusterName from tests/Integrations/PHPRedis/V5.DDTrace\Tests\Integrations\PHPRedis\V5\PHPRedisClusterTest   View in Datadog (Fix with Cursor)
DDTrace\Tests\Integrations\PHPRedis\V5\PHPRedisClusterTest::testSplitByDomainWithClusterName
RedisCluster.__construct: Expected tag format for &#39;_dd.svc_src&#39; does not match actual value
Failed asserting that string matches format description.
--- Expected
&#43;&#43;&#43; Actual
@@ @@
-phpredis
&#43;opt.redis_client_split_by_host

Received Spans graph:
...
testSplitByDomainWithClusterNameAndSeeds from tests/Integrations/PHPRedis/V5.DDTrace\Tests\Integrations\PHPRedis\V5\PHPRedisClusterTest   View in Datadog (Fix with Cursor)
DDTrace\Tests\Integrations\PHPRedis\V5\PHPRedisClusterTest::testSplitByDomainWithClusterNameAndSeeds
RedisCluster.__construct: Expected tag format for &#39;_dd.svc_src&#39; does not match actual value
Failed asserting that string matches format description.
--- Expected
&#43;&#43;&#43; Actual
@@ @@
-phpredis
&#43;opt.redis_client_split_by_host

Received Spans graph:
...

DataDog/apm-reliability/dd-trace-php | test_web_laravel_57: [7.4, apache2handler]   View in Datadog   GitLab

🧪 7 Tests failed

testDelete from tests/Integrations/Laravel/V5_7.DDTrace\Tests\Integrations\Laravel\V5_7\EloquentTest   View in Datadog (Fix with Cursor)
DDTrace\Tests\Integrations\Laravel\V5_7\EloquentTest::testDelete
eloquent.delete: Expected tag format for &#39;_dd.svc_src&#39; does not match actual value
Failed asserting that string matches format description.
--- Expected
&#43;&#43;&#43; Actual
@@ @@
-laravel
&#43;Laravel

tests/Common/SpanChecker.php:517
...
testDestroy from tests/Integrations/Laravel/V5_7.DDTrace\Tests\Integrations\Laravel\V5_7\EloquentTest   View in Datadog (Fix with Cursor)
DDTrace\Tests\Integrations\Laravel\V5_7\EloquentTest::testDestroy
eloquent.destroy: Expected tag format for &#39;_dd.svc_src&#39; does not match actual value
Failed asserting that string matches format description.
--- Expected
&#43;&#43;&#43; Actual
@@ @@
-laravel
&#43;Laravel

tests/Common/SpanChecker.php:517
...
View all 7 test failures

View all 56 failed jobs.

🧪 3 Tests failed

    testConstructor from tests/Integrations/Elasticsearch/V7.DDTrace\Tests\Integrations\Elasticsearch\V7\ElasticSearchIntegrationTest (Fix with Cursor)

    ❄️ Known flaky: testCommandWithArgument from cakephp-28-test.DDTrace\Tests\Integrations\CLI\CakePHP\V2_8\CommonScenariosTest (Fix with Cursor)

View all 3 test failures

ℹ️ Info

No other issues found (see more)

❄️ No new flaky tests detected

🔄 Datadog auto-retried 2 jobs - 2 passed on retry View in Datadog

Useful? React with 👍 / 👎

This comment will be updated automatically if new data arrives.
🔗 Commit SHA: 9c8a45d | Docs | Datadog PR Page | Give us feedback!

Leiyks added 7 commits June 9, 2026 11:09
…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.
…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.
@Leiyks Leiyks force-pushed the leiyks/svc-override-source-attribution branch from 48bb658 to 2e14e97 Compare June 9, 2026 09:09
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).
@pr-commenter

pr-commenter Bot commented Jun 9, 2026

Copy link
Copy Markdown

Benchmarks [ tracer ]

Benchmark execution time: 2026-06-09 17:31:42

Comparing candidate commit 9c8a45d in PR branch leiyks/svc-override-source-attribution with baseline commit 616c6cb in branch master.

Found 5 performance improvements and 4 performance regressions! Performance is the same for 185 metrics, 0 unstable metrics.

Explanation

This is an A/B test comparing a candidate commit's performance against that of a baseline commit. Performance changes are noted in the tables below as:

  • 🟩 = significantly better candidate vs. baseline
  • 🟥 = significantly worse candidate vs. baseline

We compute a confidence interval (CI) over the relative difference of means between metrics from the candidate and baseline commits, considering the baseline as the reference.

If the CI is entirely outside the configured SIGNIFICANT_IMPACT_THRESHOLD (or the deprecated UNCONFIDENCE_THRESHOLD), the change is considered significant.

Feel free to reach out to #apm-benchmarking-platform on Slack if you have any questions.

More details about the CI and significant changes

You can imagine this CI as a range of values that is likely to contain the true difference of means between the candidate and baseline commits.

CIs of the difference of means are often centered around 0%, because often changes are not that big:

---------------------------------(------|---^--------)-------------------------------->
                              -0.6%    0%  0.3%     +1.2%
                                 |          |        |
         lower bound of the CI --'          |        |
sample mean (center of the CI) -------------'        |
         upper bound of the CI ----------------------'

As described above, a change is considered significant if the CI is entirely outside the configured SIGNIFICANT_IMPACT_THRESHOLD (or the deprecated UNCONFIDENCE_THRESHOLD).

For instance, for an execution time metric, this confidence interval indicates a significantly worse performance:

----------------------------------------|---------|---(---------^---------)---------->
                                       0%        1%  1.3%      2.2%      3.1%
                                                  |   |         |         |
       significant impact threshold --------------'   |         |         |
                      lower bound of CI --------------'         |         |
       sample mean (center of the CI) --------------------------'         |
                      upper bound of CI ----------------------------------'

scenario:ContextPropagationBench/benchExtractHeaders64Bit-opcache

  • 🟥 execution_time [+724.826ns; +779.174ns] or [+60.554%; +65.094%]

scenario:ContextPropagationBench/benchInject64Bit-opcache

  • 🟩 execution_time [-1095.396ns; -630.604ns] or [-8.028%; -4.622%]

scenario:EmptyFileBench/benchEmptyFileDdprof

  • 🟥 execution_time [+86.284µs; +281.196µs] or [+2.433%; +7.929%]

scenario:LogsInjectionBench/benchLogsInfoBaseline-opcache

  • 🟩 execution_time [-211.034ns; -163.766ns] or [-11.485%; -8.913%]

scenario:LogsInjectionBench/benchLogsInfoInjection-opcache

  • 🟥 execution_time [+225.450ns; +518.150ns] or [+2.300%; +5.286%]

scenario:LogsInjectionBench/benchLogsNullBaseline-opcache

  • 🟥 execution_time [+0.960µs; +1.106µs] or [+4.715%; +5.428%]

scenario:LogsInjectionBench/benchLogsNullInjection-opcache

  • 🟩 execution_time [-1.546µs; -0.910µs] or [-7.158%; -4.212%]

scenario:SamplingRuleMatchingBench/benchRegexMatching1-opcache

  • 🟩 execution_time [-111.649ns; -49.551ns] or [-6.483%; -2.877%]

scenario:SamplingRuleMatchingBench/benchRegexMatching4-opcache

  • 🟩 execution_time [-123.284ns; -57.716ns] or [-7.088%; -3.318%]

Leiyks added 4 commits June 9, 2026 14:28
- 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).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant