From 2d395097f6a24521050003648a164f9e48aa8e28 Mon Sep 17 00:00:00 2001 From: William Conti Date: Mon, 11 Dec 2023 14:08:02 -0500 Subject: [PATCH 01/52] fix aws tests --- .github/workflows/plugins.yml | 23 +++++++++++++++--- docker-compose.yml | 24 ++++++++++++++++--- .../test/lambda.spec.js | 2 +- .../datadog-plugin-aws-sdk/test/s3.spec.js | 2 +- 4 files changed, 43 insertions(+), 8 deletions(-) diff --git a/.github/workflows/plugins.yml b/.github/workflows/plugins.yml index 2e2aa3b5764..7fdf623df79 100644 --- a/.github/workflows/plugins.yml +++ b/.github/workflows/plugins.yml @@ -161,9 +161,9 @@ jobs: runs-on: ubuntu-latest services: localstack: - image: localstack/localstack:1.1.0 + image: localstack/localstack:3.0.2 env: - LOCALSTACK_SERVICES: dynamodb,kinesis,s3,sqs,sns,redshift,route53,logs,serverless + LOCALSTACK_SERVICES: dynamodb,kinesis,s3,sqs,sns,redshift,route53,logs,serverless,lambda EXTRA_CORS_ALLOWED_HEADERS: x-amz-request-id,x-amzn-requestid,x-amz-id-2 EXTRA_CORS_EXPOSE_HEADERS: x-amz-request-id,x-amzn-requestid,x-amz-id-2 AWS_DEFAULT_REGION: us-east-1 @@ -172,9 +172,26 @@ jobs: START_WEB: '0' ports: - 4566:4566 + # we have two localstacks since upgrading localstack was causing lambda & S3 tests to fail + # To-Do: Debug localstack / lambda and localstack / S3 + localstack-legacy: + image: localstack/localstack:1.1.0 + ports: + - "127.0.0.1:4567:4567" # Edge + env: + LOCALSTACK_SERVICES: dynamodb,kinesis,s3,sqs,sns,redshift,route53,logs,serverless + EXTRA_CORS_ALLOWED_HEADERS: x-amz-request-id,x-amzn-requestid,x-amz-id-2 + EXTRA_CORS_EXPOSE_HEADERS: x-amz-request-id,x-amzn-requestid,x-amz-id-2 + AWS_DEFAULT_REGION: us-east-1 + FORCE_NONINTERACTIVE: 'true' + LAMBDA_EXECUTOR: local + START_WEB: '0' + GATEWAY_LISTEN: 127.0.0.1:4567 + EDGE_PORT: 4567 + EDGE_PORT_HTTP: 4567 env: PLUGINS: aws-sdk - SERVICES: localstack + SERVICES: localstack localstack-legacy steps: - uses: actions/checkout@v2 - uses: ./.github/actions/testagent/start diff --git a/docker-compose.yml b/docker-compose.yml index 2ff0e15120a..ed2cb8dfda9 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -87,11 +87,26 @@ services: ports: - "127.0.0.1:8081:8081" localstack: - # TODO: Figure out why SNS doesn't work in >=1.2 - # https://github.com/localstack/localstack/issues/7479 - image: localstack/localstack:1.1.0 + image: localstack/localstack:3.0.2 ports: - "127.0.0.1:4566:4566" # Edge + environment: + - LOCALSTACK_SERVICES=dynamodb,kinesis,s3,sqs,sns,redshift,route53,logs,serverless,lambda + - EXTRA_CORS_ALLOWED_HEADERS=x-amz-request-id,x-amzn-requestid,x-amz-id-2 + - EXTRA_CORS_EXPOSE_HEADERS=x-amz-request-id,x-amzn-requestid,x-amz-id-2 + - AWS_DEFAULT_REGION=us-east-1 + - FORCE_NONINTERACTIVE=true + - START_WEB=0 + - DEBUG=${DEBUG-} + - DOCKER_HOST=unix:///var/run/docker.sock + volumes: + - "/var/run/docker.sock:/var/run/docker.sock" + localstack-legacy: + # we have two localstacks since upgrading localstack was causing lambda & S3 tests to fail + # To-Do: Debug localstack / lambda and localstack / S3 + image: localstack/localstack:1.1.0 + ports: + - "127.0.0.1:4567:4567" # Edge environment: - LOCALSTACK_SERVICES=dynamodb,kinesis,s3,sqs,sns,redshift,route53,logs,serverless - EXTRA_CORS_ALLOWED_HEADERS=x-amz-request-id,x-amzn-requestid,x-amz-id-2 @@ -99,6 +114,9 @@ services: - AWS_DEFAULT_REGION=us-east-1 - FORCE_NONINTERACTIVE=true - START_WEB=0 + - GATEWAY_LISTEN=127.0.0.1:4567 + - EDGE_PORT=4567 + - EDGE_PORT_HTTP=4567 - LAMBDA_EXECUTOR=local kafka: image: debezium/kafka:1.7 diff --git a/packages/datadog-plugin-aws-sdk/test/lambda.spec.js b/packages/datadog-plugin-aws-sdk/test/lambda.spec.js index 2bfafed17e7..4ccf3b8b46e 100644 --- a/packages/datadog-plugin-aws-sdk/test/lambda.spec.js +++ b/packages/datadog-plugin-aws-sdk/test/lambda.spec.js @@ -40,7 +40,7 @@ describe('Plugin', () => { before(done => { AWS = require(`../../../versions/${lambdaClientName}@${version}`).get() - lambda = new AWS.Lambda({ endpoint: 'http://127.0.0.1:4566', region: 'us-east-1' }) + lambda = new AWS.Lambda({ endpoint: 'http://127.0.0.1:4567', region: 'us-east-1' }) lambda.createFunction({ FunctionName: 'ironmaiden', Code: { ZipFile }, diff --git a/packages/datadog-plugin-aws-sdk/test/s3.spec.js b/packages/datadog-plugin-aws-sdk/test/s3.spec.js index 21165ce7b3f..ab1e3911047 100644 --- a/packages/datadog-plugin-aws-sdk/test/s3.spec.js +++ b/packages/datadog-plugin-aws-sdk/test/s3.spec.js @@ -37,7 +37,7 @@ describe('Plugin', () => { before(done => { AWS = require(`../../../versions/${s3ClientName}@${version}`).get() - s3 = new AWS.S3({ endpoint: 'http://127.0.0.1:4566', s3ForcePathStyle: true, region: 'us-east-1' }) + s3 = new AWS.S3({ endpoint: 'http://127.0.0.1:4567', s3ForcePathStyle: true, region: 'us-east-1' }) s3.createBucket({ Bucket: bucketName }, (err) => { if (err) return done(err) done() From 8f7f569ea191f147b5715f68e63b94a667bf620d Mon Sep 17 00:00:00 2001 From: William Conti Date: Mon, 11 Dec 2023 15:26:23 -0500 Subject: [PATCH 02/52] fix kinesis timeouts --- packages/datadog-plugin-aws-sdk/test/kinesis.spec.js | 4 ++-- .../datadog-plugin-aws-sdk/test/kinesis_helpers.js | 10 +++++++--- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/packages/datadog-plugin-aws-sdk/test/kinesis.spec.js b/packages/datadog-plugin-aws-sdk/test/kinesis.spec.js index 41d76d61236..5c2a7642f56 100644 --- a/packages/datadog-plugin-aws-sdk/test/kinesis.spec.js +++ b/packages/datadog-plugin-aws-sdk/test/kinesis.spec.js @@ -19,7 +19,7 @@ describe('Kinesis', () => { return agent.load('aws-sdk') }) - before(done => { + before(function (done) { AWS = require(`../../../versions/${kinesisClientName}@${version}`).get() const params = { @@ -40,7 +40,7 @@ describe('Kinesis', () => { }, (err, res) => { if (err) return done(err) - helpers.waitForActiveStream(kinesis, done) + helpers.waitForActiveStream(this, kinesis, done) }) }) diff --git a/packages/datadog-plugin-aws-sdk/test/kinesis_helpers.js b/packages/datadog-plugin-aws-sdk/test/kinesis_helpers.js index f9f61ada0bf..bcf1e8d0372 100644 --- a/packages/datadog-plugin-aws-sdk/test/kinesis_helpers.js +++ b/packages/datadog-plugin-aws-sdk/test/kinesis_helpers.js @@ -45,12 +45,16 @@ function putTestRecord (kinesis, data, cb) { }, cb) } -function waitForActiveStream (kinesis, cb) { +function waitForActiveStream (mocha, kinesis, cb) { kinesis.describeStream({ StreamName: 'MyStream' }, (err, data) => { - if (err) return waitForActiveStream(kinesis, cb) + if (err) { + mocha.timeout(250) + return waitForActiveStream(kinesis, cb) + } if (data.StreamDescription.StreamStatus !== 'ACTIVE') { + mocha.timeout(250) return waitForActiveStream(kinesis, cb) } @@ -62,7 +66,7 @@ function waitForDeletedStream (kinesis, cb) { kinesis.describeStream({ StreamName: 'MyStream' }, (err, data) => { - if (!err) return waitForDeletedStream(kinesis, cb) + if (err) return waitForDeletedStream(kinesis, cb) cb() }) } From bbc5897e2a2241fb99196f2cb650e12e646668e5 Mon Sep 17 00:00:00 2001 From: William Conti Date: Mon, 11 Dec 2023 15:30:33 -0500 Subject: [PATCH 03/52] fix error --- packages/datadog-plugin-aws-sdk/test/kinesis_helpers.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/datadog-plugin-aws-sdk/test/kinesis_helpers.js b/packages/datadog-plugin-aws-sdk/test/kinesis_helpers.js index bcf1e8d0372..40f01d95e0a 100644 --- a/packages/datadog-plugin-aws-sdk/test/kinesis_helpers.js +++ b/packages/datadog-plugin-aws-sdk/test/kinesis_helpers.js @@ -51,11 +51,11 @@ function waitForActiveStream (mocha, kinesis, cb) { }, (err, data) => { if (err) { mocha.timeout(250) - return waitForActiveStream(kinesis, cb) + return waitForActiveStream(mocha, kinesis, cb) } if (data.StreamDescription.StreamStatus !== 'ACTIVE') { mocha.timeout(250) - return waitForActiveStream(kinesis, cb) + return waitForActiveStream(mocha, kinesis, cb) } cb() From 51fe16622b97eba1f1bc4717f2d4a294162750b5 Mon Sep 17 00:00:00 2001 From: William Conti Date: Mon, 11 Dec 2023 15:35:37 -0500 Subject: [PATCH 04/52] increase timeout --- packages/datadog-plugin-aws-sdk/test/kinesis_helpers.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/datadog-plugin-aws-sdk/test/kinesis_helpers.js b/packages/datadog-plugin-aws-sdk/test/kinesis_helpers.js index 40f01d95e0a..2977e3e3dfa 100644 --- a/packages/datadog-plugin-aws-sdk/test/kinesis_helpers.js +++ b/packages/datadog-plugin-aws-sdk/test/kinesis_helpers.js @@ -50,11 +50,11 @@ function waitForActiveStream (mocha, kinesis, cb) { StreamName: 'MyStream' }, (err, data) => { if (err) { - mocha.timeout(250) + mocha.timeout(2000) return waitForActiveStream(mocha, kinesis, cb) } if (data.StreamDescription.StreamStatus !== 'ACTIVE') { - mocha.timeout(250) + mocha.timeout(2000) return waitForActiveStream(mocha, kinesis, cb) } From 107e02bd911c4f530a96bf15724b56aabffdfbfc Mon Sep 17 00:00:00 2001 From: William Conti Date: Mon, 11 Dec 2023 16:02:04 -0500 Subject: [PATCH 05/52] try again --- packages/datadog-plugin-aws-sdk/test/kinesis_helpers.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/datadog-plugin-aws-sdk/test/kinesis_helpers.js b/packages/datadog-plugin-aws-sdk/test/kinesis_helpers.js index 2977e3e3dfa..f76e6119251 100644 --- a/packages/datadog-plugin-aws-sdk/test/kinesis_helpers.js +++ b/packages/datadog-plugin-aws-sdk/test/kinesis_helpers.js @@ -66,7 +66,7 @@ function waitForDeletedStream (kinesis, cb) { kinesis.describeStream({ StreamName: 'MyStream' }, (err, data) => { - if (err) return waitForDeletedStream(kinesis, cb) + if (!err) return waitForDeletedStream(kinesis, cb) cb() }) } From af16096139329754a68f87a75bcea30d78625308 Mon Sep 17 00:00:00 2001 From: William Conti Date: Mon, 11 Dec 2023 16:08:37 -0500 Subject: [PATCH 06/52] no timeout test --- packages/datadog-plugin-aws-sdk/test/kinesis.spec.js | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/datadog-plugin-aws-sdk/test/kinesis.spec.js b/packages/datadog-plugin-aws-sdk/test/kinesis.spec.js index 5c2a7642f56..db8177370c0 100644 --- a/packages/datadog-plugin-aws-sdk/test/kinesis.spec.js +++ b/packages/datadog-plugin-aws-sdk/test/kinesis.spec.js @@ -20,6 +20,7 @@ describe('Kinesis', () => { }) before(function (done) { + this.timeout(0) AWS = require(`../../../versions/${kinesisClientName}@${version}`).get() const params = { From aa84b7ec3bf29d9e1d7e32123e17a31370f381b4 Mon Sep 17 00:00:00 2001 From: William Conti Date: Mon, 11 Dec 2023 17:47:34 -0500 Subject: [PATCH 07/52] add dsm for sns sqs and kinesis --- .../src/services/kinesis.js | 14 ++ .../src/services/sns.js | 26 ++- .../src/services/sqs.js | 40 +++- .../test/kinesis.spec.js | 49 ++++- .../datadog-plugin-aws-sdk/test/sns.spec.js | 48 ++++- .../datadog-plugin-aws-sdk/test/sqs.spec.js | 186 +++++++++++++++++- 6 files changed, 338 insertions(+), 25 deletions(-) diff --git a/packages/datadog-plugin-aws-sdk/src/services/kinesis.js b/packages/datadog-plugin-aws-sdk/src/services/kinesis.js index c0e2a9d739c..a0c2ae0e2fc 100644 --- a/packages/datadog-plugin-aws-sdk/src/services/kinesis.js +++ b/packages/datadog-plugin-aws-sdk/src/services/kinesis.js @@ -1,6 +1,12 @@ 'use strict' +const { + CONTEXT_PROPAGATION_KEY, + getHeadersSize +} = require('../../../dd-trace/src/datastreams/processor') +const { encodePathwayContext } = require('../../../dd-trace/src/datastreams/pathway') const log = require('../../../dd-trace/src/log') const BaseAwsSdkPlugin = require('../base') + class Kinesis extends BaseAwsSdkPlugin { static get id () { return 'kinesis' } static get peerServicePrecursors () { return ['streamname'] } @@ -39,6 +45,14 @@ class Kinesis extends BaseAwsSdkPlugin { } const traceData = {} + if (this.config.dsmEnabled) { + const payloadSize = getHeadersSize(request.params) + const stream = request.params.StreamName + const dataStreamsContext = this.tracer + .setCheckpoint(['direction:out', `topic:${stream}`, 'type:kinesis'], span, payloadSize) + const pathwayCtx = encodePathwayContext(dataStreamsContext) + traceData[CONTEXT_PROPAGATION_KEY] = pathwayCtx.toJSON() + } this.tracer.inject(span, 'text_map', traceData) let injectPath if (request.params.Records && request.params.Records.length > 0) { diff --git a/packages/datadog-plugin-aws-sdk/src/services/sns.js b/packages/datadog-plugin-aws-sdk/src/services/sns.js index 934b59a5d5d..6f1c7298641 100644 --- a/packages/datadog-plugin-aws-sdk/src/services/sns.js +++ b/packages/datadog-plugin-aws-sdk/src/services/sns.js @@ -1,4 +1,6 @@ 'use strict' +const { CONTEXT_PROPAGATION_KEY, getHeadersSize } = require('../../../dd-trace/src/datastreams/processor') +const { encodePathwayContext } = require('../../../dd-trace/src/datastreams/pathway') const log = require('../../../dd-trace/src/log') const BaseAwsSdkPlugin = require('../base') @@ -11,12 +13,7 @@ class Sns extends BaseAwsSdkPlugin { if (!params.TopicArn && !(response.data && response.data.TopicArn)) return {} const TopicArn = params.TopicArn || response.data.TopicArn - // Split the ARN into its parts - // ex.'arn:aws:sns:us-east-1:123456789012:my-topic' - const arnParts = TopicArn.split(':') - - // Get the topic name from the last part of the ARN - const topicName = arnParts[arnParts.length - 1] + const topicName = getTopicName(TopicArn) return { 'resource.name': `${operation} ${params.TopicArn || response.data.TopicArn}`, 'aws.sns.topic_arn': TopicArn, @@ -71,6 +68,14 @@ class Sns extends BaseAwsSdkPlugin { return } const ddInfo = {} + if (this.config.dsmEnabled) { + const payloadSize = getHeadersSize(params) + const topicName = getTopicName(params.TopicArn) + const dataStreamsContext = this.tracer + .setCheckpoint(['direction:out', `topic:${topicName}`, 'type:sns'], span, payloadSize) + const pathwayCtx = encodePathwayContext(dataStreamsContext) + ddInfo[CONTEXT_PROPAGATION_KEY] = pathwayCtx.toJSON() + } this.tracer.inject(span, 'text_map', ddInfo) params.MessageAttributes._datadog = { DataType: 'Binary', @@ -79,4 +84,13 @@ class Sns extends BaseAwsSdkPlugin { } } +function getTopicName (topicArn) { + // Split the ARN into its parts + // ex.'arn:aws:sns:us-east-1:123456789012:my-topic' + const arnParts = topicArn.split(':') + + // Get the topic name from the last part of the ARN + return arnParts[arnParts.length - 1] +} + module.exports = Sns diff --git a/packages/datadog-plugin-aws-sdk/src/services/sqs.js b/packages/datadog-plugin-aws-sdk/src/services/sqs.js index 2fde8bf5214..8b98331efc6 100644 --- a/packages/datadog-plugin-aws-sdk/src/services/sqs.js +++ b/packages/datadog-plugin-aws-sdk/src/services/sqs.js @@ -3,6 +3,8 @@ const log = require('../../../dd-trace/src/log') const BaseAwsSdkPlugin = require('../base') const { storage } = require('../../../datadog-core') +const { CONTEXT_PROPAGATION_KEY, getHeadersSize } = require('../../../dd-trace/src/datastreams/processor') +const { encodePathwayContext } = require('../../../dd-trace/src/datastreams/pathway') class Sqs extends BaseAwsSdkPlugin { static get id () { return 'sqs' } @@ -19,11 +21,11 @@ class Sqs extends BaseAwsSdkPlugin { const { request, response } = obj const store = storage.getStore() const plugin = this - const maybeChildOf = this.responseExtract(request.params, request.operation, response) - if (maybeChildOf) { + const responseExtraction = this.responseExtract(request.params, request.operation, response) + if (responseExtraction && responseExtraction.maybeChildOf) { obj.needsFinish = true const options = { - childOf: maybeChildOf, + childOf: responseExtraction.maybeChildOf, tags: Object.assign( {}, this.requestTags.get(request) || {}, @@ -31,6 +33,7 @@ class Sqs extends BaseAwsSdkPlugin { ) } const span = plugin.tracer.startSpan('aws.response', options) + this.responseExtractDSMContext(request.params, responseExtraction.traceContext, span) this.enter(span, store) } }) @@ -133,19 +136,38 @@ class Sqs extends BaseAwsSdkPlugin { const datadogAttribute = message.MessageAttributes._datadog + let parsedAttributes try { if (datadogAttribute.StringValue) { const textMap = datadogAttribute.StringValue - return this.tracer.extract('text_map', JSON.parse(textMap)) + parsedAttributes = JSON.parse(textMap) + return { + maybeChildOf: this.tracer.extract('text_map', parsedAttributes), + traceContext: parsedAttributes + } } else if (datadogAttribute.Type === 'Binary') { const buffer = Buffer.from(datadogAttribute.Value, 'base64') - return this.tracer.extract('text_map', JSON.parse(buffer)) + parsedAttributes = JSON.parse(buffer) + return { + maybeChildOf: this.tracer.extract('text_map', parsedAttributes), + traceContext: parsedAttributes + } } } catch (e) { log.error(e) } } + responseExtractDSMContext (params, context, span) { + if (this.config.dsmEnabled && context && context[CONTEXT_PROPAGATION_KEY]) { + const payloadSize = getHeadersSize(params) + const queue = params.QueueUrl.split('/').pop() + this.tracer.decodeDataStreamsContext(Buffer.from(context[CONTEXT_PROPAGATION_KEY])) + this.tracer + .setCheckpoint(['direction:in', `topic:${queue}`, 'type:sqs'], span, payloadSize) + } + } + requestInject (span, request) { const operation = request.operation if (operation === 'sendMessage') { @@ -159,6 +181,14 @@ class Sqs extends BaseAwsSdkPlugin { return } const ddInfo = {} + if (this.config.dsmEnabled) { + const payloadSize = getHeadersSize(request.params) + const queue = request.params.QueueUrl.split('/').pop() + const dataStreamsContext = this.tracer + .setCheckpoint(['direction:out', `topic:${queue}`, 'type:sqs'], span, payloadSize) + const pathwayCtx = encodePathwayContext(dataStreamsContext) + ddInfo[CONTEXT_PROPAGATION_KEY] = pathwayCtx.toJSON() + } this.tracer.inject(span, 'text_map', ddInfo) request.params.MessageAttributes._datadog = { DataType: 'String', diff --git a/packages/datadog-plugin-aws-sdk/test/kinesis.spec.js b/packages/datadog-plugin-aws-sdk/test/kinesis.spec.js index db8177370c0..17f9837b0fc 100644 --- a/packages/datadog-plugin-aws-sdk/test/kinesis.spec.js +++ b/packages/datadog-plugin-aws-sdk/test/kinesis.spec.js @@ -5,6 +5,16 @@ const agent = require('../../dd-trace/test/plugins/agent') const { setup } = require('./spec_helpers') const helpers = require('./kinesis_helpers') const { rawExpectedSchema } = require('./kinesis-naming') +const { ENTRY_PARENT_HASH } = require('../../dd-trace/src/datastreams/processor') +const { computePathwayHash } = require('../../dd-trace/src/datastreams/pathway') +const DataStreamsContext = require('../../dd-trace/src/data_streams_context') + +const expectedProducerHash = computePathwayHash( + 'test', + 'tester', + ['direction:out', 'topic:MyStream', 'type:kinesis'], + ENTRY_PARENT_HASH +) describe('Kinesis', () => { setup() @@ -13,9 +23,11 @@ describe('Kinesis', () => { let AWS let kinesis + const streamName = 'MyStream' const kinesisClientName = moduleName === '@aws-sdk/smithy-client' ? '@aws-sdk/client-kinesis' : 'aws-sdk' before(() => { + process.env.DD_DATA_STREAMS_ENABLED = true return agent.load('aws-sdk') }) @@ -36,7 +48,7 @@ describe('Kinesis', () => { kinesis = new AWS.Kinesis(params) kinesis.createStream({ - StreamName: 'MyStream', + StreamName: streamName, ShardCount: 1 }, (err, res) => { if (err) return done(err) @@ -47,7 +59,7 @@ describe('Kinesis', () => { after(done => { kinesis.deleteStream({ - StreamName: 'MyStream' + StreamName: streamName }, (err, res) => { if (err) return done(err) @@ -57,7 +69,7 @@ describe('Kinesis', () => { withNamingSchema( (done) => kinesis.describeStream({ - StreamName: 'MyStream' + StreamName: streamName }, (err) => err && done(err)), rawExpectedSchema.outbound ) @@ -114,12 +126,12 @@ describe('Kinesis', () => { agent.use(traces => { const span = traces[0][0] expect(span.meta).to.include({ - 'streamname': 'MyStream', + 'streamname': streamName, 'aws_service': 'Kinesis', 'region': 'us-east-1' }) - expect(span.resource).to.equal('putRecord MyStream') - expect(span.meta).to.have.property('streamname', 'MyStream') + expect(span.resource).to.equal(`putRecord ${streamName}`) + expect(span.meta).to.have.property('streamname', streamName) }).then(done, done) helpers.putTestRecord(kinesis, helpers.dataBuffer, e => e && done(e)) @@ -148,5 +160,30 @@ describe('Kinesis', () => { }) }) }) + + describe('DSM Context Propagation', () => { + before(() => { + process.env['DD_DATA_STREAMS_ENABLED'] = 'true' + return agent.load('aws-sdk', { kinesis: { dsmEnabled: true } }) + }) + + it('injects DSM trace context to Kinesis putRecord', done => { + if (DataStreamsContext.setDataStreamsContext.isSinonProxy) { + DataStreamsContext.setDataStreamsContext.restore() + } + const setDataStreamsContextSpy = sinon.spy(DataStreamsContext, 'setDataStreamsContext') + + helpers.putTestRecord(kinesis, helpers.dataBuffer, (err, data) => { + if (err) return done(err) + + expect( + setDataStreamsContextSpy.args[0][0].hash + ).to.equal(expectedProducerHash) + + setDataStreamsContextSpy.restore() + done() + }) + }) + }) }) }) diff --git a/packages/datadog-plugin-aws-sdk/test/sns.spec.js b/packages/datadog-plugin-aws-sdk/test/sns.spec.js index 966e5ea4bbc..02ed87d7ecf 100644 --- a/packages/datadog-plugin-aws-sdk/test/sns.spec.js +++ b/packages/datadog-plugin-aws-sdk/test/sns.spec.js @@ -5,6 +5,22 @@ const semver = require('semver') const agent = require('../../dd-trace/test/plugins/agent') const { setup } = require('./spec_helpers') const { rawExpectedSchema } = require('./sns-naming') +const { ENTRY_PARENT_HASH } = require('../../dd-trace/src/datastreams/processor') +const { computePathwayHash } = require('../../dd-trace/src/datastreams/pathway') +const DataStreamsContext = require('../../dd-trace/src/data_streams_context') + +const expectedProducerHash = computePathwayHash( + 'test', + 'tester', + ['direction:out', 'topic:TestTopic', 'type:sns'], + ENTRY_PARENT_HASH +) +const expectedConsumerHash = computePathwayHash( + 'test', + 'tester', + ['direction:in', 'topic:TestQueue', 'type:sqs'], + expectedProducerHash +) describe('Sns', () => { setup() @@ -40,14 +56,16 @@ describe('Sns', () => { } beforeEach(() => { + process.env.DD_DATA_STREAMS_ENABLED = true tracer = require('../../dd-trace') + tracer.init({ dsmEnabled: true }) }) before(() => { parentId = '0' spanId = '0' - return agent.load('aws-sdk') + return agent.load('aws-sdk', { sns: { dsmEnabled: true }, sqs: { dsmEnabled: true } }) }) before(done => { @@ -216,5 +234,33 @@ describe('Sns', () => { sns.publish({ TopicArn, Message: 'message 1' }, e => e && done(e)) }) + + it('injects DSM trace context to SNS publish', done => { + if (DataStreamsContext.setDataStreamsContext.isSinonProxy) { + DataStreamsContext.setDataStreamsContext.restore() + } + const setDataStreamsContextSpy = sinon.spy(DataStreamsContext, 'setDataStreamsContext') + + sns.subscribe(subParams, (err, data) => { + if (err) return done(err) + + sqs.receiveMessage( + receiveParams, + (err, res) => { + if (err) return done(err) + + expect( + setDataStreamsContextSpy.args[setDataStreamsContextSpy.args.length - 1][0].hash + ).to.equal(expectedConsumerHash) + setDataStreamsContextSpy.restore() + done() + }) + sns.publish( + { TopicArn, Message: 'message 1' }, + (err) => { + if (err) return done(err) + }) + }) + }) }) }) diff --git a/packages/datadog-plugin-aws-sdk/test/sqs.spec.js b/packages/datadog-plugin-aws-sdk/test/sqs.spec.js index 0ee09e23d24..ea6b074ed16 100644 --- a/packages/datadog-plugin-aws-sdk/test/sqs.spec.js +++ b/packages/datadog-plugin-aws-sdk/test/sqs.spec.js @@ -3,6 +3,9 @@ const agent = require('../../dd-trace/test/plugins/agent') const { setup } = require('./spec_helpers') const { rawExpectedSchema } = require('./sqs-naming') +const { ENTRY_PARENT_HASH, DataStreamsProcessor } = require('../../dd-trace/src/datastreams/processor') +const { computePathwayHash } = require('../../dd-trace/src/datastreams/pathway') +const DataStreamsContext = require('../../dd-trace/src/data_streams_context') const queueOptions = { QueueName: 'SQS_QUEUE_NAME', @@ -18,16 +21,17 @@ describe('Plugin', () => { withVersions('aws-sdk', ['aws-sdk', '@aws-sdk/smithy-client'], (version, moduleName) => { let AWS let sqs - let QueueUrl + const QueueUrl = 'http://127.0.0.1:4566/00000000000000000000/SQS_QUEUE_NAME' let tracer const sqsClientName = moduleName === '@aws-sdk/smithy-client' ? '@aws-sdk/client-sqs' : 'aws-sdk' describe('without configuration', () => { before(() => { + process.env.DD_DATA_STREAMS_ENABLED = true tracer = require('../../dd-trace') - return agent.load('aws-sdk') + return agent.load('aws-sdk', { sqs: { dsmEnabled: false } }) }) before(done => { @@ -37,8 +41,6 @@ describe('Plugin', () => { sqs.createQueue(queueOptions, (err, res) => { if (err) return done(err) - QueueUrl = res.QueueUrl - done() }) }) @@ -184,6 +186,32 @@ describe('Plugin', () => { }) }) }) + + it('should propagate DSM context from producer to consumer', (done) => { + sqs.sendMessage({ + MessageBody: 'test DSM', + QueueUrl + }, (err) => { + if (err) return done(err) + + const beforeSpan = tracer.scope().active() + + sqs.receiveMessage({ + QueueUrl, + MessageAttributeNames: ['.*'] + }, (err) => { + if (err) return done(err) + + const span = tracer.scope().active() + + expect(span).to.not.equal(beforeSpan) + return Promise.resolve().then(() => { + expect(tracer.scope().active()).to.equal(span) + done() + }) + }) + }) + }) }) describe('with configuration', () => { @@ -192,7 +220,8 @@ describe('Plugin', () => { return agent.load('aws-sdk', { sqs: { - consumer: false + consumer: false, + dsmEnabled: false } }) }) @@ -204,8 +233,6 @@ describe('Plugin', () => { sqs.createQueue(queueOptions, (err, res) => { if (err) return done(err) - QueueUrl = res.QueueUrl - done() }) }) @@ -268,6 +295,151 @@ describe('Plugin', () => { }, 250) }) }) + + describe('data stream monitoring', () => { + const expectedProducerHash = computePathwayHash( + 'test', + 'tester', + ['direction:out', 'topic:SQS_QUEUE_NAME', 'type:sqs'], + ENTRY_PARENT_HASH + ) + const expectedConsumerHash = computePathwayHash( + 'test', + 'tester', + ['direction:in', 'topic:SQS_QUEUE_NAME', 'type:sqs'], + expectedProducerHash + ) + + beforeEach(async () => { + tracer.use('aws-sdk', { dsmEnabled: true }) + + return agent.load('aws-sdk', { + sqs: { + consumer: false, + dsmEnabled: true + } + }, + { dsmEnabled: true }) + }) + + before(done => { + AWS = require(`../../../versions/${sqsClientName}@${version}`).get() + + sqs = new AWS.SQS({ endpoint: 'http://127.0.0.1:4566', region: 'us-east-1' }) + sqs.createQueue(queueOptions, (err, res) => { + if (err) return done(err) + + done() + }) + }) + + after(done => { + sqs.deleteQueue({ QueueUrl: QueueUrl }, done) + }) + + after(() => { + return agent.close({ ritmReset: false }) + }) + + it('Should set a checkpoint on produce', (done) => { + if (DataStreamsContext.setDataStreamsContext.isSinonProxy) { + DataStreamsContext.setDataStreamsContext.restore() + } + const setDataStreamsContextSpy = sinon.spy(DataStreamsContext, 'setDataStreamsContext') + sqs.sendMessage({ + MessageBody: 'test DSM', + QueueUrl + }, (err) => { + if (err) return done(err) + + expect(setDataStreamsContextSpy.args[0][0].hash).to.equal(expectedProducerHash) + setDataStreamsContextSpy.restore() + }) + + setTimeout(() => { + try { + expect(DataStreamsContext.setDataStreamsContext.isSinonProxy).to.equal(undefined) + done() + } catch (e) { + done(e) + } + }, 250) + }) + + it('Should set a checkpoint on consume', (done) => { + if (DataStreamsContext.setDataStreamsContext.isSinonProxy) { + DataStreamsContext.setDataStreamsContext.restore() + } + const setDataStreamsContextSpy = sinon.spy(DataStreamsContext, 'setDataStreamsContext') + sqs.sendMessage({ + MessageBody: 'test DSM', + QueueUrl + }, (err) => { + if (err) return done(err) + + sqs.receiveMessage({ + QueueUrl, + MessageAttributeNames: ['.*'] + }, (err) => { + if (err) return done(err) + + expect( + setDataStreamsContextSpy.args[setDataStreamsContextSpy.args.length - 1][0].hash + ).to.equal(expectedConsumerHash) + setDataStreamsContextSpy.restore() + }) + }) + + setTimeout(() => { + try { + expect(DataStreamsContext.setDataStreamsContext.isSinonProxy).to.equal(undefined) + done() + } catch (e) { + done(e) + } + }, 250) + }) + + it('Should set a message payload size when producing a message', (done) => { + if (DataStreamsProcessor.prototype.recordCheckpoint.isSinonProxy) { + DataStreamsProcessor.prototype.recordCheckpoint.restore() + } + const recordCheckpointSpy = sinon.spy(DataStreamsProcessor.prototype, 'recordCheckpoint') + sqs.sendMessage({ + MessageBody: 'test DSM', + QueueUrl + }, (err) => { + if (err) return done(err) + expect(recordCheckpointSpy.args[0][0].hasOwnProperty('payloadSize')) + recordCheckpointSpy.restore() + done() + }) + }) + + it('Should set a message payload size when consuming a message', (done) => { + if (DataStreamsProcessor.prototype.recordCheckpoint.isSinonProxy) { + DataStreamsProcessor.prototype.recordCheckpoint.restore() + } + const recordCheckpointSpy = sinon.spy(DataStreamsProcessor.prototype, 'recordCheckpoint') + sqs.sendMessage({ + MessageBody: 'test DSM', + QueueUrl + }, (err) => { + if (err) return done(err) + + sqs.receiveMessage({ + QueueUrl, + MessageAttributeNames: ['.*'] + }, (err) => { + if (err) return done(err) + + expect(recordCheckpointSpy.args[0][0].hasOwnProperty('payloadSize')) + recordCheckpointSpy.restore() + done() + }) + }) + }) + }) }) }) }) From 1923d8764b7badc4479f3a37217a5f49be17608e Mon Sep 17 00:00:00 2001 From: William Conti Date: Mon, 11 Dec 2023 18:09:30 -0500 Subject: [PATCH 08/52] fix failing tests --- packages/datadog-plugin-aws-sdk/src/services/kinesis.js | 6 ++++-- packages/datadog-plugin-aws-sdk/src/services/sns.js | 6 ++++-- packages/datadog-plugin-aws-sdk/src/services/sqs.js | 6 ++++-- packages/datadog-plugin-aws-sdk/test/kinesis.spec.js | 5 ++++- packages/datadog-plugin-aws-sdk/test/sns.spec.js | 1 + packages/datadog-plugin-aws-sdk/test/sqs.spec.js | 6 ++++-- 6 files changed, 21 insertions(+), 9 deletions(-) diff --git a/packages/datadog-plugin-aws-sdk/src/services/kinesis.js b/packages/datadog-plugin-aws-sdk/src/services/kinesis.js index a0c2ae0e2fc..712478a7c46 100644 --- a/packages/datadog-plugin-aws-sdk/src/services/kinesis.js +++ b/packages/datadog-plugin-aws-sdk/src/services/kinesis.js @@ -50,8 +50,10 @@ class Kinesis extends BaseAwsSdkPlugin { const stream = request.params.StreamName const dataStreamsContext = this.tracer .setCheckpoint(['direction:out', `topic:${stream}`, 'type:kinesis'], span, payloadSize) - const pathwayCtx = encodePathwayContext(dataStreamsContext) - traceData[CONTEXT_PROPAGATION_KEY] = pathwayCtx.toJSON() + if (dataStreamsContext) { + const pathwayCtx = encodePathwayContext(dataStreamsContext) + traceData[CONTEXT_PROPAGATION_KEY] = pathwayCtx.toJSON() + } } this.tracer.inject(span, 'text_map', traceData) let injectPath diff --git a/packages/datadog-plugin-aws-sdk/src/services/sns.js b/packages/datadog-plugin-aws-sdk/src/services/sns.js index 6f1c7298641..23c4d65a069 100644 --- a/packages/datadog-plugin-aws-sdk/src/services/sns.js +++ b/packages/datadog-plugin-aws-sdk/src/services/sns.js @@ -73,8 +73,10 @@ class Sns extends BaseAwsSdkPlugin { const topicName = getTopicName(params.TopicArn) const dataStreamsContext = this.tracer .setCheckpoint(['direction:out', `topic:${topicName}`, 'type:sns'], span, payloadSize) - const pathwayCtx = encodePathwayContext(dataStreamsContext) - ddInfo[CONTEXT_PROPAGATION_KEY] = pathwayCtx.toJSON() + if (dataStreamsContext) { + const pathwayCtx = encodePathwayContext(dataStreamsContext) + ddInfo[CONTEXT_PROPAGATION_KEY] = pathwayCtx.toJSON() + } } this.tracer.inject(span, 'text_map', ddInfo) params.MessageAttributes._datadog = { diff --git a/packages/datadog-plugin-aws-sdk/src/services/sqs.js b/packages/datadog-plugin-aws-sdk/src/services/sqs.js index 8b98331efc6..a49e49f89e4 100644 --- a/packages/datadog-plugin-aws-sdk/src/services/sqs.js +++ b/packages/datadog-plugin-aws-sdk/src/services/sqs.js @@ -186,8 +186,10 @@ class Sqs extends BaseAwsSdkPlugin { const queue = request.params.QueueUrl.split('/').pop() const dataStreamsContext = this.tracer .setCheckpoint(['direction:out', `topic:${queue}`, 'type:sqs'], span, payloadSize) - const pathwayCtx = encodePathwayContext(dataStreamsContext) - ddInfo[CONTEXT_PROPAGATION_KEY] = pathwayCtx.toJSON() + if (dataStreamsContext) { + const pathwayCtx = encodePathwayContext(dataStreamsContext) + ddInfo[CONTEXT_PROPAGATION_KEY] = pathwayCtx.toJSON() + } } this.tracer.inject(span, 'text_map', ddInfo) request.params.MessageAttributes._datadog = { diff --git a/packages/datadog-plugin-aws-sdk/test/kinesis.spec.js b/packages/datadog-plugin-aws-sdk/test/kinesis.spec.js index 17f9837b0fc..b7db752b36e 100644 --- a/packages/datadog-plugin-aws-sdk/test/kinesis.spec.js +++ b/packages/datadog-plugin-aws-sdk/test/kinesis.spec.js @@ -22,6 +22,7 @@ describe('Kinesis', () => { withVersions('aws-sdk', ['aws-sdk', '@aws-sdk/smithy-client'], (version, moduleName) => { let AWS let kinesis + let tracer const streamName = 'MyStream' const kinesisClientName = moduleName === '@aws-sdk/smithy-client' ? '@aws-sdk/client-kinesis' : 'aws-sdk' @@ -163,7 +164,9 @@ describe('Kinesis', () => { describe('DSM Context Propagation', () => { before(() => { - process.env['DD_DATA_STREAMS_ENABLED'] = 'true' + tracer = require('../../dd-trace') + tracer.init({ dsmEnabled: true }) + tracer.use('aws-sdk', { kinesis: { dsmEnabled: true } }) return agent.load('aws-sdk', { kinesis: { dsmEnabled: true } }) }) diff --git a/packages/datadog-plugin-aws-sdk/test/sns.spec.js b/packages/datadog-plugin-aws-sdk/test/sns.spec.js index 02ed87d7ecf..328b2e03474 100644 --- a/packages/datadog-plugin-aws-sdk/test/sns.spec.js +++ b/packages/datadog-plugin-aws-sdk/test/sns.spec.js @@ -59,6 +59,7 @@ describe('Sns', () => { process.env.DD_DATA_STREAMS_ENABLED = true tracer = require('../../dd-trace') tracer.init({ dsmEnabled: true }) + tracer.use('aws-sdk', { sns: { dsmEnabled: true }, sqs: { dsmEnabled: true } }) }) before(() => { diff --git a/packages/datadog-plugin-aws-sdk/test/sqs.spec.js b/packages/datadog-plugin-aws-sdk/test/sqs.spec.js index ea6b074ed16..613af06c5b1 100644 --- a/packages/datadog-plugin-aws-sdk/test/sqs.spec.js +++ b/packages/datadog-plugin-aws-sdk/test/sqs.spec.js @@ -28,7 +28,6 @@ describe('Plugin', () => { describe('without configuration', () => { before(() => { - process.env.DD_DATA_STREAMS_ENABLED = true tracer = require('../../dd-trace') return agent.load('aws-sdk', { sqs: { dsmEnabled: false } }) @@ -311,7 +310,10 @@ describe('Plugin', () => { ) beforeEach(async () => { - tracer.use('aws-sdk', { dsmEnabled: true }) + process.env.DD_DATA_STREAMS_ENABLED = true + tracer = require('../../dd-trace') + tracer.init({ dsmEnabled: true }) + tracer.use('aws-sdk', { sqs: { dsmEnabled: true } }) return agent.load('aws-sdk', { sqs: { From b3b35b675230a2202c90bad6f91f8f3def7e1c76 Mon Sep 17 00:00:00 2001 From: William Conti Date: Tue, 12 Dec 2023 09:27:57 -0500 Subject: [PATCH 09/52] add debugging statemtn --- packages/datadog-plugin-aws-sdk/test/kinesis.spec.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/datadog-plugin-aws-sdk/test/kinesis.spec.js b/packages/datadog-plugin-aws-sdk/test/kinesis.spec.js index b7db752b36e..1665ec12a25 100644 --- a/packages/datadog-plugin-aws-sdk/test/kinesis.spec.js +++ b/packages/datadog-plugin-aws-sdk/test/kinesis.spec.js @@ -176,6 +176,8 @@ describe('Kinesis', () => { } const setDataStreamsContextSpy = sinon.spy(DataStreamsContext, 'setDataStreamsContext') + console.log(tracer._pluginManager._tracerConfig) + helpers.putTestRecord(kinesis, helpers.dataBuffer, (err, data) => { if (err) return done(err) From c118fd3a5d8a294eacef57c9c57ad60e0a81fcad Mon Sep 17 00:00:00 2001 From: William Conti Date: Tue, 12 Dec 2023 09:52:59 -0500 Subject: [PATCH 10/52] fix tests --- .../test/kinesis.spec.js | 4 +- .../datadog-plugin-aws-sdk/test/sns.spec.js | 59 +++++++++++-------- .../datadog-plugin-aws-sdk/test/sqs.spec.js | 4 +- 3 files changed, 36 insertions(+), 31 deletions(-) diff --git a/packages/datadog-plugin-aws-sdk/test/kinesis.spec.js b/packages/datadog-plugin-aws-sdk/test/kinesis.spec.js index 1665ec12a25..83ece1bb3e8 100644 --- a/packages/datadog-plugin-aws-sdk/test/kinesis.spec.js +++ b/packages/datadog-plugin-aws-sdk/test/kinesis.spec.js @@ -28,7 +28,6 @@ describe('Kinesis', () => { const kinesisClientName = moduleName === '@aws-sdk/smithy-client' ? '@aws-sdk/client-kinesis' : 'aws-sdk' before(() => { - process.env.DD_DATA_STREAMS_ENABLED = true return agent.load('aws-sdk') }) @@ -165,6 +164,7 @@ describe('Kinesis', () => { describe('DSM Context Propagation', () => { before(() => { tracer = require('../../dd-trace') + tracer._initialized = false tracer.init({ dsmEnabled: true }) tracer.use('aws-sdk', { kinesis: { dsmEnabled: true } }) return agent.load('aws-sdk', { kinesis: { dsmEnabled: true } }) @@ -176,8 +176,6 @@ describe('Kinesis', () => { } const setDataStreamsContextSpy = sinon.spy(DataStreamsContext, 'setDataStreamsContext') - console.log(tracer._pluginManager._tracerConfig) - helpers.putTestRecord(kinesis, helpers.dataBuffer, (err, data) => { if (err) return done(err) diff --git a/packages/datadog-plugin-aws-sdk/test/sns.spec.js b/packages/datadog-plugin-aws-sdk/test/sns.spec.js index 328b2e03474..c0eb70c0c0f 100644 --- a/packages/datadog-plugin-aws-sdk/test/sns.spec.js +++ b/packages/datadog-plugin-aws-sdk/test/sns.spec.js @@ -56,17 +56,15 @@ describe('Sns', () => { } beforeEach(() => { - process.env.DD_DATA_STREAMS_ENABLED = true tracer = require('../../dd-trace') - tracer.init({ dsmEnabled: true }) - tracer.use('aws-sdk', { sns: { dsmEnabled: true }, sqs: { dsmEnabled: true } }) + tracer.use('aws-sdk') }) before(() => { parentId = '0' spanId = '0' - return agent.load('aws-sdk', { sns: { dsmEnabled: true }, sqs: { dsmEnabled: true } }) + return agent.load('aws-sdk') }) before(done => { @@ -236,31 +234,40 @@ describe('Sns', () => { sns.publish({ TopicArn, Message: 'message 1' }, e => e && done(e)) }) - it('injects DSM trace context to SNS publish', done => { - if (DataStreamsContext.setDataStreamsContext.isSinonProxy) { - DataStreamsContext.setDataStreamsContext.restore() - } - const setDataStreamsContextSpy = sinon.spy(DataStreamsContext, 'setDataStreamsContext') + describe('Data Streams Monitoring', () => { + beforeEach(() => { + tracer._initialized = false + tracer.init({ dsmEnabled: true }) + tracer.use('aws-sdk', { sns: { dsmEnabled: true }, sqs: { dsmEnabled: true } }) + return agent.load('aws-sdk', { sns: { dsmEnabled: true }, sqs: { dsmEnabled: true } }) + }) - sns.subscribe(subParams, (err, data) => { - if (err) return done(err) + it('injects DSM trace context to SNS publish', done => { + if (DataStreamsContext.setDataStreamsContext.isSinonProxy) { + DataStreamsContext.setDataStreamsContext.restore() + } + const setDataStreamsContextSpy = sinon.spy(DataStreamsContext, 'setDataStreamsContext') - sqs.receiveMessage( - receiveParams, - (err, res) => { - if (err) return done(err) + sns.subscribe(subParams, (err, data) => { + if (err) return done(err) - expect( - setDataStreamsContextSpy.args[setDataStreamsContextSpy.args.length - 1][0].hash - ).to.equal(expectedConsumerHash) - setDataStreamsContextSpy.restore() - done() - }) - sns.publish( - { TopicArn, Message: 'message 1' }, - (err) => { - if (err) return done(err) - }) + sqs.receiveMessage( + receiveParams, + (err, res) => { + if (err) return done(err) + + expect( + setDataStreamsContextSpy.args[setDataStreamsContextSpy.args.length - 1][0].hash + ).to.equal(expectedConsumerHash) + setDataStreamsContextSpy.restore() + done() + }) + sns.publish( + { TopicArn, Message: 'message 1' }, + (err) => { + if (err) return done(err) + }) + }) }) }) }) diff --git a/packages/datadog-plugin-aws-sdk/test/sqs.spec.js b/packages/datadog-plugin-aws-sdk/test/sqs.spec.js index 613af06c5b1..266b74e1221 100644 --- a/packages/datadog-plugin-aws-sdk/test/sqs.spec.js +++ b/packages/datadog-plugin-aws-sdk/test/sqs.spec.js @@ -30,7 +30,7 @@ describe('Plugin', () => { before(() => { tracer = require('../../dd-trace') - return agent.load('aws-sdk', { sqs: { dsmEnabled: false } }) + return agent.load('aws-sdk') }) before(done => { @@ -310,8 +310,8 @@ describe('Plugin', () => { ) beforeEach(async () => { - process.env.DD_DATA_STREAMS_ENABLED = true tracer = require('../../dd-trace') + tracer._initialized = false tracer.init({ dsmEnabled: true }) tracer.use('aws-sdk', { sqs: { dsmEnabled: true } }) From afcefb127876c9f32f8c96263c25db3fbc3c2f63 Mon Sep 17 00:00:00 2001 From: William Conti Date: Tue, 12 Dec 2023 12:23:14 -0500 Subject: [PATCH 11/52] fix sns tests --- .../datadog-plugin-aws-sdk/test/sns.spec.js | 307 ++++++++++-------- 1 file changed, 172 insertions(+), 135 deletions(-) diff --git a/packages/datadog-plugin-aws-sdk/test/sns.spec.js b/packages/datadog-plugin-aws-sdk/test/sns.spec.js index c0eb70c0c0f..4a97ae076b1 100644 --- a/packages/datadog-plugin-aws-sdk/test/sns.spec.js +++ b/packages/datadog-plugin-aws-sdk/test/sns.spec.js @@ -8,21 +8,22 @@ const { rawExpectedSchema } = require('./sns-naming') const { ENTRY_PARENT_HASH } = require('../../dd-trace/src/datastreams/processor') const { computePathwayHash } = require('../../dd-trace/src/datastreams/pathway') const DataStreamsContext = require('../../dd-trace/src/data_streams_context') +const { producer } = require('../../dd-trace/src/service-naming/schemas/v0/messaging') const expectedProducerHash = computePathwayHash( 'test', 'tester', - ['direction:out', 'topic:TestTopic', 'type:sns'], + ['direction:out', 'topic:TestTopicDSM', 'type:sns'], ENTRY_PARENT_HASH ) const expectedConsumerHash = computePathwayHash( 'test', 'tester', - ['direction:in', 'topic:TestQueue', 'type:sqs'], + ['direction:in', 'topic:TestQueueDSM', 'type:sqs'], expectedProducerHash ) -describe('Sns', () => { +describe('Sns', function() { setup() withVersions('aws-sdk', ['aws-sdk', '@aws-sdk/smithy-client'], (version, moduleName) => { @@ -55,31 +56,19 @@ describe('Sns', () => { }).then(done, done) } - beforeEach(() => { - tracer = require('../../dd-trace') - tracer.use('aws-sdk') - }) - - before(() => { - parentId = '0' - spanId = '0' - - return agent.load('aws-sdk') - }) - - before(done => { + function createResources (queueName, topicName, cb) { const { SNS } = require(`../../../versions/${snsClientName}@${version}`).get() const { SQS } = require(`../../../versions/${sqsClientName}@${version}`).get() sns = new SNS({ endpoint: 'http://127.0.0.1:4566', region: 'us-east-1' }) sqs = new SQS({ endpoint: 'http://127.0.0.1:4566', region: 'us-east-1' }) - sns.createTopic({ Name: 'TestTopic' }, (err, data) => { + sns.createTopic({ Name: topicName }, (err, data) => { if (err) return done(err) TopicArn = data.TopicArn - sqs.createQueue({ QueueName: 'TestQueue' }, (err, data) => { + sqs.createQueue({ QueueName: queueName }, (err, data) => { if (err) return done(err) QueueUrl = data.QueueUrl @@ -101,148 +90,188 @@ describe('Sns', () => { WaitTimeSeconds: 1 } - done() + cb() }) }) }) - }) + } - after(done => { - sns.deleteTopic({ TopicArn }, done) - }) + describe('no configuration', () => { - after(done => { - sqs.deleteQueue({ QueueUrl }, done) - }) + before(() => { + parentId = '0' + spanId = '0' - after(() => { - return agent.close({ ritmReset: false }) - }) + return agent.load('aws-sdk', { sns: { dsmEnabled: false }}, { dsmEnabled: true }) + }) - withPeerService( - () => tracer, - 'aws-sdk', - (done) => sns.publish({ - TopicArn, - Message: 'message 1' - }, (err) => err && done()), - 'TestTopic', 'topicname') - - withNamingSchema( - (done) => sns.publish({ - TopicArn, - Message: 'message 1' - }, (err) => err && done()), - rawExpectedSchema.producer, - { - desc: 'producer' - } - ) - - withNamingSchema( - (done) => sns.getTopicAttributes({ - TopicArn - }, (err) => err && done(err)), - rawExpectedSchema.client, - { - desc: 'client' - } - ) + before(done => { + process.env.DD_DATA_STREAMS_ENABLED = 'true' + tracer = require('../../dd-trace') + tracer.use('aws-sdk', { sns: { dsmEnabled: false }}) - it('injects trace context to SNS publish', done => { - assertPropagation(done) + createResources('TestQueue', 'TestTopic', done) + }) - sns.subscribe(subParams, (err, data) => { - if (err) return done(err) + after(done => { + sns.deleteTopic({ TopicArn }, done) + }) - sqs.receiveMessage(receiveParams, e => e && done(e)) - sns.publish({ TopicArn, Message: 'message 1' }, e => e && done(e)) + after(done => { + sqs.deleteQueue({ QueueUrl }, done) }) - }) - // There is a bug in 3.x (but not 3.0.0) that will be fixed in 3.261 - // https://github.com/aws/aws-sdk-js-v3/issues/2861 - if (!semver.intersects(version, '<3 || >3.0.0')) { - it('injects trace context to SNS publishBatch', done => { - assertPropagation(done) + after(() => { + return agent.close({ ritmReset: false }) + }) + withPeerService( + () => tracer, + 'aws-sdk', + (done) => sns.publish({ + TopicArn, + Message: 'message 1' + }, (err) => err && done()), + 'TestTopic', 'topicname') + + withNamingSchema( + (done) => sns.publish({ + TopicArn, + Message: 'message 1' + }, (err) => err && done()), + rawExpectedSchema.producer, + { + desc: 'producer' + } + ) + + withNamingSchema( + (done) => sns.getTopicAttributes({ + TopicArn + }, (err) => err && done(err)), + rawExpectedSchema.client, + { + desc: 'client' + } + ) + + it('injects trace context to SNS publish', done => { + assertPropagation(done) + sns.subscribe(subParams, (err, data) => { if (err) return done(err) - + sqs.receiveMessage(receiveParams, e => e && done(e)) - sns.publishBatch({ - TopicArn, - PublishBatchRequestEntries: [ - { Id: '1', Message: 'message 1' }, - { Id: '2', Message: 'message 2' } - ] - }, e => e && done(e)) + sns.publish({ TopicArn, Message: 'message 1' }, (e) => { + if (e) done(e) + }) }) }) - } - - // TODO: Figure out why this fails only in 3.0.0 - if (version !== '3.0.0') { - it('skips injecting trace context to SNS if message attributes are full', done => { - sns.subscribe(subParams, (err, data) => { - if (err) return done(err) - - sqs.receiveMessage(receiveParams, (err, data) => { + + // There is a bug in 3.x (but not 3.0.0) that will be fixed in 3.261 + // https://github.com/aws/aws-sdk-js-v3/issues/2861 + if (!semver.intersects(version, '<3 || >3.0.0')) { + it('injects trace context to SNS publishBatch', done => { + assertPropagation(done) + + sns.subscribe(subParams, (err, data) => { if (err) return done(err) - - try { - expect(data.Messages[0].Body).to.not.include('datadog') - done() - } catch (e) { - done(e) - } + + sqs.receiveMessage(receiveParams, e => e && done(e)) + sns.publishBatch({ + TopicArn, + PublishBatchRequestEntries: [ + { Id: '1', Message: 'message 1' }, + { Id: '2', Message: 'message 2' } + ] + }, e => e && done(e)) }) - - sns.publish({ - TopicArn, - Message: 'message 1', - MessageAttributes: { - keyOne: { DataType: 'String', StringValue: 'keyOne' }, - keyTwo: { DataType: 'String', StringValue: 'keyTwo' }, - keyThree: { DataType: 'String', StringValue: 'keyThree' }, - keyFour: { DataType: 'String', StringValue: 'keyFour' }, - keyFive: { DataType: 'String', StringValue: 'keyFive' }, - keySix: { DataType: 'String', StringValue: 'keySix' }, - keySeven: { DataType: 'String', StringValue: 'keySeven' }, - keyEight: { DataType: 'String', StringValue: 'keyEight' }, - keyNine: { DataType: 'String', StringValue: 'keyNine' }, - keyTen: { DataType: 'String', StringValue: 'keyTen' } - } - }, e => e && done(e)) }) + } + + // TODO: Figure out why this fails only in 3.0.0 + if (version !== '3.0.0') { + it('skips injecting trace context to SNS if message attributes are full', done => { + sns.subscribe(subParams, (err, data) => { + if (err) return done(err) + + sqs.receiveMessage(receiveParams, (err, data) => { + if (err) return done(err) + + try { + expect(data.Messages[0].Body).to.not.include('datadog') + done() + } catch (e) { + done(e) + } + }) + + sns.publish({ + TopicArn, + Message: 'message 1', + MessageAttributes: { + keyOne: { DataType: 'String', StringValue: 'keyOne' }, + keyTwo: { DataType: 'String', StringValue: 'keyTwo' }, + keyThree: { DataType: 'String', StringValue: 'keyThree' }, + keyFour: { DataType: 'String', StringValue: 'keyFour' }, + keyFive: { DataType: 'String', StringValue: 'keyFive' }, + keySix: { DataType: 'String', StringValue: 'keySix' }, + keySeven: { DataType: 'String', StringValue: 'keySeven' }, + keyEight: { DataType: 'String', StringValue: 'keyEight' }, + keyNine: { DataType: 'String', StringValue: 'keyNine' }, + keyTen: { DataType: 'String', StringValue: 'keyTen' } + } + }, e => e && done(e)) + }) + }) + } + + it('generates tags for proper publish calls', done => { + agent.use(traces => { + const span = traces[0][0] + + expect(span.resource).to.equal(`publish ${TopicArn}`) + expect(span.meta).to.include({ + 'aws.sns.topic_arn': TopicArn, + 'topicname': 'TestTopic', + 'aws_service': 'SNS', + 'region': 'us-east-1' + }) + }).then(done, done) + + sns.publish({ TopicArn, Message: 'message 1' }, e => e && done(e)) }) - } + }) - it('generates tags for proper publish calls', done => { - agent.use(traces => { - const span = traces[0][0] + describe('Data Streams Monitoring', () => { + before(() => { + return agent.load('aws-sdk', { sns: { dsmEnabled: true }, sqs: { dsmEnabled: true } }, { dsmEnabled: true }) + }) - expect(span.resource).to.equal(`publish ${TopicArn}`) - expect(span.meta).to.include({ - 'aws.sns.topic_arn': TopicArn, - 'topicname': 'TestTopic', - 'aws_service': 'SNS', - 'region': 'us-east-1' - }) - }).then(done, done) + before(done => { + process.env.DD_DATA_STREAMS_ENABLED = 'true' + tracer = require('../../dd-trace') + tracer.use('aws-sdk', { sns: { dsmEnabled: true }, sqs: { dsmEnabled: true }}) - sns.publish({ TopicArn, Message: 'message 1' }, e => e && done(e)) - }) + createResources('TestQueueDSM', 'TestTopicDSM', done) + }) - describe('Data Streams Monitoring', () => { - beforeEach(() => { - tracer._initialized = false - tracer.init({ dsmEnabled: true }) - tracer.use('aws-sdk', { sns: { dsmEnabled: true }, sqs: { dsmEnabled: true } }) - return agent.load('aws-sdk', { sns: { dsmEnabled: true }, sqs: { dsmEnabled: true } }) + after(done => { + sns.deleteTopic({ TopicArn }, done) + }) + + after(done => { + sqs.deleteQueue({ QueueUrl }, done) + }) + + after(() => { + return agent.close({ ritmReset: false }) }) it('injects DSM trace context to SNS publish', done => { + let producerHashCreated = false + let consumerHashCreated = false + if (DataStreamsContext.setDataStreamsContext.isSinonProxy) { DataStreamsContext.setDataStreamsContext.restore() } @@ -256,14 +285,22 @@ describe('Sns', () => { (err, res) => { if (err) return done(err) - expect( - setDataStreamsContextSpy.args[setDataStreamsContextSpy.args.length - 1][0].hash - ).to.equal(expectedConsumerHash) + setDataStreamsContextSpy.args.forEach(functionCall => { + if (functionCall[0].hash === expectedConsumerHash) { + consumerHashCreated = true + } else if (functionCall[0].hash === expectedProducerHash) { + producerHashCreated = true + } + }) + + + expect(consumerHashCreated).to.equal(true) + expect(producerHashCreated).to.equal(true) setDataStreamsContextSpy.restore() done() }) sns.publish( - { TopicArn, Message: 'message 1' }, + { TopicArn, Message: 'message DSM' }, (err) => { if (err) return done(err) }) From f014b2ce05f757b2bf82840ff71623ed61a5a4ba Mon Sep 17 00:00:00 2001 From: William Conti Date: Tue, 12 Dec 2023 13:12:00 -0500 Subject: [PATCH 12/52] fix again --- .../test/kinesis.spec.js | 224 ++++++++++-------- .../test/kinesis_helpers.js | 28 +-- .../datadog-plugin-aws-sdk/test/sqs.spec.js | 10 +- 3 files changed, 143 insertions(+), 119 deletions(-) diff --git a/packages/datadog-plugin-aws-sdk/test/kinesis.spec.js b/packages/datadog-plugin-aws-sdk/test/kinesis.spec.js index 83ece1bb3e8..83c05f451bf 100644 --- a/packages/datadog-plugin-aws-sdk/test/kinesis.spec.js +++ b/packages/datadog-plugin-aws-sdk/test/kinesis.spec.js @@ -12,29 +12,25 @@ const DataStreamsContext = require('../../dd-trace/src/data_streams_context') const expectedProducerHash = computePathwayHash( 'test', 'tester', - ['direction:out', 'topic:MyStream', 'type:kinesis'], + ['direction:out', 'topic:MyStreamDSM', 'type:kinesis'], ENTRY_PARENT_HASH ) describe('Kinesis', () => { setup() - withVersions('aws-sdk', ['aws-sdk', '@aws-sdk/smithy-client'], (version, moduleName) => { + withVersions('aws-sdk', ['aws-sdk'], (version, moduleName) => { let AWS let kinesis let tracer const streamName = 'MyStream' + const streamNameDSM = 'MyStreamDSM' const kinesisClientName = moduleName === '@aws-sdk/smithy-client' ? '@aws-sdk/client-kinesis' : 'aws-sdk' - before(() => { - return agent.load('aws-sdk') - }) - - before(function (done) { - this.timeout(0) + function createResources (mocha, streamName, cb) { AWS = require(`../../../versions/${kinesisClientName}@${version}`).get() - + const params = { endpoint: 'http://127.0.0.1:4566', region: 'us-east-1' @@ -47,115 +43,130 @@ describe('Kinesis', () => { } kinesis = new AWS.Kinesis(params) + kinesis.createStream({ StreamName: streamName, ShardCount: 1 }, (err, res) => { - if (err) return done(err) + if (err) return cb(err) - helpers.waitForActiveStream(this, kinesis, done) + helpers.waitForActiveStream(kinesis, streamName, cb) }) - }) - - after(done => { - kinesis.deleteStream({ - StreamName: streamName - }, (err, res) => { - if (err) return done(err) + } - helpers.waitForDeletedStream(kinesis, done) - }) + before(() => { + process.env.DD_DATA_STREAMS_ENABLED = 'true' }) - withNamingSchema( - (done) => kinesis.describeStream({ - StreamName: streamName - }, (err) => err && done(err)), - rawExpectedSchema.outbound - ) - - it('injects trace context to Kinesis putRecord', done => { - helpers.putTestRecord(kinesis, helpers.dataBuffer, (err, data) => { - if (err) return done(err) - - helpers.getTestData(kinesis, data, (err, data) => { + describe('no configuration', () => { + before(() => { + return agent.load('aws-sdk', { kinesis: { dsmEnabled: false }}, { dsmEnabled: true }) + }) + + before(function(done) { + createResources(this, streamName, done) + }) + + after(done => { + kinesis.deleteStream({ + StreamName: streamName + }, (err, res) => { if (err) return done(err) - - expect(data).to.have.property('_datadog') - expect(data._datadog).to.have.property('x-datadog-trace-id') - - done() + + helpers.waitForDeletedStream(kinesis, streamName, done) }) }) - }) - - it('handles already b64 encoded data', done => { - helpers.putTestRecord(kinesis, helpers.dataBuffer.toString('base64'), (err, data) => { - if (err) return done(err) - - helpers.getTestData(kinesis, data, (err, data) => { + + withNamingSchema( + (done) => kinesis.describeStream({ + StreamName: streamName + }, (err) => err && done(err)), + rawExpectedSchema.outbound + ) + + it('injects trace context to Kinesis putRecord', done => { + helpers.putTestRecord(kinesis, streamName, helpers.dataBuffer, (err, data) => { if (err) return done(err) - - expect(data).to.have.property('_datadog') - expect(data._datadog).to.have.property('x-datadog-trace-id') - - done() + + helpers.getTestData(kinesis, streamName, data, (err, data) => { + if (err) return done(err) + + expect(data).to.have.property('_datadog') + expect(data._datadog).to.have.property('x-datadog-trace-id') + + done() + }) }) }) - }) - - it('skips injecting trace context to Kinesis if message is full', done => { - const dataBuffer = Buffer.from(JSON.stringify({ - myData: Array(1048576 - 100).join('a') - })) - - helpers.putTestRecord(kinesis, dataBuffer, (err, data) => { - if (err) return done(err) - - helpers.getTestData(kinesis, data, (err, data) => { + + it('handles already b64 encoded data', done => { + helpers.putTestRecord(kinesis, streamName, helpers.dataBuffer.toString('base64'), (err, data) => { if (err) return done(err) - - expect(data).to.not.have.property('_datadog') - - done() + + helpers.getTestData(kinesis, streamName, data, (err, data) => { + if (err) return done(err) + + expect(data).to.have.property('_datadog') + expect(data._datadog).to.have.property('x-datadog-trace-id') + + done() + }) }) }) - }) - - it('generates tags for proper input', done => { - agent.use(traces => { - const span = traces[0][0] - expect(span.meta).to.include({ - 'streamname': streamName, - 'aws_service': 'Kinesis', - 'region': 'us-east-1' + + it('skips injecting trace context to Kinesis if message is full', done => { + const dataBuffer = Buffer.from(JSON.stringify({ + myData: Array(1048576 - 100).join('a') + })) + + helpers.putTestRecord(kinesis, streamName, dataBuffer, (err, data) => { + if (err) return done(err) + + helpers.getTestData(kinesis, streamName, data, (err, data) => { + if (err) return done(err) + + expect(data).to.not.have.property('_datadog') + + done() + }) }) - expect(span.resource).to.equal(`putRecord ${streamName}`) - expect(span.meta).to.have.property('streamname', streamName) - }).then(done, done) - - helpers.putTestRecord(kinesis, helpers.dataBuffer, e => e && done(e)) - }) - - describe('Disabled', () => { - before(() => { - process.env.DD_TRACE_AWS_SDK_KINESIS_ENABLED = 'false' }) - - after(() => { - delete process.env.DD_TRACE_AWS_SDK_KINESIS_ENABLED + + it('generates tags for proper input', done => { + agent.use(traces => { + const span = traces[0][0] + expect(span.meta).to.include({ + 'streamname': streamName, + 'aws_service': 'Kinesis', + 'region': 'us-east-1' + }) + expect(span.resource).to.equal(`putRecord ${streamName}`) + expect(span.meta).to.have.property('streamname', streamName) + }).then(done, done) + + helpers.putTestRecord(kinesis, streamName, helpers.dataBuffer, e => e && done(e)) }) - it('skip injects trace context to Kinesis putRecord when disabled', done => { - helpers.putTestRecord(kinesis, helpers.dataBuffer, (err, data) => { - if (err) return done(err) - - helpers.getTestData(kinesis, data, (err, data) => { + describe('Disabled', () => { + before(() => { + process.env.DD_TRACE_AWS_SDK_KINESIS_ENABLED = 'false' + }) + + after(() => { + delete process.env.DD_TRACE_AWS_SDK_KINESIS_ENABLED + }) + + it('skip injects trace context to Kinesis putRecord when disabled', done => { + helpers.putTestRecord(kinesis, streamName, helpers.dataBuffer, (err, data) => { if (err) return done(err) - - expect(data).not.to.have.property('_datadog') - - done() + + helpers.getTestData(kinesis, streamName, data, (err, data) => { + if (err) return done(err) + + expect(data).not.to.have.property('_datadog') + + done() + }) }) }) }) @@ -163,11 +174,24 @@ describe('Kinesis', () => { describe('DSM Context Propagation', () => { before(() => { + return agent.load('aws-sdk', { kinesis: { dsmEnabled: true }}, { dsmEnabled: true }) + }) + + before(function(done) { tracer = require('../../dd-trace') - tracer._initialized = false - tracer.init({ dsmEnabled: true }) - tracer.use('aws-sdk', { kinesis: { dsmEnabled: true } }) - return agent.load('aws-sdk', { kinesis: { dsmEnabled: true } }) + tracer.use('aws-sdk', { kinesis: { dsmEnabled: true }}, { dsmEnabled: true }) + + createResources(this, streamNameDSM, done) + }) + + after(done => { + kinesis.deleteStream({ + StreamName: streamNameDSM + }, (err, res) => { + if (err) return done(err) + + helpers.waitForDeletedStream(kinesis, streamNameDSM, done) + }) }) it('injects DSM trace context to Kinesis putRecord', done => { @@ -176,7 +200,7 @@ describe('Kinesis', () => { } const setDataStreamsContextSpy = sinon.spy(DataStreamsContext, 'setDataStreamsContext') - helpers.putTestRecord(kinesis, helpers.dataBuffer, (err, data) => { + helpers.putTestRecord(kinesis, streamNameDSM, helpers.dataBuffer, (err, data) => { if (err) return done(err) expect( diff --git a/packages/datadog-plugin-aws-sdk/test/kinesis_helpers.js b/packages/datadog-plugin-aws-sdk/test/kinesis_helpers.js index f76e6119251..6ac2434f87e 100644 --- a/packages/datadog-plugin-aws-sdk/test/kinesis_helpers.js +++ b/packages/datadog-plugin-aws-sdk/test/kinesis_helpers.js @@ -8,8 +8,8 @@ const dataBuffer = Buffer.from(JSON.stringify({ from: 'Aaron Stuyvenberg' })) -function getTestData (kinesis, input, cb) { - getTestRecord(kinesis, input, (err, data) => { +function getTestData (kinesis, streamName, input, cb) { + getTestRecord(kinesis, streamName, input, (err, data) => { if (err) return cb(err) const dataBuffer = Buffer.from(data.Records[0].Data).toString() @@ -22,12 +22,12 @@ function getTestData (kinesis, input, cb) { }) } -function getTestRecord (kinesis, { ShardId, SequenceNumber }, cb) { +function getTestRecord (kinesis, streamName, { ShardId, SequenceNumber }, cb) { kinesis.getShardIterator({ ShardId, ShardIteratorType: 'AT_SEQUENCE_NUMBER', StartingSequenceNumber: SequenceNumber, - StreamName: 'MyStream' + StreamName: streamName }, (err, { ShardIterator } = {}) => { if (err) return cb(err) @@ -37,36 +37,34 @@ function getTestRecord (kinesis, { ShardId, SequenceNumber }, cb) { }) } -function putTestRecord (kinesis, data, cb) { +function putTestRecord (kinesis, streamName, data, cb) { kinesis.putRecord({ PartitionKey: id().toString(), Data: data, - StreamName: 'MyStream' + StreamName: streamName }, cb) } -function waitForActiveStream (mocha, kinesis, cb) { +function waitForActiveStream (kinesis, streamName, cb) { kinesis.describeStream({ - StreamName: 'MyStream' + StreamName: streamName }, (err, data) => { if (err) { - mocha.timeout(2000) - return waitForActiveStream(mocha, kinesis, cb) + return waitForActiveStream(kinesis, streamName, cb) } if (data.StreamDescription.StreamStatus !== 'ACTIVE') { - mocha.timeout(2000) - return waitForActiveStream(mocha, kinesis, cb) + return waitForActiveStream(kinesis, streamName, cb) } cb() }) } -function waitForDeletedStream (kinesis, cb) { +function waitForDeletedStream (kinesis, streamName, cb) { kinesis.describeStream({ - StreamName: 'MyStream' + StreamName: streamName }, (err, data) => { - if (!err) return waitForDeletedStream(kinesis, cb) + if (!err) return waitForDeletedStream(kinesis, streamName, cb) cb() }) } diff --git a/packages/datadog-plugin-aws-sdk/test/sqs.spec.js b/packages/datadog-plugin-aws-sdk/test/sqs.spec.js index 266b74e1221..05eb60cd0ce 100644 --- a/packages/datadog-plugin-aws-sdk/test/sqs.spec.js +++ b/packages/datadog-plugin-aws-sdk/test/sqs.spec.js @@ -309,12 +309,14 @@ describe('Plugin', () => { expectedProducerHash ) - beforeEach(async () => { + before(done => { + process.env.DD_DATA_STREAMS_ENABLED = 'true' tracer = require('../../dd-trace') - tracer._initialized = false - tracer.init({ dsmEnabled: true }) - tracer.use('aws-sdk', { sqs: { dsmEnabled: true } }) + tracer.use('aws-sdk', { sns: { dsmEnabled: true }, sqs: { dsmEnabled: true }}) + done() + }) + beforeEach(async () => { return agent.load('aws-sdk', { sqs: { consumer: false, From eb1367655180c3dd492d05b1012795f28c1c92f3 Mon Sep 17 00:00:00 2001 From: William Conti Date: Tue, 12 Dec 2023 13:17:04 -0500 Subject: [PATCH 13/52] try env variable to enable --- .github/workflows/plugins.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/plugins.yml b/.github/workflows/plugins.yml index 7fdf623df79..0f0334fe432 100644 --- a/.github/workflows/plugins.yml +++ b/.github/workflows/plugins.yml @@ -192,6 +192,7 @@ jobs: env: PLUGINS: aws-sdk SERVICES: localstack localstack-legacy + DD_DATA_STREAMS_ENABLED: true steps: - uses: actions/checkout@v2 - uses: ./.github/actions/testagent/start From b0cb24b38bddd442c3cf7c4b32b06733e9babf22 Mon Sep 17 00:00:00 2001 From: William Conti Date: Tue, 12 Dec 2023 13:48:48 -0500 Subject: [PATCH 14/52] fix lint --- .../test/kinesis.spec.js | 70 +++++++++---------- .../datadog-plugin-aws-sdk/test/sns.spec.js | 49 ++++++------- .../datadog-plugin-aws-sdk/test/sqs.spec.js | 2 +- 3 files changed, 59 insertions(+), 62 deletions(-) diff --git a/packages/datadog-plugin-aws-sdk/test/kinesis.spec.js b/packages/datadog-plugin-aws-sdk/test/kinesis.spec.js index 83c05f451bf..f38740f6d96 100644 --- a/packages/datadog-plugin-aws-sdk/test/kinesis.spec.js +++ b/packages/datadog-plugin-aws-sdk/test/kinesis.spec.js @@ -19,7 +19,7 @@ const expectedProducerHash = computePathwayHash( describe('Kinesis', () => { setup() - withVersions('aws-sdk', ['aws-sdk'], (version, moduleName) => { + withVersions('aws-sdk', ['aws-sdk', '@aws-sdk/smithy-client'], (version, moduleName) => { let AWS let kinesis let tracer @@ -28,9 +28,9 @@ describe('Kinesis', () => { const streamNameDSM = 'MyStreamDSM' const kinesisClientName = moduleName === '@aws-sdk/smithy-client' ? '@aws-sdk/client-kinesis' : 'aws-sdk' - function createResources (mocha, streamName, cb) { + function createResources (streamName, cb) { AWS = require(`../../../versions/${kinesisClientName}@${version}`).get() - + const params = { endpoint: 'http://127.0.0.1:4566', region: 'us-east-1' @@ -60,78 +60,78 @@ describe('Kinesis', () => { describe('no configuration', () => { before(() => { - return agent.load('aws-sdk', { kinesis: { dsmEnabled: false }}, { dsmEnabled: true }) + return agent.load('aws-sdk', { kinesis: { dsmEnabled: false } }, { dsmEnabled: true }) }) - - before(function(done) { - createResources(this, streamName, done) + + before(done => { + createResources(streamName, done) }) - + after(done => { kinesis.deleteStream({ StreamName: streamName }, (err, res) => { if (err) return done(err) - + helpers.waitForDeletedStream(kinesis, streamName, done) }) }) - + withNamingSchema( (done) => kinesis.describeStream({ StreamName: streamName }, (err) => err && done(err)), rawExpectedSchema.outbound ) - + it('injects trace context to Kinesis putRecord', done => { helpers.putTestRecord(kinesis, streamName, helpers.dataBuffer, (err, data) => { if (err) return done(err) - + helpers.getTestData(kinesis, streamName, data, (err, data) => { if (err) return done(err) - + expect(data).to.have.property('_datadog') expect(data._datadog).to.have.property('x-datadog-trace-id') - + done() }) }) }) - + it('handles already b64 encoded data', done => { helpers.putTestRecord(kinesis, streamName, helpers.dataBuffer.toString('base64'), (err, data) => { if (err) return done(err) - + helpers.getTestData(kinesis, streamName, data, (err, data) => { if (err) return done(err) - + expect(data).to.have.property('_datadog') expect(data._datadog).to.have.property('x-datadog-trace-id') - + done() }) }) }) - + it('skips injecting trace context to Kinesis if message is full', done => { const dataBuffer = Buffer.from(JSON.stringify({ myData: Array(1048576 - 100).join('a') })) - + helpers.putTestRecord(kinesis, streamName, dataBuffer, (err, data) => { if (err) return done(err) - + helpers.getTestData(kinesis, streamName, data, (err, data) => { if (err) return done(err) - + expect(data).to.not.have.property('_datadog') - + done() }) }) }) - + it('generates tags for proper input', done => { agent.use(traces => { const span = traces[0][0] @@ -143,7 +143,7 @@ describe('Kinesis', () => { expect(span.resource).to.equal(`putRecord ${streamName}`) expect(span.meta).to.have.property('streamname', streamName) }).then(done, done) - + helpers.putTestRecord(kinesis, streamName, helpers.dataBuffer, e => e && done(e)) }) @@ -151,20 +151,20 @@ describe('Kinesis', () => { before(() => { process.env.DD_TRACE_AWS_SDK_KINESIS_ENABLED = 'false' }) - + after(() => { delete process.env.DD_TRACE_AWS_SDK_KINESIS_ENABLED }) - + it('skip injects trace context to Kinesis putRecord when disabled', done => { helpers.putTestRecord(kinesis, streamName, helpers.dataBuffer, (err, data) => { if (err) return done(err) - + helpers.getTestData(kinesis, streamName, data, (err, data) => { if (err) return done(err) - + expect(data).not.to.have.property('_datadog') - + done() }) }) @@ -174,14 +174,14 @@ describe('Kinesis', () => { describe('DSM Context Propagation', () => { before(() => { - return agent.load('aws-sdk', { kinesis: { dsmEnabled: true }}, { dsmEnabled: true }) + return agent.load('aws-sdk', { kinesis: { dsmEnabled: true } }, { dsmEnabled: true }) }) - before(function(done) { + before(done => { tracer = require('../../dd-trace') - tracer.use('aws-sdk', { kinesis: { dsmEnabled: true }}, { dsmEnabled: true }) + tracer.use('aws-sdk', { kinesis: { dsmEnabled: true } }, { dsmEnabled: true }) - createResources(this, streamNameDSM, done) + createResources(streamNameDSM, done) }) after(done => { @@ -189,7 +189,7 @@ describe('Kinesis', () => { StreamName: streamNameDSM }, (err, res) => { if (err) return done(err) - + helpers.waitForDeletedStream(kinesis, streamNameDSM, done) }) }) diff --git a/packages/datadog-plugin-aws-sdk/test/sns.spec.js b/packages/datadog-plugin-aws-sdk/test/sns.spec.js index 4a97ae076b1..84e093f39a8 100644 --- a/packages/datadog-plugin-aws-sdk/test/sns.spec.js +++ b/packages/datadog-plugin-aws-sdk/test/sns.spec.js @@ -8,7 +8,6 @@ const { rawExpectedSchema } = require('./sns-naming') const { ENTRY_PARENT_HASH } = require('../../dd-trace/src/datastreams/processor') const { computePathwayHash } = require('../../dd-trace/src/datastreams/pathway') const DataStreamsContext = require('../../dd-trace/src/data_streams_context') -const { producer } = require('../../dd-trace/src/service-naming/schemas/v0/messaging') const expectedProducerHash = computePathwayHash( 'test', @@ -23,7 +22,7 @@ const expectedConsumerHash = computePathwayHash( expectedProducerHash ) -describe('Sns', function() { +describe('Sns', () => { setup() withVersions('aws-sdk', ['aws-sdk', '@aws-sdk/smithy-client'], (version, moduleName) => { @@ -64,17 +63,17 @@ describe('Sns', function() { sqs = new SQS({ endpoint: 'http://127.0.0.1:4566', region: 'us-east-1' }) sns.createTopic({ Name: topicName }, (err, data) => { - if (err) return done(err) + if (err) return cb(err) TopicArn = data.TopicArn sqs.createQueue({ QueueName: queueName }, (err, data) => { - if (err) return done(err) + if (err) return cb(err) QueueUrl = data.QueueUrl sqs.getQueueAttributes({ QueueUrl, AttributeNames: ['All'] }, (err, data) => { - if (err) return done(err) + if (err) return cb(err) QueueArn = data.Attributes.QueueArn @@ -97,18 +96,17 @@ describe('Sns', function() { } describe('no configuration', () => { - before(() => { parentId = '0' spanId = '0' - return agent.load('aws-sdk', { sns: { dsmEnabled: false }}, { dsmEnabled: true }) + return agent.load('aws-sdk', { sns: { dsmEnabled: false } }, { dsmEnabled: true }) }) before(done => { process.env.DD_DATA_STREAMS_ENABLED = 'true' tracer = require('../../dd-trace') - tracer.use('aws-sdk', { sns: { dsmEnabled: false }}) + tracer.use('aws-sdk', { sns: { dsmEnabled: false } }) createResources('TestQueue', 'TestTopic', done) }) @@ -133,7 +131,7 @@ describe('Sns', function() { Message: 'message 1' }, (err) => err && done()), 'TestTopic', 'topicname') - + withNamingSchema( (done) => sns.publish({ TopicArn, @@ -144,7 +142,7 @@ describe('Sns', function() { desc: 'producer' } ) - + withNamingSchema( (done) => sns.getTopicAttributes({ TopicArn @@ -154,29 +152,29 @@ describe('Sns', function() { desc: 'client' } ) - + it('injects trace context to SNS publish', done => { assertPropagation(done) - + sns.subscribe(subParams, (err, data) => { if (err) return done(err) - + sqs.receiveMessage(receiveParams, e => e && done(e)) sns.publish({ TopicArn, Message: 'message 1' }, (e) => { if (e) done(e) }) }) }) - + // There is a bug in 3.x (but not 3.0.0) that will be fixed in 3.261 // https://github.com/aws/aws-sdk-js-v3/issues/2861 if (!semver.intersects(version, '<3 || >3.0.0')) { it('injects trace context to SNS publishBatch', done => { assertPropagation(done) - + sns.subscribe(subParams, (err, data) => { if (err) return done(err) - + sqs.receiveMessage(receiveParams, e => e && done(e)) sns.publishBatch({ TopicArn, @@ -188,16 +186,16 @@ describe('Sns', function() { }) }) } - + // TODO: Figure out why this fails only in 3.0.0 if (version !== '3.0.0') { it('skips injecting trace context to SNS if message attributes are full', done => { sns.subscribe(subParams, (err, data) => { if (err) return done(err) - + sqs.receiveMessage(receiveParams, (err, data) => { if (err) return done(err) - + try { expect(data.Messages[0].Body).to.not.include('datadog') done() @@ -205,7 +203,7 @@ describe('Sns', function() { done(e) } }) - + sns.publish({ TopicArn, Message: 'message 1', @@ -225,11 +223,11 @@ describe('Sns', function() { }) }) } - + it('generates tags for proper publish calls', done => { agent.use(traces => { const span = traces[0][0] - + expect(span.resource).to.equal(`publish ${TopicArn}`) expect(span.meta).to.include({ 'aws.sns.topic_arn': TopicArn, @@ -238,7 +236,7 @@ describe('Sns', function() { 'region': 'us-east-1' }) }).then(done, done) - + sns.publish({ TopicArn, Message: 'message 1' }, e => e && done(e)) }) }) @@ -251,7 +249,7 @@ describe('Sns', function() { before(done => { process.env.DD_DATA_STREAMS_ENABLED = 'true' tracer = require('../../dd-trace') - tracer.use('aws-sdk', { sns: { dsmEnabled: true }, sqs: { dsmEnabled: true }}) + tracer.use('aws-sdk', { sns: { dsmEnabled: true }, sqs: { dsmEnabled: true } }) createResources('TestQueueDSM', 'TestTopicDSM', done) }) @@ -271,7 +269,7 @@ describe('Sns', function() { it('injects DSM trace context to SNS publish', done => { let producerHashCreated = false let consumerHashCreated = false - + if (DataStreamsContext.setDataStreamsContext.isSinonProxy) { DataStreamsContext.setDataStreamsContext.restore() } @@ -292,7 +290,6 @@ describe('Sns', function() { producerHashCreated = true } }) - expect(consumerHashCreated).to.equal(true) expect(producerHashCreated).to.equal(true) diff --git a/packages/datadog-plugin-aws-sdk/test/sqs.spec.js b/packages/datadog-plugin-aws-sdk/test/sqs.spec.js index 05eb60cd0ce..7cc7da4d515 100644 --- a/packages/datadog-plugin-aws-sdk/test/sqs.spec.js +++ b/packages/datadog-plugin-aws-sdk/test/sqs.spec.js @@ -312,7 +312,7 @@ describe('Plugin', () => { before(done => { process.env.DD_DATA_STREAMS_ENABLED = 'true' tracer = require('../../dd-trace') - tracer.use('aws-sdk', { sns: { dsmEnabled: true }, sqs: { dsmEnabled: true }}) + tracer.use('aws-sdk', { sns: { dsmEnabled: true }, sqs: { dsmEnabled: true } }) done() }) From 62aad551c84ba9eedecb5606fe09e7173ee41aea Mon Sep 17 00:00:00 2001 From: William Conti Date: Tue, 12 Dec 2023 17:46:23 -0500 Subject: [PATCH 15/52] add message payload size tests for sns / kinesis --- .../src/services/kinesis.js | 28 +++++++++------ .../src/services/sns.js | 13 ++++--- .../test/kinesis.spec.js | 28 ++++++++++++++- .../datadog-plugin-aws-sdk/test/sns.spec.js | 36 ++++++++++++++++++- .../dd-trace/src/datastreams/processor.js | 17 ++++++++- 5 files changed, 103 insertions(+), 19 deletions(-) diff --git a/packages/datadog-plugin-aws-sdk/src/services/kinesis.js b/packages/datadog-plugin-aws-sdk/src/services/kinesis.js index 712478a7c46..b8386458f81 100644 --- a/packages/datadog-plugin-aws-sdk/src/services/kinesis.js +++ b/packages/datadog-plugin-aws-sdk/src/services/kinesis.js @@ -43,18 +43,9 @@ class Kinesis extends BaseAwsSdkPlugin { if (!request.params) { return } - const traceData = {} - if (this.config.dsmEnabled) { - const payloadSize = getHeadersSize(request.params) - const stream = request.params.StreamName - const dataStreamsContext = this.tracer - .setCheckpoint(['direction:out', `topic:${stream}`, 'type:kinesis'], span, payloadSize) - if (dataStreamsContext) { - const pathwayCtx = encodePathwayContext(dataStreamsContext) - traceData[CONTEXT_PROPAGATION_KEY] = pathwayCtx.toJSON() - } - } + + // inject data with DD context this.tracer.inject(span, 'text_map', traceData) let injectPath if (request.params.Records && request.params.Records.length > 0) { @@ -65,9 +56,24 @@ class Kinesis extends BaseAwsSdkPlugin { log.error('No valid payload passed, unable to pass trace context') return } + const parsedData = this._tryParse(injectPath.Data) if (parsedData) { parsedData._datadog = traceData + + // set DSM hash if enabled + if (this.config.dsmEnabled) { + // get payload size of request data + const payloadSize = getHeadersSize(parsedData) + const stream = request.params.StreamName + const dataStreamsContext = this.tracer + .setCheckpoint(['direction:out', `topic:${stream}`, 'type:kinesis'], span, payloadSize) + if (dataStreamsContext) { + const pathwayCtx = encodePathwayContext(dataStreamsContext) + parsedData._datadog[CONTEXT_PROPAGATION_KEY] = pathwayCtx.toJSON() + } + } + const finalData = Buffer.from(JSON.stringify(parsedData)) const byteSize = finalData.length // Kinesis max payload size is 1MB diff --git a/packages/datadog-plugin-aws-sdk/src/services/sns.js b/packages/datadog-plugin-aws-sdk/src/services/sns.js index 23c4d65a069..8bfae0f9b56 100644 --- a/packages/datadog-plugin-aws-sdk/src/services/sns.js +++ b/packages/datadog-plugin-aws-sdk/src/services/sns.js @@ -68,6 +68,12 @@ class Sns extends BaseAwsSdkPlugin { return } const ddInfo = {} + this.tracer.inject(span, 'text_map', ddInfo) + // add ddInfo before checking DSM so we can include DD attributes in payload size + params.MessageAttributes._datadog = { + DataType: 'Binary', + BinaryValue: ddInfo + } if (this.config.dsmEnabled) { const payloadSize = getHeadersSize(params) const topicName = getTopicName(params.TopicArn) @@ -78,11 +84,8 @@ class Sns extends BaseAwsSdkPlugin { ddInfo[CONTEXT_PROPAGATION_KEY] = pathwayCtx.toJSON() } } - this.tracer.inject(span, 'text_map', ddInfo) - params.MessageAttributes._datadog = { - DataType: 'Binary', - BinaryValue: Buffer.from(JSON.stringify(ddInfo)) // BINARY types are automatically base64 encoded - } + // BINARY types are automatically base64 encoded + params.MessageAttributes._datadog.BinaryValue = Buffer.from(JSON.stringify(ddInfo)) } } diff --git a/packages/datadog-plugin-aws-sdk/test/kinesis.spec.js b/packages/datadog-plugin-aws-sdk/test/kinesis.spec.js index f38740f6d96..316b9442bf4 100644 --- a/packages/datadog-plugin-aws-sdk/test/kinesis.spec.js +++ b/packages/datadog-plugin-aws-sdk/test/kinesis.spec.js @@ -5,7 +5,11 @@ const agent = require('../../dd-trace/test/plugins/agent') const { setup } = require('./spec_helpers') const helpers = require('./kinesis_helpers') const { rawExpectedSchema } = require('./kinesis-naming') -const { ENTRY_PARENT_HASH } = require('../../dd-trace/src/datastreams/processor') +const { + ENTRY_PARENT_HASH, + DataStreamsProcessor, + getHeadersSize +} = require('../../dd-trace/src/datastreams/processor') const { computePathwayHash } = require('../../dd-trace/src/datastreams/pathway') const DataStreamsContext = require('../../dd-trace/src/data_streams_context') @@ -211,6 +215,28 @@ describe('Kinesis', () => { done() }) }) + + it('Should set a message payload size when producing a message', (done) => { + if (DataStreamsProcessor.prototype.recordCheckpoint.isSinonProxy) { + DataStreamsProcessor.prototype.recordCheckpoint.restore() + } + const recordCheckpointSpy = sinon.spy(DataStreamsProcessor.prototype, 'recordCheckpoint') + + helpers.putTestRecord(kinesis, streamNameDSM, helpers.dataBuffer, (err, data) => { + if (err) return done(err) + + helpers.getTestData(kinesis, streamNameDSM, data, (err, data) => { + if (err) return done(err) + + const payloadSize = getHeadersSize(data) + + expect(recordCheckpointSpy.args[0][0].hasOwnProperty('payloadSize')) + expect(recordCheckpointSpy.args[0][0].payloadSize).to.equal(payloadSize) + recordCheckpointSpy.restore() + done() + }) + }) + }) }) }) }) diff --git a/packages/datadog-plugin-aws-sdk/test/sns.spec.js b/packages/datadog-plugin-aws-sdk/test/sns.spec.js index 84e093f39a8..af825c51ade 100644 --- a/packages/datadog-plugin-aws-sdk/test/sns.spec.js +++ b/packages/datadog-plugin-aws-sdk/test/sns.spec.js @@ -5,9 +5,10 @@ const semver = require('semver') const agent = require('../../dd-trace/test/plugins/agent') const { setup } = require('./spec_helpers') const { rawExpectedSchema } = require('./sns-naming') -const { ENTRY_PARENT_HASH } = require('../../dd-trace/src/datastreams/processor') +const { ENTRY_PARENT_HASH, getHeadersSize, DataStreamsProcessor } = require('../../dd-trace/src/datastreams/processor') const { computePathwayHash } = require('../../dd-trace/src/datastreams/pathway') const DataStreamsContext = require('../../dd-trace/src/data_streams_context') +const snsPlugin = require('../src/services/sns') const expectedProducerHash = computePathwayHash( 'test', @@ -303,6 +304,39 @@ describe('Sns', () => { }) }) }) + + it('sets a message payload size when DSM is enabled', done => { + if (DataStreamsProcessor.prototype.recordCheckpoint.isSinonProxy) { + DataStreamsProcessor.prototype.recordCheckpoint.restore() + } + const recordCheckpointSpy = sinon.spy(DataStreamsProcessor.prototype, 'recordCheckpoint') + + if (snsPlugin.prototype._injectMessageAttributes.isSinonProxy) { + snsPlugin.prototype._injectMessageAttributes.restore() + } + const injectMessageSpy = sinon.spy(snsPlugin.prototype, '_injectMessageAttributes') + + sns.subscribe(subParams, (err, data) => { + if (err) return done(err) + + sns.publish( + { TopicArn, Message: 'message DSM' }, + (err) => { + if (err) return done(err) + + const params = injectMessageSpy.args[0][1] + // decode the raw buffer + params.MessageAttributes._datadog.BinaryValue = JSON.parse(Buffer.from(params.MessageAttributes._datadog.BinaryValue, 'base64')) + const payloadSize = getHeadersSize(params) + + expect(recordCheckpointSpy.args[0][0].hasOwnProperty('payloadSize')) + expect(recordCheckpointSpy.args[0][0].payloadSize).to.equal(payloadSize) + injectMessageSpy.restore() + recordCheckpointSpy.restore() + done() + }) + }) + }) }) }) }) diff --git a/packages/dd-trace/src/datastreams/processor.js b/packages/dd-trace/src/datastreams/processor.js index 601d81441d8..121ca14336e 100644 --- a/packages/dd-trace/src/datastreams/processor.js +++ b/packages/dd-trace/src/datastreams/processor.js @@ -66,6 +66,21 @@ function getSizeOrZero (obj) { if (Buffer.isBuffer(obj)) { return obj.length } + if (Array.isArray(obj) && obj.length > 0) { + if (typeof obj[0] === 'number') return Buffer.from(obj).length + let payloadSize = 0 + obj.forEach(item => { + payloadSize += getSizeOrZero(item) + }) + return payloadSize + } + if (typeof obj === 'object') { + try { + return getHeadersSize(obj) + } catch { + // pass + } + } return 0 } @@ -191,7 +206,7 @@ class DataStreamsProcessor { } if (direction === 'direction:out') { // Add the header for this now, as the callee doesn't have access to context when producing - payloadSize += getSizeOrZero(encodePathwayContext(dataStreamsContext)) + payloadSize += getHeadersSize(encodePathwayContext(dataStreamsContext).toJSON()) payloadSize += CONTEXT_PROPAGATION_KEY.length } const checkpoint = { From 43bb3f1ceedfd96e350f0eacfa94a8636f5b08c0 Mon Sep 17 00:00:00 2001 From: William Conti Date: Wed, 13 Dec 2023 12:12:10 -0500 Subject: [PATCH 16/52] add more payloadSize tests --- .../src/services/sns.js | 2 +- .../src/services/sqs.js | 28 ++++++----- .../datadog-plugin-aws-sdk/test/sns.spec.js | 6 ++- .../datadog-plugin-aws-sdk/test/sqs.spec.js | 46 ++++++++++++++++--- .../dd-trace/src/datastreams/processor.js | 6 ++- 5 files changed, 65 insertions(+), 23 deletions(-) diff --git a/packages/datadog-plugin-aws-sdk/src/services/sns.js b/packages/datadog-plugin-aws-sdk/src/services/sns.js index 8bfae0f9b56..53a960bcf07 100644 --- a/packages/datadog-plugin-aws-sdk/src/services/sns.js +++ b/packages/datadog-plugin-aws-sdk/src/services/sns.js @@ -72,7 +72,7 @@ class Sns extends BaseAwsSdkPlugin { // add ddInfo before checking DSM so we can include DD attributes in payload size params.MessageAttributes._datadog = { DataType: 'Binary', - BinaryValue: ddInfo + BinaryValue: JSON.stringify(ddInfo) } if (this.config.dsmEnabled) { const payloadSize = getHeadersSize(params) diff --git a/packages/datadog-plugin-aws-sdk/src/services/sqs.js b/packages/datadog-plugin-aws-sdk/src/services/sqs.js index a49e49f89e4..22ac1714a49 100644 --- a/packages/datadog-plugin-aws-sdk/src/services/sqs.js +++ b/packages/datadog-plugin-aws-sdk/src/services/sqs.js @@ -33,7 +33,7 @@ class Sqs extends BaseAwsSdkPlugin { ) } const span = plugin.tracer.startSpan('aws.response', options) - this.responseExtractDSMContext(request.params, responseExtraction.traceContext, span) + this.responseExtractDSMContext(request.params, responseExtraction, span) this.enter(span, store) } }) @@ -143,14 +143,16 @@ class Sqs extends BaseAwsSdkPlugin { parsedAttributes = JSON.parse(textMap) return { maybeChildOf: this.tracer.extract('text_map', parsedAttributes), - traceContext: parsedAttributes + traceContext: parsedAttributes, + message: message } } else if (datadogAttribute.Type === 'Binary') { const buffer = Buffer.from(datadogAttribute.Value, 'base64') parsedAttributes = JSON.parse(buffer) return { maybeChildOf: this.tracer.extract('text_map', parsedAttributes), - traceContext: parsedAttributes + traceContext: parsedAttributes, + message: message } } } catch (e) { @@ -159,10 +161,13 @@ class Sqs extends BaseAwsSdkPlugin { } responseExtractDSMContext (params, context, span) { - if (this.config.dsmEnabled && context && context[CONTEXT_PROPAGATION_KEY]) { - const payloadSize = getHeadersSize(params) + if (this.config.dsmEnabled && context && context.traceContext && context.traceContext[CONTEXT_PROPAGATION_KEY]) { + const payloadSize = getHeadersSize({ + Body: context.message.Body, + MessageAttributes: context.message.MessageAttributes + }) const queue = params.QueueUrl.split('/').pop() - this.tracer.decodeDataStreamsContext(Buffer.from(context[CONTEXT_PROPAGATION_KEY])) + this.tracer.decodeDataStreamsContext(Buffer.from(context.traceContext[CONTEXT_PROPAGATION_KEY])) this.tracer .setCheckpoint(['direction:in', `topic:${queue}`, 'type:sqs'], span, payloadSize) } @@ -181,6 +186,11 @@ class Sqs extends BaseAwsSdkPlugin { return } const ddInfo = {} + this.tracer.inject(span, 'text_map', ddInfo) + request.params.MessageAttributes._datadog = { + DataType: 'String', + StringValue: JSON.stringify(ddInfo) + } if (this.config.dsmEnabled) { const payloadSize = getHeadersSize(request.params) const queue = request.params.QueueUrl.split('/').pop() @@ -191,11 +201,7 @@ class Sqs extends BaseAwsSdkPlugin { ddInfo[CONTEXT_PROPAGATION_KEY] = pathwayCtx.toJSON() } } - this.tracer.inject(span, 'text_map', ddInfo) - request.params.MessageAttributes._datadog = { - DataType: 'String', - StringValue: JSON.stringify(ddInfo) - } + request.params.MessageAttributes._datadog.StringValue = JSON.stringify(ddInfo) } } } diff --git a/packages/datadog-plugin-aws-sdk/test/sns.spec.js b/packages/datadog-plugin-aws-sdk/test/sns.spec.js index af825c51ade..64cc43d58bb 100644 --- a/packages/datadog-plugin-aws-sdk/test/sns.spec.js +++ b/packages/datadog-plugin-aws-sdk/test/sns.spec.js @@ -325,8 +325,10 @@ describe('Sns', () => { if (err) return done(err) const params = injectMessageSpy.args[0][1] - // decode the raw buffer - params.MessageAttributes._datadog.BinaryValue = JSON.parse(Buffer.from(params.MessageAttributes._datadog.BinaryValue, 'base64')) + // decode the raw buffer to JSON string + params.MessageAttributes._datadog.BinaryValue = JSON.stringify( + JSON.parse(Buffer.from(params.MessageAttributes._datadog.BinaryValue, 'base64')) + ) const payloadSize = getHeadersSize(params) expect(recordCheckpointSpy.args[0][0].hasOwnProperty('payloadSize')) diff --git a/packages/datadog-plugin-aws-sdk/test/sqs.spec.js b/packages/datadog-plugin-aws-sdk/test/sqs.spec.js index 7cc7da4d515..9ac95fbde9b 100644 --- a/packages/datadog-plugin-aws-sdk/test/sqs.spec.js +++ b/packages/datadog-plugin-aws-sdk/test/sqs.spec.js @@ -3,9 +3,10 @@ const agent = require('../../dd-trace/test/plugins/agent') const { setup } = require('./spec_helpers') const { rawExpectedSchema } = require('./sqs-naming') -const { ENTRY_PARENT_HASH, DataStreamsProcessor } = require('../../dd-trace/src/datastreams/processor') +const { ENTRY_PARENT_HASH, DataStreamsProcessor, getHeadersSize } = require('../../dd-trace/src/datastreams/processor') const { computePathwayHash } = require('../../dd-trace/src/datastreams/pathway') const DataStreamsContext = require('../../dd-trace/src/data_streams_context') +const sqsPlugin = require('../src/services/sqs') const queueOptions = { QueueName: 'SQS_QUEUE_NAME', @@ -28,9 +29,10 @@ describe('Plugin', () => { describe('without configuration', () => { before(() => { + process.env.DD_DATA_STREAMS_ENABLED = 'true' tracer = require('../../dd-trace') - return agent.load('aws-sdk') + return agent.load('aws-sdk', { sqs: { dsmEnabled: false } }, { dsmEnabled: true }) }) before(done => { @@ -222,7 +224,9 @@ describe('Plugin', () => { consumer: false, dsmEnabled: false } - }) + }, + { dsmEnabled: true } + ) }) before(done => { @@ -312,7 +316,7 @@ describe('Plugin', () => { before(done => { process.env.DD_DATA_STREAMS_ENABLED = 'true' tracer = require('../../dd-trace') - tracer.use('aws-sdk', { sns: { dsmEnabled: true }, sqs: { dsmEnabled: true } }) + tracer.use('aws-sdk', { sqs: { dsmEnabled: true } }) done() }) @@ -409,36 +413,64 @@ describe('Plugin', () => { DataStreamsProcessor.prototype.recordCheckpoint.restore() } const recordCheckpointSpy = sinon.spy(DataStreamsProcessor.prototype, 'recordCheckpoint') + + if (sqsPlugin.prototype.requestInject.isSinonProxy) { + sqsPlugin.prototype.requestInject.restore() + } + const injectMessageSpy = sinon.spy(sqsPlugin.prototype, 'requestInject') + sqs.sendMessage({ MessageBody: 'test DSM', QueueUrl }, (err) => { if (err) return done(err) + + const payloadSize = getHeadersSize(injectMessageSpy.args[0][1].params) + expect(recordCheckpointSpy.args[0][0].hasOwnProperty('payloadSize')) + expect(recordCheckpointSpy.args[0][0].payloadSize).to.equal(payloadSize) + recordCheckpointSpy.restore() + injectMessageSpy.restore() + done() }) }) it('Should set a message payload size when consuming a message', (done) => { - if (DataStreamsProcessor.prototype.recordCheckpoint.isSinonProxy) { - DataStreamsProcessor.prototype.recordCheckpoint.restore() + if (sqsPlugin.prototype.responseExtractDSMContext.isSinonProxy) { + sqsPlugin.prototype.responseExtractDSMContext.restore() } - const recordCheckpointSpy = sinon.spy(DataStreamsProcessor.prototype, 'recordCheckpoint') + const extractContextSpy = sinon.spy(sqsPlugin.prototype, 'responseExtractDSMContext') + sqs.sendMessage({ MessageBody: 'test DSM', QueueUrl }, (err) => { if (err) return done(err) + if (DataStreamsProcessor.prototype.recordCheckpoint.isSinonProxy) { + DataStreamsProcessor.prototype.recordCheckpoint.restore() + } + const recordCheckpointSpy = sinon.spy(DataStreamsProcessor.prototype, 'recordCheckpoint') + sqs.receiveMessage({ QueueUrl, MessageAttributeNames: ['.*'] }, (err) => { if (err) return done(err) + const payloadSize = getHeadersSize({ + Body: extractContextSpy.args[0][1].message.Body, + MessageAttributes: extractContextSpy.args[0][1].message.MessageAttributes + }) + expect(recordCheckpointSpy.args[0][0].hasOwnProperty('payloadSize')) + expect(recordCheckpointSpy.args[0][0].payloadSize).to.equal(payloadSize) + recordCheckpointSpy.restore() + extractContextSpy.restore() + done() }) }) diff --git a/packages/dd-trace/src/datastreams/processor.js b/packages/dd-trace/src/datastreams/processor.js index 121ca14336e..5e434aa4201 100644 --- a/packages/dd-trace/src/datastreams/processor.js +++ b/packages/dd-trace/src/datastreams/processor.js @@ -206,8 +206,10 @@ class DataStreamsProcessor { } if (direction === 'direction:out') { // Add the header for this now, as the callee doesn't have access to context when producing - payloadSize += getHeadersSize(encodePathwayContext(dataStreamsContext).toJSON()) - payloadSize += CONTEXT_PROPAGATION_KEY.length + // - 1 to account for extra byte for { + const ddInfoContinued = {} + ddInfoContinued[CONTEXT_PROPAGATION_KEY] = encodePathwayContext(dataStreamsContext).toJSON() + payloadSize += getSizeOrZero(JSON.stringify(ddInfoContinued)) - 1 } const checkpoint = { currentTimestamp: nowNs, From 3f38244191a5ddef0885ebf72433b16cda10037c Mon Sep 17 00:00:00 2001 From: William Conti Date: Wed, 13 Dec 2023 12:24:32 -0500 Subject: [PATCH 17/52] fix kinesis payload size --- packages/datadog-plugin-aws-sdk/src/services/kinesis.js | 4 ++-- packages/datadog-plugin-aws-sdk/test/kinesis.spec.js | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/datadog-plugin-aws-sdk/src/services/kinesis.js b/packages/datadog-plugin-aws-sdk/src/services/kinesis.js index b8386458f81..0634f7f31c1 100644 --- a/packages/datadog-plugin-aws-sdk/src/services/kinesis.js +++ b/packages/datadog-plugin-aws-sdk/src/services/kinesis.js @@ -1,7 +1,7 @@ 'use strict' const { CONTEXT_PROPAGATION_KEY, - getHeadersSize + getSizeOrZero } = require('../../../dd-trace/src/datastreams/processor') const { encodePathwayContext } = require('../../../dd-trace/src/datastreams/pathway') const log = require('../../../dd-trace/src/log') @@ -64,7 +64,7 @@ class Kinesis extends BaseAwsSdkPlugin { // set DSM hash if enabled if (this.config.dsmEnabled) { // get payload size of request data - const payloadSize = getHeadersSize(parsedData) + const payloadSize = getSizeOrZero(JSON.stringify(parsedData)) const stream = request.params.StreamName const dataStreamsContext = this.tracer .setCheckpoint(['direction:out', `topic:${stream}`, 'type:kinesis'], span, payloadSize) diff --git a/packages/datadog-plugin-aws-sdk/test/kinesis.spec.js b/packages/datadog-plugin-aws-sdk/test/kinesis.spec.js index 316b9442bf4..76e3926aa80 100644 --- a/packages/datadog-plugin-aws-sdk/test/kinesis.spec.js +++ b/packages/datadog-plugin-aws-sdk/test/kinesis.spec.js @@ -8,7 +8,7 @@ const { rawExpectedSchema } = require('./kinesis-naming') const { ENTRY_PARENT_HASH, DataStreamsProcessor, - getHeadersSize + getSizeOrZero } = require('../../dd-trace/src/datastreams/processor') const { computePathwayHash } = require('../../dd-trace/src/datastreams/pathway') const DataStreamsContext = require('../../dd-trace/src/data_streams_context') @@ -228,7 +228,7 @@ describe('Kinesis', () => { helpers.getTestData(kinesis, streamNameDSM, data, (err, data) => { if (err) return done(err) - const payloadSize = getHeadersSize(data) + const payloadSize = getSizeOrZero(JSON.stringify(data)) expect(recordCheckpointSpy.args[0][0].hasOwnProperty('payloadSize')) expect(recordCheckpointSpy.args[0][0].payloadSize).to.equal(payloadSize) From d8e7bff301b90d072128d36443ff1fa586f170b4 Mon Sep 17 00:00:00 2001 From: William Conti Date: Wed, 13 Dec 2023 13:10:43 -0500 Subject: [PATCH 18/52] fix kinesis tests --- packages/datadog-plugin-aws-sdk/test/kinesis.spec.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/datadog-plugin-aws-sdk/test/kinesis.spec.js b/packages/datadog-plugin-aws-sdk/test/kinesis.spec.js index 76e3926aa80..b72667dfbc1 100644 --- a/packages/datadog-plugin-aws-sdk/test/kinesis.spec.js +++ b/packages/datadog-plugin-aws-sdk/test/kinesis.spec.js @@ -20,7 +20,8 @@ const expectedProducerHash = computePathwayHash( ENTRY_PARENT_HASH ) -describe('Kinesis', () => { +describe('Kinesis', function () { + this.timeout(10000) setup() withVersions('aws-sdk', ['aws-sdk', '@aws-sdk/smithy-client'], (version, moduleName) => { @@ -228,7 +229,7 @@ describe('Kinesis', () => { helpers.getTestData(kinesis, streamNameDSM, data, (err, data) => { if (err) return done(err) - const payloadSize = getSizeOrZero(JSON.stringify(data)) + const payloadSize = getSizeOrZero(data) expect(recordCheckpointSpy.args[0][0].hasOwnProperty('payloadSize')) expect(recordCheckpointSpy.args[0][0].payloadSize).to.equal(payloadSize) From 564bff3f64d0e1721c46d831b7145caf60496585 Mon Sep 17 00:00:00 2001 From: William Conti Date: Wed, 13 Dec 2023 14:11:14 -0500 Subject: [PATCH 19/52] add context propagation to kinesis --- .../src/services/kinesis.js | 89 ++++++++++++++++++- .../test/kinesis.spec.js | 51 +++++++++-- 2 files changed, 131 insertions(+), 9 deletions(-) diff --git a/packages/datadog-plugin-aws-sdk/src/services/kinesis.js b/packages/datadog-plugin-aws-sdk/src/services/kinesis.js index 0634f7f31c1..4f8751275cb 100644 --- a/packages/datadog-plugin-aws-sdk/src/services/kinesis.js +++ b/packages/datadog-plugin-aws-sdk/src/services/kinesis.js @@ -1,16 +1,56 @@ 'use strict' const { CONTEXT_PROPAGATION_KEY, - getSizeOrZero + getSizeOrZero, + getHeadersSize } = require('../../../dd-trace/src/datastreams/processor') const { encodePathwayContext } = require('../../../dd-trace/src/datastreams/pathway') const log = require('../../../dd-trace/src/log') const BaseAwsSdkPlugin = require('../base') +const { storage } = require('../../../datadog-core') class Kinesis extends BaseAwsSdkPlugin { static get id () { return 'kinesis' } static get peerServicePrecursors () { return ['streamname'] } + constructor (...args) { + super(...args) + + // TODO(bengl) Find a way to create the response span tags without this WeakMap being populated + // in the base class + this.requestTags = new WeakMap() + + this.addSub('apm:aws:response:start:kinesis', obj => { + const { request, response } = obj + const store = storage.getStore() + const plugin = this + const streamName = this.getStreamName(request.params, request.operation) + if (streamName) { + this.requestTags.streamName = streamName + } + const responseExtraction = this.responseExtract(request.params, request.operation, response) + if (responseExtraction && responseExtraction.maybeChildOf) { + obj.needsFinish = true + const options = { + childOf: responseExtraction.maybeChildOf, + tags: Object.assign( + {}, + this.requestTags.get(request) || {}, + { 'span.kind': 'server' } + ) + } + const span = plugin.tracer.startSpan('aws.response', options) + this.responseExtractDSMContext(response, responseExtraction.traceContext, this.requestTags.streamName, span) + this.enter(span, store) + } + }) + + this.addSub('apm:aws:response:finish:kinesis', err => { + const { span } = storage.getStore() + this.finish(span, null, err) + }) + } + generateTags (params, operation, response) { if (!params || !params.StreamName) return {} @@ -21,6 +61,46 @@ class Kinesis extends BaseAwsSdkPlugin { } } + getStreamName (params, operation) { + if (!operation || operation !== 'getShardIterator') return null + if (!params || !params.StreamName) return null + + return params.StreamName + } + + responseExtract (params, operation, response) { + if (operation !== 'getRecords') return + if (params.Limit && params.Limit !== 1) return + if (!response || !response.Records || !response.Records[0] || response.Records.length > 1) return + + const record = response.Records[0] + + try { + const decodedData = JSON.parse(Buffer.from(record.Data).toString()) + + return { + maybeChildOf: this.tracer.extract('text_map', decodedData._datadog), + traceContext: decodedData._datadog + } + } catch (e) { + log.error(e) + } + } + + responseExtractDSMContext (response, context, streamName, span) { + if (this.config.dsmEnabled && context && context[CONTEXT_PROPAGATION_KEY] && streamName) { + let payloadSize = 0 + if (response && response.Records) { + for (const record of response.Records) { + payloadSize += getSizeOrZero(record.Data) + } + } + this.tracer.decodeDataStreamsContext(Buffer.from(context[CONTEXT_PROPAGATION_KEY])) + this.tracer + .setCheckpoint(['direction:in', `topic:${streamName}`, 'type:kinesis'], span, payloadSize) + } + } + // AWS-SDK will b64 kinesis payloads // or will accept an already b64 encoded payload // This method handles both @@ -60,11 +140,15 @@ class Kinesis extends BaseAwsSdkPlugin { const parsedData = this._tryParse(injectPath.Data) if (parsedData) { parsedData._datadog = traceData + const originalData = injectPath.Data + + // set this now so that computed dsm payload size is correct + injectPath.Data = JSON.stringify(parsedData) // set DSM hash if enabled if (this.config.dsmEnabled) { // get payload size of request data - const payloadSize = getSizeOrZero(JSON.stringify(parsedData)) + const payloadSize = getHeadersSize(request.params) const stream = request.params.StreamName const dataStreamsContext = this.tracer .setCheckpoint(['direction:out', `topic:${stream}`, 'type:kinesis'], span, payloadSize) @@ -79,6 +163,7 @@ class Kinesis extends BaseAwsSdkPlugin { // Kinesis max payload size is 1MB // So we must ensure adding DD context won't go over that (512b is an estimate) if (byteSize >= 1048576) { + injectPath.Data = originalData log.info('Payload size too large to pass context') return } diff --git a/packages/datadog-plugin-aws-sdk/test/kinesis.spec.js b/packages/datadog-plugin-aws-sdk/test/kinesis.spec.js index b72667dfbc1..7f8a46633ae 100644 --- a/packages/datadog-plugin-aws-sdk/test/kinesis.spec.js +++ b/packages/datadog-plugin-aws-sdk/test/kinesis.spec.js @@ -8,10 +8,12 @@ const { rawExpectedSchema } = require('./kinesis-naming') const { ENTRY_PARENT_HASH, DataStreamsProcessor, - getSizeOrZero + getSizeOrZero, + getHeadersSize } = require('../../dd-trace/src/datastreams/processor') const { computePathwayHash } = require('../../dd-trace/src/datastreams/pathway') const DataStreamsContext = require('../../dd-trace/src/data_streams_context') +const kinesisPlugin = require('../src/services/kinesis') const expectedProducerHash = computePathwayHash( 'test', @@ -223,18 +225,53 @@ describe('Kinesis', function () { } const recordCheckpointSpy = sinon.spy(DataStreamsProcessor.prototype, 'recordCheckpoint') + if (kinesisPlugin.prototype.requestInject.isSinonProxy) { + kinesisPlugin.prototype.requestInject.restore() + } + const requestInjectSpy = sinon.spy(kinesisPlugin.prototype, 'requestInject') + + helpers.putTestRecord(kinesis, streamNameDSM, helpers.dataBuffer, (err) => { + if (err) return done(err) + + const payloadSize = getHeadersSize(requestInjectSpy.args[0][1].params) + + expect(recordCheckpointSpy.args[0][0].hasOwnProperty('payloadSize')) + expect(recordCheckpointSpy.args[0][0].payloadSize).to.equal(payloadSize) + recordCheckpointSpy.restore() + requestInjectSpy.restore() + done() + }) + }) + + it('Should set a message payload size when consuming a message', (done) => { helpers.putTestRecord(kinesis, streamNameDSM, helpers.dataBuffer, (err, data) => { if (err) return done(err) - helpers.getTestData(kinesis, streamNameDSM, data, (err, data) => { + if (DataStreamsProcessor.prototype.recordCheckpoint.isSinonProxy) { + DataStreamsProcessor.prototype.recordCheckpoint.restore() + } + const recordCheckpointSpy = sinon.spy(DataStreamsProcessor.prototype, 'recordCheckpoint') + + kinesis.getShardIterator({ + ShardId: data.ShardId, + ShardIteratorType: 'AT_SEQUENCE_NUMBER', + StartingSequenceNumber: data.SequenceNumber, + StreamName: streamNameDSM + }, (err, { ShardIterator } = {}) => { if (err) return done(err) - const payloadSize = getSizeOrZero(data) + kinesis.getRecords({ + ShardIterator + }, (err, response) => { + if (err) return done(err) - expect(recordCheckpointSpy.args[0][0].hasOwnProperty('payloadSize')) - expect(recordCheckpointSpy.args[0][0].payloadSize).to.equal(payloadSize) - recordCheckpointSpy.restore() - done() + const payloadSize = getSizeOrZero(response.Records[0].Data) + + expect(recordCheckpointSpy.args[0][0].hasOwnProperty('payloadSize')) + expect(recordCheckpointSpy.args[0][0].payloadSize).to.equal(payloadSize) + recordCheckpointSpy.restore() + done() + }) }) }) }) From 3a692aa30c47ada59be6d52fe659fc5ace74f201 Mon Sep 17 00:00:00 2001 From: William Conti Date: Wed, 13 Dec 2023 15:38:30 -0500 Subject: [PATCH 20/52] more tests --- .../test/kinesis.spec.js | 82 +++++++++++++++++-- 1 file changed, 75 insertions(+), 7 deletions(-) diff --git a/packages/datadog-plugin-aws-sdk/test/kinesis.spec.js b/packages/datadog-plugin-aws-sdk/test/kinesis.spec.js index 7f8a46633ae..3d9656ab408 100644 --- a/packages/datadog-plugin-aws-sdk/test/kinesis.spec.js +++ b/packages/datadog-plugin-aws-sdk/test/kinesis.spec.js @@ -21,15 +21,22 @@ const expectedProducerHash = computePathwayHash( ['direction:out', 'topic:MyStreamDSM', 'type:kinesis'], ENTRY_PARENT_HASH ) +const expectedConsumerHash = computePathwayHash( + 'test', + 'tester', + ['direction:in', 'topic:MyStreamDSM', 'type:kinesis'], + expectedProducerHash +) describe('Kinesis', function () { - this.timeout(10000) setup() withVersions('aws-sdk', ['aws-sdk', '@aws-sdk/smithy-client'], (version, moduleName) => { let AWS let kinesis let tracer + let parentId + let spanId const streamName = 'MyStream' const streamNameDSM = 'MyStreamDSM' @@ -55,18 +62,34 @@ describe('Kinesis', function () { StreamName: streamName, ShardCount: 1 }, (err, res) => { - if (err) return cb(err) - helpers.waitForActiveStream(kinesis, streamName, cb) }) } + const assertPropagation = done => { + agent.use(traces => { + const span = traces[0][0] + + if (span.resource.startsWith('putRecord')) { + spanId = span.span_id.toString() + } else if (span.name === 'aws.response') { + parentId = span.parent_id.toString() + } + + expect(parentId).to.not.equal('0') + expect(parentId).to.equal(spanId) + }).then(done, done) + } + before(() => { process.env.DD_DATA_STREAMS_ENABLED = 'true' }) describe('no configuration', () => { before(() => { + parentId = '0' + spanId = '0' + return agent.load('aws-sdk', { kinesis: { dsmEnabled: false } }, { dsmEnabled: true }) }) @@ -91,7 +114,9 @@ describe('Kinesis', function () { rawExpectedSchema.outbound ) - it('injects trace context to Kinesis putRecord', done => { + it('injects trace context to Kinesis putRecord and propagates to Kinesis getRecord', done => { + assertPropagation(done) + helpers.putTestRecord(kinesis, streamName, helpers.dataBuffer, (err, data) => { if (err) return done(err) @@ -100,8 +125,6 @@ describe('Kinesis', function () { expect(data).to.have.property('_datadog') expect(data._datadog).to.have.property('x-datadog-trace-id') - - done() }) }) }) @@ -219,6 +242,50 @@ describe('Kinesis', function () { }) }) + it('extracts DSM trace context during Kinesis getRecords', done => { + helpers.putTestRecord(kinesis, streamNameDSM, helpers.dataBuffer, (err, data) => { + if (err) return done(err) + + kinesis.getShardIterator({ + ShardId: data.ShardId, + ShardIteratorType: 'AT_SEQUENCE_NUMBER', + StartingSequenceNumber: data.SequenceNumber, + StreamName: streamNameDSM + }, (err, { ShardIterator } = {}) => { + if (err) return done(err) + + if (DataStreamsContext.setDataStreamsContext.isSinonProxy) { + DataStreamsContext.setDataStreamsContext.restore() + } + const setDataStreamsContextSpy = sinon.spy(DataStreamsContext, 'setDataStreamsContext') + + kinesis.getRecords({ + ShardIterator, + Limit: 1 + }, (err, response) => { + if (err) return done(err) + + let parsedData + try { + parsedData = JSON.parse(response.Records[0].Data) + } catch { + parsedData = JSON.parse(Buffer.from(response.Records[0].Data)) + } + + expect(parsedData).to.have.property('_datadog') + expect(parsedData._datadog).to.have.property('x-datadog-trace-id') + + expect( + setDataStreamsContextSpy.args[setDataStreamsContextSpy.args.length - 1][0].hash + ).to.equal(expectedConsumerHash) + + setDataStreamsContextSpy.restore() + done() + }) + }) + }) + }) + it('Should set a message payload size when producing a message', (done) => { if (DataStreamsProcessor.prototype.recordCheckpoint.isSinonProxy) { DataStreamsProcessor.prototype.recordCheckpoint.restore() @@ -261,7 +328,8 @@ describe('Kinesis', function () { if (err) return done(err) kinesis.getRecords({ - ShardIterator + ShardIterator, + Limit: 1 }, (err, response) => { if (err) return done(err) From 0e9010d36f58df096dc137dd4e285b956156c9cb Mon Sep 17 00:00:00 2001 From: William Conti Date: Wed, 13 Dec 2023 15:50:12 -0500 Subject: [PATCH 21/52] increase timeout --- packages/datadog-plugin-aws-sdk/test/kinesis.spec.js | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/datadog-plugin-aws-sdk/test/kinesis.spec.js b/packages/datadog-plugin-aws-sdk/test/kinesis.spec.js index 3d9656ab408..5d33e3c6d50 100644 --- a/packages/datadog-plugin-aws-sdk/test/kinesis.spec.js +++ b/packages/datadog-plugin-aws-sdk/test/kinesis.spec.js @@ -29,6 +29,7 @@ const expectedConsumerHash = computePathwayHash( ) describe('Kinesis', function () { + this.timeout(5000) setup() withVersions('aws-sdk', ['aws-sdk', '@aws-sdk/smithy-client'], (version, moduleName) => { From c4a7d54e756d719ad859cf14ae2d2f18293c43ed Mon Sep 17 00:00:00 2001 From: William Conti Date: Thu, 14 Dec 2023 16:43:28 -0500 Subject: [PATCH 22/52] rewrite SQS to set DSM checkpoint for each message --- .../src/services/sqs.js | 88 ++++++++++++------- .../datadog-plugin-aws-sdk/test/sqs.spec.js | 26 ++---- 2 files changed, 61 insertions(+), 53 deletions(-) diff --git a/packages/datadog-plugin-aws-sdk/src/services/sqs.js b/packages/datadog-plugin-aws-sdk/src/services/sqs.js index 22ac1714a49..f96cec2a6fc 100644 --- a/packages/datadog-plugin-aws-sdk/src/services/sqs.js +++ b/packages/datadog-plugin-aws-sdk/src/services/sqs.js @@ -21,21 +21,23 @@ class Sqs extends BaseAwsSdkPlugin { const { request, response } = obj const store = storage.getStore() const plugin = this - const responseExtraction = this.responseExtract(request.params, request.operation, response) - if (responseExtraction && responseExtraction.maybeChildOf) { + const maybeChildOf = this.responseExtract(request.params, request.operation, response) + let span + if (maybeChildOf) { obj.needsFinish = true const options = { - childOf: responseExtraction.maybeChildOf, + childOf: maybeChildOf, tags: Object.assign( {}, this.requestTags.get(request) || {}, { 'span.kind': 'server' } ) } - const span = plugin.tracer.startSpan('aws.response', options) - this.responseExtractDSMContext(request.params, responseExtraction, span) + span = plugin.tracer.startSpan('aws.response', options) this.enter(span, store) } + // extract DSM context after as we might not have a parent-child but maybe have a DSM context + this.responseExtractDSMContext(request.operation, request.params, response, span ?? null) }) this.addSub('apm:aws:response:finish:sqs', err => { @@ -136,41 +138,61 @@ class Sqs extends BaseAwsSdkPlugin { const datadogAttribute = message.MessageAttributes._datadog - let parsedAttributes + const parsedAttributes = this.parseDatadogAttributes(datadogAttribute) + if (parsedAttributes) return this.tracer.extract('text_map', parsedAttributes) + } + + parseDatadogAttributes (attributes) { try { - if (datadogAttribute.StringValue) { - const textMap = datadogAttribute.StringValue - parsedAttributes = JSON.parse(textMap) - return { - maybeChildOf: this.tracer.extract('text_map', parsedAttributes), - traceContext: parsedAttributes, - message: message - } - } else if (datadogAttribute.Type === 'Binary') { - const buffer = Buffer.from(datadogAttribute.Value, 'base64') - parsedAttributes = JSON.parse(buffer) - return { - maybeChildOf: this.tracer.extract('text_map', parsedAttributes), - traceContext: parsedAttributes, - message: message - } + if (attributes.StringValue) { + const textMap = attributes.StringValue + return JSON.parse(textMap) + } else if (attributes.Type === 'Binary') { + const buffer = Buffer.from(attributes.Value, 'base64') + return JSON.parse(buffer) } } catch (e) { log.error(e) } } - responseExtractDSMContext (params, context, span) { - if (this.config.dsmEnabled && context && context.traceContext && context.traceContext[CONTEXT_PROPAGATION_KEY]) { - const payloadSize = getHeadersSize({ - Body: context.message.Body, - MessageAttributes: context.message.MessageAttributes - }) - const queue = params.QueueUrl.split('/').pop() - this.tracer.decodeDataStreamsContext(Buffer.from(context.traceContext[CONTEXT_PROPAGATION_KEY])) - this.tracer - .setCheckpoint(['direction:in', `topic:${queue}`, 'type:sqs'], span, payloadSize) - } + responseExtractDSMContext (operation, params, response, span) { + if (!this.config.dsmEnabled) return + if (operation !== 'receiveMessage') return + if (!response || !response.Messages || !response.Messages[0]) return + + // we only want to set the payloadSize on the span if we have one message + span = response.Messages.length > 1 ? null : span + + response.Messages.forEach(message => { + if (message.Body) { + try { + const body = JSON.parse(message.Body) + + // SNS to SQS + if (body.Type === 'Notification') { + message = body + } + } catch (e) { + // SQS to SQS + } + } + + if (message.MessageAttributes && message.MessageAttributes._datadog) { + const datadogAttributes = this.parseDatadogAttributes(message.MessageAttributes._datadog) + + if (datadogAttributes && datadogAttributes[CONTEXT_PROPAGATION_KEY]) { + const payloadSize = getHeadersSize({ + Body: message.Body, + MessageAttributes: message.MessageAttributes + }) + const queue = params.QueueUrl.split('/').pop() + this.tracer.decodeDataStreamsContext(Buffer.from(datadogAttributes[CONTEXT_PROPAGATION_KEY])) + this.tracer + .setCheckpoint(['direction:in', `topic:${queue}`, 'type:sqs'], span, payloadSize) + } + } + }) } requestInject (span, request) { diff --git a/packages/datadog-plugin-aws-sdk/test/sqs.spec.js b/packages/datadog-plugin-aws-sdk/test/sqs.spec.js index 9ac95fbde9b..b97023f7d92 100644 --- a/packages/datadog-plugin-aws-sdk/test/sqs.spec.js +++ b/packages/datadog-plugin-aws-sdk/test/sqs.spec.js @@ -362,16 +362,8 @@ describe('Plugin', () => { expect(setDataStreamsContextSpy.args[0][0].hash).to.equal(expectedProducerHash) setDataStreamsContextSpy.restore() + done() }) - - setTimeout(() => { - try { - expect(DataStreamsContext.setDataStreamsContext.isSinonProxy).to.equal(undefined) - done() - } catch (e) { - done(e) - } - }, 250) }) it('Should set a checkpoint on consume', (done) => { @@ -395,17 +387,9 @@ describe('Plugin', () => { setDataStreamsContextSpy.args[setDataStreamsContextSpy.args.length - 1][0].hash ).to.equal(expectedConsumerHash) setDataStreamsContextSpy.restore() + done() }) }) - - setTimeout(() => { - try { - expect(DataStreamsContext.setDataStreamsContext.isSinonProxy).to.equal(undefined) - done() - } catch (e) { - done(e) - } - }, 250) }) it('Should set a message payload size when producing a message', (done) => { @@ -461,8 +445,10 @@ describe('Plugin', () => { if (err) return done(err) const payloadSize = getHeadersSize({ - Body: extractContextSpy.args[0][1].message.Body, - MessageAttributes: extractContextSpy.args[0][1].message.MessageAttributes + Body: extractContextSpy.args[extractContextSpy.args.length - 1][2].Messages[0].Body, + MessageAttributes: extractContextSpy.args[ + extractContextSpy.args.length - 1 + ][2].Messages[0].MessageAttributes }) expect(recordCheckpointSpy.args[0][0].hasOwnProperty('payloadSize')) From 8fc0843d8fd085ee781b5ea1b8a6f79aed9db440 Mon Sep 17 00:00:00 2001 From: William Conti Date: Fri, 15 Dec 2023 11:34:45 -0500 Subject: [PATCH 23/52] change payloadSize calculations --- .../src/services/sns.js | 25 ++++++------ .../src/services/sqs.js | 5 ++- .../datadog-plugin-aws-sdk/test/sns.spec.js | 39 +++++++++++-------- .../datadog-plugin-aws-sdk/test/sqs.spec.js | 15 +++---- 4 files changed, 47 insertions(+), 37 deletions(-) diff --git a/packages/datadog-plugin-aws-sdk/src/services/sns.js b/packages/datadog-plugin-aws-sdk/src/services/sns.js index 53a960bcf07..c32d09fae1c 100644 --- a/packages/datadog-plugin-aws-sdk/src/services/sns.js +++ b/packages/datadog-plugin-aws-sdk/src/services/sns.js @@ -13,7 +13,13 @@ class Sns extends BaseAwsSdkPlugin { if (!params.TopicArn && !(response.data && response.data.TopicArn)) return {} const TopicArn = params.TopicArn || response.data.TopicArn - const topicName = getTopicName(TopicArn) + + // Split the ARN into its parts + // ex.'arn:aws:sns:us-east-1:123456789012:my-topic' + const arnParts = TopicArn.split(':') + + // Get the topic name from the last part of the ARN + const topicName = arnParts[arnParts.length - 1] return { 'resource.name': `${operation} ${params.TopicArn || response.data.TopicArn}`, 'aws.sns.topic_arn': TopicArn, @@ -75,10 +81,12 @@ class Sns extends BaseAwsSdkPlugin { BinaryValue: JSON.stringify(ddInfo) } if (this.config.dsmEnabled) { - const payloadSize = getHeadersSize(params) - const topicName = getTopicName(params.TopicArn) + const payloadSize = getHeadersSize({ + Message: params.Message, + MessageAttributes: params.MessageAttributes + }) const dataStreamsContext = this.tracer - .setCheckpoint(['direction:out', `topic:${topicName}`, 'type:sns'], span, payloadSize) + .setCheckpoint(['direction:out', `topic:${params.TopicArn}`, 'type:sns'], span, payloadSize) if (dataStreamsContext) { const pathwayCtx = encodePathwayContext(dataStreamsContext) ddInfo[CONTEXT_PROPAGATION_KEY] = pathwayCtx.toJSON() @@ -89,13 +97,4 @@ class Sns extends BaseAwsSdkPlugin { } } -function getTopicName (topicArn) { - // Split the ARN into its parts - // ex.'arn:aws:sns:us-east-1:123456789012:my-topic' - const arnParts = topicArn.split(':') - - // Get the topic name from the last part of the ARN - return arnParts[arnParts.length - 1] -} - module.exports = Sns diff --git a/packages/datadog-plugin-aws-sdk/src/services/sqs.js b/packages/datadog-plugin-aws-sdk/src/services/sqs.js index f96cec2a6fc..3f7b256c6ce 100644 --- a/packages/datadog-plugin-aws-sdk/src/services/sqs.js +++ b/packages/datadog-plugin-aws-sdk/src/services/sqs.js @@ -214,7 +214,10 @@ class Sqs extends BaseAwsSdkPlugin { StringValue: JSON.stringify(ddInfo) } if (this.config.dsmEnabled) { - const payloadSize = getHeadersSize(request.params) + const payloadSize = getHeadersSize({ + Body: request.params.MessageBody, + MessageAttributes: request.params.MessageAttributes + }) const queue = request.params.QueueUrl.split('/').pop() const dataStreamsContext = this.tracer .setCheckpoint(['direction:out', `topic:${queue}`, 'type:sqs'], span, payloadSize) diff --git a/packages/datadog-plugin-aws-sdk/test/sns.spec.js b/packages/datadog-plugin-aws-sdk/test/sns.spec.js index 64cc43d58bb..52e2ed59023 100644 --- a/packages/datadog-plugin-aws-sdk/test/sns.spec.js +++ b/packages/datadog-plugin-aws-sdk/test/sns.spec.js @@ -10,19 +10,6 @@ const { computePathwayHash } = require('../../dd-trace/src/datastreams/pathway') const DataStreamsContext = require('../../dd-trace/src/data_streams_context') const snsPlugin = require('../src/services/sns') -const expectedProducerHash = computePathwayHash( - 'test', - 'tester', - ['direction:out', 'topic:TestTopicDSM', 'type:sns'], - ENTRY_PARENT_HASH -) -const expectedConsumerHash = computePathwayHash( - 'test', - 'tester', - ['direction:in', 'topic:TestQueueDSM', 'type:sqs'], - expectedProducerHash -) - describe('Sns', () => { setup() @@ -243,6 +230,23 @@ describe('Sns', () => { }) describe('Data Streams Monitoring', () => { + const expectedProducerHash = function (topicArn) { + return computePathwayHash( + 'test', + 'tester', + ['direction:out', `topic:${topicArn}`, 'type:sns'], + ENTRY_PARENT_HASH + ) + } + const expectedConsumerHash = function (topicArn) { + return computePathwayHash( + 'test', + 'tester', + ['direction:in', 'topic:TestQueueDSM', 'type:sqs'], + expectedProducerHash(topicArn) + ) + } + before(() => { return agent.load('aws-sdk', { sns: { dsmEnabled: true }, sqs: { dsmEnabled: true } }, { dsmEnabled: true }) }) @@ -285,9 +289,9 @@ describe('Sns', () => { if (err) return done(err) setDataStreamsContextSpy.args.forEach(functionCall => { - if (functionCall[0].hash === expectedConsumerHash) { + if (functionCall[0].hash === expectedConsumerHash(TopicArn)) { consumerHashCreated = true - } else if (functionCall[0].hash === expectedProducerHash) { + } else if (functionCall[0].hash === expectedProducerHash(TopicArn)) { producerHashCreated = true } }) @@ -329,7 +333,10 @@ describe('Sns', () => { params.MessageAttributes._datadog.BinaryValue = JSON.stringify( JSON.parse(Buffer.from(params.MessageAttributes._datadog.BinaryValue, 'base64')) ) - const payloadSize = getHeadersSize(params) + const payloadSize = getHeadersSize({ + Message: params.Message, + MessageAttributes: params.MessageAttributes + }) expect(recordCheckpointSpy.args[0][0].hasOwnProperty('payloadSize')) expect(recordCheckpointSpy.args[0][0].payloadSize).to.equal(payloadSize) diff --git a/packages/datadog-plugin-aws-sdk/test/sqs.spec.js b/packages/datadog-plugin-aws-sdk/test/sqs.spec.js index b97023f7d92..b9cfcf0ba05 100644 --- a/packages/datadog-plugin-aws-sdk/test/sqs.spec.js +++ b/packages/datadog-plugin-aws-sdk/test/sqs.spec.js @@ -409,7 +409,10 @@ describe('Plugin', () => { }, (err) => { if (err) return done(err) - const payloadSize = getHeadersSize(injectMessageSpy.args[0][1].params) + const payloadSize = getHeadersSize({ + Body: injectMessageSpy.args[0][1].params.MessageBody, + MessageAttributes: injectMessageSpy.args[0][1].params.MessageAttributes + }) expect(recordCheckpointSpy.args[0][0].hasOwnProperty('payloadSize')) expect(recordCheckpointSpy.args[0][0].payloadSize).to.equal(payloadSize) @@ -425,7 +428,7 @@ describe('Plugin', () => { if (sqsPlugin.prototype.responseExtractDSMContext.isSinonProxy) { sqsPlugin.prototype.responseExtractDSMContext.restore() } - const extractContextSpy = sinon.spy(sqsPlugin.prototype, 'responseExtractDSMContext') + const extractSpy = sinon.spy(sqsPlugin.prototype, 'responseExtractDSMContext') sqs.sendMessage({ MessageBody: 'test DSM', @@ -445,17 +448,15 @@ describe('Plugin', () => { if (err) return done(err) const payloadSize = getHeadersSize({ - Body: extractContextSpy.args[extractContextSpy.args.length - 1][2].Messages[0].Body, - MessageAttributes: extractContextSpy.args[ - extractContextSpy.args.length - 1 - ][2].Messages[0].MessageAttributes + Body: extractSpy.args[extractSpy.args.length - 1][2].Messages[0].Body, + MessageAttributes: extractSpy.args[extractSpy.args.length - 1][2].Messages[0].MessageAttributes }) expect(recordCheckpointSpy.args[0][0].hasOwnProperty('payloadSize')) expect(recordCheckpointSpy.args[0][0].payloadSize).to.equal(payloadSize) recordCheckpointSpy.restore() - extractContextSpy.restore() + extractSpy.restore() done() }) From 8c5f65369d6768279762862f5b861590dca3b035 Mon Sep 17 00:00:00 2001 From: William Conti Date: Fri, 15 Dec 2023 12:49:20 -0500 Subject: [PATCH 24/52] fix payload size error --- packages/datadog-plugin-aws-sdk/test/kinesis.spec.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/datadog-plugin-aws-sdk/test/kinesis.spec.js b/packages/datadog-plugin-aws-sdk/test/kinesis.spec.js index b72667dfbc1..bb81038fe22 100644 --- a/packages/datadog-plugin-aws-sdk/test/kinesis.spec.js +++ b/packages/datadog-plugin-aws-sdk/test/kinesis.spec.js @@ -229,7 +229,7 @@ describe('Kinesis', function () { helpers.getTestData(kinesis, streamNameDSM, data, (err, data) => { if (err) return done(err) - const payloadSize = getSizeOrZero(data) + const payloadSize = getSizeOrZero(JSON.stringify(data)) expect(recordCheckpointSpy.args[0][0].hasOwnProperty('payloadSize')) expect(recordCheckpointSpy.args[0][0].payloadSize).to.equal(payloadSize) From 73519f331adf9b57934adea7059529f7dd0a421e Mon Sep 17 00:00:00 2001 From: William Conti Date: Fri, 15 Dec 2023 13:20:40 -0500 Subject: [PATCH 25/52] use stream name for checkpoint if arn unavailable --- packages/datadog-plugin-aws-sdk/src/services/kinesis.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/packages/datadog-plugin-aws-sdk/src/services/kinesis.js b/packages/datadog-plugin-aws-sdk/src/services/kinesis.js index 0634f7f31c1..0c372026dd3 100644 --- a/packages/datadog-plugin-aws-sdk/src/services/kinesis.js +++ b/packages/datadog-plugin-aws-sdk/src/services/kinesis.js @@ -65,7 +65,13 @@ class Kinesis extends BaseAwsSdkPlugin { if (this.config.dsmEnabled) { // get payload size of request data const payloadSize = getSizeOrZero(JSON.stringify(parsedData)) - const stream = request.params.StreamName + let stream + // users can optionally use either stream name or stream arn + if (request.params && request.params.StreamArn) { + stream = request.params.StreamArn + } else if (request.params && request.params.StreamName) { + stream = request.params.StreamName + } const dataStreamsContext = this.tracer .setCheckpoint(['direction:out', `topic:${stream}`, 'type:kinesis'], span, payloadSize) if (dataStreamsContext) { From 6e0d6959c779c575944a8873d462b8ef3a87c4f2 Mon Sep 17 00:00:00 2001 From: William Conti Date: Wed, 20 Dec 2023 13:10:26 -0500 Subject: [PATCH 26/52] add xray propagation --- packages/datadog-plugin-aws-sdk/src/base.js | 12 +++++ .../src/services/kinesis.js | 3 +- .../src/services/sqs.js | 46 +++++++++++++++++-- packages/dd-trace/src/config.js | 2 +- .../src/opentracing/propagation/text_map.js | 44 ++++++++++++++++++ .../opentracing/propagation/text_map.spec.js | 29 ++++++++++++ 6 files changed, 128 insertions(+), 8 deletions(-) diff --git a/packages/datadog-plugin-aws-sdk/src/base.js b/packages/datadog-plugin-aws-sdk/src/base.js index cb6cf2b6126..29a5ffcd143 100644 --- a/packages/datadog-plugin-aws-sdk/src/base.js +++ b/packages/datadog-plugin-aws-sdk/src/base.js @@ -74,6 +74,18 @@ class BaseAwsSdkPlugin extends ClientPlugin { }) } + parseAWSTraceHeader (header) { + // parses AWSTraceHeader string to object + // ex: 'Root=1-00000000-00000000fffffffffffffffe;Parent=ffffffffffffffff;Sampled=1;_dd.origin=fakeOrigin; + const obj = {} + const keyValuePairs = header.split(';') + keyValuePairs.forEach(pair => { + const [key, value] = pair.split('=') + obj[key.toLowerCase()] = value.toLowerCase() + }) + return obj + } + requestInject (span, request) { // implemented by subclasses, or not } diff --git a/packages/datadog-plugin-aws-sdk/src/services/kinesis.js b/packages/datadog-plugin-aws-sdk/src/services/kinesis.js index 7cc103d3e20..c384d797840 100644 --- a/packages/datadog-plugin-aws-sdk/src/services/kinesis.js +++ b/packages/datadog-plugin-aws-sdk/src/services/kinesis.js @@ -1,8 +1,7 @@ 'use strict' const { CONTEXT_PROPAGATION_KEY, - getSizeOrZero, - getHeadersSize + getSizeOrZero } = require('../../../dd-trace/src/datastreams/processor') const { encodePathwayContext } = require('../../../dd-trace/src/datastreams/pathway') const log = require('../../../dd-trace/src/log') diff --git a/packages/datadog-plugin-aws-sdk/src/services/sqs.js b/packages/datadog-plugin-aws-sdk/src/services/sqs.js index 3f7b256c6ce..cf1bf38ba6e 100644 --- a/packages/datadog-plugin-aws-sdk/src/services/sqs.js +++ b/packages/datadog-plugin-aws-sdk/src/services/sqs.js @@ -134,12 +134,19 @@ class Sqs extends BaseAwsSdkPlugin { } } - if (!message.MessageAttributes || !message.MessageAttributes._datadog) return - - const datadogAttribute = message.MessageAttributes._datadog + let parsedAttributes = {} + if (message.MessageAttributes && message.MessageAttributes._datadog) { + const datadogAttribute = message.MessageAttributes._datadog + parsedAttributes = this.parseDatadogAttributes(datadogAttribute) + } - const parsedAttributes = this.parseDatadogAttributes(datadogAttribute) - if (parsedAttributes) return this.tracer.extract('text_map', parsedAttributes) + if (message.Attributes && message.Attributes.AWSTraceHeader) { + parsedAttributes = { + ...parsedAttributes, + ...this.parseAWSTraceHeader(message.Attributes.AWSTraceHeader) + } + } + if (Object.keys(parsedAttributes).length !== 0) return this.tracer.extract('text_map', parsedAttributes) } parseDatadogAttributes (attributes) { @@ -197,6 +204,35 @@ class Sqs extends BaseAwsSdkPlugin { requestInject (span, request) { const operation = request.operation + // inject params for receiveMessage to capture any possible context propagation + if (operation === 'receiveMessage') { + if (!request.params) { + request.params = { + MessageAttributeNames: ['_datadog'], + AttributeNames: ['AWSTraceHeader'] // for compatability with Java producer + } + return + } + + if (!request.params.MessageAttributeNames) { + request.params.MessageAttributeNames = ['_datadog'] + } else if ( + !request.params.MessageAttributeNames.includes('_datadog') && + !request.params.MessageAttributeNames.includes('All') + ) { + request.params.MessageAttributeNames.push('_datadog') + } + + if (!request.params.AttributeNames) { + request.params.AttributeNames = ['AWSTraceHeader'] + } else if ( + !request.params.AttributeNames.includes('AWSTraceHeader') && + !request.params.AttributeNames.includes('All') + ) { + request.params.AttributeNames.push('AWSTraceHeader') + } + return + } if (operation === 'sendMessage') { if (!request.params) { request.params = {} diff --git a/packages/dd-trace/src/config.js b/packages/dd-trace/src/config.js index bc03636f6ef..274febdbca2 100644 --- a/packages/dd-trace/src/config.js +++ b/packages/dd-trace/src/config.js @@ -284,7 +284,7 @@ class Config { process.env.DD_TRACE_EXPERIMENTAL_B3_ENABLED, false ) - const defaultPropagationStyle = ['datadog', 'tracecontext'] + const defaultPropagationStyle = ['datadog', 'tracecontext', 'aws xray'] if (isTrue(DD_TRACE_B3_ENABLED)) { defaultPropagationStyle.push('b3') defaultPropagationStyle.push('b3 single header') diff --git a/packages/dd-trace/src/opentracing/propagation/text_map.js b/packages/dd-trace/src/opentracing/propagation/text_map.js index 20a257bb61a..4c3879b7e70 100644 --- a/packages/dd-trace/src/opentracing/propagation/text_map.js +++ b/packages/dd-trace/src/opentracing/propagation/text_map.js @@ -223,6 +223,9 @@ class TextMapPropagator { case 'b3 single header': // TODO: delete in major after singular "b3" spanContext = this._extractB3SingleContext(carrier) break + case 'aws xray': + spanContext = this._extractAwsXrayContext(carrier) + break } if (spanContext !== null) { @@ -536,6 +539,47 @@ class TextMapPropagator { return spanContext._traceId.toString(16) } + + _extractAwsXrayContext (carrier) { + let traceId + let spanId + let samplingPriority + let ddOrigin + + for (const key in carrier) { + const value = carrier[key] + if (key === 'root') { + const awsTraceIdSegments = value.split('-') + for (let i = 0; i < awsTraceIdSegments.length; i++) { + if (awsTraceIdSegments[i] === '1') { + continue + } else if (i === 1 && awsTraceIdSegments.length === 3) { + continue + } else if (i === 2 && awsTraceIdSegments.length === 3) { + traceId = awsTraceIdSegments[i].substring(8) + } + } + } else if (key === 'parent') { + spanId = value + } else if (key === 'sampled') { + samplingPriority = parseInt(value) + } else if (key === '_dd.origin') { + ddOrigin = String(value) + } + } + + if (traceId && spanId) { + const spanContext = new DatadogSpanContext({ + traceId: id(traceId, 16), + spanId: id(spanId, 16), + sampling: { samplingPriority } + }) + if (ddOrigin) { + spanContext._trace.origin = ddOrigin + } + return spanContext + } + } } module.exports = TextMapPropagator diff --git a/packages/dd-trace/test/opentracing/propagation/text_map.spec.js b/packages/dd-trace/test/opentracing/propagation/text_map.spec.js index 469126010f0..8d19e3ac5a5 100644 --- a/packages/dd-trace/test/opentracing/propagation/text_map.spec.js +++ b/packages/dd-trace/test/opentracing/propagation/text_map.spec.js @@ -6,6 +6,7 @@ const Config = require('../../../src/config') const id = require('../../../src/id') const SpanContext = require('../../../src/opentracing/span_context') const TraceState = require('../../../src/opentracing/propagation/tracestate') +const BaseAwsSdkPlugin = require('../../../../datadog-plugin-aws-sdk/src/base') const { AUTO_KEEP, AUTO_REJECT, USER_KEEP } = require('../../../../../ext/priority') const { SAMPLING_MECHANISM_MANUAL } = require('../../../src/constants') @@ -498,6 +499,20 @@ describe('TextMapPropagator', () => { expect(spanContext._tracestate).to.be.undefined }) + it(`should extract AWSTraceHeader`, () => { + const traceId = '4ef684dbd03d632e' + const spanId = '7e8d56262375628a' + const sampled = 1 + const unparsedHeader = `Root=1-6583199d-00000000${traceId};Parent=${spanId};Sampled=${sampled}` + const carrier = BaseAwsSdkPlugin.prototype.parseAWSTraceHeader(unparsedHeader) + + const spanContext = propagator.extract(carrier) + + expect(spanContext.toTraceId()).to.equal(id(traceId, 16).toString(10)) + expect(spanContext.toSpanId()).to.equal(id(spanId, 16).toString(10)) + expect(spanContext._sampling.samlingPriority).to.equal(sampled) + }) + describe('with B3 propagation as multiple headers', () => { beforeEach(() => { config.tracePropagationStyle.extract = ['b3multi'] @@ -820,6 +835,20 @@ describe('TextMapPropagator', () => { expect(carrier['x-datadog-tags']).to.include('_dd.p.dm=-0') expect(spanContext._trace.tags['_dd.p.dm']).to.eql('-0') }) + + it('should extract from AWS Xray header', () => { + textMap['traceparent'] = 'Root=1-657ca447-000000000253d2d11f6cd9d1;Parent=0fdb23a8c03f4bf5;Sampled=1' + textMap['tracestate'] = 'other=bleh,dd=t.foo_bar_baz_:abc_!@#$%^&*()_+`-~;s:2;o:foo;t.dm:-0' + config.tracePropagationStyle.extract = ['aws xray'] + + const carrier = {} + const spanContext = propagator.extract(textMap) + + propagator.inject(spanContext, carrier) + + expect(carrier['x-datadog-tags']).to.include('_dd.p.dm=-0') + expect(spanContext._trace.tags['_dd.p.dm']).to.eql('-0') + }) }) }) }) From 8cb98ef4827d161cbd3bbb89c786f842c2d2b9b0 Mon Sep 17 00:00:00 2001 From: William Conti Date: Wed, 20 Dec 2023 13:15:06 -0500 Subject: [PATCH 27/52] remove test that i don't remember adding --- .../test/opentracing/propagation/text_map.spec.js | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/packages/dd-trace/test/opentracing/propagation/text_map.spec.js b/packages/dd-trace/test/opentracing/propagation/text_map.spec.js index 8d19e3ac5a5..0c6e87e7fba 100644 --- a/packages/dd-trace/test/opentracing/propagation/text_map.spec.js +++ b/packages/dd-trace/test/opentracing/propagation/text_map.spec.js @@ -835,20 +835,6 @@ describe('TextMapPropagator', () => { expect(carrier['x-datadog-tags']).to.include('_dd.p.dm=-0') expect(spanContext._trace.tags['_dd.p.dm']).to.eql('-0') }) - - it('should extract from AWS Xray header', () => { - textMap['traceparent'] = 'Root=1-657ca447-000000000253d2d11f6cd9d1;Parent=0fdb23a8c03f4bf5;Sampled=1' - textMap['tracestate'] = 'other=bleh,dd=t.foo_bar_baz_:abc_!@#$%^&*()_+`-~;s:2;o:foo;t.dm:-0' - config.tracePropagationStyle.extract = ['aws xray'] - - const carrier = {} - const spanContext = propagator.extract(textMap) - - propagator.inject(spanContext, carrier) - - expect(carrier['x-datadog-tags']).to.include('_dd.p.dm=-0') - expect(spanContext._trace.tags['_dd.p.dm']).to.eql('-0') - }) }) }) }) From b9c8bf3d309d4e8734e46288f476152e10c006f4 Mon Sep 17 00:00:00 2001 From: William Conti Date: Wed, 20 Dec 2023 13:34:42 -0500 Subject: [PATCH 28/52] fix failing tests --- packages/dd-trace/src/opentracing/propagation/text_map.js | 1 + packages/dd-trace/test/config.spec.js | 4 ++-- .../dd-trace/test/opentracing/propagation/text_map.spec.js | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/packages/dd-trace/src/opentracing/propagation/text_map.js b/packages/dd-trace/src/opentracing/propagation/text_map.js index 4c3879b7e70..b5fb100ac44 100644 --- a/packages/dd-trace/src/opentracing/propagation/text_map.js +++ b/packages/dd-trace/src/opentracing/propagation/text_map.js @@ -579,6 +579,7 @@ class TextMapPropagator { } return spanContext } + return null } } diff --git a/packages/dd-trace/test/config.spec.js b/packages/dd-trace/test/config.spec.js index 3459c50e260..c2573a3ad66 100644 --- a/packages/dd-trace/test/config.spec.js +++ b/packages/dd-trace/test/config.spec.js @@ -93,8 +93,8 @@ describe('Config', () => { expect(config).to.have.property('spanComputePeerService', false) expect(config).to.have.property('spanRemoveIntegrationFromService', false) expect(config).to.have.deep.property('serviceMapping', {}) - expect(config).to.have.nested.deep.property('tracePropagationStyle.inject', ['datadog', 'tracecontext']) - expect(config).to.have.nested.deep.property('tracePropagationStyle.extract', ['datadog', 'tracecontext']) + expect(config).to.have.nested.deep.property('tracePropagationStyle.inject', ['datadog', 'tracecontext', 'aws xray']) + expect(config).to.have.nested.deep.property('tracePropagationStyle.extract', ['datadog', 'tracecontext', 'aws xray']) expect(config).to.have.nested.property('experimental.runtimeId', false) expect(config).to.have.nested.property('experimental.exporter', undefined) expect(config).to.have.nested.property('experimental.enableGetRumData', false) diff --git a/packages/dd-trace/test/opentracing/propagation/text_map.spec.js b/packages/dd-trace/test/opentracing/propagation/text_map.spec.js index 0c6e87e7fba..6148aacc289 100644 --- a/packages/dd-trace/test/opentracing/propagation/text_map.spec.js +++ b/packages/dd-trace/test/opentracing/propagation/text_map.spec.js @@ -510,7 +510,7 @@ describe('TextMapPropagator', () => { expect(spanContext.toTraceId()).to.equal(id(traceId, 16).toString(10)) expect(spanContext.toSpanId()).to.equal(id(spanId, 16).toString(10)) - expect(spanContext._sampling.samlingPriority).to.equal(sampled) + expect(spanContext._sampling.samplingPriority).to.equal(sampled) }) describe('with B3 propagation as multiple headers', () => { From bea667374541c2d5e86ee296a710650f21ecd517 Mon Sep 17 00:00:00 2001 From: William Conti Date: Thu, 4 Apr 2024 18:50:59 -0400 Subject: [PATCH 29/52] clean up code --- .../src/opentracing/propagation/text_map.js | 107 ++++++++++++------ 1 file changed, 74 insertions(+), 33 deletions(-) diff --git a/packages/dd-trace/src/opentracing/propagation/text_map.js b/packages/dd-trace/src/opentracing/propagation/text_map.js index b5fb100ac44..77aca613899 100644 --- a/packages/dd-trace/src/opentracing/propagation/text_map.js +++ b/packages/dd-trace/src/opentracing/propagation/text_map.js @@ -39,6 +39,15 @@ const tracestateTagKeyFilter = /[^\x21-\x2b\x2d-\x3c\x3e-\x7e]/g // Tag values in tracestate replace ',', '~' and ';' with '_' const tracestateTagValueFilter = /[^\x20-\x2b\x2d-\x3a\x3c-\x7d]/g const invalidSegment = /^0+$/ +// AWS X-Ray specific constants +const xrayHeaderKey = 'x-amzn-trace-id' +const xrayRootKey = 'root' +const xrayRootPrefix = '1-00000000' +const xrayParentKey = 'parent' +const xraySampledKey = 'sampled' +const xrayE2EStartTimeKey = 't0' +const xraySelfKey = 'self' +const xrayOriginKey = '_dd.origin' class TextMapPropagator { constructor (config) { @@ -541,45 +550,77 @@ class TextMapPropagator { } _extractAwsXrayContext (carrier) { - let traceId - let spanId - let samplingPriority - let ddOrigin - - for (const key in carrier) { - const value = carrier[key] - if (key === 'root') { - const awsTraceIdSegments = value.split('-') - for (let i = 0; i < awsTraceIdSegments.length; i++) { - if (awsTraceIdSegments[i] === '1') { - continue - } else if (i === 1 && awsTraceIdSegments.length === 3) { - continue - } else if (i === 2 && awsTraceIdSegments.length === 3) { - traceId = awsTraceIdSegments[i].substring(8) + if (xrayHeaderKey in carrier) { + const parsedHeader = this._parseAWSTraceHeader(carrier[xrayHeaderKey]) + + let traceId + let spanId + let samplingPriority + let ddOrigin + const baggage = {} + + if (!(xrayRootKey in carrier) || !(xrayRootPrefix in carrier['root'])) { + // header doesn't match our padded version, ignore it + return null + } + + for (const key in parsedHeader) { + const value = parsedHeader[key] + if (key === xrayRootKey) { + const awsTraceIdSegments = value.split('-') + for (let i = 0; i < awsTraceIdSegments.length; i++) { + if (awsTraceIdSegments[i] === '1') { + continue + } else if (i === 1 && awsTraceIdSegments.length === 3) { + continue + } else if (i === 2 && awsTraceIdSegments.length === 3) { + // trace id padded by: "00000000" + traceId = awsTraceIdSegments[i].substring(8) + } } + } else if (key === xrayParentKey) { + spanId = value + } else if (key === xraySampledKey) { + samplingPriority = parseInt(value) + } else if (key === xrayOriginKey) { + ddOrigin = String(value) + } else if (key === xraySelfKey) { + // self is added by load balancers and should be ignored + continue + } else if (key === xrayE2EStartTimeKey) { + // startTime = parseInt(value) + } else { + baggage[key] = value } - } else if (key === 'parent') { - spanId = value - } else if (key === 'sampled') { - samplingPriority = parseInt(value) - } else if (key === '_dd.origin') { - ddOrigin = String(value) } - } - if (traceId && spanId) { - const spanContext = new DatadogSpanContext({ - traceId: id(traceId, 16), - spanId: id(spanId, 16), - sampling: { samplingPriority } - }) - if (ddOrigin) { - spanContext._trace.origin = ddOrigin + if (traceId && spanId) { + const spanContext = new DatadogSpanContext({ + traceId: id(traceId, 16), + spanId: id(spanId, 16), + sampling: { samplingPriority } + }) + if (ddOrigin) { + spanContext._trace.origin = ddOrigin + } + return spanContext } - return spanContext + return null + } else { + return null } - return null + } + + _parseAWSTraceHeader (header) { + // parses AWSTraceHeader string to object + // ex: 'Root=1-00000000-00000000fffffffffffffffe;Parent=ffffffffffffffff;Sampled=1;_dd.origin=fakeOrigin; + const obj = {} + const keyValuePairs = header.split(';') + keyValuePairs.forEach(pair => { + const [key, value] = pair.split('=') + obj[key.toLowerCase()] = value.toLowerCase() + }) + return obj } } From 85289042525735616ba8b1ef401147b95a6b071b Mon Sep 17 00:00:00 2001 From: William Conti Date: Thu, 4 Apr 2024 19:01:24 -0400 Subject: [PATCH 30/52] Revert all changes except propagation class --- packages/datadog-plugin-aws-sdk/src/base.js | 12 - .../src/services/sns.js | 270 ++++-------------- .../src/services/sqs.js | 29 +- .../test/kinesis.spec.js | 9 - .../datadog-plugin-aws-sdk/test/sns.spec.js | 4 - .../datadog-plugin-aws-sdk/test/sqs.spec.js | 4 - 6 files changed, 58 insertions(+), 270 deletions(-) diff --git a/packages/datadog-plugin-aws-sdk/src/base.js b/packages/datadog-plugin-aws-sdk/src/base.js index 0941084d1bc..21e4bfa47f6 100644 --- a/packages/datadog-plugin-aws-sdk/src/base.js +++ b/packages/datadog-plugin-aws-sdk/src/base.js @@ -74,18 +74,6 @@ class BaseAwsSdkPlugin extends ClientPlugin { }) } - parseAWSTraceHeader (header) { - // parses AWSTraceHeader string to object - // ex: 'Root=1-00000000-00000000fffffffffffffffe;Parent=ffffffffffffffff;Sampled=1;_dd.origin=fakeOrigin; - const obj = {} - const keyValuePairs = header.split(';') - keyValuePairs.forEach(pair => { - const [key, value] = pair.split('=') - obj[key.toLowerCase()] = value.toLowerCase() - }) - return obj - } - requestInject (span, request) { // implemented by subclasses, or not } diff --git a/packages/datadog-plugin-aws-sdk/src/services/sns.js b/packages/datadog-plugin-aws-sdk/src/services/sns.js index 769d9fc9ac6..ee5191ddabc 100644 --- a/packages/datadog-plugin-aws-sdk/src/services/sns.js +++ b/packages/datadog-plugin-aws-sdk/src/services/sns.js @@ -1,64 +1,39 @@ 'use strict' - -const log = require('../../../dd-trace/src/log') -const BaseAwsSdkPlugin = require('../base') -const { storage } = require('../../../datadog-core') const { getHeadersSize } = require('../../../dd-trace/src/datastreams/processor') const { DsmPathwayCodec } = require('../../../dd-trace/src/datastreams/pathway') +const log = require('../../../dd-trace/src/log') +const BaseAwsSdkPlugin = require('../base') -class Sqs extends BaseAwsSdkPlugin { - static get id () { return 'sqs' } - static get peerServicePrecursors () { return ['queuename'] } +class Sns extends BaseAwsSdkPlugin { + static get id () { return 'sns' } + static get peerServicePrecursors () { return ['topicname'] } - constructor (...args) { - super(...args) - // - // TODO(bengl) Find a way to create the response span tags without this WeakMap being populated - // in the base class - this.requestTags = new WeakMap() + generateTags (params, operation, response) { + if (!params) return {} - this.addSub('apm:aws:response:start:sqs', obj => { - const { request, response } = obj - const store = storage.getStore() - const plugin = this - const contextExtraction = this.responseExtract(request.params, request.operation, response) - let span - let parsedMessageAttributes - if (contextExtraction && contextExtraction.datadogContext) { - obj.needsFinish = true - const options = { - childOf: contextExtraction.datadogContext, - tags: Object.assign( - {}, - this.requestTags.get(request) || {}, - { 'span.kind': 'server' } - ) - } - parsedMessageAttributes = contextExtraction.parsedAttributes - span = plugin.tracer.startSpan('aws.response', options) - this.enter(span, store) - } - // extract DSM context after as we might not have a parent-child but may have a DSM context - this.responseExtractDSMContext( - request.operation, request.params, response, span || null, parsedMessageAttributes || null - ) - }) + if (!params.TopicArn && !(response.data && response.data.TopicArn)) return {} + const TopicArn = params.TopicArn || response.data.TopicArn - this.addSub('apm:aws:response:finish:sqs', err => { - const { span } = storage.getStore() - this.finish(span, null, err) - }) + // Split the ARN into its parts + // ex.'arn:aws:sns:us-east-1:123456789012:my-topic' + const arnParts = TopicArn.split(':') + + // Get the topic name from the last part of the ARN + const topicName = arnParts[arnParts.length - 1] + return { + 'resource.name': `${operation} ${params.TopicArn || response.data.TopicArn}`, + 'aws.sns.topic_arn': TopicArn, + topicname: topicName + } + + // TODO: should arn be sanitized or quantized in some way here, + // for example if it contains a phone number? } operationFromRequest (request) { switch (request.operation) { - case 'receiveMessage': - return this.operationName({ - type: 'messaging', - kind: 'consumer' - }) - case 'sendMessage': - case 'sendMessageBatch': + case 'publish': + case 'publishBatch': return this.operationName({ type: 'messaging', kind: 'producer' @@ -69,139 +44,7 @@ class Sqs extends BaseAwsSdkPlugin { id: 'aws', type: 'web', kind: 'client', - awsService: 'sqs' - }) - } - - isEnabled (request) { - // TODO(bengl) Figure out a way to make separate plugins for consumer and producer so that - // config can be isolated to `.configure()` instead of this whole isEnabled() thing. - const config = this.config - switch (request.operation) { - case 'receiveMessage': - return config.consumer !== false - case 'sendMessage': - case 'sendMessageBatch': - return config.producer !== false - default: - return true - } - } - - generateTags (params, operation, response) { - const tags = {} - - if (!params || (!params.QueueName && !params.QueueUrl)) return tags - // 'https://sqs.us-east-1.amazonaws.com/123456789012/my-queue'; - let queueName = params.QueueName - if (params.QueueUrl) { - queueName = params.QueueUrl.split('/')[params.QueueUrl.split('/').length - 1] - } - - Object.assign(tags, { - 'resource.name': `${operation} ${params.QueueName || params.QueueUrl}`, - 'aws.sqs.queue_name': params.QueueName || params.QueueUrl, - queuename: queueName - }) - - switch (operation) { - case 'receiveMessage': - tags['span.type'] = 'worker' - tags['span.kind'] = 'consumer' - break - case 'sendMessage': - case 'sendMessageBatch': - tags['span.kind'] = 'producer' - break - } - - return tags - } - - responseExtract (params, operation, response) { - if (operation !== 'receiveMessage') return - if (params.MaxNumberOfMessages && params.MaxNumberOfMessages !== 1) return - if (!response || !response.Messages || !response.Messages[0]) return - - let message = response.Messages[0] - - if (message.Body) { - try { - const body = JSON.parse(message.Body) - - // SNS to SQS - if (body.Type === 'Notification') { - message = body - } - } catch (e) { - // SQS to SQS - } - } - - if (!message.MessageAttributes || !message.MessageAttributes._datadog) return - - const datadogAttribute = message.MessageAttributes._datadog - - const parsedAttributes = this.parseDatadogAttributes(datadogAttribute) - if (parsedAttributes) { - return { - datadogContext: this.tracer.extract('text_map', parsedAttributes), - parsedAttributes - } - } - } - - parseDatadogAttributes (attributes) { - try { - if (attributes.StringValue) { - const textMap = attributes.StringValue - return JSON.parse(textMap) - } else if (attributes.Type === 'Binary') { - const buffer = Buffer.from(attributes.Value, 'base64') - return JSON.parse(buffer) - } - } catch (e) { - log.error(e) - } - } - - responseExtractDSMContext (operation, params, response, span, parsedAttributes) { - if (!this.config.dsmEnabled) return - if (operation !== 'receiveMessage') return - if (!response || !response.Messages || !response.Messages[0]) return - - // we only want to set the payloadSize on the span if we have one message - span = response.Messages.length > 1 ? null : span - - response.Messages.forEach(message => { - // we may have already parsed the message attributes when extracting trace context - if (!parsedAttributes) { - if (message.Body) { - try { - const body = JSON.parse(message.Body) - - // SNS to SQS - if (body.Type === 'Notification') { - message = body - } - } catch (e) { - // SQS to SQS - } - } - if (message.MessageAttributes && message.MessageAttributes._datadog) { - parsedAttributes = this.parseDatadogAttributes(message.MessageAttributes._datadog) - } - } - if (parsedAttributes && DsmPathwayCodec.contextExists(parsedAttributes)) { - const payloadSize = getHeadersSize({ - Body: message.Body, - MessageAttributes: message.MessageAttributes - }) - const queue = params.QueueUrl.split('/').pop() - this.tracer.decodeDataStreamsContext(parsedAttributes) - this.tracer - .setCheckpoint(['direction:in', `topic:${queue}`, 'type:sqs'], span, payloadSize) - } + awsService: 'sns' }) } @@ -211,68 +54,67 @@ class Sqs extends BaseAwsSdkPlugin { if (!params) return switch (operation) { - case 'sendMessage': - this.injectToMessage(span, params, params.QueueUrl, true) + case 'publish': + this.injectToMessage(span, params, params.TopicArn, true) break - case 'sendMessageBatch': - for (let i = 0; i < params.Entries.length; i++) { - this.injectToMessage(span, params.Entries[i], params.QueueUrl, i === 0) + case 'publishBatch': + for (let i = 0; i < params.PublishBatchRequestEntries.length; i++) { + this.injectToMessage(span, params.PublishBatchRequestEntries[i], params.TopicArn, i === 0) } break } } - injectToMessage (span, params, queueUrl, injectTraceContext) { - if (!params) { - params = {} - } + injectToMessage (span, params, topicArn, injectTraceContext) { if (!params.MessageAttributes) { params.MessageAttributes = {} - } else if (Object.keys(params.MessageAttributes).length >= 10) { // SQS quota - // TODO: add test when the test suite is fixed + } + if (Object.keys(params.MessageAttributes).length >= 10) { // SNS quota + log.info('Message attributes full, skipping trace context injection') return } + const ddInfo = {} // for now, we only want to inject to the first message, this may change for batches in the future if (injectTraceContext) { this.tracer.inject(span, 'text_map', ddInfo) + // add ddInfo before checking DSM so we can include DD attributes in payload size params.MessageAttributes._datadog = { - DataType: 'String', - StringValue: JSON.stringify(ddInfo) + DataType: 'Binary', + BinaryValue: ddInfo } } if (this.config.dsmEnabled) { if (!params.MessageAttributes._datadog) { params.MessageAttributes._datadog = { - DataType: 'String', - StringValue: JSON.stringify(ddInfo) + DataType: 'Binary', + BinaryValue: ddInfo } } - const dataStreamsContext = this.setDSMCheckpoint(span, params, queueUrl) - if (dataStreamsContext) { - DsmPathwayCodec.encode(dataStreamsContext, ddInfo) - params.MessageAttributes._datadog.StringValue = JSON.stringify(ddInfo) - } + const dataStreamsContext = this.setDSMCheckpoint(span, params, topicArn) + DsmPathwayCodec.encode(dataStreamsContext, ddInfo) } - if (params.MessageAttributes._datadog && Object.keys(ddInfo).length === 0) { + if (Object.keys(ddInfo).length !== 0) { + // BINARY types are automatically base64 encoded + params.MessageAttributes._datadog.BinaryValue = Buffer.from(JSON.stringify(ddInfo)) + } else if (params.MessageAttributes._datadog) { // let's avoid adding any additional information to payload if we failed to inject delete params.MessageAttributes._datadog } } - setDSMCheckpoint (span, params, queueUrl) { - const payloadSize = getHeadersSize({ - Body: params.MessageBody, - MessageAttributes: params.MessageAttributes - }) - const queue = queueUrl.split('/').pop() - const dataStreamsContext = this.tracer - .setCheckpoint(['direction:out', `topic:${queue}`, 'type:sqs'], span, payloadSize) - return dataStreamsContext + setDSMCheckpoint (span, params, topicArn) { + // only set a checkpoint if publishing to a topic + if (topicArn) { + const payloadSize = getHeadersSize(params) + const dataStreamsContext = this.tracer + .setCheckpoint(['direction:out', `topic:${topicArn}`, 'type:sns'], span, payloadSize) + return dataStreamsContext + } } } -module.exports = Sqs +module.exports = Sns diff --git a/packages/datadog-plugin-aws-sdk/src/services/sqs.js b/packages/datadog-plugin-aws-sdk/src/services/sqs.js index 0285b984b43..769d9fc9ac6 100644 --- a/packages/datadog-plugin-aws-sdk/src/services/sqs.js +++ b/packages/datadog-plugin-aws-sdk/src/services/sqs.js @@ -138,20 +138,9 @@ class Sqs extends BaseAwsSdkPlugin { } } - let parsedAttributes = {} - if (message.MessageAttributes && message.MessageAttributes._datadog) { - const datadogAttribute = message.MessageAttributes._datadog - parsedAttributes = this.parseDatadogAttributes(datadogAttribute) - } + if (!message.MessageAttributes || !message.MessageAttributes._datadog) return - if (message.Attributes && message.Attributes.AWSTraceHeader) { - parsedAttributes = { - ...parsedAttributes, - ...this.parseAWSTraceHeader(message.Attributes.AWSTraceHeader) - } - } - if (Object.keys(parsedAttributes).length !== 0) return this.tracer.extract('text_map', parsedAttributes) - } + const datadogAttribute = message.MessageAttributes._datadog const parsedAttributes = this.parseDatadogAttributes(datadogAttribute) if (parsedAttributes) { @@ -251,20 +240,6 @@ class Sqs extends BaseAwsSdkPlugin { DataType: 'String', StringValue: JSON.stringify(ddInfo) } - if (this.config.dsmEnabled) { - const payloadSize = getHeadersSize({ - Body: request.params.MessageBody, - MessageAttributes: request.params.MessageAttributes - }) - const queue = request.params.QueueUrl.split('/').pop() - const dataStreamsContext = this.tracer - .setCheckpoint(['direction:out', `topic:${queue}`, 'type:sqs'], span, payloadSize) - if (dataStreamsContext) { - const pathwayCtx = encodePathwayContext(dataStreamsContext) - ddInfo[CONTEXT_PROPAGATION_KEY] = pathwayCtx.toJSON() - } - } - request.params.MessageAttributes._datadog.StringValue = JSON.stringify(ddInfo) } if (this.config.dsmEnabled) { diff --git a/packages/datadog-plugin-aws-sdk/test/kinesis.spec.js b/packages/datadog-plugin-aws-sdk/test/kinesis.spec.js index 6dd713a6afb..89518c45cdc 100644 --- a/packages/datadog-plugin-aws-sdk/test/kinesis.spec.js +++ b/packages/datadog-plugin-aws-sdk/test/kinesis.spec.js @@ -6,15 +6,6 @@ const agent = require('../../dd-trace/test/plugins/agent') const { setup } = require('./spec_helpers') const helpers = require('./kinesis_helpers') const { rawExpectedSchema } = require('./kinesis-naming') -const { - ENTRY_PARENT_HASH, - DataStreamsProcessor, - getSizeOrZero, - getHeadersSize -} = require('../../dd-trace/src/datastreams/processor') -const { computePathwayHash } = require('../../dd-trace/src/datastreams/pathway') -const DataStreamsContext = require('../../dd-trace/src/data_streams_context') -const kinesisPlugin = require('../src/services/kinesis') describe('Kinesis', function () { this.timeout(10000) diff --git a/packages/datadog-plugin-aws-sdk/test/sns.spec.js b/packages/datadog-plugin-aws-sdk/test/sns.spec.js index c5bdd9b8089..27f194abc7e 100644 --- a/packages/datadog-plugin-aws-sdk/test/sns.spec.js +++ b/packages/datadog-plugin-aws-sdk/test/sns.spec.js @@ -6,10 +6,6 @@ const semver = require('semver') const agent = require('../../dd-trace/test/plugins/agent') const { setup } = require('./spec_helpers') const { rawExpectedSchema } = require('./sns-naming') -const { ENTRY_PARENT_HASH, getHeadersSize, DataStreamsProcessor } = require('../../dd-trace/src/datastreams/processor') -const { computePathwayHash } = require('../../dd-trace/src/datastreams/pathway') -const DataStreamsContext = require('../../dd-trace/src/data_streams_context') -const snsPlugin = require('../src/services/sns') describe('Sns', () => { setup() diff --git a/packages/datadog-plugin-aws-sdk/test/sqs.spec.js b/packages/datadog-plugin-aws-sdk/test/sqs.spec.js index 3d38a7a41a0..fee2a918810 100644 --- a/packages/datadog-plugin-aws-sdk/test/sqs.spec.js +++ b/packages/datadog-plugin-aws-sdk/test/sqs.spec.js @@ -4,10 +4,6 @@ const sinon = require('sinon') const agent = require('../../dd-trace/test/plugins/agent') const { setup } = require('./spec_helpers') const { rawExpectedSchema } = require('./sqs-naming') -const { ENTRY_PARENT_HASH, DataStreamsProcessor, getHeadersSize } = require('../../dd-trace/src/datastreams/processor') -const { computePathwayHash } = require('../../dd-trace/src/datastreams/pathway') -const DataStreamsContext = require('../../dd-trace/src/data_streams_context') -const sqsPlugin = require('../src/services/sqs') const queueName = 'SQS_QUEUE_NAME' const queueNameDSM = 'SQS_QUEUE_NAME_DSM' From 2b58872e8fcf5b6b2591b752abce07f301f85ea1 Mon Sep 17 00:00:00 2001 From: William Conti Date: Fri, 5 Apr 2024 10:57:44 -0400 Subject: [PATCH 31/52] add baggage tags --- packages/dd-trace/src/opentracing/propagation/text_map.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/dd-trace/src/opentracing/propagation/text_map.js b/packages/dd-trace/src/opentracing/propagation/text_map.js index e7167ab7734..a034d2e3fc2 100644 --- a/packages/dd-trace/src/opentracing/propagation/text_map.js +++ b/packages/dd-trace/src/opentracing/propagation/text_map.js @@ -617,7 +617,8 @@ class TextMapPropagator { const spanContext = new DatadogSpanContext({ traceId: id(traceId, 16), spanId: id(spanId, 16), - sampling: { samplingPriority } + sampling: { samplingPriority }, + baggage: baggage }) if (ddOrigin) { spanContext._trace.origin = ddOrigin From abf28ca4fc0540854c80ae52eaa98ba6e93e94c5 Mon Sep 17 00:00:00 2001 From: William Conti Date: Fri, 5 Apr 2024 11:14:08 -0400 Subject: [PATCH 32/52] change encoding / decoding of hashes to match other tracers --- packages/dd-trace/src/datastreams/encoding.js | 103 ++++++------------ packages/dd-trace/src/datastreams/pathway.js | 65 ++++++----- 2 files changed, 74 insertions(+), 94 deletions(-) diff --git a/packages/dd-trace/src/datastreams/encoding.js b/packages/dd-trace/src/datastreams/encoding.js index 5ab4e84d4ce..2ef9f8e9d09 100644 --- a/packages/dd-trace/src/datastreams/encoding.js +++ b/packages/dd-trace/src/datastreams/encoding.js @@ -1,80 +1,49 @@ -// encodes positive and negative numbers, using zig zag encoding to reduce the size of the variable length encoding. -// uses high and low part to ensure those parts are under the limit for byte operations in javascript (32 bits) -// maximum number possible to encode is MAX_SAFE_INTEGER/2 (using zig zag shifts the bits by 1 to the left) -function encodeVarint (v) { - const sign = v >= 0 ? 0 : 1 - // we leave the least significant bit for the sign. - const double = Math.abs(v) * 2 - if (double > Number.MAX_SAFE_INTEGER) { - return undefined - } - const high = Math.floor(double / 0x100000000) - const low = (double & 0xffffffff) | sign - return encodeUvarint64(low, high) -} - -// decodes positive and negative numbers, using zig zag encoding to reduce the size of the variable length encoding. -// uses high and low part to ensure those parts are under the limit for byte operations in javascript (32 bits) -function decodeVarint (b) { - const [low, high, bytes] = decodeUvarint64(b) - if (low === undefined || high === undefined) { - return [undefined, bytes] - } - const positive = (low & 1) === 0 - const abs = (low >>> 1) + high * 0x80000000 - return [positive ? abs : -abs, bytes] -} +const { toBufferLE } = require('bigint-buffer') const maxVarLen64 = 9 -function encodeUvarint64 (low, high) { - const result = new Uint8Array(maxVarLen64) - let i = 0 - // if first byte is 1, the number is negative in javascript, but we want to interpret it as positive - while ((high !== 0 || low < 0 || low > 0x80) && i < maxVarLen64 - 1) { - result[i] = (low & 0x7f) | 0x80 - low >>>= 7 - low |= (high & 0x7f) << 25 - high >>>= 7 - i++ - } - result[i] = low & 0x7f - return result.slice(0, i + 1) -} - -function decodeUvarint64 ( - bytes -) { - let low = 0 - let high = 0 +function decodeVarUint64 (b) { + let x = 0 let s = 0 - for (let i = 0; ; i++) { - if (bytes.length <= i) { - return [undefined, undefined, bytes.slice(bytes.length)] + for (let i = 0; i < maxVarLen64; i++) { + if (b.length <= i) { + throw new Error('EOFError') } - const n = bytes[i] + const n = b[i] if (n < 0x80 || i === maxVarLen64 - 1) { - bytes = bytes.slice(i + 1) - if (s < 32) { - low |= n << s - } - if (s > 0) { - high |= s - 32 > 0 ? n << (s - 32) : n >> (32 - s) - } - return [low, high, bytes] - } - if (s < 32) { - low |= (n & 0x7f) << s - } - if (s > 0) { - high |= - s - 32 > 0 ? (n & 0x7f) << (s - 32) : (n & 0x7f) >> (32 - s) + return [x | n << s, b.slice(i + 1)] } + x |= (n & 0x7F) << s s += 7 } + throw new Error('EOFError') +} + +function decodeVarInt64 (b) { + const result = decodeVarUint64(b) + const v = result[0] + b = result[1] + return [(v >> 1) ^ -(v & 1), b] +} + +function encodeVarInt64 (v) { + return encodeVarUint64(v >> BigInt(64 - 1) ^ (v << BigInt(1))) +} + +function encodeVarUint64 (v) { + let b = Buffer.from('') + for (let i = 0; i < maxVarLen64; i++) { + if (v < 0x80n) { + break + } + b = Buffer.concat([b, toBufferLE(v & 0xffn | 0x80n, 1)]) + v >>= 7n + } + b = Buffer.concat([b, toBufferLE(v & 0xffn, 1)]) + return b } module.exports = { - encodeVarint, - decodeVarint + decodeVarInt64, + encodeVarInt64 } diff --git a/packages/dd-trace/src/datastreams/pathway.js b/packages/dd-trace/src/datastreams/pathway.js index b813b622ca6..82732e745e3 100644 --- a/packages/dd-trace/src/datastreams/pathway.js +++ b/packages/dd-trace/src/datastreams/pathway.js @@ -1,39 +1,50 @@ -// encoding used here is sha256 -// other languages use FNV1 -// this inconsistency is ok because hashes do not need to be consistent across services -const crypto = require('crypto') -const { encodeVarint, decodeVarint } = require('./encoding') -const LRUCache = require('lru-cache') - -const options = { max: 500 } -const cache = new LRUCache(options) +const { toBufferLE } = require('bigint-buffer') +const { encodeVarInt64, decodeVarInt64 } = require('./encoding') const CONTEXT_PROPAGATION_KEY = 'dd-pathway-ctx' const CONTEXT_PROPAGATION_KEY_BASE64 = 'dd-pathway-ctx-base64' -function shaHash (checkpointString) { - const hash = crypto.createHash('md5').update(checkpointString).digest('hex').slice(0, 16) - return Buffer.from(hash, 'hex') +const FNV_64_PRIME = BigInt('0x100000001B3') +const FNV1_64_INIT = BigInt('0xCBF29CE484222325') + +function fnv (data, hvalInit, fnvPrime, fnvSize) { + let hval = hvalInit + for (let i = 0; i < data.length; i++) { + hval = (hval * fnvPrime) % fnvSize + hval ^= BigInt(data[i]) + } + return hval +} + +function fnv1Base64 (data) { + return fnv(data, FNV1_64_INIT, FNV_64_PRIME, 2n ** 64n) } -function computeHash (service, env, edgeTags, parentHash) { - const key = `${service}${env}` + edgeTags.join('') + parentHash.toString() - if (cache.get(key)) { - return cache.get(key) +function getBytes (s) { + return Buffer.from(s, 'utf-8') +} + +function computeHash (service, env, tags, parentHash) { + let b = Buffer.concat([getBytes(service), getBytes(env)]) + for (const t of tags) { + b = Buffer.concat([b, getBytes(t)]) } - const currentHash = shaHash(`${service}${env}` + edgeTags.join('')) - const buf = Buffer.concat([currentHash, parentHash], 16) - const val = shaHash(buf.toString()) - cache.set(key, val) - return val + const nodeHash = fnv1Base64(b) + // console.log(node_hash) + const nodeHashBuffer = toBufferLE(nodeHash, 8) + + const parentHashBuffer = toBufferLE(BigInt(parentHash), 8) + + const combinedBuffer = Buffer.concat([nodeHashBuffer, parentHashBuffer]) + return fnv1Base64(combinedBuffer) } function encodePathwayContext (dataStreamsContext) { return Buffer.concat([ - dataStreamsContext.hash, - Buffer.from(encodeVarint(Math.round(dataStreamsContext.pathwayStartNs / 1e6))), - Buffer.from(encodeVarint(Math.round(dataStreamsContext.edgeStartNs / 1e6))) - ], 20) + toBufferLE(BigInt(dataStreamsContext.hash), 8), + encodeVarInt64(BigInt(dataStreamsContext.pathway_start_sec * 1e3)), + encodeVarInt64(BigInt(dataStreamsContext.current_edge_start_sec * 1e3)) + ]) } function encodePathwayContextBase64 (dataStreamsContext) { @@ -48,11 +59,11 @@ function decodePathwayContext (pathwayContext) { // hash and parent hash are in LE const pathwayHash = pathwayContext.subarray(0, 8) const encodedTimestamps = pathwayContext.subarray(8) - const [pathwayStartMs, encodedTimeSincePrev] = decodeVarint(encodedTimestamps) + const [pathwayStartMs, encodedTimeSincePrev] = decodeVarInt64(encodedTimestamps) if (pathwayStartMs === undefined) { return null } - const [edgeStartMs] = decodeVarint(encodedTimeSincePrev) + const [edgeStartMs] = decodeVarInt64(encodedTimeSincePrev) if (edgeStartMs === undefined) { return null } From 7d99016a495cac4d6737e74466f6939f7c50894b Mon Sep 17 00:00:00 2001 From: William Conti Date: Fri, 5 Apr 2024 11:24:21 -0400 Subject: [PATCH 33/52] Revert "change encoding / decoding of hashes to match other tracers" This reverts commit abf28ca4fc0540854c80ae52eaa98ba6e93e94c5. --- packages/dd-trace/src/datastreams/encoding.js | 103 ++++++++++++------ packages/dd-trace/src/datastreams/pathway.js | 65 +++++------ 2 files changed, 94 insertions(+), 74 deletions(-) diff --git a/packages/dd-trace/src/datastreams/encoding.js b/packages/dd-trace/src/datastreams/encoding.js index 2ef9f8e9d09..5ab4e84d4ce 100644 --- a/packages/dd-trace/src/datastreams/encoding.js +++ b/packages/dd-trace/src/datastreams/encoding.js @@ -1,49 +1,80 @@ -const { toBufferLE } = require('bigint-buffer') - -const maxVarLen64 = 9 - -function decodeVarUint64 (b) { - let x = 0 - let s = 0 - for (let i = 0; i < maxVarLen64; i++) { - if (b.length <= i) { - throw new Error('EOFError') - } - const n = b[i] - if (n < 0x80 || i === maxVarLen64 - 1) { - return [x | n << s, b.slice(i + 1)] - } - x |= (n & 0x7F) << s - s += 7 +// encodes positive and negative numbers, using zig zag encoding to reduce the size of the variable length encoding. +// uses high and low part to ensure those parts are under the limit for byte operations in javascript (32 bits) +// maximum number possible to encode is MAX_SAFE_INTEGER/2 (using zig zag shifts the bits by 1 to the left) +function encodeVarint (v) { + const sign = v >= 0 ? 0 : 1 + // we leave the least significant bit for the sign. + const double = Math.abs(v) * 2 + if (double > Number.MAX_SAFE_INTEGER) { + return undefined } - throw new Error('EOFError') + const high = Math.floor(double / 0x100000000) + const low = (double & 0xffffffff) | sign + return encodeUvarint64(low, high) } -function decodeVarInt64 (b) { - const result = decodeVarUint64(b) - const v = result[0] - b = result[1] - return [(v >> 1) ^ -(v & 1), b] +// decodes positive and negative numbers, using zig zag encoding to reduce the size of the variable length encoding. +// uses high and low part to ensure those parts are under the limit for byte operations in javascript (32 bits) +function decodeVarint (b) { + const [low, high, bytes] = decodeUvarint64(b) + if (low === undefined || high === undefined) { + return [undefined, bytes] + } + const positive = (low & 1) === 0 + const abs = (low >>> 1) + high * 0x80000000 + return [positive ? abs : -abs, bytes] } -function encodeVarInt64 (v) { - return encodeVarUint64(v >> BigInt(64 - 1) ^ (v << BigInt(1))) +const maxVarLen64 = 9 + +function encodeUvarint64 (low, high) { + const result = new Uint8Array(maxVarLen64) + let i = 0 + // if first byte is 1, the number is negative in javascript, but we want to interpret it as positive + while ((high !== 0 || low < 0 || low > 0x80) && i < maxVarLen64 - 1) { + result[i] = (low & 0x7f) | 0x80 + low >>>= 7 + low |= (high & 0x7f) << 25 + high >>>= 7 + i++ + } + result[i] = low & 0x7f + return result.slice(0, i + 1) } -function encodeVarUint64 (v) { - let b = Buffer.from('') - for (let i = 0; i < maxVarLen64; i++) { - if (v < 0x80n) { - break +function decodeUvarint64 ( + bytes +) { + let low = 0 + let high = 0 + let s = 0 + for (let i = 0; ; i++) { + if (bytes.length <= i) { + return [undefined, undefined, bytes.slice(bytes.length)] } - b = Buffer.concat([b, toBufferLE(v & 0xffn | 0x80n, 1)]) - v >>= 7n + const n = bytes[i] + if (n < 0x80 || i === maxVarLen64 - 1) { + bytes = bytes.slice(i + 1) + if (s < 32) { + low |= n << s + } + if (s > 0) { + high |= s - 32 > 0 ? n << (s - 32) : n >> (32 - s) + } + return [low, high, bytes] + } + if (s < 32) { + low |= (n & 0x7f) << s + } + if (s > 0) { + high |= + s - 32 > 0 ? (n & 0x7f) << (s - 32) : (n & 0x7f) >> (32 - s) + } + s += 7 } - b = Buffer.concat([b, toBufferLE(v & 0xffn, 1)]) - return b } module.exports = { - decodeVarInt64, - encodeVarInt64 + encodeVarint, + decodeVarint } diff --git a/packages/dd-trace/src/datastreams/pathway.js b/packages/dd-trace/src/datastreams/pathway.js index 82732e745e3..b813b622ca6 100644 --- a/packages/dd-trace/src/datastreams/pathway.js +++ b/packages/dd-trace/src/datastreams/pathway.js @@ -1,50 +1,39 @@ -const { toBufferLE } = require('bigint-buffer') -const { encodeVarInt64, decodeVarInt64 } = require('./encoding') +// encoding used here is sha256 +// other languages use FNV1 +// this inconsistency is ok because hashes do not need to be consistent across services +const crypto = require('crypto') +const { encodeVarint, decodeVarint } = require('./encoding') +const LRUCache = require('lru-cache') + +const options = { max: 500 } +const cache = new LRUCache(options) const CONTEXT_PROPAGATION_KEY = 'dd-pathway-ctx' const CONTEXT_PROPAGATION_KEY_BASE64 = 'dd-pathway-ctx-base64' -const FNV_64_PRIME = BigInt('0x100000001B3') -const FNV1_64_INIT = BigInt('0xCBF29CE484222325') - -function fnv (data, hvalInit, fnvPrime, fnvSize) { - let hval = hvalInit - for (let i = 0; i < data.length; i++) { - hval = (hval * fnvPrime) % fnvSize - hval ^= BigInt(data[i]) - } - return hval -} - -function fnv1Base64 (data) { - return fnv(data, FNV1_64_INIT, FNV_64_PRIME, 2n ** 64n) +function shaHash (checkpointString) { + const hash = crypto.createHash('md5').update(checkpointString).digest('hex').slice(0, 16) + return Buffer.from(hash, 'hex') } -function getBytes (s) { - return Buffer.from(s, 'utf-8') -} - -function computeHash (service, env, tags, parentHash) { - let b = Buffer.concat([getBytes(service), getBytes(env)]) - for (const t of tags) { - b = Buffer.concat([b, getBytes(t)]) +function computeHash (service, env, edgeTags, parentHash) { + const key = `${service}${env}` + edgeTags.join('') + parentHash.toString() + if (cache.get(key)) { + return cache.get(key) } - const nodeHash = fnv1Base64(b) - // console.log(node_hash) - const nodeHashBuffer = toBufferLE(nodeHash, 8) - - const parentHashBuffer = toBufferLE(BigInt(parentHash), 8) - - const combinedBuffer = Buffer.concat([nodeHashBuffer, parentHashBuffer]) - return fnv1Base64(combinedBuffer) + const currentHash = shaHash(`${service}${env}` + edgeTags.join('')) + const buf = Buffer.concat([currentHash, parentHash], 16) + const val = shaHash(buf.toString()) + cache.set(key, val) + return val } function encodePathwayContext (dataStreamsContext) { return Buffer.concat([ - toBufferLE(BigInt(dataStreamsContext.hash), 8), - encodeVarInt64(BigInt(dataStreamsContext.pathway_start_sec * 1e3)), - encodeVarInt64(BigInt(dataStreamsContext.current_edge_start_sec * 1e3)) - ]) + dataStreamsContext.hash, + Buffer.from(encodeVarint(Math.round(dataStreamsContext.pathwayStartNs / 1e6))), + Buffer.from(encodeVarint(Math.round(dataStreamsContext.edgeStartNs / 1e6))) + ], 20) } function encodePathwayContextBase64 (dataStreamsContext) { @@ -59,11 +48,11 @@ function decodePathwayContext (pathwayContext) { // hash and parent hash are in LE const pathwayHash = pathwayContext.subarray(0, 8) const encodedTimestamps = pathwayContext.subarray(8) - const [pathwayStartMs, encodedTimeSincePrev] = decodeVarInt64(encodedTimestamps) + const [pathwayStartMs, encodedTimeSincePrev] = decodeVarint(encodedTimestamps) if (pathwayStartMs === undefined) { return null } - const [edgeStartMs] = decodeVarInt64(encodedTimeSincePrev) + const [edgeStartMs] = decodeVarint(encodedTimeSincePrev) if (edgeStartMs === undefined) { return null } From f7a542ffab3ad1e24d2ffa5b7d18bc9fe8334e4e Mon Sep 17 00:00:00 2001 From: William Conti Date: Fri, 13 Dec 2024 14:49:47 -0500 Subject: [PATCH 34/52] add inject method --- .../src/opentracing/propagation/text_map.js | 52 ++++++++++++++++++- 1 file changed, 51 insertions(+), 1 deletion(-) diff --git a/packages/dd-trace/src/opentracing/propagation/text_map.js b/packages/dd-trace/src/opentracing/propagation/text_map.js index cc9cea69d55..1126db78ca2 100644 --- a/packages/dd-trace/src/opentracing/propagation/text_map.js +++ b/packages/dd-trace/src/opentracing/propagation/text_map.js @@ -55,6 +55,7 @@ const xraySampledKey = 'sampled' const xrayE2EStartTimeKey = 't0' const xraySelfKey = 'self' const xrayOriginKey = '_dd.origin' +const XRAY_MAX_ADDITIONAL_BYTES = 256; class TextMapPropagator { constructor (config) { @@ -261,6 +262,55 @@ class TextMapPropagator { carrier.tracestate = ts.toString() } + _injectAwsXrayHeader (spanContext, carrier) { + const e2eStart = spanContext.start_ms; + + let str = ( + ROOT_PREFIX + + String(NANOSECONDS.toSeconds(e2eStart && e2eStart > 0 ? e2eStart : Date.now() / 1000)) + + TRACE_ID_PADDING + + context._traceId.toString(16).padStart(16, '0') + + ';' + PARENT_PREFIX + + context._spanId.toString(16).padStart(8, '0') + ) + + if (context.lockSamplingPriority()) { + str += ';' + SAMPLED_PREFIX + convertSamplingPriority(context._sampling); + } + + const maxCapacity = str.length + XRAY_MAX_ADDITIONAL_BYTES; + + const origin = context._trace.origin; + if (origin) { + additionalPart(str, ORIGIN_KEY, origin, maxCapacity); + } + if (e2eStart > 0) { + additionalPart(str, E2E_START_KEY, e2eStart.toString(), maxCapacity); + } + + for (const [key, value] of context._baggageItems) { + if (!isReserved(key)) { + additionalPart(str, key, value, maxCapacity); + } + } + + carrier['X-Amzn-Trace-Id'] = str; + } + + _isReserved(key) { + return [ROOT_PREFIX, PARENT_PREFIX, SAMPLED_PREFIX].includes(key); + } + + _convertSamplingPriority(samplingPriority) { + return samplingPriority > 0 ? '1' : '0'; + } + + _additionalPart(str, key, value, maxCapacity) { + if (str.length + key.length + value.length + 2 <= maxCapacity) { + str += `;${key}=${value}`; + } + } + _hasPropagationStyle (mode, name) { return this._config.tracePropagationStyle[mode].includes(name) } @@ -743,7 +793,7 @@ class TextMapPropagator { // self is added by load balancers and should be ignored continue } else if (key === xrayE2EStartTimeKey) { - // startTime = parseInt(value) + baggage[endToEndStartTime] = parseInt(value) } else { baggage[key] = value } From 33ee7de8299e709ec127bf49ef2b9af97f6ab79a Mon Sep 17 00:00:00 2001 From: William Conti Date: Fri, 13 Dec 2024 15:25:07 -0500 Subject: [PATCH 35/52] clean up code --- .../src/opentracing/propagation/text_map.js | 63 +++++++++++-------- 1 file changed, 37 insertions(+), 26 deletions(-) diff --git a/packages/dd-trace/src/opentracing/propagation/text_map.js b/packages/dd-trace/src/opentracing/propagation/text_map.js index 1126db78ca2..7092455d479 100644 --- a/packages/dd-trace/src/opentracing/propagation/text_map.js +++ b/packages/dd-trace/src/opentracing/propagation/text_map.js @@ -49,13 +49,14 @@ const zeroTraceId = '0000000000000000' // AWS X-Ray specific constants const xrayHeaderKey = 'x-amzn-trace-id' const xrayRootKey = 'root' -const xrayRootPrefix = '1-00000000' +const xrayRootPrefix = '1-' +const xrayTraceIdPadding = '-00000000' const xrayParentKey = 'parent' const xraySampledKey = 'sampled' const xrayE2EStartTimeKey = 't0' const xraySelfKey = 'self' const xrayOriginKey = '_dd.origin' -const XRAY_MAX_ADDITIONAL_BYTES = 256; +const xrayMaxAdditionalBytes = 256; class TextMapPropagator { constructor (config) { @@ -70,6 +71,7 @@ class TextMapPropagator { this._injectB3MultipleHeaders(spanContext, carrier) this._injectB3SingleHeader(spanContext, carrier) this._injectTraceparent(spanContext, carrier) + this._injectAwsXrayContext(spanContext, carrier) if (injectCh.hasSubscribers) { injectCh.publish({ spanContext, carrier }) @@ -262,52 +264,61 @@ class TextMapPropagator { carrier.tracestate = ts.toString() } - _injectAwsXrayHeader (spanContext, carrier) { - const e2eStart = spanContext.start_ms; + _injectAwsXrayContext (spanContext, carrier) { + if (!this._hasPropagationStyle('inject', 'xray')) return + + const e2eStart = _getEndToEndStartTime(spanContext.start_ms) let str = ( - ROOT_PREFIX + - String(NANOSECONDS.toSeconds(e2eStart && e2eStart > 0 ? e2eStart : Date.now() / 1000)) + - TRACE_ID_PADDING + - context._traceId.toString(16).padStart(16, '0') + - ';' + PARENT_PREFIX + - context._spanId.toString(16).padStart(8, '0') + xrayRootPrefix + + e2eStart + + xrayTraceIdPadding + + context._traceId.toString(16).padStart(16, '0') + + ';' + xrayParentKey + + context._spanId.toString(16).padStart(8, '0') ) - if (context.lockSamplingPriority()) { - str += ';' + SAMPLED_PREFIX + convertSamplingPriority(context._sampling); + if (context._sampling !== null || context._sampling !== undefined) { + str += ';' + xraySampledKey + _convertSamplingPriority(context._sampling) } - const maxCapacity = str.length + XRAY_MAX_ADDITIONAL_BYTES; + const maxAdditionalCapacity = xrayMaxAdditionalBytes - str.length const origin = context._trace.origin; if (origin) { - additionalPart(str, ORIGIN_KEY, origin, maxCapacity); + _additionalPart(str, xrayOriginKey, origin, maxAdditionalCapacity) } if (e2eStart > 0) { - additionalPart(str, E2E_START_KEY, e2eStart.toString(), maxCapacity); + _additionalPart(str, xrayE2EStartTimeKey, e2eStart.toString(), maxAdditionalCapacity) } for (const [key, value] of context._baggageItems) { - if (!isReserved(key)) { - additionalPart(str, key, value, maxCapacity); + if (!_isReservedXrayKey(key)) { + _additionalPart(str, key, value, maxAdditionalCapacity) } } - carrier['X-Amzn-Trace-Id'] = str; + carrier['X-Amzn-Trace-Id'] = str + } + + _getEndToEndStartTime (start) { + if (!start) return "00000000" + + const e2eStart = start > 0 ? start : Date.now() / 1000 + return e2eStart.toString().padStart(8, '0') } - _isReserved(key) { - return [ROOT_PREFIX, PARENT_PREFIX, SAMPLED_PREFIX].includes(key); + _isReservedXrayKey (key) { + return [xrayRootKey, xrayParentKey, xrayOriginKey, xrayE2EStartTimeKey, xraySampledKey].includes(key) } - _convertSamplingPriority(samplingPriority) { - return samplingPriority > 0 ? '1' : '0'; + _convertSamplingPriority (samplingPriority) { + return samplingPriority > 0 ? '1' : '0' } - _additionalPart(str, key, value, maxCapacity) { + _additionalPart (str, key, value, maxCapacity) { if (str.length + key.length + value.length + 2 <= maxCapacity) { - str += `;${key}=${value}`; + str += `;${key}=${value}` } } @@ -362,7 +373,7 @@ class TextMapPropagator { case 'b3 single header': // TODO: delete in major after singular "b3" extractedContext = this._extractB3SingleContext(carrier) break - case 'aws xray': + case 'xray': spanContext = this._extractAwsXrayContext(carrier) break case 'b3': @@ -804,7 +815,7 @@ class TextMapPropagator { traceId: id(traceId, 16), spanId: id(spanId, 16), sampling: { samplingPriority }, - baggage: baggage + baggage }) if (ddOrigin) { spanContext._trace.origin = ddOrigin From 991d69b6a24585cc65f39b6a0dc415fad0f0fac8 Mon Sep 17 00:00:00 2001 From: William Conti Date: Mon, 16 Dec 2024 15:00:22 -0500 Subject: [PATCH 36/52] add testing --- packages/dd-trace/src/config.js | 2 +- .../src/opentracing/propagation/text_map.js | 73 +++++++++------- packages/dd-trace/test/config.spec.js | 4 +- .../opentracing/propagation/text_map.spec.js | 84 ++++++++++++++++++- 4 files changed, 128 insertions(+), 35 deletions(-) diff --git a/packages/dd-trace/src/config.js b/packages/dd-trace/src/config.js index 92eb3b6091e..808704bd7e4 100644 --- a/packages/dd-trace/src/config.js +++ b/packages/dd-trace/src/config.js @@ -410,7 +410,7 @@ class Config { process.env.DD_TRACE_EXPERIMENTAL_B3_ENABLED, false ) - const defaultPropagationStyle = ['datadog', 'tracecontext', 'xray'] + const defaultPropagationStyle = ['datadog', 'tracecontext'] if (isTrue(DD_TRACE_B3_ENABLED)) { defaultPropagationStyle.push('b3') defaultPropagationStyle.push('b3 single header') diff --git a/packages/dd-trace/src/opentracing/propagation/text_map.js b/packages/dd-trace/src/opentracing/propagation/text_map.js index 7092455d479..58ab1f95eff 100644 --- a/packages/dd-trace/src/opentracing/propagation/text_map.js +++ b/packages/dd-trace/src/opentracing/propagation/text_map.js @@ -50,7 +50,6 @@ const zeroTraceId = '0000000000000000' const xrayHeaderKey = 'x-amzn-trace-id' const xrayRootKey = 'root' const xrayRootPrefix = '1-' -const xrayTraceIdPadding = '-00000000' const xrayParentKey = 'parent' const xraySampledKey = 'sampled' const xrayE2EStartTimeKey = 't0' @@ -265,40 +264,45 @@ class TextMapPropagator { } _injectAwsXrayContext (spanContext, carrier) { + // injects AWS Trace Header (X-Amzn-Trace-Id) to carrier + // ex: 'Root=1-00000000-00000000fffffffffffffffe;Parent=ffffffffffffffff;Sampled=1;_dd.origin=fakeOrigin; + + // based off: https://docs.aws.amazon.com/xray/latest/devguide/xray-concepts.html#xray-concepts-tracingheader if (!this._hasPropagationStyle('inject', 'xray')) return - const e2eStart = _getEndToEndStartTime(spanContext.start_ms) + // TODO: Do we have access to a start time? Java does + const e2eStart = this._getEndToEndStartTime(spanContext.start_ms) let str = ( + xrayRootKey + "=" + xrayRootPrefix + - e2eStart + - xrayTraceIdPadding + - context._traceId.toString(16).padStart(16, '0') + - ';' + xrayParentKey + - context._spanId.toString(16).padStart(8, '0') + e2eStart + "-" + + spanContext._traceId.toString().padStart(24, '0') + + ';' + xrayParentKey + "=" + + spanContext._spanId.toString().padStart(16, '0') ) - if (context._sampling !== null || context._sampling !== undefined) { - str += ';' + xraySampledKey + _convertSamplingPriority(context._sampling) + if (spanContext?._sampling?.priority) { + str += ';' + xraySampledKey + "=" + (spanContext._sampling.priority > 0 ? '1' : '0') } const maxAdditionalCapacity = xrayMaxAdditionalBytes - str.length - const origin = context._trace.origin; + const origin = spanContext._trace.origin; if (origin) { - _additionalPart(str, xrayOriginKey, origin, maxAdditionalCapacity) + this._addXrayBaggage(str, xrayOriginKey, origin, maxAdditionalCapacity) } - if (e2eStart > 0) { - _additionalPart(str, xrayE2EStartTimeKey, e2eStart.toString(), maxAdditionalCapacity) + if (e2eStart !== '00000000') { + this._addXrayBaggage(str, xrayE2EStartTimeKey, e2eStart.toString(), maxAdditionalCapacity) } - for (const [key, value] of context._baggageItems) { - if (!_isReservedXrayKey(key)) { - _additionalPart(str, key, value, maxAdditionalCapacity) - } + for (const [key, value] of Object.entries(spanContext._baggageItems)) { + if (!this._isReservedXrayKey(key)) { + str = this._addXrayBaggage(str, key, value, maxAdditionalCapacity) + } } - carrier['X-Amzn-Trace-Id'] = str + carrier[xrayHeaderKey] = str } _getEndToEndStartTime (start) { @@ -309,17 +313,14 @@ class TextMapPropagator { } _isReservedXrayKey (key) { - return [xrayRootKey, xrayParentKey, xrayOriginKey, xrayE2EStartTimeKey, xraySampledKey].includes(key) - } - - _convertSamplingPriority (samplingPriority) { - return samplingPriority > 0 ? '1' : '0' + return [xrayRootKey, xrayParentKey, xrayOriginKey, xrayE2EStartTimeKey, xraySampledKey].includes(key.toLowerCase()) } - _additionalPart (str, key, value, maxCapacity) { - if (str.length + key.length + value.length + 2 <= maxCapacity) { + _addXrayBaggage (str, key, value, maxCapacity) { + if (str.length + key.length + value.toString().length + 2 <= maxCapacity) { str += `;${key}=${value}` } + return str } _hasPropagationStyle (mode, name) { @@ -374,7 +375,7 @@ class TextMapPropagator { extractedContext = this._extractB3SingleContext(carrier) break case 'xray': - spanContext = this._extractAwsXrayContext(carrier) + extractedContext = this._extractAwsXrayContext(carrier) break case 'b3': if (this._config.tracePropagationStyle.otelPropagators) { @@ -766,7 +767,7 @@ class TextMapPropagator { } _extractAwsXrayContext (carrier) { - if (xrayHeaderKey in carrier) { + if (carrier[xrayHeaderKey]) { const parsedHeader = this._parseAWSTraceHeader(carrier[xrayHeaderKey]) let traceId @@ -775,8 +776,20 @@ class TextMapPropagator { let ddOrigin const baggage = {} - if (!(xrayRootKey in carrier) || !(xrayRootPrefix in carrier['root'])) { - // header doesn't match our padded version, ignore it + if (!( + xrayRootKey in parsedHeader && + // Regex check to ensure received header is in the same format as expected + // Format: + // 'Root=1-' + // 8 hexadecimal characters representing start time + // '-' + // 24 hexadecimal characters representing trace id + // ';' + // 'Parent=' + // 16 hexadecimal characters representing parent span id + /^(?=.*Root=1-[0-9a-f]{8}-[0-9a-f]{24})(?=.*Parent=[0-9a-f]{16}).*$/i.test(carrier[xrayHeaderKey]) + )) { + // header doesn't match formatting return null } @@ -815,7 +828,7 @@ class TextMapPropagator { traceId: id(traceId, 16), spanId: id(spanId, 16), sampling: { samplingPriority }, - baggage + baggageItems: baggage }) if (ddOrigin) { spanContext._trace.origin = ddOrigin diff --git a/packages/dd-trace/test/config.spec.js b/packages/dd-trace/test/config.spec.js index 357ad73b6dc..1eb711dbd2c 100644 --- a/packages/dd-trace/test/config.spec.js +++ b/packages/dd-trace/test/config.spec.js @@ -241,8 +241,8 @@ describe('Config', () => { expect(config).to.have.property('spanRemoveIntegrationFromService', false) expect(config).to.have.property('instrumentation_config_id', undefined) expect(config).to.have.deep.property('serviceMapping', {}) - expect(config).to.have.nested.deep.property('tracePropagationStyle.inject', ['datadog', 'tracecontext', 'xray', 'baggage']) - expect(config).to.have.nested.deep.property('tracePropagationStyle.extract', ['datadog', 'tracecontext', 'xray', 'baggage']) + expect(config).to.have.nested.deep.property('tracePropagationStyle.inject', ['datadog', 'tracecontext', 'baggage']) + expect(config).to.have.nested.deep.property('tracePropagationStyle.extract', ['datadog', 'tracecontext', 'baggage']) expect(config).to.have.nested.property('experimental.runtimeId', false) expect(config).to.have.nested.property('experimental.exporter', undefined) expect(config).to.have.nested.property('experimental.enableGetRumData', false) diff --git a/packages/dd-trace/test/opentracing/propagation/text_map.spec.js b/packages/dd-trace/test/opentracing/propagation/text_map.spec.js index 965bf2c7ea5..b9b5fa3fbd4 100644 --- a/packages/dd-trace/test/opentracing/propagation/text_map.spec.js +++ b/packages/dd-trace/test/opentracing/propagation/text_map.spec.js @@ -392,6 +392,42 @@ describe('TextMapPropagator', () => { injectCh.unsubscribe(onSpanInject) } }) + + it('should skip injection of aws xray header without the feature flag', () => { + const carrier = {} + const spanContext = createContext({ + traceId: id('0000000000000123'), + spanId: id('0000000000000456') + }) + + config.tracePropagationStyle.inject = [] + + propagator.inject(spanContext, carrier) + + expect(carrier).to.not.have.property('x-amzn-trace-id') + }) + + it(`should inject AWSTraceHeader X-Amzn-Trace-Id when configured`, () => { + const baggageItems = { + bool: true, + a: "b" + } + const spanContext = createContext({ baggageItems, sampling: { + priority: 1 + }}) + + config.tracePropagationStyle.inject = ['xray'] + + const traceId = spanContext._traceId.toString().padStart(24, '0') + const spanId = spanContext._spanId.toString().padStart(16, '0') + const expectedHeader = `root=1-00000000-${traceId};parent=${spanId};sampled=1` + const additionalParts = `;bool=true;a=b` + const carrier = {} + + propagator.inject(spanContext, carrier) + + expect(carrier['x-amzn-trace-id']).to.equal(expectedHeader + additionalParts) + }) }) describe('extract', () => { @@ -663,18 +699,62 @@ describe('TextMapPropagator', () => { expect(spanContext._tracestate).to.be.undefined }) - it(`should extract AWSTraceHeader`, () => { + it(`should not extract AWSTraceHeader X-Amzn-Trace-Id when not configured`, () => { const traceId = '4ef684dbd03d632e' const spanId = '7e8d56262375628a' const sampled = 1 const unparsedHeader = `Root=1-6583199d-00000000${traceId};Parent=${spanId};Sampled=${sampled}` - const carrier = BaseAwsSdkPlugin.prototype.parseAWSTraceHeader(unparsedHeader) + const carrier = { + 'x-amzn-trace-id': unparsedHeader + } + + config.tracePropagationStyle.extract = [] + + const spanContext = propagator.extract(carrier) + + expect(spanContext).to.be.null + }) + + it(`should extract AWSTraceHeader X-Amzn-Trace-Id when configured`, () => { + const traceId = '4ef684dbd03d632e' + const spanId = '7e8d56262375628a' + const sampled = 1 + const unparsedHeader = `Root=1-6583199d-00000000${traceId};Parent=${spanId};Sampled=${sampled}` + const carrier = { + 'x-amzn-trace-id': unparsedHeader + } + + config.tracePropagationStyle.extract = ['xray'] + + const spanContext = propagator.extract(carrier) + + expect(spanContext.toTraceId()).to.equal(id(traceId, 16).toString(10)) + expect(spanContext.toSpanId()).to.equal(id(spanId, 16).toString(10)) + expect(spanContext._sampling.samplingPriority).to.equal(sampled) + }) + + it(`should extract AWSTraceHeader X-Amzn-Trace-Id with origin and baggage`, () => { + const traceId = '4ef684dbd03d632e' + const spanId = '7e8d56262375628a' + const sampled = 1 + const unparsedHeader = `Root=1-6583199d-00000000${traceId};Parent=${spanId};Sampled=${sampled}` + const additionalParts = `;_dd.origin=localhost;baggage_key=baggage_value;foo=bar` + const carrier = { + 'x-amzn-trace-id': unparsedHeader + additionalParts + } + + config.tracePropagationStyle.extract = ['xray'] const spanContext = propagator.extract(carrier) expect(spanContext.toTraceId()).to.equal(id(traceId, 16).toString(10)) expect(spanContext.toSpanId()).to.equal(id(spanId, 16).toString(10)) expect(spanContext._sampling.samplingPriority).to.equal(sampled) + + expect(spanContext._baggageItems).to.deep.equal({ + baggage_key: "baggage_value", foo: "bar" + }) + expect(spanContext._trace.origin).to.equal('localhost') }) it('extracts span_id from tracecontext headers and stores datadog parent-id in trace_distributed_tags', () => { From 4e306b2cd00f92aadbc4124fd7c85a733399b304 Mon Sep 17 00:00:00 2001 From: William Conti Date: Mon, 16 Dec 2024 16:45:40 -0500 Subject: [PATCH 37/52] add more test cases --- .../opentracing/propagation/text_map.spec.js | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/packages/dd-trace/test/opentracing/propagation/text_map.spec.js b/packages/dd-trace/test/opentracing/propagation/text_map.spec.js index b9b5fa3fbd4..c822d92ee0d 100644 --- a/packages/dd-trace/test/opentracing/propagation/text_map.spec.js +++ b/packages/dd-trace/test/opentracing/propagation/text_map.spec.js @@ -733,6 +733,42 @@ describe('TextMapPropagator', () => { expect(spanContext._sampling.samplingPriority).to.equal(sampled) }) + it(`should not extract AWSTraceHeader X-Amzn-Trace-Id if in an unexpected format or missing data`, () => { + const traceId = '4ef684dbd03d632e' + const spanId = '7e8d56262375628a' + const sampled = 1 + + // All necessary fields included but trace id root segment is not formatted properly + let unparsedHeader = `Root=${traceId};Parent=${spanId};Sampled=${sampled}` + const carrier = { + 'x-amzn-trace-id': unparsedHeader + } + + config.tracePropagationStyle.extract = ['xray'] + + let spanContext = propagator.extract(carrier) + + expect(spanContext).to.be.null + + // Missing necessary parent field + unparsedHeader = `Root=1-6583199d-00000000${traceId};Sampled=${sampled}` + + config.tracePropagationStyle.extract = ['xray'] + + spanContext = propagator.extract(carrier) + + expect(spanContext).to.be.null + + // Missing necessary root field + unparsedHeader = `Parent=${spanId};Sampled=${sampled}` + + config.tracePropagationStyle.extract = ['xray'] + + spanContext = propagator.extract(carrier) + + expect(spanContext).to.be.null + }) + it(`should extract AWSTraceHeader X-Amzn-Trace-Id with origin and baggage`, () => { const traceId = '4ef684dbd03d632e' const spanId = '7e8d56262375628a' From c4de3145386d14d8c49d6f774fbf0763f1e8e581 Mon Sep 17 00:00:00 2001 From: William Conti Date: Tue, 17 Dec 2024 13:28:45 -0500 Subject: [PATCH 38/52] fix lint --- .../src/opentracing/propagation/text_map.js | 52 +++++++++---------- .../opentracing/propagation/text_map.spec.js | 30 ++++++----- 2 files changed, 42 insertions(+), 40 deletions(-) diff --git a/packages/dd-trace/src/opentracing/propagation/text_map.js b/packages/dd-trace/src/opentracing/propagation/text_map.js index 58ab1f95eff..499345a3bce 100644 --- a/packages/dd-trace/src/opentracing/propagation/text_map.js +++ b/packages/dd-trace/src/opentracing/propagation/text_map.js @@ -55,7 +55,7 @@ const xraySampledKey = 'sampled' const xrayE2EStartTimeKey = 't0' const xraySelfKey = 'self' const xrayOriginKey = '_dd.origin' -const xrayMaxAdditionalBytes = 256; +const xrayMaxAdditionalBytes = 256 class TextMapPropagator { constructor (config) { @@ -272,23 +272,23 @@ class TextMapPropagator { // TODO: Do we have access to a start time? Java does const e2eStart = this._getEndToEndStartTime(spanContext.start_ms) - + let str = ( - xrayRootKey + "=" + + xrayRootKey + '=' + xrayRootPrefix + - e2eStart + "-" + + e2eStart + '-' + spanContext._traceId.toString().padStart(24, '0') + - ';' + xrayParentKey + "=" + + ';' + xrayParentKey + '=' + spanContext._spanId.toString().padStart(16, '0') ) if (spanContext?._sampling?.priority) { - str += ';' + xraySampledKey + "=" + (spanContext._sampling.priority > 0 ? '1' : '0') + str += ';' + xraySampledKey + '=' + (spanContext._sampling.priority > 0 ? '1' : '0') } - const maxAdditionalCapacity = xrayMaxAdditionalBytes - str.length + const maxAdditionalCapacity = xrayMaxAdditionalBytes - str.length - const origin = spanContext._trace.origin; + const origin = spanContext._trace.origin if (origin) { this._addXrayBaggage(str, xrayOriginKey, origin, maxAdditionalCapacity) } @@ -306,8 +306,8 @@ class TextMapPropagator { } _getEndToEndStartTime (start) { - if (!start) return "00000000" - + if (!start) return '00000000' + const e2eStart = start > 0 ? start : Date.now() / 1000 return e2eStart.toString().padStart(8, '0') } @@ -318,10 +318,10 @@ class TextMapPropagator { _addXrayBaggage (str, key, value, maxCapacity) { if (str.length + key.length + value.toString().length + 2 <= maxCapacity) { - str += `;${key}=${value}` + str += `;${key}=${value}` } return str - } + } _hasPropagationStyle (mode, name) { return this._config.tracePropagationStyle[mode].includes(name) @@ -777,18 +777,18 @@ class TextMapPropagator { const baggage = {} if (!( - xrayRootKey in parsedHeader && - // Regex check to ensure received header is in the same format as expected - // Format: - // 'Root=1-' - // 8 hexadecimal characters representing start time - // '-' - // 24 hexadecimal characters representing trace id - // ';' - // 'Parent=' - // 16 hexadecimal characters representing parent span id - /^(?=.*Root=1-[0-9a-f]{8}-[0-9a-f]{24})(?=.*Parent=[0-9a-f]{16}).*$/i.test(carrier[xrayHeaderKey]) - )) { + xrayRootKey in parsedHeader && + // Regex check to ensure received header is in the same format as expected + // Format: + // 'Root=1-' + // 8 hexadecimal characters representing start time + // '-' + // 24 hexadecimal characters representing trace id + // ';' + // 'Parent=' + // 16 hexadecimal characters representing parent span id + /^(?=.*Root=1-[0-9a-f]{8}-[0-9a-f]{24})(?=.*Parent=[0-9a-f]{16}).*$/i.test(carrier[xrayHeaderKey]) + )) { // header doesn't match formatting return null } @@ -817,7 +817,7 @@ class TextMapPropagator { // self is added by load balancers and should be ignored continue } else if (key === xrayE2EStartTimeKey) { - baggage[endToEndStartTime] = parseInt(value) + baggage.startMs = parseInt(value) } else { baggage[key] = value } @@ -852,7 +852,7 @@ class TextMapPropagator { }) return obj } - + static _convertOtelContextToDatadog (traceId, spanId, traceFlag, ts, meta = {}) { const origin = null let samplingPriority = traceFlag diff --git a/packages/dd-trace/test/opentracing/propagation/text_map.spec.js b/packages/dd-trace/test/opentracing/propagation/text_map.spec.js index c822d92ee0d..184a5aac741 100644 --- a/packages/dd-trace/test/opentracing/propagation/text_map.spec.js +++ b/packages/dd-trace/test/opentracing/propagation/text_map.spec.js @@ -6,7 +6,6 @@ const Config = require('../../../src/config') const id = require('../../../src/id') const SpanContext = require('../../../src/opentracing/span_context') const TraceState = require('../../../src/opentracing/propagation/tracestate') -const BaseAwsSdkPlugin = require('../../../../datadog-plugin-aws-sdk/src/base') const { channel } = require('dc-polyfill') const { AUTO_KEEP, AUTO_REJECT, USER_KEEP } = require('../../../../../ext/priority') @@ -407,21 +406,24 @@ describe('TextMapPropagator', () => { expect(carrier).to.not.have.property('x-amzn-trace-id') }) - it(`should inject AWSTraceHeader X-Amzn-Trace-Id when configured`, () => { + it('should inject AWSTraceHeader X-Amzn-Trace-Id when configured', () => { const baggageItems = { bool: true, - a: "b" + a: 'b' } - const spanContext = createContext({ baggageItems, sampling: { - priority: 1 - }}) + const spanContext = createContext({ + baggageItems, + sampling: { + priority: 1 + } + }) config.tracePropagationStyle.inject = ['xray'] const traceId = spanContext._traceId.toString().padStart(24, '0') const spanId = spanContext._spanId.toString().padStart(16, '0') const expectedHeader = `root=1-00000000-${traceId};parent=${spanId};sampled=1` - const additionalParts = `;bool=true;a=b` + const additionalParts = ';bool=true;a=b' const carrier = {} propagator.inject(spanContext, carrier) @@ -699,7 +701,7 @@ describe('TextMapPropagator', () => { expect(spanContext._tracestate).to.be.undefined }) - it(`should not extract AWSTraceHeader X-Amzn-Trace-Id when not configured`, () => { + it('should not extract AWSTraceHeader X-Amzn-Trace-Id when not configured', () => { const traceId = '4ef684dbd03d632e' const spanId = '7e8d56262375628a' const sampled = 1 @@ -715,7 +717,7 @@ describe('TextMapPropagator', () => { expect(spanContext).to.be.null }) - it(`should extract AWSTraceHeader X-Amzn-Trace-Id when configured`, () => { + it('should extract AWSTraceHeader X-Amzn-Trace-Id when configured', () => { const traceId = '4ef684dbd03d632e' const spanId = '7e8d56262375628a' const sampled = 1 @@ -733,7 +735,7 @@ describe('TextMapPropagator', () => { expect(spanContext._sampling.samplingPriority).to.equal(sampled) }) - it(`should not extract AWSTraceHeader X-Amzn-Trace-Id if in an unexpected format or missing data`, () => { + it('should not extract AWSTraceHeader X-Amzn-Trace-Id if in an unexpected format or missing data', () => { const traceId = '4ef684dbd03d632e' const spanId = '7e8d56262375628a' const sampled = 1 @@ -769,12 +771,12 @@ describe('TextMapPropagator', () => { expect(spanContext).to.be.null }) - it(`should extract AWSTraceHeader X-Amzn-Trace-Id with origin and baggage`, () => { + it('should extract AWSTraceHeader X-Amzn-Trace-Id with origin and baggage', () => { const traceId = '4ef684dbd03d632e' const spanId = '7e8d56262375628a' const sampled = 1 const unparsedHeader = `Root=1-6583199d-00000000${traceId};Parent=${spanId};Sampled=${sampled}` - const additionalParts = `;_dd.origin=localhost;baggage_key=baggage_value;foo=bar` + const additionalParts = ';_dd.origin=localhost;baggage_key=baggage_value;foo=bar' const carrier = { 'x-amzn-trace-id': unparsedHeader + additionalParts } @@ -787,8 +789,8 @@ describe('TextMapPropagator', () => { expect(spanContext.toSpanId()).to.equal(id(spanId, 16).toString(10)) expect(spanContext._sampling.samplingPriority).to.equal(sampled) - expect(spanContext._baggageItems).to.deep.equal({ - baggage_key: "baggage_value", foo: "bar" + expect(spanContext._baggageItems).to.deep.equal({ + baggage_key: 'baggage_value', foo: 'bar' }) expect(spanContext._trace.origin).to.equal('localhost') }) From f6ff0b5291eff5d805ac969c4be5b0347d64bc34 Mon Sep 17 00:00:00 2001 From: William Conti Date: Thu, 19 Dec 2024 14:58:21 -0500 Subject: [PATCH 39/52] small changes --- .../datadog-plugin-http/test/client.spec.js | 3 +-- packages/dd-trace/src/config.js | 2 +- .../src/opentracing/propagation/text_map.js | 26 ++++++++++++++++--- 3 files changed, 24 insertions(+), 7 deletions(-) diff --git a/packages/datadog-plugin-http/test/client.spec.js b/packages/datadog-plugin-http/test/client.spec.js index 42f4c8436f8..b6aa0a51f2f 100644 --- a/packages/datadog-plugin-http/test/client.spec.js +++ b/packages/datadog-plugin-http/test/client.spec.js @@ -1113,8 +1113,7 @@ describe('Plugin', () => { app.get('/', (req, res) => { try { - expect(req.get('x-datadog-trace-id')).to.be.a('string') - expect(req.get('x-datadog-parent-id')).to.be.a('string') + expect(req.get('x-amzn-trace-id')).to.be.a('string') res.status(200).send() diff --git a/packages/dd-trace/src/config.js b/packages/dd-trace/src/config.js index 808704bd7e4..bcda8fbf20c 100644 --- a/packages/dd-trace/src/config.js +++ b/packages/dd-trace/src/config.js @@ -61,7 +61,7 @@ const otelDdEnvMapping = { OTEL_LOGS_EXPORTER: undefined } -const VALID_PROPAGATION_STYLES = new Set(['datadog', 'tracecontext', 'b3', 'b3 single header', 'none']) +const VALID_PROPAGATION_STYLES = new Set(['datadog', 'tracecontext', 'b3', 'b3 single header', 'xray', 'none']) const VALID_LOG_LEVELS = new Set(['debug', 'info', 'warn', 'error']) diff --git a/packages/dd-trace/src/opentracing/propagation/text_map.js b/packages/dd-trace/src/opentracing/propagation/text_map.js index 499345a3bce..68f0605534a 100644 --- a/packages/dd-trace/src/opentracing/propagation/text_map.js +++ b/packages/dd-trace/src/opentracing/propagation/text_map.js @@ -55,7 +55,7 @@ const xraySampledKey = 'sampled' const xrayE2EStartTimeKey = 't0' const xraySelfKey = 'self' const xrayOriginKey = '_dd.origin' -const xrayMaxAdditionalBytes = 256 +const xrayMaxAdditionalBaggageBytes = 256 class TextMapPropagator { constructor (config) { @@ -265,8 +265,21 @@ class TextMapPropagator { _injectAwsXrayContext (spanContext, carrier) { // injects AWS Trace Header (X-Amzn-Trace-Id) to carrier - // ex: 'Root=1-00000000-00000000fffffffffffffffe;Parent=ffffffffffffffff;Sampled=1;_dd.origin=fakeOrigin; - + // + // ex: 'Root=1-00000000-00000000fffffffffffffffe;Parent=ffffffffffffffff;Sampled=1;_dd.origin=fakeOrigin;baggage_k=baggage_v...; + // + // Header Format: + // 'Root=1-' (always uses '1-') + // 8 hexadecimal characters representing start time of the operation + // '-' + // 24 hexadecimal characters representing trace id + // ';' + // 'Parent=' + // 16 hexadecimal characters representing parent span id + // 'Sampled=' + // int of [0, 1] representing sampling decision + // Additional baggage in k=v pairs separated by ';' delimiter/ + // // based off: https://docs.aws.amazon.com/xray/latest/devguide/xray-concepts.html#xray-concepts-tracingheader if (!this._hasPropagationStyle('inject', 'xray')) return @@ -286,7 +299,8 @@ class TextMapPropagator { str += ';' + xraySampledKey + '=' + (spanContext._sampling.priority > 0 ? '1' : '0') } - const maxAdditionalCapacity = xrayMaxAdditionalBytes - str.length + // limit trace header size to the size of the necessary bytes plus 256 + const maxAdditionalCapacity = str.length + xrayMaxAdditionalBaggageBytes const origin = spanContext._trace.origin if (origin) { @@ -767,6 +781,10 @@ class TextMapPropagator { } _extractAwsXrayContext (carrier) { + if (!this._hasPropagationStyle('inject', 'xray')) { + return null + } + if (carrier[xrayHeaderKey]) { const parsedHeader = this._parseAWSTraceHeader(carrier[xrayHeaderKey]) From f427e9b2d3988b6ce8c2bdd0d74434b7ffc8f6fc Mon Sep 17 00:00:00 2001 From: William Conti Date: Thu, 19 Dec 2024 15:19:01 -0500 Subject: [PATCH 40/52] remove check for aws signed requests --- packages/datadog-plugin-http/src/client.js | 39 +---------------- .../datadog-plugin-http/test/client.spec.js | 43 ------------------- .../src/opentracing/propagation/text_map.js | 20 ++------- 3 files changed, 5 insertions(+), 97 deletions(-) diff --git a/packages/datadog-plugin-http/src/client.js b/packages/datadog-plugin-http/src/client.js index d4c105d2508..f38427318f0 100644 --- a/packages/datadog-plugin-http/src/client.js +++ b/packages/datadog-plugin-http/src/client.js @@ -58,7 +58,7 @@ class HttpClientPlugin extends ClientPlugin { span._spanContext._trace.record = false } - if (this.shouldInjectTraceHeaders(options, uri)) { + if (!this.config.propagationFilter(uri)) { this.tracer.inject(span, HTTP_HEADERS, options.headers) } @@ -71,18 +71,6 @@ class HttpClientPlugin extends ClientPlugin { return message.currentStore } - shouldInjectTraceHeaders (options, uri) { - if (hasAmazonSignature(options) && !this.config.enablePropagationWithAmazonHeaders) { - return false - } - - if (!this.config.propagationFilter(uri)) { - return false - } - - return true - } - bindAsyncStart ({ parentStore }) { return parentStore } @@ -212,31 +200,6 @@ function getHooks (config) { return { request } } -function hasAmazonSignature (options) { - if (!options) { - return false - } - - if (options.headers) { - const headers = Object.keys(options.headers) - .reduce((prev, next) => Object.assign(prev, { - [next.toLowerCase()]: options.headers[next] - }), {}) - - if (headers['x-amz-signature']) { - return true - } - - if ([].concat(headers.authorization).some(startsWith('AWS4-HMAC-SHA256'))) { - return true - } - } - - const search = options.search || options.path - - return search && search.toLowerCase().indexOf('x-amz-signature=') !== -1 -} - function extractSessionDetails (options) { if (typeof options === 'string') { return new URL(options).host diff --git a/packages/datadog-plugin-http/test/client.spec.js b/packages/datadog-plugin-http/test/client.spec.js index b6aa0a51f2f..c655f2e791c 100644 --- a/packages/datadog-plugin-http/test/client.spec.js +++ b/packages/datadog-plugin-http/test/client.spec.js @@ -1093,49 +1093,6 @@ describe('Plugin', () => { }) }) - describe('with config enablePropagationWithAmazonHeaders enabled', () => { - let config - - beforeEach(() => { - config = { - enablePropagationWithAmazonHeaders: true - } - - return agent.load('http', config) - .then(() => { - http = require(pluginToBeLoaded) - express = require('express') - }) - }) - - it('should inject tracing header into AWS signed request', done => { - const app = express() - - app.get('/', (req, res) => { - try { - expect(req.get('x-amzn-trace-id')).to.be.a('string') - - res.status(200).send() - - done() - } catch (e) { - done(e) - } - }) - - appListener = server(app, port => { - const req = http.request({ - port, - headers: { - Authorization: 'AWS4-HMAC-SHA256 ...' - } - }) - - req.end() - }) - }) - }) - describe('with validateStatus configuration', () => { let config diff --git a/packages/dd-trace/src/opentracing/propagation/text_map.js b/packages/dd-trace/src/opentracing/propagation/text_map.js index 68f0605534a..91150b61fd4 100644 --- a/packages/dd-trace/src/opentracing/propagation/text_map.js +++ b/packages/dd-trace/src/opentracing/propagation/text_map.js @@ -55,6 +55,7 @@ const xraySampledKey = 'sampled' const xrayE2EStartTimeKey = 't0' const xraySelfKey = 'self' const xrayOriginKey = '_dd.origin' +const xrayDefaultE2EStartTime = '00000000' const xrayMaxAdditionalBaggageBytes = 256 class TextMapPropagator { @@ -306,7 +307,7 @@ class TextMapPropagator { if (origin) { this._addXrayBaggage(str, xrayOriginKey, origin, maxAdditionalCapacity) } - if (e2eStart !== '00000000') { + if (e2eStart !== xrayDefaultStartTime) { this._addXrayBaggage(str, xrayE2EStartTimeKey, e2eStart.toString(), maxAdditionalCapacity) } @@ -320,7 +321,7 @@ class TextMapPropagator { } _getEndToEndStartTime (start) { - if (!start) return '00000000' + if (!start) return xrayDefaultE2EStartTime const e2eStart = start > 0 ? start : Date.now() / 1000 return e2eStart.toString().padStart(8, '0') @@ -794,20 +795,7 @@ class TextMapPropagator { let ddOrigin const baggage = {} - if (!( - xrayRootKey in parsedHeader && - // Regex check to ensure received header is in the same format as expected - // Format: - // 'Root=1-' - // 8 hexadecimal characters representing start time - // '-' - // 24 hexadecimal characters representing trace id - // ';' - // 'Parent=' - // 16 hexadecimal characters representing parent span id - /^(?=.*Root=1-[0-9a-f]{8}-[0-9a-f]{24})(?=.*Parent=[0-9a-f]{16}).*$/i.test(carrier[xrayHeaderKey]) - )) { - // header doesn't match formatting + if (!(xrayRootKey in parsedHeader)) { return null } From 2f702c74853ef7aa02d3bcc22dc2c82b9ca5038f Mon Sep 17 00:00:00 2001 From: William Conti Date: Thu, 19 Dec 2024 15:33:52 -0500 Subject: [PATCH 41/52] add test for baggage too large --- .../src/opentracing/propagation/text_map.js | 4 +-- .../opentracing/propagation/text_map.spec.js | 27 +++++++++++++++++++ 2 files changed, 29 insertions(+), 2 deletions(-) diff --git a/packages/dd-trace/src/opentracing/propagation/text_map.js b/packages/dd-trace/src/opentracing/propagation/text_map.js index 91150b61fd4..a9123c42028 100644 --- a/packages/dd-trace/src/opentracing/propagation/text_map.js +++ b/packages/dd-trace/src/opentracing/propagation/text_map.js @@ -307,7 +307,7 @@ class TextMapPropagator { if (origin) { this._addXrayBaggage(str, xrayOriginKey, origin, maxAdditionalCapacity) } - if (e2eStart !== xrayDefaultStartTime) { + if (e2eStart !== xrayDefaultE2EStartTime) { this._addXrayBaggage(str, xrayE2EStartTimeKey, e2eStart.toString(), maxAdditionalCapacity) } @@ -782,7 +782,7 @@ class TextMapPropagator { } _extractAwsXrayContext (carrier) { - if (!this._hasPropagationStyle('inject', 'xray')) { + if (!this._hasPropagationStyle('extract', 'xray')) { return null } diff --git a/packages/dd-trace/test/opentracing/propagation/text_map.spec.js b/packages/dd-trace/test/opentracing/propagation/text_map.spec.js index 184a5aac741..0b63e3617da 100644 --- a/packages/dd-trace/test/opentracing/propagation/text_map.spec.js +++ b/packages/dd-trace/test/opentracing/propagation/text_map.spec.js @@ -430,6 +430,33 @@ describe('TextMapPropagator', () => { expect(carrier['x-amzn-trace-id']).to.equal(expectedHeader + additionalParts) }) + + it('should inject skip adding baggage to the X-Amzn-Trace-Id when exceeding the 256 byte limit', () => { + const baggageItems = { + bool: true, + a: 'b', + mySuperLongBaggageIReallyShouldLearnToPackLighter: 'datadog'.repeat(50), + shortBaggage: 'thisFits' + } + const spanContext = createContext({ + baggageItems, + sampling: { + priority: 1 + } + }) + + config.tracePropagationStyle.inject = ['xray'] + + const traceId = spanContext._traceId.toString().padStart(24, '0') + const spanId = spanContext._spanId.toString().padStart(16, '0') + const expectedHeader = `root=1-00000000-${traceId};parent=${spanId};sampled=1` + const additionalParts = ';bool=true;a=b;shortBaggage=thisFits' + const carrier = {} + + propagator.inject(spanContext, carrier) + + expect(carrier['x-amzn-trace-id']).to.equal(expectedHeader + additionalParts) + }) }) describe('extract', () => { From 70cbfd8879562d8642f9c4bf62f1242ed0b31c89 Mon Sep 17 00:00:00 2001 From: William Conti Date: Thu, 26 Dec 2024 14:01:14 -0500 Subject: [PATCH 42/52] add more tests to ensure new context propagation works with existing types --- .../opentracing/propagation/text_map.spec.js | 50 +++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/packages/dd-trace/test/opentracing/propagation/text_map.spec.js b/packages/dd-trace/test/opentracing/propagation/text_map.spec.js index 0b63e3617da..44a4f9e1d2a 100644 --- a/packages/dd-trace/test/opentracing/propagation/text_map.spec.js +++ b/packages/dd-trace/test/opentracing/propagation/text_map.spec.js @@ -457,6 +457,28 @@ describe('TextMapPropagator', () => { expect(carrier['x-amzn-trace-id']).to.equal(expectedHeader + additionalParts) }) + + it('should inject AWSTraceHeader X-Amzn-Trace-Id and Datatog context when configured', () => { + const spanContext = createContext({ + sampling: { + priority: 1 + }, + baggageItems: null + }) + const carrier = {} + + config.tracePropagationStyle.inject = ['datadog', 'xray'] + + const traceId = spanContext._traceId.toString().padStart(24, '0') + const spanId = spanContext._spanId.toString().padStart(16, '0') + const expectedHeader = `root=1-00000000-${traceId};parent=${spanId};sampled=1` + + propagator.inject(spanContext, carrier) + + expect(carrier).to.have.property('x-datadog-trace-id', '123') + expect(carrier).to.have.property('x-datadog-parent-id', '456') + expect(carrier).to.have.property('x-amzn-trace-id', expectedHeader) + }) }) describe('extract', () => { @@ -822,6 +844,34 @@ describe('TextMapPropagator', () => { expect(spanContext._trace.origin).to.equal('localhost') }) + it('should extract AWSTraceHeader X-Amzn-Trace-Id if no valid datadog context is found', () => { + const traceId = '4ef684dbd03d632e' + const spanId = '7e8d56262375628a' + const sampled = 1 + const unparsedHeader = `Root=1-6583199d-00000000${traceId};Parent=${spanId};Sampled=${sampled}` + const carrier = { + 'x-datadog-trace-id': '0', + 'x-datadog-parent-id': '0' + } + + config.tracePropagationStyle.extract = ['datadog', 'xray'] + + // try to extract a span context but there's no valid context + let spanContext = propagator.extract(carrier) + + expect(spanContext).to.be.null + + // add an AWS X-ray header with valid context value + carrier['x-amzn-trace-id'] = unparsedHeader + + spanContext = propagator.extract(carrier) + + // ensure context was extracted correctly + expect(spanContext.toTraceId()).to.equal(id(traceId, 16).toString(10)) + expect(spanContext.toSpanId()).to.equal(id(spanId, 16).toString(10)) + expect(spanContext._sampling.samplingPriority).to.equal(sampled) + }) + it('extracts span_id from tracecontext headers and stores datadog parent-id in trace_distributed_tags', () => { textMap['x-datadog-trace-id'] = '61185' textMap['x-datadog-parent-id'] = '15' From 504b5848407aff10813cb5acb034e0430f92d726 Mon Sep 17 00:00:00 2001 From: William Conti Date: Fri, 3 Jan 2025 11:42:35 -0500 Subject: [PATCH 43/52] fix lint and other errors --- packages/datadog-plugin-http/src/client.js | 6 +----- packages/dd-trace/src/opentracing/propagation/text_map.js | 3 ++- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/packages/datadog-plugin-http/src/client.js b/packages/datadog-plugin-http/src/client.js index f38427318f0..55a025f4970 100644 --- a/packages/datadog-plugin-http/src/client.js +++ b/packages/datadog-plugin-http/src/client.js @@ -58,7 +58,7 @@ class HttpClientPlugin extends ClientPlugin { span._spanContext._trace.record = false } - if (!this.config.propagationFilter(uri)) { + if (this.config.propagationFilter(uri)) { this.tracer.inject(span, HTTP_HEADERS, options.headers) } @@ -211,8 +211,4 @@ function extractSessionDetails (options) { return { host, port } } -function startsWith (searchString) { - return value => String(value).startsWith(searchString) -} - module.exports = HttpClientPlugin diff --git a/packages/dd-trace/src/opentracing/propagation/text_map.js b/packages/dd-trace/src/opentracing/propagation/text_map.js index 1f658886d93..e54a8fc94f8 100644 --- a/packages/dd-trace/src/opentracing/propagation/text_map.js +++ b/packages/dd-trace/src/opentracing/propagation/text_map.js @@ -267,7 +267,8 @@ class TextMapPropagator { _injectAwsXrayContext (spanContext, carrier) { // injects AWS Trace Header (X-Amzn-Trace-Id) to carrier // - // ex: 'Root=1-00000000-00000000fffffffffffffffe;Parent=ffffffffffffffff;Sampled=1;_dd.origin=fakeOrigin;baggage_k=baggage_v...; + // ex: 'Root=1-00000000-00000000fffffffffffffffe;Parent=ffffffffffffffff;... + // ...Sampled=1;_dd.origin=fakeOrigin;baggage_k=baggage_v...; // // Header Format: // 'Root=1-' (always uses '1-') From 7b07cce299ee7ed124fe364b6dea5e73b2ec06ad Mon Sep 17 00:00:00 2001 From: William Conti Date: Fri, 3 Jan 2025 11:53:42 -0500 Subject: [PATCH 44/52] remove aws signature tests --- .../datadog-plugin-fetch/test/index.spec.js | 50 --------------- .../datadog-plugin-http/test/client.spec.js | 56 ---------------- .../datadog-plugin-http2/test/client.spec.js | 64 ------------------- 3 files changed, 170 deletions(-) diff --git a/packages/datadog-plugin-fetch/test/index.spec.js b/packages/datadog-plugin-fetch/test/index.spec.js index 1d322de04a4..9b3acd62501 100644 --- a/packages/datadog-plugin-fetch/test/index.spec.js +++ b/packages/datadog-plugin-fetch/test/index.spec.js @@ -215,56 +215,6 @@ describe('Plugin', () => { }) }) - it('should skip injecting if the Authorization header contains an AWS signature', done => { - const app = express() - - app.get('/', (req, res) => { - try { - expect(req.get('x-datadog-trace-id')).to.be.undefined - expect(req.get('x-datadog-parent-id')).to.be.undefined - - res.status(200).send() - - done() - } catch (e) { - done(e) - } - }) - - appListener = server(app, port => { - fetch(`http://localhost:${port}/`, { - headers: { - Authorization: 'AWS4-HMAC-SHA256 ...' - } - }) - }) - }) - - it('should skip injecting if one of the Authorization headers contains an AWS signature', done => { - const app = express() - - app.get('/', (req, res) => { - try { - expect(req.get('x-datadog-trace-id')).to.be.undefined - expect(req.get('x-datadog-parent-id')).to.be.undefined - - res.status(200).send() - - done() - } catch (e) { - done(e) - } - }) - - appListener = server(app, port => { - fetch(`http://localhost:${port}/`, { - headers: { - Authorization: ['AWS4-HMAC-SHA256 ...'] - } - }) - }) - }) - it('should skip injecting if the X-Amz-Signature header is set', done => { const app = express() diff --git a/packages/datadog-plugin-http/test/client.spec.js b/packages/datadog-plugin-http/test/client.spec.js index c655f2e791c..58d857f1466 100644 --- a/packages/datadog-plugin-http/test/client.spec.js +++ b/packages/datadog-plugin-http/test/client.spec.js @@ -446,62 +446,6 @@ describe('Plugin', () => { }) }) - it('should skip injecting if the Authorization header contains an AWS signature', done => { - const app = express() - - app.get('/', (req, res) => { - try { - expect(req.get('x-datadog-trace-id')).to.be.undefined - expect(req.get('x-datadog-parent-id')).to.be.undefined - - res.status(200).send() - - done() - } catch (e) { - done(e) - } - }) - - appListener = server(app, port => { - const req = http.request({ - port, - headers: { - Authorization: 'AWS4-HMAC-SHA256 ...' - } - }) - - req.end() - }) - }) - - it('should skip injecting if one of the Authorization headers contains an AWS signature', done => { - const app = express() - - app.get('/', (req, res) => { - try { - expect(req.get('x-datadog-trace-id')).to.be.undefined - expect(req.get('x-datadog-parent-id')).to.be.undefined - - res.status(200).send() - - done() - } catch (e) { - done(e) - } - }) - - appListener = server(app, port => { - const req = http.request({ - port, - headers: { - Authorization: ['AWS4-HMAC-SHA256 ...'] - } - }) - - req.end() - }) - }) - it('should skip injecting if the X-Amz-Signature header is set', done => { const app = express() diff --git a/packages/datadog-plugin-http2/test/client.spec.js b/packages/datadog-plugin-http2/test/client.spec.js index f8d44f3ac0b..05e95ad651e 100644 --- a/packages/datadog-plugin-http2/test/client.spec.js +++ b/packages/datadog-plugin-http2/test/client.spec.js @@ -365,70 +365,6 @@ describe('Plugin', () => { }) }) - it('should skip injecting if the Authorization header contains an AWS signature', done => { - const app = (stream, headers) => { - try { - expect(headers['x-datadog-trace-id']).to.be.undefined - expect(headers['x-datadog-parent-id']).to.be.undefined - - stream.respond({ - ':status': 200 - }) - stream.end() - - done() - } catch (e) { - done(e) - } - } - - appListener = server(app, port => { - const headers = { - Authorization: 'AWS4-HMAC-SHA256 ...' - } - const client = http2 - .connect(`${protocol}://localhost:${port}`) - .on('error', done) - - const req = client.request(headers) - req.on('error', done) - - req.end() - }) - }) - - it('should skip injecting if one of the Authorization headers contains an AWS signature', done => { - const app = (stream, headers) => { - try { - expect(headers['x-datadog-trace-id']).to.be.undefined - expect(headers['x-datadog-parent-id']).to.be.undefined - - stream.respond({ - ':status': 200 - }) - stream.end() - - done() - } catch (e) { - done(e) - } - } - - appListener = server(app, port => { - const headers = { - Authorization: ['AWS4-HMAC-SHA256 ...'] - } - const client = http2 - .connect(`${protocol}://localhost:${port}`) - .on('error', done) - - const req = client.request(headers) - req.on('error', done) - - req.end() - }) - }) - it('should skip injecting if the X-Amz-Signature header is set', done => { const app = (stream, headers) => { try { From fc1e874effb210d2d7281d7dcc08fd057bf627ad Mon Sep 17 00:00:00 2001 From: William Conti Date: Fri, 3 Jan 2025 12:11:11 -0500 Subject: [PATCH 45/52] fix signature tests --- .../datadog-plugin-fetch/test/index.spec.js | 50 +++++++++ packages/datadog-plugin-http/src/client.js | 47 +++++++- .../datadog-plugin-http/test/client.spec.js | 100 ++++++++++++++++++ .../datadog-plugin-http2/test/client.spec.js | 64 +++++++++++ 4 files changed, 260 insertions(+), 1 deletion(-) diff --git a/packages/datadog-plugin-fetch/test/index.spec.js b/packages/datadog-plugin-fetch/test/index.spec.js index 9b3acd62501..1d322de04a4 100644 --- a/packages/datadog-plugin-fetch/test/index.spec.js +++ b/packages/datadog-plugin-fetch/test/index.spec.js @@ -215,6 +215,56 @@ describe('Plugin', () => { }) }) + it('should skip injecting if the Authorization header contains an AWS signature', done => { + const app = express() + + app.get('/', (req, res) => { + try { + expect(req.get('x-datadog-trace-id')).to.be.undefined + expect(req.get('x-datadog-parent-id')).to.be.undefined + + res.status(200).send() + + done() + } catch (e) { + done(e) + } + }) + + appListener = server(app, port => { + fetch(`http://localhost:${port}/`, { + headers: { + Authorization: 'AWS4-HMAC-SHA256 ...' + } + }) + }) + }) + + it('should skip injecting if one of the Authorization headers contains an AWS signature', done => { + const app = express() + + app.get('/', (req, res) => { + try { + expect(req.get('x-datadog-trace-id')).to.be.undefined + expect(req.get('x-datadog-parent-id')).to.be.undefined + + res.status(200).send() + + done() + } catch (e) { + done(e) + } + }) + + appListener = server(app, port => { + fetch(`http://localhost:${port}/`, { + headers: { + Authorization: ['AWS4-HMAC-SHA256 ...'] + } + }) + }) + }) + it('should skip injecting if the X-Amz-Signature header is set', done => { const app = express() diff --git a/packages/datadog-plugin-http/src/client.js b/packages/datadog-plugin-http/src/client.js index 55a025f4970..be556feed67 100644 --- a/packages/datadog-plugin-http/src/client.js +++ b/packages/datadog-plugin-http/src/client.js @@ -58,7 +58,7 @@ class HttpClientPlugin extends ClientPlugin { span._spanContext._trace.record = false } - if (this.config.propagationFilter(uri)) { + if (this.shouldInjectTraceHeaders(options, uri)) { this.tracer.inject(span, HTTP_HEADERS, options.headers) } @@ -71,6 +71,22 @@ class HttpClientPlugin extends ClientPlugin { return message.currentStore } + shouldInjectTraceHeaders (options, uri) { + if (hasAmazonSignature(options) && !this.tracer._hasPropagationStyle('inject', 'xray')) { + log.debug( + 'AWS Signature detected on HTTP request, skipping injecting headers. To enable header injection' + + ' for signed AWS requests, please set DD_TRACE_PROPAGATION_STYLE=["xray", "datadog"]' + ) + return false + } + + if (!this.config.propagationFilter(uri)) { + return false + } + + return true + } + bindAsyncStart ({ parentStore }) { return parentStore } @@ -200,6 +216,31 @@ function getHooks (config) { return { request } } +function hasAmazonSignature (options) { + if (!options) { + return false + } + + if (options.headers) { + const headers = Object.keys(options.headers) + .reduce((prev, next) => Object.assign(prev, { + [next.toLowerCase()]: options.headers[next] + }), {}) + + if (headers['x-amz-signature']) { + return true + } + + if ([].concat(headers.authorization).some(startsWith('AWS4-HMAC-SHA256'))) { + return true + } + } + + const search = options.search || options.path + + return search && search.toLowerCase().indexOf('x-amz-signature=') !== -1 +} + function extractSessionDetails (options) { if (typeof options === 'string') { return new URL(options).host @@ -211,4 +252,8 @@ function extractSessionDetails (options) { return { host, port } } +function startsWith (searchString) { + return value => String(value).startsWith(searchString) +} + module.exports = HttpClientPlugin diff --git a/packages/datadog-plugin-http/test/client.spec.js b/packages/datadog-plugin-http/test/client.spec.js index 58d857f1466..42f4c8436f8 100644 --- a/packages/datadog-plugin-http/test/client.spec.js +++ b/packages/datadog-plugin-http/test/client.spec.js @@ -446,6 +446,62 @@ describe('Plugin', () => { }) }) + it('should skip injecting if the Authorization header contains an AWS signature', done => { + const app = express() + + app.get('/', (req, res) => { + try { + expect(req.get('x-datadog-trace-id')).to.be.undefined + expect(req.get('x-datadog-parent-id')).to.be.undefined + + res.status(200).send() + + done() + } catch (e) { + done(e) + } + }) + + appListener = server(app, port => { + const req = http.request({ + port, + headers: { + Authorization: 'AWS4-HMAC-SHA256 ...' + } + }) + + req.end() + }) + }) + + it('should skip injecting if one of the Authorization headers contains an AWS signature', done => { + const app = express() + + app.get('/', (req, res) => { + try { + expect(req.get('x-datadog-trace-id')).to.be.undefined + expect(req.get('x-datadog-parent-id')).to.be.undefined + + res.status(200).send() + + done() + } catch (e) { + done(e) + } + }) + + appListener = server(app, port => { + const req = http.request({ + port, + headers: { + Authorization: ['AWS4-HMAC-SHA256 ...'] + } + }) + + req.end() + }) + }) + it('should skip injecting if the X-Amz-Signature header is set', done => { const app = express() @@ -1037,6 +1093,50 @@ describe('Plugin', () => { }) }) + describe('with config enablePropagationWithAmazonHeaders enabled', () => { + let config + + beforeEach(() => { + config = { + enablePropagationWithAmazonHeaders: true + } + + return agent.load('http', config) + .then(() => { + http = require(pluginToBeLoaded) + express = require('express') + }) + }) + + it('should inject tracing header into AWS signed request', done => { + const app = express() + + app.get('/', (req, res) => { + try { + expect(req.get('x-datadog-trace-id')).to.be.a('string') + expect(req.get('x-datadog-parent-id')).to.be.a('string') + + res.status(200).send() + + done() + } catch (e) { + done(e) + } + }) + + appListener = server(app, port => { + const req = http.request({ + port, + headers: { + Authorization: 'AWS4-HMAC-SHA256 ...' + } + }) + + req.end() + }) + }) + }) + describe('with validateStatus configuration', () => { let config diff --git a/packages/datadog-plugin-http2/test/client.spec.js b/packages/datadog-plugin-http2/test/client.spec.js index 05e95ad651e..f8d44f3ac0b 100644 --- a/packages/datadog-plugin-http2/test/client.spec.js +++ b/packages/datadog-plugin-http2/test/client.spec.js @@ -365,6 +365,70 @@ describe('Plugin', () => { }) }) + it('should skip injecting if the Authorization header contains an AWS signature', done => { + const app = (stream, headers) => { + try { + expect(headers['x-datadog-trace-id']).to.be.undefined + expect(headers['x-datadog-parent-id']).to.be.undefined + + stream.respond({ + ':status': 200 + }) + stream.end() + + done() + } catch (e) { + done(e) + } + } + + appListener = server(app, port => { + const headers = { + Authorization: 'AWS4-HMAC-SHA256 ...' + } + const client = http2 + .connect(`${protocol}://localhost:${port}`) + .on('error', done) + + const req = client.request(headers) + req.on('error', done) + + req.end() + }) + }) + + it('should skip injecting if one of the Authorization headers contains an AWS signature', done => { + const app = (stream, headers) => { + try { + expect(headers['x-datadog-trace-id']).to.be.undefined + expect(headers['x-datadog-parent-id']).to.be.undefined + + stream.respond({ + ':status': 200 + }) + stream.end() + + done() + } catch (e) { + done(e) + } + } + + appListener = server(app, port => { + const headers = { + Authorization: ['AWS4-HMAC-SHA256 ...'] + } + const client = http2 + .connect(`${protocol}://localhost:${port}`) + .on('error', done) + + const req = client.request(headers) + req.on('error', done) + + req.end() + }) + }) + it('should skip injecting if the X-Amz-Signature header is set', done => { const app = (stream, headers) => { try { From 246994e7c3694727759f0005de1def2bbc547d99 Mon Sep 17 00:00:00 2001 From: William Conti Date: Fri, 3 Jan 2025 12:25:59 -0500 Subject: [PATCH 46/52] fix function call --- packages/datadog-plugin-http/src/client.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/datadog-plugin-http/src/client.js b/packages/datadog-plugin-http/src/client.js index be556feed67..8dd23832103 100644 --- a/packages/datadog-plugin-http/src/client.js +++ b/packages/datadog-plugin-http/src/client.js @@ -72,7 +72,7 @@ class HttpClientPlugin extends ClientPlugin { } shouldInjectTraceHeaders (options, uri) { - if (hasAmazonSignature(options) && !this.tracer._hasPropagationStyle('inject', 'xray')) { + if (hasAmazonSignature(options) && !this._config.tracePropagationStyle.inject.includes('xray')) { log.debug( 'AWS Signature detected on HTTP request, skipping injecting headers. To enable header injection' + ' for signed AWS requests, please set DD_TRACE_PROPAGATION_STYLE=["xray", "datadog"]' From 397984ca8173417fa3a9c417972f51969d37991a Mon Sep 17 00:00:00 2001 From: William Conti Date: Fri, 3 Jan 2025 12:32:50 -0500 Subject: [PATCH 47/52] another fix --- packages/datadog-plugin-http/src/client.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/datadog-plugin-http/src/client.js b/packages/datadog-plugin-http/src/client.js index 8dd23832103..5d1230942c5 100644 --- a/packages/datadog-plugin-http/src/client.js +++ b/packages/datadog-plugin-http/src/client.js @@ -72,7 +72,7 @@ class HttpClientPlugin extends ClientPlugin { } shouldInjectTraceHeaders (options, uri) { - if (hasAmazonSignature(options) && !this._config.tracePropagationStyle.inject.includes('xray')) { + if (hasAmazonSignature(options) && !this.config.tracePropagationStyle.inject.includes('xray')) { log.debug( 'AWS Signature detected on HTTP request, skipping injecting headers. To enable header injection' + ' for signed AWS requests, please set DD_TRACE_PROPAGATION_STYLE=["xray", "datadog"]' From ba57fba7e8a1347c45c0fa120e3febef1acfc5e2 Mon Sep 17 00:00:00 2001 From: William Conti Date: Fri, 3 Jan 2025 12:50:48 -0500 Subject: [PATCH 48/52] another fixx --- packages/datadog-plugin-http/src/client.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/datadog-plugin-http/src/client.js b/packages/datadog-plugin-http/src/client.js index 5d1230942c5..b06d1f4de28 100644 --- a/packages/datadog-plugin-http/src/client.js +++ b/packages/datadog-plugin-http/src/client.js @@ -72,7 +72,7 @@ class HttpClientPlugin extends ClientPlugin { } shouldInjectTraceHeaders (options, uri) { - if (hasAmazonSignature(options) && !this.config.tracePropagationStyle.inject.includes('xray')) { + if (hasAmazonSignature(options) && !this.tracer._config.tracePropagationStyle.inject.includes('xray')) { log.debug( 'AWS Signature detected on HTTP request, skipping injecting headers. To enable header injection' + ' for signed AWS requests, please set DD_TRACE_PROPAGATION_STYLE=["xray", "datadog"]' From f2be5c3e2637282b2cc022c211b56a29ad9a8ecb Mon Sep 17 00:00:00 2001 From: William Conti Date: Fri, 3 Jan 2025 13:17:03 -0500 Subject: [PATCH 49/52] remove test --- .../datadog-plugin-http/test/client.spec.js | 44 ------------------- 1 file changed, 44 deletions(-) diff --git a/packages/datadog-plugin-http/test/client.spec.js b/packages/datadog-plugin-http/test/client.spec.js index 42f4c8436f8..c655f2e791c 100644 --- a/packages/datadog-plugin-http/test/client.spec.js +++ b/packages/datadog-plugin-http/test/client.spec.js @@ -1093,50 +1093,6 @@ describe('Plugin', () => { }) }) - describe('with config enablePropagationWithAmazonHeaders enabled', () => { - let config - - beforeEach(() => { - config = { - enablePropagationWithAmazonHeaders: true - } - - return agent.load('http', config) - .then(() => { - http = require(pluginToBeLoaded) - express = require('express') - }) - }) - - it('should inject tracing header into AWS signed request', done => { - const app = express() - - app.get('/', (req, res) => { - try { - expect(req.get('x-datadog-trace-id')).to.be.a('string') - expect(req.get('x-datadog-parent-id')).to.be.a('string') - - res.status(200).send() - - done() - } catch (e) { - done(e) - } - }) - - appListener = server(app, port => { - const req = http.request({ - port, - headers: { - Authorization: 'AWS4-HMAC-SHA256 ...' - } - }) - - req.end() - }) - }) - }) - describe('with validateStatus configuration', () => { let config From 6acb364c67b6436b0f53a2211983aad58aa4719a Mon Sep 17 00:00:00 2001 From: William Conti Date: Mon, 6 Jan 2025 14:11:56 -0500 Subject: [PATCH 50/52] be more careful --- packages/dd-trace/src/opentracing/propagation/text_map.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/dd-trace/src/opentracing/propagation/text_map.js b/packages/dd-trace/src/opentracing/propagation/text_map.js index e54a8fc94f8..ca06d9a634e 100644 --- a/packages/dd-trace/src/opentracing/propagation/text_map.js +++ b/packages/dd-trace/src/opentracing/propagation/text_map.js @@ -855,7 +855,9 @@ class TextMapPropagator { const keyValuePairs = header.split(';') keyValuePairs.forEach(pair => { const [key, value] = pair.split('=') - obj[key.toLowerCase()] = value.toLowerCase() + if (key && value) { + obj[key.toLowerCase()] = value.toLowerCase() + } }) return obj } From 42ba09406888bbea887f7d1168524b0a74d98264 Mon Sep 17 00:00:00 2001 From: William Conti Date: Mon, 6 Jan 2025 14:44:16 -0500 Subject: [PATCH 51/52] fix logging --- packages/dd-trace/src/opentracing/propagation/text_map.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/dd-trace/src/opentracing/propagation/text_map.js b/packages/dd-trace/src/opentracing/propagation/text_map.js index ca06d9a634e..31051454182 100644 --- a/packages/dd-trace/src/opentracing/propagation/text_map.js +++ b/packages/dd-trace/src/opentracing/propagation/text_map.js @@ -34,7 +34,7 @@ const baggageExpr = new RegExp(`^${baggagePrefix}(.+)$`) const tagKeyExpr = /^_dd\.p\.[\x21-\x2b\x2d-\x7e]+$/ // ASCII minus spaces and commas const tagValueExpr = /^[\x20-\x2b\x2d-\x7e]*$/ // ASCII minus commas const ddKeys = [traceKey, spanKey, samplingKey, originKey] -const b3Keys = [b3TraceKey, b3SpanKey, b3ParentKey, b3SampledKey, b3FlagsKey, b3HeaderKey] +const b3Keys = [b3TraceKey, b3SpanKey, b3ParentKey, b3SampledKey, b3FlagsKey, b3HeaderKey, xrayHeaderKey] const logKeys = ddKeys.concat(b3Keys) const traceparentExpr = /^([a-f0-9]{2})-([a-f0-9]{32})-([a-f0-9]{16})-([a-f0-9]{2})(-.*)?$/i const traceparentKey = 'traceparent' From 57d68a7cf67defc920a27faf32099e5f390cea14 Mon Sep 17 00:00:00 2001 From: William Conti Date: Mon, 6 Jan 2025 14:57:17 -0500 Subject: [PATCH 52/52] fix error --- .../src/opentracing/propagation/text_map.js | 23 ++++++++++--------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/packages/dd-trace/src/opentracing/propagation/text_map.js b/packages/dd-trace/src/opentracing/propagation/text_map.js index 31051454182..d6d1f75f8b7 100644 --- a/packages/dd-trace/src/opentracing/propagation/text_map.js +++ b/packages/dd-trace/src/opentracing/propagation/text_map.js @@ -29,6 +29,18 @@ const b3SampledKey = 'x-b3-sampled' const b3FlagsKey = 'x-b3-flags' const b3HeaderKey = 'b3' const sqsdHeaderHey = 'x-aws-sqsd-attr-_datadog' +// AWS X-Ray specific constants +const xrayHeaderKey = 'x-amzn-trace-id' +const xrayRootKey = 'root' +const xrayRootPrefix = '1-' +const xrayParentKey = 'parent' +const xraySampledKey = 'sampled' +const xrayE2EStartTimeKey = 't0' +const xraySelfKey = 'self' +const xrayOriginKey = '_dd.origin' +const xrayDefaultE2EStartTime = '00000000' +const xrayMaxAdditionalBaggageBytes = 256 +// const b3HeaderExpr = /^(([0-9a-f]{16}){1,2}-[0-9a-f]{16}(-[01d](-[0-9a-f]{16})?)?|[01d])$/i const baggageExpr = new RegExp(`^${baggagePrefix}(.+)$`) const tagKeyExpr = /^_dd\.p\.[\x21-\x2b\x2d-\x7e]+$/ // ASCII minus spaces and commas @@ -46,17 +58,6 @@ const tracestateTagKeyFilter = /[^\x21-\x2b\x2d-\x3c\x3e-\x7e]/g const tracestateTagValueFilter = /[^\x20-\x2b\x2d-\x3a\x3c-\x7d]/g const invalidSegment = /^0+$/ const zeroTraceId = '0000000000000000' -// AWS X-Ray specific constants -const xrayHeaderKey = 'x-amzn-trace-id' -const xrayRootKey = 'root' -const xrayRootPrefix = '1-' -const xrayParentKey = 'parent' -const xraySampledKey = 'sampled' -const xrayE2EStartTimeKey = 't0' -const xraySelfKey = 'self' -const xrayOriginKey = '_dd.origin' -const xrayDefaultE2EStartTime = '00000000' -const xrayMaxAdditionalBaggageBytes = 256 class TextMapPropagator { constructor (config) {