Skip to content

feat: add DSM support for Amazon EventBridge#8476

Open
jeastham1993 wants to merge 43 commits into
masterfrom
james.eastham/support-eventbridge-dsm
Open

feat: add DSM support for Amazon EventBridge#8476
jeastham1993 wants to merge 43 commits into
masterfrom
james.eastham/support-eventbridge-dsm

Conversation

@jeastham1993

Copy link
Copy Markdown

What does this PR do?

Extends the DSM auto checkpointing and injection support into Amazon Event Bridge. Replicating the same way the DSM propagation works for SQS/SNS/Kinesis

Motivation

Customers working with serverless often use Amazon EventBridge as a messaging technology, extend DSM support to cover all AWS native messaging technologies.

Additional Notes

@jeastham1993
jeastham1993 requested a review from a team as a code owner May 14, 2026 10:16
@dd-octo-sts

dd-octo-sts Bot commented May 14, 2026

Copy link
Copy Markdown
Contributor

Overall package size

Self size: 6.78 MB
Deduped: 7.44 MB
No deduping: 7.44 MB

Dependency sizes | name | version | self size | total size | |------|---------|-----------|------------| | import-in-the-middle | 3.3.1 | 122.62 kB | 438.86 kB | | opentracing | 0.14.7 | 194.81 kB | 194.81 kB | | dc-polyfill | 0.1.11 | 25.74 kB | 25.74 kB |

🤖 This report was automatically generated by heaviest-objects-in-the-universe

@codecov

codecov Bot commented May 14, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 98.35%. Comparing base (87b974e) to head (0138f3f).

Additional details and impacted files
@@           Coverage Diff            @@
##           master    #8476    +/-   ##
========================================
  Coverage   98.34%   98.35%            
========================================
  Files         924      924            
  Lines      123056   123197   +141     
  Branches    10869    10536   -333     
========================================
+ Hits       121024   121165   +141     
  Misses       2032     2032            
