Skip to content

feat(kafkajs): add kafka.messages.offsets to producer span#8468

Merged
piochelepiotr merged 7 commits into
masterfrom
kafkajs-producer-partition-offset
May 14, 2026
Merged

feat(kafkajs): add kafka.messages.offsets to producer span#8468
piochelepiotr merged 7 commits into
masterfrom
kafkajs-producer-partition-offset

Conversation

@piochelepiotr

@piochelepiotr piochelepiotr commented May 13, 2026

Copy link
Copy Markdown
Contributor

What does this PR do?

Adds a kafka.messages.offsets tag to the kafkajs producer span, carrying the list of (partition, start_offset) pairs the broker returned from producer.send. start_offset is the offset assigned to the first record sent to that partition (kafkajs's baseOffset).

Example value for a batch that landed in three partitions:
```json
[{"partition":0,"start_offset":7},{"partition":1,"start_offset":2},{"partition":2,"start_offset":3}]
```

For single-record sends, the producer span additionally gets flat kafka.partition and kafka.message.offset tags — in that case the broker response describes exactly one record, so the flat tags are unambiguous and easy to filter on.

No changes to consumer spans. The eachMessage consume span already carries kafka.partition and kafka.message.offset (per-record, exact); the eachBatch consume span is single-partition by construction, so its existing kafka.partition tag is sufficient.

Motivation

Inspired by dd-trace-java #11107, which adds per-record partition and offset to the producer span. The Java client emits one span per send call (one record), so a single value is exact. kafkajs's producer.send is batched — one span covers multiple records that may land in multiple partitions — so a list better reflects what actually happened.

Combined with the already-tagged kafka.cluster_id, kafka.topic, and kafka.batch_size, this lets a producer trace span be correlated to the exact set of partitions and starting offsets it produced to.

Per-partition record counts are not in the broker response and aren't included; total batch size remains available via kafka.batch_size.

Additional Notes

  • Verified end-to-end against a real Kafka broker (kafkajs producer + consumer, 3-partition topic). Single- and multi-partition batches both produced the expected tags.
  • Unit tests cover both the single-record and multi-record cases.

🤖 Generated with Claude Code

Mirror dd-trace-java PR #11107 for kafkajs: set kafka.partition and
kafka.message.offset on the producer span using the broker-returned
RecordMetadata. Only tagged when the send resolves to a single
(topic, partition) tuple, since one producer span covers a
multi-partition batch and a single value would be misleading.

kafka.cluster_id was already tagged on both producer and consumer spans,
and the consumer span already carried partition and offset, so no
additional changes are needed for those tags.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
@piochelepiotr
piochelepiotr requested a review from a team as a code owner May 13, 2026 17:14
@github-actions

github-actions Bot commented May 13, 2026

Copy link
Copy Markdown
Contributor

Overall package size

Self size: 5.85 MB
Deduped: 6.69 MB
No deduping: 6.69 MB

Dependency sizes | name | version | self size | total size | |------|---------|-----------|------------| | import-in-the-middle | 3.0.1 | 82.56 kB | 817.39 kB | | dc-polyfill | 0.1.11 | 25.74 kB | 25.74 kB |

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

@datadog-datadog-prod-us1-2

datadog-datadog-prod-us1-2 Bot commented May 13, 2026

Copy link
Copy Markdown

Tests

🎉 All green!

❄️ No new flaky tests detected
🧪 All tests passed

🎯 Code Coverage (details)
Patch Coverage: 86.67%
Overall Coverage: 86.44% (-0.37%)

This comment will be updated automatically if new data arrives.
🔗 Commit SHA: 1f113ca | 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: e88a20ad89

ℹ️ About Codex in GitHub

Codex has been enabled to automatically 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 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

span.setTag('kafka.partition', partition)
}
if (offsetAsLong !== undefined) {
span.setTag('kafka.message.offset', Number(offsetAsLong))

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Preserve full Kafka offsets when setting span tag

Avoid coercing the broker offset to Number here: Kafka offsets are 64-bit values, so once a topic offset grows past Number.MAX_SAFE_INTEGER this conversion silently loses precision. In those high-throughput/long-lived topics, kafka.message.offset on producer spans no longer matches the exact message offset, which undermines the commit’s stated goal of linking producer and consumer spans by (cluster, topic, partition, offset).

Useful? React with 👍 / 👎.

@codecov

codecov Bot commented May 13, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 89.68%. Comparing base (56b6048) to head (1f113ca).
⚠️ Report is 5 commits behind head on master.

Additional details and impacted files
@@            Coverage Diff             @@
##           master    #8468      +/-   ##
==========================================
- Coverage   90.06%   89.68%   -0.39%     
==========================================
  Files         838      844       +6     
  Lines       45088    45403     +315     
  Branches     8424     8446      +22     
==========================================
+ Hits        40607    40718     +111     
- Misses       4481     4685     +204     
Flag Coverage Δ
aiguard-integration-active 41.21% <ø> (-0.03%) ⬇️
aiguard-integration-latest 41.16% <ø> (-0.03%) ⬇️
aiguard-integration-maintenance 41.21% <ø> (-0.03%) ⬇️
aiguard-macos 35.38% <ø> (-0.11%) ⬇️
aiguard-ubuntu 35.48% <ø> (-0.11%) ⬇️
aiguard-windows 35.29% <ø> (-0.11%) ⬇️
apm-capabilities-tracing-macos 49.00% <0.00%> (-0.05%) ⬇️
apm-capabilities-tracing-ubuntu-active 49.21% <0.00%> (+0.14%) ⬆️
apm-capabilities-tracing-ubuntu-latest 49.00% <0.00%> (-0.23%) ⬇️
apm-capabilities-tracing-ubuntu-maintenance 49.21% <0.00%> (-0.05%) ⬇️
apm-capabilities-tracing-ubuntu-oldest 49.02% <0.00%> (-0.23%) ⬇️
apm-capabilities-tracing-windows 48.82% <0.00%> (-0.23%) ⬇️
apm-integrations-aerospike-18-gte.5.2.0 34.42% <ø> (-0.13%) ⬇️
apm-integrations-aerospike-20-gte.5.5.0 34.43% <ø> (-0.13%) ⬇️
apm-integrations-aerospike-22-gte.5.12.1 34.43% <ø> (-0.13%) ⬇️
apm-integrations-aerospike-22-gte.6.0.0 34.43% <ø> (-0.13%) ⬇️
apm-integrations-aerospike-eol- 34.34% <ø> (-0.13%) ⬇️
apm-integrations-child-process 35.42% <ø> (-0.11%) ⬇️
apm-integrations-confluentinc-kafka-javascript-18 41.31% <100.00%> (-0.04%) ⬇️
apm-integrations-confluentinc-kafka-javascript-20 41.32% <100.00%> (-0.04%) ⬇️
apm-integrations-confluentinc-kafka-javascript-22 41.32% <100.00%> (-0.04%) ⬇️
apm-integrations-confluentinc-kafka-javascript-24 41.26% <100.00%> (-0.04%) ⬇️
apm-integrations-couchbase-18 34.59% <ø> (-0.13%) ⬇️
apm-integrations-couchbase-eol 34.70% <ø> (-0.05%) ⬇️
apm-integrations-dns 34.30% <ø> (-0.11%) ⬇️
apm-integrations-elasticsearch 35.06% <ø> (-0.12%) ⬇️
apm-integrations-http-latest 42.20% <ø> (-0.12%) ⬇️
apm-integrations-http-maintenance 42.26% <ø> (-0.12%) ⬇️
apm-integrations-http-oldest ?
apm-integrations-http2 39.68% <ø> (-0.14%) ⬇️
apm-integrations-kafkajs-latest 41.14% <100.00%> (-0.06%) ⬇️
apm-integrations-kafkajs-oldest 41.20% <100.00%> (-0.06%) ⬇️
apm-integrations-net 35.00% <ø> (-0.11%) ⬇️
apm-integrations-next-11.1.4 29.09% <ø> (-0.11%) ⬇️
apm-integrations-next-13.2.0 30.78% <ø> (-0.12%) ⬇️
apm-integrations-next-gte.10.2.0.and.lt.11 24.94% <ø> (-0.21%) ⬇️
apm-integrations-next-gte.11.0.0.and.lt.13 30.79% <ø> (-0.12%) ⬇️
apm-integrations-next-gte.13.0.0.and.lt.14 31.05% <ø> (-0.12%) ⬇️
apm-integrations-next-gte.14.0.0.and.lte.14.2.6 30.85% <ø> (-0.12%) ⬇️
apm-integrations-next-gte.14.2.7.and.lt.15 30.85% <ø> (-0.12%) ⬇️
apm-integrations-next-gte.15.0.0 30.91% <ø> (-0.12%) ⬇️
apm-integrations-oracledb 34.70% <ø> (-0.12%) ⬇️
apm-integrations-prisma-18-gte.6.16.0.and.lt.7.0.0 36.69% <ø> (-0.11%) ⬇️
apm-integrations-prisma-latest-all 35.52% <ø> (-0.11%) ⬇️
apm-integrations-restify 36.54% <ø> (-0.04%) ⬇️
apm-integrations-sharedb 33.98% <ø> (-0.11%) ⬇️
apm-integrations-tedious 34.50% <ø> (-0.12%) ⬇️
appsec-express 51.98% <ø> (-0.08%) ⬇️
appsec-fastify 48.66% <ø> (-0.09%) ⬇️
appsec-graphql 48.90% <ø> (-0.18%) ⬇️
appsec-integration-active 36.79% <ø> (-0.14%) ⬇️
appsec-integration-latest 36.76% <ø> (-0.14%) ⬇️
appsec-integration-maintenance 36.78% <ø> (-0.14%) ⬇️
appsec-integration-oldest 36.78% <ø> (-0.14%) ⬇️
appsec-kafka 41.61% <100.00%> (+0.01%) ⬆️
appsec-ldapjs 40.74% <ø> (-0.09%) ⬇️
appsec-lodash 40.85% <ø> (-0.09%) ⬇️
appsec-macos 55.67% <ø> (-0.09%) ⬇️
appsec-mongodb-core 45.18% <ø> (-0.08%) ⬇️
appsec-mongoose 46.01% <ø> (-0.10%) ⬇️
appsec-mysql 48.06% <ø> (+0.05%) ⬆️
appsec-next-latest-11.1.4 29.22% <ø> (-0.10%) ⬇️
appsec-next-latest-13.2.0 30.93% <ø> (-0.10%) ⬇️
appsec-next-latest-gte.10.2.0.and.lt.11 29.30% <ø> (-0.09%) ⬇️
appsec-next-latest-gte.11.0.0.and.lt.13 30.92% <ø> (-0.10%) ⬇️
appsec-next-latest-gte.13.0.0.and.lt.14 31.16% <ø> (-0.10%) ⬇️
appsec-next-latest-gte.14.0.0.and.lte.14.2.6 31.01% <ø> (-0.10%) ⬇️
appsec-next-latest-gte.14.2.7.and.lt.15 31.01% <ø> (-0.10%) ⬇️
appsec-next-latest-gte.15.0.0 31.01% <ø> (-0.10%) ⬇️
appsec-next-oldest-11.1.4 29.24% <ø> (-0.10%) ⬇️
appsec-next-oldest-13.2.0 31.21% <ø> (-0.10%) ⬇️
appsec-next-oldest-gte.10.2.0.and.lt.11 29.42% <ø> (-0.09%) ⬇️
appsec-next-oldest-gte.11.0.0.and.lt.13 30.93% <ø> (-0.10%) ⬇️
appsec-next-oldest-gte.13.0.0.and.lt.14 31.40% <ø> (-0.10%) ⬇️
appsec-next-oldest-gte.14.0.0.and.lte.14.2.6 31.25% <ø> (-0.10%) ⬇️
appsec-next-oldest-gte.14.2.7.and.lt.15 31.25% <ø> (-0.10%) ⬇️
appsec-next-oldest-gte.15.0.0 31.25% <ø> (-0.10%) ⬇️
appsec-node-serialize 40.07% <ø> (-0.09%) ⬇️
appsec-passport 43.77% <ø> (-0.10%) ⬇️
appsec-postgres 47.63% <ø> (-0.09%) ⬇️
appsec-sourcing 39.42% <ø> (-0.09%) ⬇️
appsec-stripe 41.57% <ø> (-0.11%) ⬇️
appsec-template 40.22% <ø> (-0.09%) ⬇️
appsec-ubuntu 55.74% <ø> (-0.09%) ⬇️
appsec-windows 55.60% <ø> (-0.10%) ⬇️
debugger-ubuntu-active 43.26% <ø> (-0.02%) ⬇️
debugger-ubuntu-latest 43.21% <ø> (-0.02%) ⬇️
debugger-ubuntu-maintenance 43.27% <ø> (-0.02%) ⬇️
debugger-ubuntu-oldest 43.76% <ø> (+0.03%) ⬆️
instrumentations-instrumentation-bluebird 29.34% <ø> (-0.11%) ⬇️
instrumentations-instrumentation-body-parser 37.00% <ø> (-0.11%) ⬇️
instrumentations-instrumentation-child_process 34.91% <ø> (-0.11%) ⬇️
instrumentations-instrumentation-cookie-parser 31.16% <ø> (-0.10%) ⬇️
instrumentations-instrumentation-express 31.37% <ø> (-0.10%) ⬇️
instrumentations-instrumentation-express-mongo-sanitize 31.27% <ø> (-0.10%) ⬇️
instrumentations-instrumentation-express-session 36.69% <ø> (-0.11%) ⬇️
instrumentations-instrumentation-fs 29.02% <ø> (-0.11%) ⬇️
instrumentations-instrumentation-generic-pool 29.15% <ø> (-0.03%) ⬇️
instrumentations-instrumentation-http 36.36% <ø> (-0.11%) ⬇️
instrumentations-instrumentation-knex 29.31% <ø> (-0.11%) ⬇️
instrumentations-instrumentation-light-my-request 36.62% <ø> (-0.11%) ⬇️
instrumentations-instrumentation-mongoose 30.42% <ø> (-0.10%) ⬇️
instrumentations-instrumentation-multer 36.79% <ø> (-0.11%) ⬇️
instrumentations-instrumentation-mysql2 34.94% <ø> (-0.11%) ⬇️
instrumentations-instrumentation-passport 40.43% <ø> (-0.10%) ⬇️
instrumentations-instrumentation-passport-http 40.21% <ø> (-0.10%) ⬇️
instrumentations-instrumentation-passport-local 40.69% <ø> (-0.10%) ⬇️
instrumentations-instrumentation-pg 34.53% <ø> (-0.11%) ⬇️
instrumentations-instrumentation-promise 29.27% <ø> (-0.11%) ⬇️
instrumentations-instrumentation-promise-js 29.28% <ø> (-0.11%) ⬇️
instrumentations-instrumentation-q 29.31% <ø> (-0.11%) ⬇️
instrumentations-instrumentation-url 29.28% <ø> (-0.11%) ⬇️
instrumentations-instrumentation-when 29.29% <ø> (-0.11%) ⬇️
instrumentations-integration-esbuild-active 19.37% <ø> (-0.07%) ⬇️
instrumentations-integration-esbuild-latest 19.36% <ø> (-0.07%) ⬇️
instrumentations-integration-esbuild-maintenance 19.37% <ø> (-0.07%) ⬇️
instrumentations-integration-esbuild-oldest 19.36% <ø> (-0.07%) ⬇️
llmobs-ai 37.83% <ø> (-0.10%) ⬇️
llmobs-anthropic 37.87% <ø> (-0.10%) ⬇️
llmobs-bedrock 36.59% <ø> (-0.09%) ⬇️
llmobs-google-genai 36.90% <ø> (-0.10%) ⬇️
llmobs-langchain 38.18% <ø> (+1.50%) ⬆️
llmobs-openai ?
llmobs-openai-latest 40.57% <ø> (?)
llmobs-openai-oldest 40.61% <ø> (?)
llmobs-sdk-active 45.48% <ø> (-0.11%) ⬇️
llmobs-sdk-latest 45.42% <ø> (-0.11%) ⬇️
llmobs-sdk-maintenance 45.48% <ø> (-0.11%) ⬇️
llmobs-sdk-oldest 45.47% <ø> (-0.11%) ⬇️
llmobs-vertex-ai 37.05% <ø> (-0.10%) ⬇️
openfeature-macos 38.03% <ø> (-0.02%) ⬇️
openfeature-ubuntu 38.11% <ø> (-0.02%) ⬇️
openfeature-unit-active 48.86% <ø> (-0.13%) ⬇️
openfeature-unit-latest 48.70% <ø> (-0.13%) ⬇️
openfeature-unit-maintenance 48.86% <ø> (-0.13%) ⬇️
openfeature-unit-oldest 48.86% <ø> (-0.13%) ⬇️
openfeature-windows 37.92% <ø> (-0.02%) ⬇️
platform-core 36.49% <ø> (-0.79%) ⬇️
platform-esbuild 39.87% <ø> (-0.74%) ⬇️
platform-instrumentations-misc 30.89% <ø> (+0.06%) ⬆️
platform-integration-active 47.02% <ø> (-0.07%) ⬇️
platform-integration-latest 47.03% <ø> (-0.03%) ⬇️
platform-integration-maintenance 47.08% <ø> (-0.03%) ⬇️
platform-integration-oldest 47.25% <ø> (-0.03%) ⬇️
platform-shimmer 41.68% <ø> (-0.62%) ⬇️
platform-unit-guardrails 35.56% <ø> (-0.56%) ⬇️
platform-webpack 20.50% <ø> (-0.07%) ⬇️
plugins-azure-durable-functions 37.04% <ø> (-0.02%) ⬇️
plugins-azure-event-hubs 34.90% <ø> (-0.02%) ⬇️
plugins-azure-service-bus 35.35% <ø> (-0.02%) ⬇️
plugins-bullmq 40.26% <ø> (+<0.01%) ⬆️
plugins-cassandra 34.84% <ø> (-0.12%) ⬇️
plugins-cookie 28.72% <ø> (-0.29%) ⬇️
plugins-cookie-parser 28.53% <ø> (-0.28%) ⬇️
plugins-crypto 27.19% <ø> (-0.51%) ⬇️
plugins-dd-trace-api 34.82% <ø> (-0.11%) ⬇️
plugins-express-mongo-sanitize 28.64% <ø> (-0.28%) ⬇️
plugins-express-session 28.49% <ø> (-0.28%) ⬇️
plugins-fastify 38.76% <ø> (-0.11%) ⬇️
plugins-fetch ?
plugins-fs 35.19% <ø> (-0.11%) ⬇️
plugins-generic-pool 27.69% <ø> (-0.27%) ⬇️
plugins-google-cloud-pubsub 42.44% <ø> (-0.13%) ⬇️
plugins-grpc 37.51% <ø> (-0.12%) ⬇️
plugins-handlebars 28.68% <ø> (-0.28%) ⬇️
plugins-hapi 36.80% <ø> (+0.02%) ⬆️
plugins-hono 36.95% <ø> (-0.11%) ⬇️
plugins-ioredis 35.29% <ø> (-0.13%) ⬇️
plugins-knex 28.29% <ø> (-0.27%) ⬇️
plugins-langgraph 34.54% <ø> (-0.11%) ⬇️
plugins-ldapjs 26.30% <ø> (-0.25%) ⬇️
plugins-light-my-request 28.09% <ø> (-0.27%) ⬇️
plugins-limitd-client 29.63% <ø> (-0.05%) ⬇️
plugins-lodash 27.78% <ø> (-0.28%) ⬇️
plugins-mariadb 36.20% <ø> (-0.13%) ⬇️
plugins-memcached 34.91% <ø> (-0.13%) ⬇️
plugins-microgateway-core 35.79% <ø> (-0.11%) ⬇️
plugins-modelcontextprotocol-sdk 33.83% <ø> (-0.11%) ⬇️
plugins-moleculer 37.60% <ø> (-0.13%) ⬇️
plugins-mongodb 36.22% <ø> (-0.02%) ⬇️
plugins-mongodb-core 35.85% <ø> (-0.15%) ⬇️
plugins-mongoose 35.61% <ø> (-0.11%) ⬇️
plugins-multer 28.49% <ø> (-0.28%) ⬇️
plugins-mysql 35.92% <ø> (-0.13%) ⬇️
plugins-mysql2 36.00% <ø> (-0.13%) ⬇️
plugins-node-serialize 28.76% <ø> (-0.29%) ⬇️
plugins-opensearch 34.59% <ø> (-0.12%) ⬇️
plugins-passport-http 28.51% <ø> (-0.28%) ⬇️
plugins-pino 31.27% <ø> (-0.11%) ⬇️
plugins-postgres 33.97% <ø> (-0.12%) ⬇️
plugins-process 27.19% <ø> (-0.51%) ⬇️
plugins-pug 28.72% <ø> (-0.29%) ⬇️
plugins-redis 35.34% <ø> (-0.07%) ⬇️
plugins-router 39.10% <ø> (-0.24%) ⬇️
plugins-sequelize 27.48% <ø> (-0.27%) ⬇️
plugins-test-and-upstream-amqp10 35.15% <ø> (-0.13%) ⬇️
plugins-test-and-upstream-amqplib 40.31% <ø> (-0.14%) ⬇️
plugins-test-and-upstream-apollo 36.07% <ø> (-0.12%) ⬇️
plugins-test-and-upstream-avsc 34.51% <ø> (-0.10%) ⬇️
plugins-test-and-upstream-bunyan 30.65% <ø> (-0.11%) ⬇️
plugins-test-and-upstream-connect 37.33% <ø> (-0.11%) ⬇️
plugins-test-and-upstream-graphql 37.12% <ø> (-0.11%) ⬇️
plugins-test-and-upstream-koa 36.93% <ø> (-0.11%) ⬇️
plugins-test-and-upstream-protobufjs 34.72% <ø> (-0.10%) ⬇️
plugins-test-and-upstream-rhea 40.32% <ø> (-0.14%) ⬇️
plugins-undici 35.80% <ø> (+<0.01%) ⬆️
plugins-url 27.19% <ø> (-0.51%) ⬇️
plugins-valkey 34.93% <ø> (-0.13%) ⬇️
plugins-vm 27.19% <ø> (-0.51%) ⬇️
plugins-winston 31.15% <ø> (-0.11%) ⬇️
plugins-ws 38.29% <ø> (-0.11%) ⬇️
profiling-macos 42.74% <ø> (-0.09%) ⬇️
profiling-ubuntu 43.09% <ø> (-0.09%) ⬇️
profiling-windows 40.05% <ø> (-0.08%) ⬇️
serverless-aws-sdk-latest-aws-sdk 34.67% <ø> (-0.09%) ⬇️
serverless-aws-sdk-latest-bedrockruntime 33.14% <ø> (-0.10%) ⬇️
serverless-aws-sdk-latest-client 24.35% <ø> (-0.21%) ⬇️
serverless-aws-sdk-latest-dynamodb 35.76% <ø> (-0.10%) ⬇️
serverless-aws-sdk-latest-eventbridge 28.74% <ø> (-0.09%) ⬇️
serverless-aws-sdk-latest-kinesis 38.64% <ø> (-0.12%) ⬇️
serverless-aws-sdk-latest-lambda 35.92% <ø> (-0.11%) ⬇️
serverless-aws-sdk-latest-s3 33.89% <ø> (-0.11%) ⬇️
serverless-aws-sdk-latest-serverless-peer-service 40.01% <ø> (-0.12%) ⬇️
serverless-aws-sdk-latest-sns 39.73% <ø> (-0.12%) ⬇️
serverless-aws-sdk-latest-sqs 39.01% <ø> (-0.12%) ⬇️
serverless-aws-sdk-latest-stepfunctions 34.51% <ø> (-0.10%) ⬇️
serverless-aws-sdk-latest-util 48.37% <ø> (-0.30%) ⬇️
serverless-aws-sdk-oldest-aws-sdk 34.76% <ø> (-0.09%) ⬇️
serverless-aws-sdk-oldest-bedrockruntime 33.42% <ø> (-0.10%) ⬇️
serverless-aws-sdk-oldest-client 24.70% <ø> (-0.21%) ⬇️
serverless-aws-sdk-oldest-dynamodb 35.83% <ø> (-0.10%) ⬇️
serverless-aws-sdk-oldest-eventbridge 28.78% <ø> (-0.09%) ⬇️
serverless-aws-sdk-oldest-kinesis 38.77% <ø> (-0.12%) ⬇️
serverless-aws-sdk-oldest-lambda 36.00% <ø> (-0.11%) ⬇️
serverless-aws-sdk-oldest-s3 33.96% <ø> (-0.11%) ⬇️
serverless-aws-sdk-oldest-serverless-peer-service 40.07% <ø> (-0.12%) ⬇️
serverless-aws-sdk-oldest-sns 39.81% <ø> (-0.24%) ⬇️
serverless-aws-sdk-oldest-sqs 38.90% <ø> (-0.12%) ⬇️
serverless-aws-sdk-oldest-stepfunctions 34.59% <ø> (-0.10%) ⬇️
serverless-aws-sdk-oldest-util 48.68% <ø> (-0.30%) ⬇️
serverless-azure-functions-eventhubs 38.51% <ø> (-0.02%) ⬇️
serverless-azure-functions-servicebus 38.57% <ø> (-0.02%) ⬇️
serverless-lambda 34.49% <ø> (-0.11%) ⬇️
test-optimization-cucumber-latest-7.0.0 50.28% <ø> (-0.01%) ⬇️
test-optimization-cucumber-latest-latest 52.99% <ø> (+0.10%) ⬆️
test-optimization-cucumber-oldest-7.0.0 50.32% <ø> (+0.10%) ⬆️
test-optimization-cypress-latest-12.0.0-commonJS 48.67% <ø> (+0.05%) ⬆️
test-optimization-cypress-latest-12.0.0-esm 48.70% <ø> (+0.05%) ⬆️
test-optimization-cypress-latest-14.5.4-commonJS 48.52% <ø> (+0.05%) ⬆️
test-optimization-cypress-latest-14.5.4-esm 48.55% <ø> (+0.05%) ⬆️
test-optimization-cypress-latest-latest-commonJS 49.01% <ø> (+0.05%) ⬆️
test-optimization-cypress-latest-latest-esm 49.04% <ø> (+0.05%) ⬆️
test-optimization-cypress-oldest-12.0.0-commonJS 48.71% <ø> (+0.05%) ⬆️
test-optimization-cypress-oldest-12.0.0-esm 48.74% <ø> (+0.05%) ⬆️
test-optimization-cypress-oldest-14.5.4-commonJS 48.55% <ø> (+0.05%) ⬆️
test-optimization-cypress-oldest-14.5.4-esm 48.58% <ø> (+0.05%) ⬆️
test-optimization-jest-latest-latest 54.75% <ø> (+0.06%) ⬆️
test-optimization-jest-latest-oldest 53.57% <ø> (+0.07%) ⬆️
test-optimization-jest-oldest-latest 54.76% <ø> (+0.06%) ⬆️
test-optimization-jest-oldest-oldest 53.51% <ø> (+0.07%) ⬆️
test-optimization-mocha-latest-latest 53.37% <ø> (+0.06%) ⬆️
test-optimization-mocha-latest-oldest 50.99% <ø> (+0.06%) ⬆️
test-optimization-mocha-oldest-latest 53.43% <ø> (+0.06%) ⬆️
test-optimization-mocha-oldest-oldest 50.93% <ø> (+0.06%) ⬆️
test-optimization-playwright-latest-latest-playwright-active-test-span 44.22% <ø> (+0.27%) ⬆️
test-optimization-playwright-latest-latest-playwright-atr 42.99% <ø> (+0.09%) ⬆️
test-optimization-playwright-latest-latest-playwright-efd 43.42% <ø> (+0.08%) ⬆️
test-optimization-playwright-latest-latest-playwright-final-status 43.46% <ø> (+0.09%) ⬆️
test-optimization-playwright-latest-latest-playwright-impacted-tests 42.92% <ø> (-0.02%) ⬇️
test-optimization-playwright-latest-latest-playwright-reporting 42.87% <ø> (+0.08%) ⬆️
test-optimization-playwright-latest-latest-playwright-test-management 44.64% <ø> (+0.09%) ⬆️
test-optimization-playwright-latest-oldest-playwright-active-test-span 44.29% <ø> (+0.26%) ⬆️
test-optimization-playwright-latest-oldest-playwright-atr 43.20% <ø> (+0.10%) ⬆️
test-optimization-playwright-latest-oldest-playwright-efd 43.47% <ø> (+0.08%) ⬆️
test-optimization-playwright-latest-oldest-playwright-final-status 43.49% <ø> (+0.10%) ⬆️
test-optimization-playwright-latest-oldest-playwright-impacted-tests 42.97% <ø> (-0.02%) ⬇️
test-optimization-playwright-latest-oldest-playwright-reporting 42.94% <ø> (+0.08%) ⬆️
test-optimization-playwright-latest-oldest-playwright-test-management 44.72% <ø> (+0.09%) ⬆️
test-optimization-playwright-oldest-latest-playwright-active-test-span 44.26% <ø> (+0.27%) ⬆️
test-optimization-playwright-oldest-latest-playwright-atr 43.03% <ø> (+0.09%) ⬆️
test-optimization-playwright-oldest-latest-playwright-efd 43.43% <ø> (+0.08%) ⬆️
test-optimization-playwright-oldest-latest-playwright-final-status 43.47% <ø> (+0.09%) ⬆️
test-optimization-playwright-oldest-latest-playwright-impacted-tests 42.96% <ø> (-0.02%) ⬇️
test-optimization-playwright-oldest-latest-playwright-reporting 42.88% <ø> (+0.08%) ⬆️
test-optimization-playwright-oldest-latest-playwright-test-management 44.66% <ø> (+0.09%) ⬆️
test-optimization-playwright-oldest-oldest-playwright-active-test-span 44.33% <ø> (+0.26%) ⬆️
test-optimization-playwright-oldest-oldest-playwright-atr 43.24% <ø> (+0.10%) ⬆️
test-optimization-playwright-oldest-oldest-playwright-efd 43.51% <ø> (+0.10%) ⬆️
test-optimization-playwright-oldest-oldest-playwright-final-status 43.53% <ø> (+0.10%) ⬆️
test-optimization-playwright-oldest-oldest-playwright-impacted-tests 43.01% <ø> (-0.02%) ⬇️
test-optimization-playwright-oldest-oldest-playwright-reporting 42.95% <ø> (+0.08%) ⬆️
test-optimization-playwright-oldest-oldest-playwright-test-management 44.73% <ø> (+0.09%) ⬆️
test-optimization-selenium-latest 45.56% <ø> (+0.07%) ⬆️
test-optimization-selenium-oldest 45.05% <ø> (+0.06%) ⬆️
test-optimization-testopt-active 46.87% <ø> (+0.12%) ⬆️
test-optimization-testopt-latest 46.84% <ø> (+0.12%) ⬆️
test-optimization-testopt-maintenance 46.87% <ø> (+0.12%) ⬆️
test-optimization-testopt-oldest 47.73% <ø> (+0.14%) ⬆️
test-optimization-vitest-latest 50.96% <ø> (+0.09%) ⬆️
test-optimization-vitest-oldest 47.99% <ø> (+0.34%) ⬆️

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

☔ View full report in Codecov by Sentry.
📢 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.

…messages.offsets

Replace single-value kafka.partition and kafka.message.offset on the
producer span with a single kafka.messages.offsets tag carrying the full
list of (partition, start_offset) pairs returned by the broker. One
producer span covers a multi-partition batch, so a list captures every
partition the batch landed in rather than only the single-partition
case.

Total record count is still available via the pre-existing
kafka.batch_size metric. Per-partition record counts are not in the
broker response and would require replicating kafkajs's partitioner, so
they are not included.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
@piochelepiotr piochelepiotr changed the title feat(kafkajs): tag producer span with partition and offset feat(kafkajs): add kafka.messages.offsets to producer span May 13, 2026
piochelepiotr and others added 3 commits May 13, 2026 11:40
Restore kafka.partition and kafka.message.offset flat tags on the
producer span, but only when the send is a single-record batch. In that
case the one response entry's partition/offset describes the exact
record, so the flat tags are unambiguous. Multi-message batches still
expose data through kafka.messages.offsets only.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
Mirror the producer change on the batch consumer side. kafkajs's
eachBatch hands the user one batch per (topic, partition), so all
records share a single partition and a known start offset (the first
record's offset). Tag kafka.messages.offsets accordingly, and surface
the flat kafka.message.offset metric only when the batch is a single
record — matching the producer's single-record convention.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
@pr-commenter

pr-commenter Bot commented May 13, 2026

Copy link
Copy Markdown

Benchmarks

Benchmark execution time: 2026-05-14 15:07:46

Comparing candidate commit 1f113ca in PR branch kafkajs-producer-partition-offset with baseline commit 56b6048 in branch master.

Found 0 performance improvements and 0 performance regressions! Performance is the same for 1508 metrics, 85 unstable metrics.

…er span"

eachBatch is always single-partition by construction, so the list-form
tag is redundant with the flat kafka.partition that's already set on
the consume-batch span. Revert and leave the consume-batch span as it
was on master.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
chnn
chnn previously approved these changes May 13, 2026

@crysmags crysmags left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Two blockers, a few nits and otherwise this looks good.

offsets are 64-bit, Number(longValue) rounds past 2^53. on busy topics this breaks the correlation the PR is trying to make, codex flagged the same thing above

the multi-message test still produces a single-entry list because the topic is single-partition — so the headline case isn't covered.

for (const entry of result) {
const offsetAsLong = entry.offset || entry.baseOffset
if (entry.partition === undefined || offsetAsLong === undefined) continue
offsets.push({ partition: entry.partition, start_offset: Number(offsetAsLong) })

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

this loses precision on long-lived topics. keep it a string:

offsets.push({ partition: entry.partition, start_offset: String(offsetAsLong) })

flat kafka.message.offset has the same issue but it's a metric in the test, so it has to move to meta to be safe. fine as a follow-up if you'd rather keep this PR small.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Also, can you add a regression test that exercises a > 2^53 offset? commit.spec.js uses sinon and you could too to mock this.

// response, only the starting offset.
const offsets = []
for (const entry of result) {
const offsetAsLong = entry.offset || entry.baseOffset

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

?? instead of || here — defensive against a literal 0.

offsets.push({ partition: entry.partition, start_offset: Number(offsetAsLong) })
}
if (offsets.length > 0) {
span.setTag('kafka.messages.offsets', JSON.stringify(offsets))

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

sort by partition before stringify so the tag is deterministic:

offsets.sort((a, b) => a.partition - b.partition)

assertObjectContains(span, {
name: expectedSchema.send.opName,
meta: {
'kafka.messages.offsets': JSON.stringify([{ partition: 0, start_offset: 0 }]),

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

this test still asserts a one-entry list:

'kafka.messages.offsets': JSON.stringify([{ partition: 0, start_offset: 0 }])

multi-partition isn't actually covered. either bump the topic to numPartitions: 3 or stub ctx.result directly.

Keep offsets as strings to avoid Number() precision loss past 2^53,
sort by partition for deterministic tag output, and use ?? so a
literal-zero baseOffset isn't dropped. Move kafka.message.offset from
metric to meta to match the string type.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>

@crysmags crysmags left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Good to go 🫡

@piochelepiotr
piochelepiotr merged commit 94c1d4e into master May 14, 2026
932 of 933 checks passed
@piochelepiotr
piochelepiotr deleted the kafkajs-producer-partition-offset branch May 14, 2026 15:18
@dd-octo-sts dd-octo-sts Bot mentioned this pull request May 15, 2026
BridgeAR added a commit that referenced this pull request May 19, 2026
This adds tracing, header injection, and DSM checkpoints for the
kafkajs producer.sendBatch API, which was previously uninstrumented:
calls produced no spans and propagated no trace or DSM context.

One kafka.produce span per topicMessages[] entry matches what
producer.send already emits for the single-topic case and mirrors
how kafkajs implements send internally (producer/messageProducer.js
delegates send to a single-entry sendBatch). Per-entry granularity
keeps DSM checkpoints correct on multi-topic batches. The broker
response is one aggregated array covering every topic, so the
plugin's finish filters by ctx.topic before tagging
kafka.messages.offsets; otherwise each per-topic span would carry
every other topic's offsets.

Fixes: #1711
Refs: #8468
BridgeAR added a commit that referenced this pull request May 19, 2026
This adds tracing, header injection, and DSM checkpoints for the
kafkajs producer.sendBatch API, which was previously uninstrumented:
calls produced no spans and propagated no trace or DSM context.

One kafka.produce span per topicMessages[] entry matches what
producer.send already emits for the single-topic case and mirrors
how kafkajs implements send internally (producer/messageProducer.js
delegates send to a single-entry sendBatch). Per-entry granularity
keeps DSM checkpoints correct on multi-topic batches. The broker
response is one aggregated array covering every topic, so the
plugin's finish filters by ctx.topic before tagging
kafka.messages.offsets; otherwise each per-topic span would carry
every other topic's offsets.

Fixes: #1711
Refs: #8468
pabloerhard pushed a commit that referenced this pull request May 20, 2026
* feat(kafkajs): tag producer span with partition and offset

Mirror dd-trace-java PR #11107 for kafkajs: set kafka.partition and
kafka.message.offset on the producer span using the broker-returned
RecordMetadata. Only tagged when the send resolves to a single
(topic, partition) tuple, since one producer span covers a
multi-partition batch and a single value would be misleading.

kafka.cluster_id was already tagged on both producer and consumer spans,
and the consumer span already carried partition and offset, so no
additional changes are needed for those tags.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>

* refactor(kafkajs): replace producer partition/offset tags with kafka.messages.offsets

Replace single-value kafka.partition and kafka.message.offset on the
producer span with a single kafka.messages.offsets tag carrying the full
list of (partition, start_offset) pairs returned by the broker. One
producer span covers a multi-partition batch, so a list captures every
partition the batch landed in rather than only the single-partition
case.

Total record count is still available via the pre-existing
kafka.batch_size metric. Per-partition record counts are not in the
broker response and would require replicating kafkajs's partitioner, so
they are not included.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>

* test(kafkajs): cover single- and multi-message producer batches

Restore kafka.partition and kafka.message.offset flat tags on the
producer span, but only when the send is a single-record batch. In that
case the one response entry's partition/offset describes the exact
record, so the flat tags are unambiguous. Multi-message batches still
expose data through kafka.messages.offsets only.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>

* feat(kafkajs): add kafka.messages.offsets to eachBatch consumer span

Mirror the producer change on the batch consumer side. kafkajs's
eachBatch hands the user one batch per (topic, partition), so all
records share a single partition and a known start offset (the first
record's offset). Tag kafka.messages.offsets accordingly, and surface
the flat kafka.message.offset metric only when the batch is a single
record — matching the producer's single-record convention.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>

* fix(kafkajs): flip negated condition to satisfy lint

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>

* Revert "feat(kafkajs): add kafka.messages.offsets to eachBatch consumer span"

eachBatch is always single-partition by construction, so the list-form
tag is redundant with the flat kafka.partition that's already set on
the consume-batch span. Revert and leave the consume-batch span as it
was on master.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>

* fix(kafkajs): preserve 64-bit precision in producer offset tags

Keep offsets as strings to avoid Number() precision loss past 2^53,
sort by partition for deterministic tag output, and use ?? so a
literal-zero baseOffset isn't dropped. Move kafka.message.offset from
metric to meta to match the string type.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <[email protected]>
BridgeAR added a commit that referenced this pull request May 22, 2026
This adds tracing, header injection, and DSM checkpoints for the
kafkajs producer.sendBatch API, which was previously uninstrumented:
calls produced no spans and propagated no trace or DSM context.

One kafka.produce span per topicMessages[] entry matches what
producer.send already emits for the single-topic case and mirrors
how kafkajs implements send internally (producer/messageProducer.js
delegates send to a single-entry sendBatch). Per-entry granularity
keeps DSM checkpoints correct on multi-topic batches. The broker
response is one aggregated array covering every topic, so the
plugin's finish filters by ctx.topic before tagging
kafka.messages.offsets; otherwise each per-topic span would carry
every other topic's offsets.

Fixes: #1711
Refs: #8468
BridgeAR added a commit that referenced this pull request May 26, 2026
* feat(kafkajs): instrument producer.sendBatch

This adds tracing, header injection, and DSM checkpoints for the
kafkajs producer.sendBatch API, which was previously uninstrumented:
calls produced no spans and propagated no trace or DSM context.

One kafka.produce span per topicMessages[] entry matches what
producer.send already emits for the single-topic case and mirrors
how kafkajs implements send internally (producer/messageProducer.js
delegates send to a single-entry sendBatch). Per-entry granularity
keeps DSM checkpoints correct on multi-topic batches. The broker
response is one aggregated array covering every topic, so the
plugin's finish filters by ctx.topic before tagging
kafka.messages.offsets; otherwise each per-topic span would carry
every other topic's offsets.

Fixes: #1711
Refs: #8468

* test(kafkajs): cover producer-boundary slow path and disable arms

The integration spec in `packages/datadog-plugin-kafkajs/test/index.spec.js`
needs Docker Kafka and always connects the producer before sending, so
the slow-path branch through `cluster.refreshMetadataIfNecessary().then(...)`
and its `refreshMetadataIfNecessary`-missing fallback never ran outside
that one CI flag. This adds an instrumentation-level spec that drives
the boundary with a faked cluster, pinning the resolve / reject / missing
arms of the slow path, the proactive and reactive `refreshHeaderSupport`
disable, and the `producerStartCh.hasSubscribers` fast skip without
spinning a broker.

Refs: #8390

* ci(kafkajs): run the datadog-instrumentations spec in CI

The instrumentation-level spec at
`packages/datadog-instrumentations/test/kafkajs.spec.js` is matched
by `test:instrumentations`'s `PLUGINS=kafkajs` glob, but no
workflow runs it. Without a job, codecov sees no session for the
producer-boundary slow path or the `refreshHeaderSupport` disable
arms.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants