Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit c6044ad

Browse files
authored
feat(aws/sqs): add cloud.resource_id as resource tag (#6308)
1 parent 1c6c7ac commit c6044ad

File tree

4 files changed

+209
-10
lines changed

4 files changed

+209
-10
lines changed

packages/datadog-plugin-aws-sdk/src/services/sqs.js

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
const log = require('../../../dd-trace/src/log')
44
const BaseAwsSdkPlugin = require('../base')
55
const { DsmPathwayCodec, getHeadersSize } = require('../../../dd-trace/src/datastreams')
6+
const { extractQueueMetadata } = require('../util')
67

78
class Sqs extends BaseAwsSdkPlugin {
89
static id = 'sqs'
@@ -92,16 +93,18 @@ class Sqs extends BaseAwsSdkPlugin {
9293

9394
generateTags (params, operation, response) {
9495
if (!params || (!params.QueueName && !params.QueueUrl)) return {}
95-
// 'https://sqs.us-east-1.amazonaws.com/123456789012/my-queue';
96-
let queueName = params.QueueName
97-
if (params.QueueUrl) {
98-
queueName = params.QueueUrl.split('/').at(-1)
99-
}
96+
97+
const queueMetadata = extractQueueMetadata(params.QueueUrl)
98+
const queueName = queueMetadata?.queueName || params.QueueName
10099

101100
const tags = {
102101
'resource.name': `${operation} ${params.QueueName || params.QueueUrl}`,
103102
'aws.sqs.queue_name': params.QueueName || params.QueueUrl,
104-
queuename: queueName
103+
queuename: queueName,
104+
}
105+
106+
if (queueMetadata?.arn) {
107+
tags['cloud.resource_id'] = queueMetadata.arn
105108
}
106109

107110
switch (operation) {

packages/datadog-plugin-aws-sdk/src/util.js

Lines changed: 61 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,8 +84,68 @@ const extractPrimaryKeys = (keyNames, keyValuePairs) => {
8484
}
8585
}
8686

87+
/**
88+
* Extracts queue metadata from an SQS queue URL for span tagging.
89+
* Handles modern and legacy AWS endpoint formats, with or without schemes.
90+
* Automatically detects AWS partitions (standard, China, GovCloud) from region.
91+
*
92+
* @param {string} queueURL - SQS queue URL in any supported format
93+
* @returns {Object|null} Object with queueName and arn, or null if URL format is invalid
94+
*
95+
* @example
96+
* // Modern AWS SQS URLs
97+
* extractQueueMetadata('https://sqs.us-east-1.amazonaws.com/123456789012/my-queue')
98+
* // Returns { queueName: 'my-queue', arn: 'arn:aws:sqs:us-east-1:123456789012:my-queue' }
99+
*
100+
* extractQueueMetadata('sqs.eu-west-1.amazonaws.com/123456789012/my-queue') // no scheme
101+
* // Returns { queueName: 'my-queue', arn: 'arn:aws:sqs:eu-west-1:123456789012:my-queue' }
102+
*
103+
* // Legacy AWS SQS URLs
104+
* extractQueueMetadata('https://us-west-2.queue.amazonaws.com/123456789012/legacy-queue')
105+
* // Returns { queueName: 'legacy-queue', arn: 'arn:aws:sqs:us-west-2:123456789012:legacy-queue' }
106+
*
107+
* extractQueueMetadata('https://queue.amazonaws.com/123456789012/global-legacy-queue')
108+
* // Returns { queueName: 'global-legacy-queue', arn: 'arn:aws:sqs:us-east-1:123456789012:global-legacy-queue' }
109+
*/
110+
const extractQueueMetadata = queueURL => {
111+
if (!queueURL) {
112+
return null
113+
}
114+
115+
const parts = queueURL.split('/').filter(Boolean)
116+
117+
// Check if URL has scheme
118+
const hasScheme = Boolean(parts[0]?.startsWith('http'))
119+
const minParts = hasScheme ? 4 : 3
120+
121+
if (parts.length < minParts) return null
122+
123+
const accountId = parts[parts.length - 2]
124+
const queueName = parts[parts.length - 1]
125+
const host = hasScheme ? parts[1] : parts[0]
126+
127+
let region = 'us-east-1' // Default region if not found in URL
128+
if (host.includes('.amazonaws.com') && !host.startsWith('queue')) {
129+
// sqs.{region}.amazonaws.com or {region}.queue.amazonaws.com
130+
const startFrom = host.startsWith('sqs.') ? 4 : 0
131+
const nextDot = host.indexOf('.', startFrom)
132+
region = host.slice(startFrom, nextDot)
133+
}
134+
135+
let partition = 'aws'
136+
if (region.startsWith('cn-')) {
137+
partition = 'aws-cn'
138+
} else if (region.startsWith('us-gov')) {
139+
partition = 'aws-us-gov'
140+
}
141+
142+
const arn = `arn:${partition}:sqs:${region}:${accountId}:${queueName}`
143+
return { queueName, arn }
144+
}
145+
87146
module.exports = {
88147
generatePointerHash,
89148
encodeValue,
90-
extractPrimaryKeys
149+
extractPrimaryKeys,
150+
extractQueueMetadata
91151
}

packages/datadog-plugin-aws-sdk/test/sqs.spec.js

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -150,7 +150,8 @@ describe('Plugin', () => {
150150

151151
expect(span.resource.startsWith('sendMessage')).to.equal(true)
152152
expect(span.meta).to.include({
153-
queuename: queueName
153+
queuename: queueName,
154+
'cloud.resource_id': `arn:aws:sqs:us-east-1:00000000000000000000:${queueName}`
154155
})
155156

156157
parentId = span.span_id.toString()
@@ -200,7 +201,8 @@ describe('Plugin', () => {
200201

201202
expect(span.resource.startsWith('sendMessageBatch')).to.equal(true)
202203
expect(span.meta).to.include({
203-
queuename: queueName
204+
queuename: queueName,
205+
'cloud.resource_id': `arn:aws:sqs:us-east-1:00000000000000000000:${queueName}`
204206
})
205207

206208
parentId = span.span_id.toString()
@@ -373,6 +375,7 @@ describe('Plugin', () => {
373375

374376
expect(span.meta).to.include({
375377
queuename: queueName,
378+
'cloud.resource_id': `arn:aws:sqs:us-east-1:00000000000000000000:${queueName}`,
376379
aws_service: 'SQS',
377380
region: 'us-east-1'
378381
})

packages/datadog-plugin-aws-sdk/test/util.spec.js

Lines changed: 134 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ const { describe, it } = require('mocha')
55

66
const { Buffer } = require('node:buffer')
77

8-
const { generatePointerHash, encodeValue, extractPrimaryKeys } = require('../src/util')
8+
const { generatePointerHash, encodeValue, extractPrimaryKeys, extractQueueMetadata } = require('../src/util')
99

1010
describe('generatePointerHash', () => {
1111
describe('should generate a valid hash for S3 object with', () => {
@@ -218,3 +218,136 @@ describe('extractPrimaryKeys', () => {
218218
})
219219
})
220220
})
221+
222+
describe('extractQueueMetadata', () => {
223+
describe('standard AWS SQS URLs', () => {
224+
it('handles standard AWS SQS URL', () => {
225+
const result = extractQueueMetadata('https://sqs.eu-west-1.amazonaws.com/987654321098/test-queue')
226+
expect(result).to.deep.equal({
227+
queueName: 'test-queue',
228+
arn: 'arn:aws:sqs:eu-west-1:987654321098:test-queue'
229+
})
230+
})
231+
232+
it('handles AWS China region', () => {
233+
const result = extractQueueMetadata('https://sqs.cn-north-1.amazonaws.com.cn/123456789012/china-queue')
234+
expect(result).to.deep.equal({
235+
queueName: 'china-queue',
236+
arn: 'arn:aws-cn:sqs:cn-north-1:123456789012:china-queue'
237+
})
238+
})
239+
240+
it('handles AWS GovCloud region', () => {
241+
const result = extractQueueMetadata('https://sqs.us-gov-west-1.amazonaws.com/123456789012/gov-queue')
242+
expect(result).to.deep.equal({
243+
queueName: 'gov-queue',
244+
arn: 'arn:aws-us-gov:sqs:us-gov-west-1:123456789012:gov-queue'
245+
})
246+
})
247+
248+
it('handles queue name with special characters', () => {
249+
const result = extractQueueMetadata('https://sqs.us-west-2.amazonaws.com/123456789012/my-queue-test_123')
250+
expect(result).to.deep.equal({
251+
queueName: 'my-queue-test_123',
252+
arn: 'arn:aws:sqs:us-west-2:123456789012:my-queue-test_123'
253+
})
254+
})
255+
})
256+
257+
describe('LocalStack URLs', () => {
258+
it('handles LocalStack URL with default port', () => {
259+
const result = extractQueueMetadata('http://localhost:4566/000000000000/local-queue')
260+
expect(result).to.deep.equal({
261+
queueName: 'local-queue',
262+
arn: 'arn:aws:sqs:us-east-1:000000000000:local-queue'
263+
})
264+
})
265+
266+
it('handles LocalStack URL with custom port', () => {
267+
const result = extractQueueMetadata('http://127.0.0.1:9324/123456789012/dev-queue')
268+
expect(result).to.deep.equal({
269+
queueName: 'dev-queue',
270+
arn: 'arn:aws:sqs:us-east-1:123456789012:dev-queue'
271+
})
272+
})
273+
})
274+
275+
describe('legacy AWS SQS URLs', () => {
276+
it('handles regional legacy format', () => {
277+
const result = extractQueueMetadata('https://us-west-2.queue.amazonaws.com/123456789012/legacy-queue')
278+
expect(result).to.deep.equal({
279+
queueName: 'legacy-queue',
280+
arn: 'arn:aws:sqs:us-west-2:123456789012:legacy-queue'
281+
})
282+
})
283+
284+
it('handles global legacy format', () => {
285+
const result = extractQueueMetadata('https://queue.amazonaws.com/123456789012/global-legacy-queue')
286+
expect(result).to.deep.equal({
287+
queueName: 'global-legacy-queue',
288+
arn: 'arn:aws:sqs:us-east-1:123456789012:global-legacy-queue'
289+
})
290+
})
291+
292+
it('handles legacy format without scheme', () => {
293+
const result = extractQueueMetadata('eu-central-1.queue.amazonaws.com/987654321098/no-scheme-legacy')
294+
expect(result).to.deep.equal({
295+
queueName: 'no-scheme-legacy',
296+
arn: 'arn:aws:sqs:eu-central-1:987654321098:no-scheme-legacy'
297+
})
298+
})
299+
})
300+
301+
describe('URLs without schemes', () => {
302+
it('handles modern format without scheme', () => {
303+
const result = extractQueueMetadata('sqs.eu-west-1.amazonaws.com/123456789012/no-scheme-queue')
304+
expect(result).to.deep.equal({
305+
queueName: 'no-scheme-queue',
306+
arn: 'arn:aws:sqs:eu-west-1:123456789012:no-scheme-queue'
307+
})
308+
})
309+
310+
it('handles localstack without scheme', () => {
311+
const result = extractQueueMetadata('localhost:4566/000000000000/local-no-scheme')
312+
expect(result).to.deep.equal({
313+
queueName: 'local-no-scheme',
314+
arn: 'arn:aws:sqs:us-east-1:000000000000:local-no-scheme'
315+
})
316+
})
317+
})
318+
319+
describe('edge cases', () => {
320+
it('returns null for invalid URL with insufficient parts', () => {
321+
const result = extractQueueMetadata('https://sqs.us-east-1.amazonaws.com/incomplete')
322+
expect(result).to.be.null
323+
})
324+
325+
it('returns null for completely malformed URL', () => {
326+
const result = extractQueueMetadata('not-a-valid-url')
327+
expect(result).to.be.null
328+
})
329+
330+
it('returns null for empty string', () => {
331+
const result = extractQueueMetadata('')
332+
expect(result).to.be.null
333+
})
334+
335+
it('returns null for null input', () => {
336+
const result = extractQueueMetadata(null)
337+
expect(result).to.be.null
338+
})
339+
340+
it('returns null for undefined input', () => {
341+
const result = extractQueueMetadata(undefined)
342+
expect(result).to.be.null
343+
})
344+
345+
it('handles URL with trailing slash', () => {
346+
const result = extractQueueMetadata('https://sqs.us-west-2.amazonaws.com/123456789012/my-queue/')
347+
expect(result).to.deep.equal({
348+
queueName: 'my-queue',
349+
arn: 'arn:aws:sqs:us-west-2:123456789012:my-queue'
350+
})
351+
})
352+
})
353+
})

0 commit comments

Comments
 (0)