Flag Coverage Δ
aiguard 58.22% <ø> (-0.03%) ⬇️
aiguard-integration 57.08% <ø> (ø)
apm-bucket-0 58.48% <ø> (-0.03%) ⬇️
apm-bucket-1 64.59% <ø> (-0.03%) ⬇️
apm-bucket-2 63.64% <ø> (-0.03%) ⬇️
apm-bucket-3 60.93% <ø> (-0.03%) ⬇️
apm-capabilities-tracing 62.27% <28.82%> (-0.05%) ⬇️
apm-integrations-aerospike 57.61% <ø> (-0.03%) ⬇️
apm-integrations-confluentinc-kafka-javascript 62.53% <ø> (-0.03%) ⬇️
apm-integrations-couchbase 57.89% <ø> (-0.04%) ⬇️
apm-integrations-http 63.63% <ø> (-0.03%) ⬇️
apm-integrations-kafkajs 63.09% <ø> (-0.03%) ⬇️
apm-integrations-next 60.02% <ø> (-0.03%) ⬇️
apm-integrations-prisma 59.45% <ø> (-0.03%) ⬇️
appsec 73.94% <ø> (-0.02%) ⬇️
appsec-express_fastify_graphql 71.53% <ø> (-0.02%) ⬇️
appsec-integration 51.90% <83.33%> (+<0.01%) ⬆️
appsec-kafka_ldapjs_lodash 64.82% <ø> (-0.03%) ⬇️
appsec-mongodb-core_mongoose_mysql 68.55% <ø> (-0.02%) ⬇️
appsec-next 58.19% <ø> (-0.03%) ⬇️
appsec-node-serialize_passport_postgres 68.24% <ø> (-0.02%) ⬇️
appsec-sourcing_stripe_template 66.54% <ø> (-0.02%) ⬇️
debugger 65.87% <ø> (-0.01%) ⬇️
instrumentations-bucket-0 52.40% <100.00%> (-0.02%) ⬇️
instrumentations-bucket-1 61.12% <ø> (-0.03%) ⬇️
instrumentations-bucket-10 62.98% <ø> (-0.03%) ⬇️
instrumentations-bucket-11 52.40% <ø> (-0.03%) ⬇️
instrumentations-bucket-12 52.85% <ø> (-0.03%) ⬇️
instrumentations-bucket-13 52.35% <ø> (-0.03%) ⬇️
instrumentations-bucket-2 54.32% <ø> (-0.03%) ⬇️
instrumentations-bucket-3 60.09% <ø> (-0.03%) ⬇️
instrumentations-bucket-4 52.98% <ø> (-0.03%) ⬇️
instrumentations-bucket-5 58.32% <ø> (-0.03%) ⬇️
instrumentations-bucket-6 61.68% <ø> (-0.03%) ⬇️
instrumentations-bucket-7 59.22% <ø> (-0.03%) ⬇️
instrumentations-bucket-8 60.49% <ø> (-0.03%) ⬇️
instrumentations-bucket-9 62.35% <ø> (-0.03%) ⬇️
instrumentations-instrumentation-couchbase 51.91% <ø> (-0.03%) ⬇️
instrumentations-integration-esbuild 34.06% <83.33%> (+<0.01%) ⬆️
llmobs-ai_anthropic_bedrock 63.24% <31.25%> (-0.10%) ⬇️
llmobs-bucket-1 62.54% <30.68%> (-0.09%) ⬇️
llmobs-openai 63.29% <ø> (-0.03%) ⬇️
llmobs-sdk 65.41% <ø> (-0.03%) ⬇️
llmobs-vertex-ai 59.97% <ø> (-0.03%) ⬇️
master-coverage 98.35% <100.00%> (?)
openfeature 54.69% <ø> (-0.01%) ⬇️
openfeature-unit 53.43% <ø> (-0.03%) ⬇️
platform-core_esbuild_instrumentations-misc 40.67% <83.33%> (-0.01%) ⬇️
platform-integration 62.31% <ø> (ø)
platform-shimmer_unit-guardrails_webpack 39.35% <83.33%> (-0.01%) ⬇️
plugins-bucket-0 57.69% <30.68%> (-0.09%) ⬇️
plugins-bucket-1 55.17% <ø> (ø)
plugins-bucket-11 63.09% <ø> (-0.03%) ⬇️
plugins-bucket-18 62.90% <ø> (-0.03%) ⬇️
plugins-bucket-19 60.96% <ø> (-0.02%) ⬇️
plugins-bucket-20 62.90% <ø> (-0.03%) ⬇️
plugins-bucket-4 59.39% <ø> (-0.03%) ⬇️
plugins-bullmq_cassandra_cookie 62.68% <ø> (-0.03%) ⬇️
plugins-cookie-parser_crypto_dd-trace-api 57.56% <ø> (-0.03%) ⬇️
plugins-fetch_fs_generic-pool 59.62% <ø> (-0.07%) ⬇️
plugins-google-cloud-pubsub_grpc_handlebars 65.67% <ø> (-0.03%) ⬇️
plugins-hapi_hono_ioredis 61.10% <ø> (-0.03%) ⬇️
plugins-jest_knex_langgraph 56.36% <ø> (-0.03%) ⬇️
plugins-ldapjs_light-my-request_limitd-client 59.36% <ø> (-0.02%) ⬇️
plugins-lodash_mariadb_memcached 58.85% <ø> (-0.03%) ⬇️
plugins-moleculer_mongodb_mongodb-core 62.84% <ø> (-0.03%) ⬇️
plugins-mongoose_multer_mysql 59.85% <ø> (-0.03%) ⬇️
plugins-mysql2_nats_node-serialize 61.63% <ø> (-0.03%) ⬇️
plugins-opensearch_passport-http_pino 60.41% <ø> (-0.03%) ⬇️
plugins-postgres_process_pug 59.13% <ø> (-0.03%) ⬇️
plugins-redis_router_sequelize 62.97% <ø> (-0.03%) ⬇️
plugins-test-and-upstream-rhea_undici_url 62.54% <ø> (-0.02%) ⬇️
plugins-valkey_vm_winston 58.79% <ø> (-0.03%) ⬇️
plugins-ws 60.46% <ø> (-0.03%) ⬇️
profiling 63.07% <ø> (-0.03%) ⬇️
serverless-aws-sdk-aws-sdk 55.65% <31.25%> (-0.08%) ⬇️
serverless-aws-sdk-bedrockruntime 55.37% <31.25%> (-0.09%) ⬇️
serverless-aws-sdk-client 57.10% <31.25%> (-0.09%) ⬇️
serverless-aws-sdk-dynamodb 56.31% <31.25%> (-0.09%) ⬇️
serverless-aws-sdk-eventbridge 56.01% <99.43%> (+6.06%) ⬆️
serverless-aws-sdk-kinesis 60.07% <31.25%> (-0.10%) ⬇️
serverless-aws-sdk-lambda 58.07% <31.25%> (-0.09%) ⬇️
serverless-aws-sdk-s3 56.24% <31.25%> (-0.09%) ⬇️
serverless-aws-sdk-serverless-peer-service 60.49% <31.81%> (-0.10%) ⬇️
serverless-aws-sdk-sns 60.89% <31.25%> (-0.10%) ⬇️
serverless-aws-sdk-sqs 61.32% <31.25%> (-0.10%) ⬇️
serverless-aws-sdk-stepfunctions 56.22% <31.25%> (-0.09%) ⬇️
serverless-aws-sdk-util 52.19% <ø> (-0.03%) ⬇️
serverless-bucket-0 55.22% <ø> (ø)
serverless-bucket-1 60.09% <ø> (-0.03%) ⬇️
test-optimization-cucumber 73.04% <ø> (+0.03%) ⬆️
test-optimization-cypress 66.42% <ø> (+0.08%) ⬆️
test-optimization-jest 74.39% <ø> (-0.05%) ⬇️
test-optimization-mocha 74.73% <ø> (+0.04%) ⬆️
test-optimization-playwright-playwright-atr 61.43% <ø> (-0.01%) ⬇️
test-optimization-playwright-playwright-efd 61.62% <ø> (ø)
test-optimization-playwright-playwright-final-status 61.59% <ø> (-0.17%) ⬇️
test-optimization-playwright-playwright-impacted-tests 61.31% <ø> (+0.16%) ⬆️
test-optimization-playwright-playwright-reporting 61.21% <ø> (-0.01%) ⬇️
test-optimization-playwright-playwright-test-management 62.15% <ø> (-0.12%) ⬇️
test-optimization-playwright-playwright-test-span 61.33% <ø> (-0.07%) ⬇️
test-optimization-selenium 60.71% <ø> (-0.11%) ⬇️
test-optimization-testopt 59.21% <ø> (+0.08%) ⬆️
test-optimization-vitest 71.33% <ø> (+0.01%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@datadog-prod-us1-6

datadog-prod-us1-6 Bot commented May 14, 2026

Copy link
Copy Markdown

Tests

⚠️ Warnings

🧪 1 Test failed in 1 job

All Green | all-green   GitHub Actions

Plugin with electron &gt;=37.0.0 (37.0.0) electron without configuration should do automatic instrumentation for fetch from without configuration   View in Datadog
No matching trace received within 1000ms.

Error: No matching trace received within 1000ms.
    at Timeout._onTimeout (packages/dd-trace/test/plugins/agent.js:383:14)
    at listOnTimeout (node:internal/timers:605:17)
    at process.processTimers (node:internal/timers:541:7)

ℹ️ Info

No other issues found (see more)

❄️ No new flaky tests detected

🔄 Datadog auto-retried 1 job - 1 passed on retry View in Datadog

🎯 Code Coverage (details)
Patch Coverage: 100.00%
Overall Coverage: 98.34% (+0.00%)

Useful? React with 👍 / 👎

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

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 07e96ad374

ℹ️ 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".

Comment thread packages/datadog-plugin-aws-sdk/src/services/eventbridge.js Outdated
@pr-commenter

pr-commenter Bot commented May 14, 2026

Copy link
Copy Markdown

Benchmarks

Benchmark execution time: 2026-07-17 12:23:20

Comparing candidate commit 0138f3f in PR branch james.eastham/support-eventbridge-dsm with baseline commit 87b974e in branch master.

📊 Benchmarking dashboard

Found 0 performance improvements and 0 performance regressions! Performance is the same for 2313 metrics, 45 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 ----------------------------------'

Unstable benchmarks

These benchmarks have a confidence interval too wide to call a change; treat them as noise rather than signal.

scenario:appsec-appsec-enabled-24

  • unstable execution_time [-208.757ms; +205.881ms] or [-7.863%; +7.755%]

scenario:appsec-appsec-enabled-26

  • unstable execution_time [-235418.989µs; +234853.822µs] or [-9.246%; +9.224%]

scenario:appsec-appsec-enabled-with-attacks-24

  • unstable execution_time [-162.662ms; +167.026ms] or [-5.198%; +5.337%]

scenario:appsec-appsec-enabled-with-attacks-26

  • unstable execution_time [-194.153ms; +203.155ms] or [-6.689%; +6.999%]

scenario:appsec-control-20

  • unstable execution_time [-122.609ms; +128.664ms] or [-7.438%; +7.806%]

scenario:appsec-control-24

  • unstable execution_time [-112.596ms; +116.233ms] or [-9.063%; +9.356%]

scenario:appsec-control-26

  • unstable execution_time [-126.625ms; +119.618ms] or [-10.215%; +9.650%]

scenario:appsec-iast-no-vulnerability-control-20

  • unstable execution_time [-14.778ms; +19.886ms] or [-5.621%; +7.565%]

scenario:appsec-iast-no-vulnerability-iast-enabled-always-active-20

  • unstable execution_time [-13187.625µs; +15167.625µs] or [-5.133%; +5.904%]

scenario:appsec-iast-with-vulnerability-iast-enabled-always-active-20

  • unstable execution_time [-33813.184µs; +33185.396µs] or [-6.112%; +5.998%]

scenario:child_process-shell-string-24

  • unstable execution_time [-11.319ms; +21.263ms] or [-3.505%; +6.584%]

scenario:debugger-enabled-but-breakpoint-not-hit-24

  • unstable execution_time [-416.700ms; +739.154ms] or [-3.615%; +6.413%]

scenario:debugger-line-probe-with-snapshot-default-26

  • unstable cpu_user_time [-2626.371ms; +4192.968ms] or [-27.461%; +43.841%]
  • unstable execution_time [-2658.186ms; +4196.647ms] or [-25.826%; +40.773%]
  • unstable instructions [-23.1G instructions; +36.8G instructions] or [-29.055%; +46.210%]
  • unstable max_rss_usage [-8.776MB; +12.772MB] or [-5.498%; +8.001%]
  • unstable throughput [-816.829op/s; +520.349op/s] or [-25.358%; +16.154%]

scenario:debugger-line-probe-with-snapshot-minimal-26

  • unstable cpu_user_time [-2528.475ms; +819.591ms] or [-26.257%; +8.511%]
  • unstable execution_time [-2556.426ms; +803.464ms] or [-24.717%; +7.768%]
  • unstable instructions [-22.5G instructions; +7.4G instructions] or [-27.950%; +9.142%]
  • unstable throughput [-158.614op/s; +444.564op/s] or [-4.931%; +13.822%]

scenario:debugger-line-probe-without-snapshot-24

  • unstable cpu_user_time [-1932.590ms; +3067.765ms] or [-23.416%; +37.170%]
  • unstable execution_time [-1918.852ms; +3072.326ms] or [-21.387%; +34.244%]
  • unstable instructions [-16.7G instructions; +26.7G instructions] or [-24.744%; +39.492%]
  • unstable max_rss_usage [-8.247MB; +12.818MB] or [-5.255%; +8.168%]
  • unstable throughput [-832.682op/s; +517.960op/s] or [-22.717%; +14.131%]

scenario:debugger-line-probe-without-snapshot-26

  • unstable cpu_user_time [-3586.866ms; +3596.803ms] or [-34.809%; +34.906%]
  • unstable execution_time [-3585757.132µs; +3586188.532µs] or [-32.427%; +32.430%]
  • unstable instructions [-32210.5M instructions; +32346.5M instructions] or [-37.225%; +37.383%]
  • unstable max_rss_usage [-11746.595KB; +11609.795KB] or [-7.277%; +7.193%]
  • unstable throughput [-709.462op/s; +699.337op/s] or [-23.112%; +22.782%]

scenario:dogstatsd-aggregated-20

  • unstable cpu_usage_percentage [-6.933%; +4.511%]
  • unstable execution_time [-83.146ms; +127.629ms] or [-6.459%; +9.915%]
  • unstable throughput [-830486.823op/s; +549455.887op/s] or [-7.051%; +4.665%]

scenario:dogstatsd-with-tags-20

  • unstable cpu_user_time [-229.755ms; +454.749ms] or [-4.826%; +9.553%]
  • unstable execution_time [-228.461ms; +458.897ms] or [-4.724%; +9.489%]
  • unstable throughput [-162412.477op/s; +80380.769op/s] or [-9.353%; +4.629%]

scenario:plugin-claude-agent-sdk-compact-stream-scan-24

  • unstable cpu_usage_percentage [-5.063%; +6.299%]

scenario:plugin-graphql-long-with-depth-off-20

  • unstable max_rss_usage [-6546.437KB; +6368.722KB] or [-5.071%; +4.933%]

scenario:plugin-graphql-long-with-depth-on-max-20

  • unstable cpu_user_time [-615.317ms; +561.178ms] or [-5.322%; +4.854%]
  • unstable execution_time [-632.070ms; +573.956ms] or [-5.360%; +4.867%]
  • unstable throughput [-3.379op/s; +3.677op/s] or [-4.952%; +5.388%]

scenario:plugin-pg-service-26

  • unstable cpu_usage_percentage [-10.824%; +6.691%]
  • unstable execution_time [-104.983ms; +170.066ms] or [-11.510%; +18.645%]
  • unstable throughput [-802321.035op/s; +496807.161op/s] or [-11.916%; +7.378%]

Comment thread packages/datadog-plugin-aws-sdk/src/services/eventbridge.js Outdated
Comment thread packages/datadog-plugin-aws-sdk/src/services/eventbridge.js Outdated
Comment thread packages/datadog-plugin-aws-sdk/src/services/eventbridge.js Outdated
Comment thread packages/datadog-plugin-aws-sdk/src/services/eventbridge.js Outdated
Comment thread packages/datadog-plugin-aws-sdk/src/services/eventbridge.js Outdated
@watson

watson commented May 21, 2026

Copy link
Copy Markdown
Collaborator

A new linting rule just landed in master that will fail this PR if merged as-is. I'll merge in master so that it's more clear - sorry for the inconvenience.

@robcarlan-datadog robcarlan-datadog left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(req changes pending some internal discussion on data model)

Comment thread packages/datadog-plugin-aws-sdk/test/eventbridge-inject-detail.spec.js Outdated

@BridgeAR BridgeAR left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks very solid!

Could you just please adjust the tests to be more real world like and also add an end to end test to verify the hash is calculated as expected? :)

Comment thread packages/datadog-plugin-aws-sdk/test/eventbridge-inject-detail.spec.js Outdated
Comment thread packages/datadog-plugin-aws-sdk/test/eventbridge-inject-detail.spec.js Outdated
Comment thread packages/datadog-plugin-aws-sdk/src/services/eventbridge.js Outdated
Comment thread packages/datadog-plugin-aws-sdk/src/services/eventbridge.js Outdated
@jeastham1993
jeastham1993 requested a review from a team as a code owner July 7, 2026 08:55
@gh-worker-ownership-write-b05516
gh-worker-ownership-write-b05516 Bot removed the request for review from a team July 7, 2026 09:26
@jeastham1993
jeastham1993 requested a review from BridgeAR July 8, 2026 15:20
BridgeAR added a commit that referenced this pull request Jul 10, 2026
When a Kinesis record exceeds the 1 MiB cap, injectToMessage bailed after
setDSMCheckpoint had already run: it recorded a produce checkpoint, advanced the
pathway context, and tagged pathway.hash on the span, yet params.Data was never
written so nothing shipped on the wire. The producer left a dangling pathway edge
no consumer completes and a span tagged with a hash for a message it never sent.

Gate on the trace-context payload before setDSMCheckpoint so a record we cannot
ship records no checkpoint, advances no pathway, and tags no hash. The bounded
dd-pathway-ctx-base64 bytes are added after the gate; they cannot realistically
tip a record that already cleared 1 MiB with room for them.

EventBridge shares this checkpoint-before-gate pattern and is tracked separately
in #8476; this change is Kinesis-only.
tlhunter pushed a commit that referenced this pull request Jul 14, 2026
…9183)

* fix(aws-sdk): record kinesis DSM checkpoint only when context ships

When a Kinesis record exceeds the 1 MiB cap, injectToMessage bailed after
setDSMCheckpoint had already run: it recorded a produce checkpoint, advanced the
pathway context, and tagged pathway.hash on the span, yet params.Data was never
written so nothing shipped on the wire. The producer left a dangling pathway edge
no consumer completes and a span tagged with a hash for a message it never sent.

Gate on the trace-context payload before setDSMCheckpoint so a record we cannot
ship records no checkpoint, advances no pathway, and tags no hash. The bounded
dd-pathway-ctx-base64 bytes are added after the gate; they cannot realistically
tip a record that already cleared 1 MiB with room for them.

EventBridge shares this checkpoint-before-gate pattern and is tracked separately
in #8476; this change is Kinesis-only.

* refactor(aws-sdk): track message injection with a boolean, drop isEmpty

The kinesis, sqs, and sns producers decided whether to attach `_datadog`
by re-scanning the freshly built `ddInfo` carrier with an `isEmpty`
for-in helper. A local `injected` flag set at each population site
carries the same fact without the per-send rescan or the shared helper,
and matches `DsmPathwayCodec.encode`'s own `!dataStreamsContext.hash`
guard on the sns path so an empty pathway context no longer counts as
injected.

Replace the faked kinesis inject-to-message spec — an
`Object.create(Kinesis.prototype)` instance with a stubbed tracer — with
a real agent-loaded regression in kinesis.dsm.spec.js: a record that
only clears the 1 MiB cap before the trace context is attached must
record no produce checkpoint, asserted through the `putRecord` span
carrying no `pathway.hash`. Drop the sqs unit case that asserted a
carrier state (`injectTraceContext` true, `inject` a no-op) no real
caller reaches.

* refactor(core): return whether inject wrote anything into the carrier

`Tracer.inject` and every propagator now report whether they wrote any
context, so the AWS SDK messaging producers stop attaching an empty
`_datadog` message attribute when nothing was injected. Previously they
assumed a write happened whenever injection was attempted, which shipped an
empty carrier under `DD_TRACE_PROPAGATION_STYLE_INJECT=none` and whenever a
disabled-APM-tracing (standalone ASM) trace was stripped after injection.

The standalone ASM strip runs on the `dd-trace:span:inject` channel after the
propagator writes, so the text_map propagator re-checks the `_dd.p.ts` trace
tag when APM tracing is disabled to keep the returned value truthful without
rescanning the carrier.

* test: align inject test doubles with the boolean carrier-write return

`Tracer.inject` now reports whether it wrote context into the carrier, and the
AWS SDK producers read that instead of rescanning with `isEmpty`. Four
expectations still modelled the old `undefined` return and failed in CI:

1. sqs-inject-to-message: stubs mutate the carrier but returned `undefined`, so
   the producer dropped `_datadog`; return `true` from the writing stubs and
   `false` from the no-op default.
2. dd-trace-api: `inject` forwards its return through the public API, now
   `false` for the empty test args; assert `false`.
3. pathway: type the no-hash `encode` argument concretely to clear
   jsdoc/reject-any-type.
4. kinesis.dsm: `assert.match(resource, /^putRecord/)` clears
   eslint-prefer-assert-match.

* fix(aws-sdk): reserve the DSM pathway field before the kinesis size gate

The size gate ran on the trace-context payload before DsmPathwayCodec.encode
appended the fixed-size dd-pathway-ctx-base64 field. A record sitting within 55
bytes of the 1 MiB cap cleared the gate, recorded a checkpoint, and then wrote an
over-cap params.Data that Kinesis rejects, failing the user's putRecord. Reserve
the pathway field's bytes in the gate so a record that only fits without it
records no checkpoint and never ships over the cap.

* fix(types): mirror inject boolean return in the v5 type surface

Tracer.inject now reports whether it wrote context into the carrier, and
index.d.ts was updated to return boolean. index.d.v5.ts still declared
void. inject exists on v5 and this return is not v6-only, so the v5
release type swap (scripts/release/swap-v5-types.js) would ship a stale
void declaration and hide the return value from v5 TypeScript users even
though the runtime provides it. Mirror the signature and JSDoc so the two
public surfaces diverge only by the intended v6 cleanups.

Refs: #9183 (comment)

* fix(types): keep public inject void on v5/v6, expose boolean on v7

The prior commit widened Tracer.inject's declared return from void to
boolean across the public surface. That is a breaking TypeScript change:
under strict mode `const x: void = tracer.inject(...)` and returning
`tracer.inject(...)` from a void-typed function stop compiling. It must
not ship on a shipping major.

The runtime already returns the boolean and the internal AWS SDK
producers consume it through the unwrapped DatadogTracer (a plugin's
`this.tracer` resolves to `_tracer._tracer`, and the DSM checkpointer
holds that inner tracer directly), never through the public proxy. So
the boolean stays an internal signal regardless of what the public
`.d.ts` promises.

Freeze the contract per major:

1. index.d.v5.ts and the new index.d.v6.ts declare `inject(): void` —
   the current shipping majors keep their contract.
2. index.d.ts (master is 7.0.0-pre) declares `inject(): boolean` as the
   next-major public API, documented in MIGRATING.md under 6.0 to 7.0.

Generalize the release type swap: swap-v5-types.js only handled v5, so a
v6 release would publish the v7 index.d.ts. Rename it to
swap-legacy-types.js and pick index.d.v<major>.ts by the release major;
the newest major has no frozen file and is a no-op. Adding a future
frozen surface is now a single file drop. The .v<major>.ts files stay
out of the npm `files` array by design — the swap copies the chosen one
over index.d.ts at pack time, so only index.d.ts ships.

Refs: #9183 (comment)

* fix(propagation): return lazily created carriers

Standalone ASM can remove trace headers while leaving baggage in the carrier. A boolean result reported false for that non-empty carrier, causing AWS messaging producers to drop valid context. Return the carrier internally after its first write while keeping the public tracer API void at the proxy boundary.

Kinesis limits cover Data plus PartitionKey. Include both before recording a DSM checkpoint and allow records at the exact default limit.

No-write injection measured 11.96-12.21 ns eager vs 6.92-6.98 ns lazy; one write in eight measured 29.30-29.46 ns vs 26.09-26.66 ns on Node 24.18.0 / V8 13.6.233.17, 5M iterations x 7 trials with best and worst dropped.
juan-fernandez pushed a commit that referenced this pull request Jul 16, 2026
…9183)

* fix(aws-sdk): record kinesis DSM checkpoint only when context ships

When a Kinesis record exceeds the 1 MiB cap, injectToMessage bailed after
setDSMCheckpoint had already run: it recorded a produce checkpoint, advanced the
pathway context, and tagged pathway.hash on the span, yet params.Data was never
written so nothing shipped on the wire. The producer left a dangling pathway edge
no consumer completes and a span tagged with a hash for a message it never sent.

Gate on the trace-context payload before setDSMCheckpoint so a record we cannot
ship records no checkpoint, advances no pathway, and tags no hash. The bounded
dd-pathway-ctx-base64 bytes are added after the gate; they cannot realistically
tip a record that already cleared 1 MiB with room for them.

EventBridge shares this checkpoint-before-gate pattern and is tracked separately
in #8476; this change is Kinesis-only.

* refactor(aws-sdk): track message injection with a boolean, drop isEmpty

The kinesis, sqs, and sns producers decided whether to attach `_datadog`
by re-scanning the freshly built `ddInfo` carrier with an `isEmpty`
for-in helper. A local `injected` flag set at each population site
carries the same fact without the per-send rescan or the shared helper,
and matches `DsmPathwayCodec.encode`'s own `!dataStreamsContext.hash`
guard on the sns path so an empty pathway context no longer counts as
injected.

Replace the faked kinesis inject-to-message spec — an
`Object.create(Kinesis.prototype)` instance with a stubbed tracer — with
a real agent-loaded regression in kinesis.dsm.spec.js: a record that
only clears the 1 MiB cap before the trace context is attached must
record no produce checkpoint, asserted through the `putRecord` span
carrying no `pathway.hash`. Drop the sqs unit case that asserted a
carrier state (`injectTraceContext` true, `inject` a no-op) no real
caller reaches.

* refactor(core): return whether inject wrote anything into the carrier

`Tracer.inject` and every propagator now report whether they wrote any
context, so the AWS SDK messaging producers stop attaching an empty
`_datadog` message attribute when nothing was injected. Previously they
assumed a write happened whenever injection was attempted, which shipped an
empty carrier under `DD_TRACE_PROPAGATION_STYLE_INJECT=none` and whenever a
disabled-APM-tracing (standalone ASM) trace was stripped after injection.

The standalone ASM strip runs on the `dd-trace:span:inject` channel after the
propagator writes, so the text_map propagator re-checks the `_dd.p.ts` trace
tag when APM tracing is disabled to keep the returned value truthful without
rescanning the carrier.

* test: align inject test doubles with the boolean carrier-write return

`Tracer.inject` now reports whether it wrote context into the carrier, and the
AWS SDK producers read that instead of rescanning with `isEmpty`. Four
expectations still modelled the old `undefined` return and failed in CI:

1. sqs-inject-to-message: stubs mutate the carrier but returned `undefined`, so
   the producer dropped `_datadog`; return `true` from the writing stubs and
   `false` from the no-op default.
2. dd-trace-api: `inject` forwards its return through the public API, now
   `false` for the empty test args; assert `false`.
3. pathway: type the no-hash `encode` argument concretely to clear
   jsdoc/reject-any-type.
4. kinesis.dsm: `assert.match(resource, /^putRecord/)` clears
   eslint-prefer-assert-match.

* fix(aws-sdk): reserve the DSM pathway field before the kinesis size gate

The size gate ran on the trace-context payload before DsmPathwayCodec.encode
appended the fixed-size dd-pathway-ctx-base64 field. A record sitting within 55
bytes of the 1 MiB cap cleared the gate, recorded a checkpoint, and then wrote an
over-cap params.Data that Kinesis rejects, failing the user's putRecord. Reserve
the pathway field's bytes in the gate so a record that only fits without it
records no checkpoint and never ships over the cap.

* fix(types): mirror inject boolean return in the v5 type surface

Tracer.inject now reports whether it wrote context into the carrier, and
index.d.ts was updated to return boolean. index.d.v5.ts still declared
void. inject exists on v5 and this return is not v6-only, so the v5
release type swap (scripts/release/swap-v5-types.js) would ship a stale
void declaration and hide the return value from v5 TypeScript users even
though the runtime provides it. Mirror the signature and JSDoc so the two
public surfaces diverge only by the intended v6 cleanups.

Refs: #9183 (comment)

* fix(types): keep public inject void on v5/v6, expose boolean on v7

The prior commit widened Tracer.inject's declared return from void to
boolean across the public surface. That is a breaking TypeScript change:
under strict mode `const x: void = tracer.inject(...)` and returning
`tracer.inject(...)` from a void-typed function stop compiling. It must
not ship on a shipping major.

The runtime already returns the boolean and the internal AWS SDK
producers consume it through the unwrapped DatadogTracer (a plugin's
`this.tracer` resolves to `_tracer._tracer`, and the DSM checkpointer
holds that inner tracer directly), never through the public proxy. So
the boolean stays an internal signal regardless of what the public
`.d.ts` promises.

Freeze the contract per major:

1. index.d.v5.ts and the new index.d.v6.ts declare `inject(): void` —
   the current shipping majors keep their contract.
2. index.d.ts (master is 7.0.0-pre) declares `inject(): boolean` as the
   next-major public API, documented in MIGRATING.md under 6.0 to 7.0.

Generalize the release type swap: swap-v5-types.js only handled v5, so a
v6 release would publish the v7 index.d.ts. Rename it to
swap-legacy-types.js and pick index.d.v<major>.ts by the release major;
the newest major has no frozen file and is a no-op. Adding a future
frozen surface is now a single file drop. The .v<major>.ts files stay
out of the npm `files` array by design — the swap copies the chosen one
over index.d.ts at pack time, so only index.d.ts ships.

Refs: #9183 (comment)

* fix(propagation): return lazily created carriers

Standalone ASM can remove trace headers while leaving baggage in the carrier. A boolean result reported false for that non-empty carrier, causing AWS messaging producers to drop valid context. Return the carrier internally after its first write while keeping the public tracer API void at the proxy boundary.

Kinesis limits cover Data plus PartitionKey. Include both before recording a DSM checkpoint and allow records at the exact default limit.

No-write injection measured 11.96-12.21 ns eager vs 6.92-6.98 ns lazy; one write in eight measured 29.30-29.46 ns vs 26.09-26.66 ns on Node 24.18.0 / V8 13.6.233.17, 5M iterations x 7 trials with best and worst dropped.
juan-fernandez pushed a commit that referenced this pull request Jul 16, 2026
…9183)

* fix(aws-sdk): record kinesis DSM checkpoint only when context ships

When a Kinesis record exceeds the 1 MiB cap, injectToMessage bailed after
setDSMCheckpoint had already run: it recorded a produce checkpoint, advanced the
pathway context, and tagged pathway.hash on the span, yet params.Data was never
written so nothing shipped on the wire. The producer left a dangling pathway edge
no consumer completes and a span tagged with a hash for a message it never sent.

Gate on the trace-context payload before setDSMCheckpoint so a record we cannot
ship records no checkpoint, advances no pathway, and tags no hash. The bounded
dd-pathway-ctx-base64 bytes are added after the gate; they cannot realistically
tip a record that already cleared 1 MiB with room for them.

EventBridge shares this checkpoint-before-gate pattern and is tracked separately
in #8476; this change is Kinesis-only.

* refactor(aws-sdk): track message injection with a boolean, drop isEmpty

The kinesis, sqs, and sns producers decided whether to attach `_datadog`
by re-scanning the freshly built `ddInfo` carrier with an `isEmpty`
for-in helper. A local `injected` flag set at each population site
carries the same fact without the per-send rescan or the shared helper,
and matches `DsmPathwayCodec.encode`'s own `!dataStreamsContext.hash`
guard on the sns path so an empty pathway context no longer counts as
injected.

Replace the faked kinesis inject-to-message spec — an
`Object.create(Kinesis.prototype)` instance with a stubbed tracer — with
a real agent-loaded regression in kinesis.dsm.spec.js: a record that
only clears the 1 MiB cap before the trace context is attached must
record no produce checkpoint, asserted through the `putRecord` span
carrying no `pathway.hash`. Drop the sqs unit case that asserted a
carrier state (`injectTraceContext` true, `inject` a no-op) no real
caller reaches.

* refactor(core): return whether inject wrote anything into the carrier

`Tracer.inject` and every propagator now report whether they wrote any
context, so the AWS SDK messaging producers stop attaching an empty
`_datadog` message attribute when nothing was injected. Previously they
assumed a write happened whenever injection was attempted, which shipped an
empty carrier under `DD_TRACE_PROPAGATION_STYLE_INJECT=none` and whenever a
disabled-APM-tracing (standalone ASM) trace was stripped after injection.

The standalone ASM strip runs on the `dd-trace:span:inject` channel after the
propagator writes, so the text_map propagator re-checks the `_dd.p.ts` trace
tag when APM tracing is disabled to keep the returned value truthful without
rescanning the carrier.

* test: align inject test doubles with the boolean carrier-write return

`Tracer.inject` now reports whether it wrote context into the carrier, and the
AWS SDK producers read that instead of rescanning with `isEmpty`. Four
expectations still modelled the old `undefined` return and failed in CI:

1. sqs-inject-to-message: stubs mutate the carrier but returned `undefined`, so
   the producer dropped `_datadog`; return `true` from the writing stubs and
   `false` from the no-op default.
2. dd-trace-api: `inject` forwards its return through the public API, now
   `false` for the empty test args; assert `false`.
3. pathway: type the no-hash `encode` argument concretely to clear
   jsdoc/reject-any-type.
4. kinesis.dsm: `assert.match(resource, /^putRecord/)` clears
   eslint-prefer-assert-match.

* fix(aws-sdk): reserve the DSM pathway field before the kinesis size gate

The size gate ran on the trace-context payload before DsmPathwayCodec.encode
appended the fixed-size dd-pathway-ctx-base64 field. A record sitting within 55
bytes of the 1 MiB cap cleared the gate, recorded a checkpoint, and then wrote an
over-cap params.Data that Kinesis rejects, failing the user's putRecord. Reserve
the pathway field's bytes in the gate so a record that only fits without it
records no checkpoint and never ships over the cap.

* fix(types): mirror inject boolean return in the v5 type surface

Tracer.inject now reports whether it wrote context into the carrier, and
index.d.ts was updated to return boolean. index.d.v5.ts still declared
void. inject exists on v5 and this return is not v6-only, so the v5
release type swap (scripts/release/swap-v5-types.js) would ship a stale
void declaration and hide the return value from v5 TypeScript users even
though the runtime provides it. Mirror the signature and JSDoc so the two
public surfaces diverge only by the intended v6 cleanups.

Refs: #9183 (comment)

* fix(types): keep public inject void on v5/v6, expose boolean on v7

The prior commit widened Tracer.inject's declared return from void to
boolean across the public surface. That is a breaking TypeScript change:
under strict mode `const x: void = tracer.inject(...)` and returning
`tracer.inject(...)` from a void-typed function stop compiling. It must
not ship on a shipping major.

The runtime already returns the boolean and the internal AWS SDK
producers consume it through the unwrapped DatadogTracer (a plugin's
`this.tracer` resolves to `_tracer._tracer`, and the DSM checkpointer
holds that inner tracer directly), never through the public proxy. So
the boolean stays an internal signal regardless of what the public
`.d.ts` promises.

Freeze the contract per major:

1. index.d.v5.ts and the new index.d.v6.ts declare `inject(): void` —
   the current shipping majors keep their contract.
2. index.d.ts (master is 7.0.0-pre) declares `inject(): boolean` as the
   next-major public API, documented in MIGRATING.md under 6.0 to 7.0.

Generalize the release type swap: swap-v5-types.js only handled v5, so a
v6 release would publish the v7 index.d.ts. Rename it to
swap-legacy-types.js and pick index.d.v<major>.ts by the release major;
the newest major has no frozen file and is a no-op. Adding a future
frozen surface is now a single file drop. The .v<major>.ts files stay
out of the npm `files` array by design — the swap copies the chosen one
over index.d.ts at pack time, so only index.d.ts ships.

Refs: #9183 (comment)

* fix(propagation): return lazily created carriers

Standalone ASM can remove trace headers while leaving baggage in the carrier. A boolean result reported false for that non-empty carrier, causing AWS messaging producers to drop valid context. Return the carrier internally after its first write while keeping the public tracer API void at the proxy boundary.

Kinesis limits cover Data plus PartitionKey. Include both before recording a DSM checkpoint and allow records at the exact default limit.

No-write injection measured 11.96-12.21 ns eager vs 6.92-6.98 ns lazy; one write in eight measured 29.30-29.46 ns vs 26.09-26.66 ns on Node 24.18.0 / V8 13.6.233.17, 5M iterations x 7 trials with best and worst dropped.

@BridgeAR BridgeAR left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please rebase and I have two minor comments right now :)

Comment thread integration-tests/init.spec.js Outdated
Comment on lines +160 to +162
const ddInfo = {}
if (injectTraceContext) {
this.tracer.inject(span, 'text_map', ddInfo)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To identify in all cases when something got injected or not, I changed the API. Calling inject will now return an context object if something got injected and return undefined if not in case no context was provided as third argument.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@BridgeAR could I just double check what change your asking for here. Do I now need to ddInfo = this.tracer.inject(span, 'text_map') because the context is going to be returned?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants