dd-trace 5.65.0 → 5.67.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/index.d.ts +38 -0
- package/package.json +8 -8
- package/packages/datadog-instrumentations/src/express.js +3 -7
- package/packages/datadog-instrumentations/src/graphql.js +10 -6
- package/packages/datadog-instrumentations/src/helpers/hooks.js +2 -1
- package/packages/datadog-instrumentations/src/helpers/register.js +10 -2
- package/packages/datadog-instrumentations/src/playwright.js +25 -9
- package/packages/datadog-instrumentations/src/prisma.js +8 -10
- package/packages/datadog-instrumentations/src/ws.js +136 -0
- package/packages/datadog-plugin-aws-sdk/src/services/bedrockruntime/utils.js +30 -3
- package/packages/datadog-plugin-aws-sdk/src/services/sqs.js +9 -6
- package/packages/datadog-plugin-aws-sdk/src/util.js +61 -1
- package/packages/datadog-plugin-express/src/code_origin.js +11 -0
- package/packages/datadog-plugin-graphql/src/index.js +3 -0
- package/packages/datadog-plugin-ws/src/close.js +69 -0
- package/packages/datadog-plugin-ws/src/index.js +26 -0
- package/packages/datadog-plugin-ws/src/producer.js +60 -0
- package/packages/datadog-plugin-ws/src/receiver.js +70 -0
- package/packages/datadog-plugin-ws/src/server.js +79 -0
- package/packages/datadog-shimmer/src/shimmer.js +11 -2
- package/packages/dd-trace/src/appsec/blocking.js +29 -0
- package/packages/dd-trace/src/appsec/channels.js +4 -2
- package/packages/dd-trace/src/appsec/index.js +7 -2
- package/packages/dd-trace/src/appsec/rasp/fs-plugin.js +1 -0
- package/packages/dd-trace/src/appsec/rasp/index.js +25 -7
- package/packages/dd-trace/src/appsec/rasp/lfi.js +1 -1
- package/packages/dd-trace/src/appsec/rasp/utils.js +13 -2
- package/packages/dd-trace/src/config.js +12 -0
- package/packages/dd-trace/src/guardrails/index.js +11 -3
- package/packages/dd-trace/src/guardrails/telemetry.js +15 -16
- package/packages/dd-trace/src/llmobs/index.js +7 -0
- package/packages/dd-trace/src/llmobs/sdk.js +28 -0
- package/packages/dd-trace/src/llmobs/span_processor.js +124 -28
- package/packages/dd-trace/src/llmobs/tagger.js +2 -1
- package/packages/dd-trace/src/llmobs/telemetry.js +7 -1
- package/packages/dd-trace/src/log/writer.js +1 -1
- package/packages/dd-trace/src/plugin_manager.js +8 -2
- package/packages/dd-trace/src/plugins/index.js +2 -1
- package/packages/dd-trace/src/plugins/util/ip_extractor.js +48 -45
- package/packages/dd-trace/src/profiling/profilers/wall.js +9 -3
- package/packages/dd-trace/src/service-naming/schemas/v0/index.js +2 -1
- package/packages/dd-trace/src/service-naming/schemas/v0/websocket.js +30 -0
- package/packages/dd-trace/src/service-naming/schemas/v1/index.js +2 -1
- package/packages/dd-trace/src/service-naming/schemas/v1/websocket.js +30 -0
- package/packages/dd-trace/src/supported-configurations.json +3 -0
package/index.d.ts
CHANGED
|
@@ -2566,6 +2566,25 @@ declare namespace tracer {
|
|
|
2566
2566
|
annotate (options: llmobs.AnnotationOptions): void
|
|
2567
2567
|
annotate (span: tracer.Span | undefined, options: llmobs.AnnotationOptions): void
|
|
2568
2568
|
|
|
2569
|
+
/**
|
|
2570
|
+
* Register a processor to be called on each LLMObs span.
|
|
2571
|
+
*
|
|
2572
|
+
* This can be used to modify the span before it is sent to LLMObs. For example, you can modify the input/output.
|
|
2573
|
+
* You can also return `null` to omit the span entirely from being sent to LLM Observability.
|
|
2574
|
+
*
|
|
2575
|
+
* Otherwise, if the return value from the processor is not an instance of `LLMObservabilitySpan`, the span will be dropped.
|
|
2576
|
+
*
|
|
2577
|
+
* To deregister the processor, call `llmobs.deregisterProcessor()`
|
|
2578
|
+
* @param processor A function that will be called for each span.
|
|
2579
|
+
* @throws {Error} If a processor is already registered.
|
|
2580
|
+
*/
|
|
2581
|
+
registerProcessor (processor: ((span: LLMObservabilitySpan) => LLMObservabilitySpan | null)): void
|
|
2582
|
+
|
|
2583
|
+
/**
|
|
2584
|
+
* Deregister a processor.
|
|
2585
|
+
*/
|
|
2586
|
+
deregisterProcessor (): void
|
|
2587
|
+
|
|
2569
2588
|
/**
|
|
2570
2589
|
* Submits a custom evaluation metric for a given span ID and trace ID.
|
|
2571
2590
|
* @param spanContext The span context of the span to submit the evaluation metric for.
|
|
@@ -2579,6 +2598,25 @@ declare namespace tracer {
|
|
|
2579
2598
|
flush (): void
|
|
2580
2599
|
}
|
|
2581
2600
|
|
|
2601
|
+
interface LLMObservabilitySpan {
|
|
2602
|
+
/**
|
|
2603
|
+
* The input content associated with the span.
|
|
2604
|
+
*/
|
|
2605
|
+
input: { content: string, role?: string }[]
|
|
2606
|
+
|
|
2607
|
+
/**
|
|
2608
|
+
* The output content associated with the span.
|
|
2609
|
+
*/
|
|
2610
|
+
output: { content: string, role?: string }[]
|
|
2611
|
+
|
|
2612
|
+
/**
|
|
2613
|
+
* Get a tag from the span.
|
|
2614
|
+
* @param key The key of the tag to get.
|
|
2615
|
+
* @returns The value of the tag, or `undefined` if the tag does not exist.
|
|
2616
|
+
*/
|
|
2617
|
+
getTag (key: string): string | undefined
|
|
2618
|
+
}
|
|
2619
|
+
|
|
2582
2620
|
interface EvaluationOptions {
|
|
2583
2621
|
/**
|
|
2584
2622
|
* The name of the evaluation metric
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dd-trace",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.67.0",
|
|
4
4
|
"description": "Datadog APM tracing client for JavaScript",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"typings": "index.d.ts",
|
|
@@ -12,8 +12,8 @@
|
|
|
12
12
|
"dependencies:dedupe": "yarn-deduplicate yarn.lock",
|
|
13
13
|
"type:doc": "cd docs && yarn && yarn build",
|
|
14
14
|
"type:test": "cd docs && yarn && yarn test",
|
|
15
|
-
"lint": "node scripts/check_licenses.js && eslint . --max-warnings 0",
|
|
16
|
-
"lint:fix": "node scripts/check_licenses.js && eslint . --max-warnings 0 --fix",
|
|
15
|
+
"lint": "node scripts/check_licenses.js && eslint . --concurrency=auto --max-warnings 0",
|
|
16
|
+
"lint:fix": "node scripts/check_licenses.js && eslint . --concurrency=auto --max-warnings 0 --fix",
|
|
17
17
|
"lint:inspect": "npx @eslint/config-inspector@latest",
|
|
18
18
|
"release:proposal": "node scripts/release/proposal",
|
|
19
19
|
"services": "node ./scripts/install_plugin_modules && node packages/dd-trace/test/setup/services",
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
"test:debugger": "mocha -r 'packages/dd-trace/test/setup/mocha.js' 'packages/dd-trace/test/debugger/**/*.spec.js'",
|
|
26
26
|
"test:debugger:ci": "nyc --no-clean --include 'packages/dd-trace/src/debugger/**/*.js' -- npm run test:debugger",
|
|
27
27
|
"test:eslint-rules": "node eslint-rules/*.test.mjs",
|
|
28
|
-
"test:trace:core": "tap packages/dd-trace/test/*.spec.js \"packages/dd-trace/test/{ci-visibility,datastreams,encode,exporters,opentelemetry,opentracing,plugins,remote_config,service-naming,standalone,telemetry}/**/*.spec.js\"",
|
|
28
|
+
"test:trace:core": "tap packages/dd-trace/test/*.spec.js \"packages/dd-trace/test/{ci-visibility,datastreams,encode,exporters,opentelemetry,opentracing,plugins,remote_config,service-naming,standalone,telemetry,external-logger}/**/*.spec.js\"",
|
|
29
29
|
"test:trace:core:ci": "npm run test:trace:core -- --coverage --nyc-arg=--include=\"packages/dd-trace/src/**/*.js\"",
|
|
30
30
|
"test:trace:guardrails": "mocha -r \"packages/dd-trace/test/setup/mocha.js\" \"packages/dd-trace/test/guardrails/**/*.spec.js\"",
|
|
31
31
|
"test:trace:guardrails:ci": "nyc --no-clean --include \"packages/dd-trace/src/guardrails/**/*.js\" -- npm run test:trace:guardrails",
|
|
@@ -114,10 +114,10 @@
|
|
|
114
114
|
],
|
|
115
115
|
"dependencies": {
|
|
116
116
|
"@datadog/libdatadog": "0.7.0",
|
|
117
|
-
"@datadog/native-appsec": "10.1
|
|
117
|
+
"@datadog/native-appsec": "10.2.1",
|
|
118
118
|
"@datadog/native-iast-taint-tracking": "4.0.0",
|
|
119
119
|
"@datadog/native-metrics": "3.1.1",
|
|
120
|
-
"@datadog/pprof": "5.
|
|
120
|
+
"@datadog/pprof": "5.10.0",
|
|
121
121
|
"@datadog/sketches-js": "2.1.1",
|
|
122
122
|
"@datadog/wasm-js-rewriter": "4.0.1",
|
|
123
123
|
"@isaacs/ttlcache": "^1.4.1",
|
|
@@ -138,7 +138,7 @@
|
|
|
138
138
|
"mutexify": "^1.4.0",
|
|
139
139
|
"opentracing": ">=0.14.7",
|
|
140
140
|
"path-to-regexp": "^0.1.12",
|
|
141
|
-
"pprof-format": "^2.1.
|
|
141
|
+
"pprof-format": "^2.1.1",
|
|
142
142
|
"protobufjs": "^7.5.3",
|
|
143
143
|
"retry": "^0.13.1",
|
|
144
144
|
"rfdc": "^1.4.1",
|
|
@@ -177,7 +177,7 @@
|
|
|
177
177
|
"nock": "^13.5.6",
|
|
178
178
|
"nyc": "^15.1.0",
|
|
179
179
|
"octokit": "^5.0.3",
|
|
180
|
-
"proxyquire": "^1.
|
|
180
|
+
"proxyquire": "^2.1.3",
|
|
181
181
|
"rimraf": "^3.0.2",
|
|
182
182
|
"semver": "^7.7.2",
|
|
183
183
|
"sinon": "^18.0.1",
|
|
@@ -67,6 +67,9 @@ addHook({ name: 'express', versions: ['>=4'] }, express => {
|
|
|
67
67
|
return express
|
|
68
68
|
})
|
|
69
69
|
|
|
70
|
+
// Express 5 does not rely on router in the same way as v4 and should not be instrumented anymore.
|
|
71
|
+
// It would otherwise produce spans for router and express, and so duplicating them.
|
|
72
|
+
// We now fall back to router instrumentation
|
|
70
73
|
addHook({ name: 'express', versions: ['4'] }, express => {
|
|
71
74
|
shimmer.wrap(express.Router, 'use', wrapRouterMethod)
|
|
72
75
|
shimmer.wrap(express.Router, 'route', wrapRouterMethod)
|
|
@@ -74,13 +77,6 @@ addHook({ name: 'express', versions: ['4'] }, express => {
|
|
|
74
77
|
return express
|
|
75
78
|
})
|
|
76
79
|
|
|
77
|
-
addHook({ name: 'express', versions: ['>=5.0.0'] }, express => {
|
|
78
|
-
shimmer.wrap(express.Router.prototype, 'use', wrapRouterMethod)
|
|
79
|
-
shimmer.wrap(express.Router.prototype, 'route', wrapRouterMethod)
|
|
80
|
-
|
|
81
|
-
return express
|
|
82
|
-
})
|
|
83
|
-
|
|
84
80
|
const queryParserReadCh = channel('datadog:query:read:finish')
|
|
85
81
|
|
|
86
82
|
function publishQueryParsedAndNext (req, res, next) {
|
|
@@ -235,14 +235,18 @@ function callInAsyncScope (fn, thisArg, args, abortController, cb) {
|
|
|
235
235
|
try {
|
|
236
236
|
const result = fn.apply(thisArg, args)
|
|
237
237
|
if (result && typeof result.then === 'function') {
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
238
|
+
return result.then(
|
|
239
|
+
res => {
|
|
240
|
+
cb(null, res)
|
|
241
|
+
return res
|
|
242
|
+
},
|
|
243
|
+
err => {
|
|
244
|
+
cb(err)
|
|
245
|
+
throw err
|
|
246
|
+
}
|
|
242
247
|
)
|
|
243
|
-
} else {
|
|
244
|
-
cb(null, result)
|
|
245
248
|
}
|
|
249
|
+
cb(null, result)
|
|
246
250
|
return result
|
|
247
251
|
} catch (err) {
|
|
248
252
|
cb(err)
|
|
@@ -146,7 +146,11 @@ for (const packageName of names) {
|
|
|
146
146
|
`error_type:${e.constructor.name}`,
|
|
147
147
|
`integration:${name}`,
|
|
148
148
|
`integration_version:${version}`
|
|
149
|
-
]
|
|
149
|
+
], {
|
|
150
|
+
result: 'error',
|
|
151
|
+
result_class: 'internal_error',
|
|
152
|
+
result_reason: `Error during instrumentation of ${name}@${version}: ${e.message}`
|
|
153
|
+
})
|
|
150
154
|
}
|
|
151
155
|
namesAndSuccesses[`${name}@${version}`] = true
|
|
152
156
|
}
|
|
@@ -160,7 +164,11 @@ for (const packageName of names) {
|
|
|
160
164
|
telemetry('abort.integration', [
|
|
161
165
|
`integration:${name}`,
|
|
162
166
|
`integration_version:${version}`
|
|
163
|
-
]
|
|
167
|
+
], {
|
|
168
|
+
result: 'abort',
|
|
169
|
+
result_class: 'incompatible_library',
|
|
170
|
+
result_reason: `Incompatible integration version: ${name}@${version}`
|
|
171
|
+
})
|
|
164
172
|
log.info('Found incompatible integration version: %s', nameVersion)
|
|
165
173
|
seenCombo.add(nameVersion)
|
|
166
174
|
}
|
|
@@ -60,9 +60,15 @@ let testManagementTests = {}
|
|
|
60
60
|
let isImpactedTestsEnabled = false
|
|
61
61
|
let modifiedTests = {}
|
|
62
62
|
const quarantinedOrDisabledTestsAttemptToFix = []
|
|
63
|
+
let quarantinedButNotAttemptToFixFqns = new Set()
|
|
63
64
|
let rootDir = ''
|
|
64
65
|
const MINIMUM_SUPPORTED_VERSION_RANGE_EFD = '>=1.38.0' // TODO: remove this once we drop support for v5
|
|
65
66
|
|
|
67
|
+
function getTestFullyQualifiedName (test) {
|
|
68
|
+
const fullname = getTestFullname(test)
|
|
69
|
+
return `${test._requireFile} ${fullname}`
|
|
70
|
+
}
|
|
71
|
+
|
|
66
72
|
function getTestProperties (test) {
|
|
67
73
|
const testName = getTestFullname(test)
|
|
68
74
|
const testSuite = getTestSuitePath(test._requireFile, rootDir)
|
|
@@ -327,8 +333,7 @@ function testEndHandler (test, annotations, testStatus, error, isTimeout, isMain
|
|
|
327
333
|
return
|
|
328
334
|
}
|
|
329
335
|
|
|
330
|
-
const
|
|
331
|
-
const testFqn = `${testSuiteAbsolutePath} ${testFullName}`
|
|
336
|
+
const testFqn = getTestFullyQualifiedName(test)
|
|
332
337
|
const testStatuses = testsToTestStatuses.get(testFqn) || []
|
|
333
338
|
|
|
334
339
|
if (testStatuses.length === 0) {
|
|
@@ -618,19 +623,25 @@ function runAllTestsWrapper (runAllTests, playwrightVersion) {
|
|
|
618
623
|
if (isTestManagementTestsEnabled && sessionStatus === 'failed') {
|
|
619
624
|
let totalFailedTestCount = 0
|
|
620
625
|
let totalAttemptToFixFailedTestCount = 0
|
|
626
|
+
let totalPureQuarantinedFailedTestCount = 0
|
|
621
627
|
|
|
622
|
-
for (const testStatuses of testsToTestStatuses.
|
|
623
|
-
|
|
628
|
+
for (const [fqn, testStatuses] of testsToTestStatuses.entries()) {
|
|
629
|
+
const failedCount = testStatuses.filter(status => status === 'fail').length
|
|
630
|
+
totalFailedTestCount += failedCount
|
|
631
|
+
if (quarantinedButNotAttemptToFixFqns.has(fqn)) {
|
|
632
|
+
totalPureQuarantinedFailedTestCount += failedCount
|
|
633
|
+
}
|
|
624
634
|
}
|
|
625
635
|
|
|
626
636
|
for (const test of quarantinedOrDisabledTestsAttemptToFix) {
|
|
627
|
-
const
|
|
628
|
-
const
|
|
629
|
-
const testStatuses = testsToTestStatuses.get(fqn)
|
|
637
|
+
const testFqn = getTestFullyQualifiedName(test)
|
|
638
|
+
const testStatuses = testsToTestStatuses.get(testFqn)
|
|
630
639
|
totalAttemptToFixFailedTestCount += testStatuses.filter(status => status === 'fail').length
|
|
631
640
|
}
|
|
632
641
|
|
|
633
|
-
|
|
642
|
+
const totalIgnorableFailures = totalAttemptToFixFailedTestCount + totalPureQuarantinedFailedTestCount
|
|
643
|
+
|
|
644
|
+
if (totalFailedTestCount > 0 && totalFailedTestCount === totalIgnorableFailures) {
|
|
634
645
|
runAllTestsReturn = 'passed'
|
|
635
646
|
}
|
|
636
647
|
}
|
|
@@ -648,6 +659,7 @@ function runAllTestsWrapper (runAllTests, playwrightVersion) {
|
|
|
648
659
|
|
|
649
660
|
startedSuites = []
|
|
650
661
|
remainingTestsByFile = {}
|
|
662
|
+
quarantinedButNotAttemptToFixFqns = new Set()
|
|
651
663
|
|
|
652
664
|
// TODO: we can trick playwright into thinking the session passed by returning
|
|
653
665
|
// 'passed' here. We might be able to use this for both EFD and Test Management tests.
|
|
@@ -776,8 +788,12 @@ addHook({
|
|
|
776
788
|
if (testProperties.disabled || testProperties.quarantined) {
|
|
777
789
|
quarantinedOrDisabledTestsAttemptToFix.push(test)
|
|
778
790
|
}
|
|
779
|
-
} else if (testProperties.disabled
|
|
791
|
+
} else if (testProperties.disabled) {
|
|
780
792
|
test.expectedStatus = 'skipped'
|
|
793
|
+
} else if (testProperties.quarantined) {
|
|
794
|
+
// Do not skip quarantined tests, let them run and overwrite results post-run if they fail
|
|
795
|
+
const testFqn = getTestFullyQualifiedName(test)
|
|
796
|
+
quarantinedButNotAttemptToFixFqns.add(testFqn)
|
|
781
797
|
}
|
|
782
798
|
}
|
|
783
799
|
}
|
|
@@ -66,23 +66,21 @@ class TracingHelper {
|
|
|
66
66
|
addHook({ name: '@prisma/client', versions: ['>=6.1.0'] }, (prisma, version) => {
|
|
67
67
|
const tracingHelper = new TracingHelper()
|
|
68
68
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
const datasources = this._engine?.config.inlineDatasources?.db.url?.value
|
|
69
|
+
// we need to patch the prototype to get db config since this works for ESM and CJS alike.
|
|
70
|
+
const originalRequest = prisma.PrismaClient.prototype._request
|
|
71
|
+
prisma.PrismaClient.prototype._request = function () {
|
|
72
|
+
if (!tracingHelper.dbConfig) {
|
|
73
|
+
const inlineDatasources = this._engine?.config.inlineDatasources
|
|
74
|
+
const overrideDatasources = this._engine?.config.overrideDatasources
|
|
75
|
+
const datasources = inlineDatasources?.db.url?.value ?? overrideDatasources?.db?.url
|
|
78
76
|
if (datasources) {
|
|
79
77
|
const result = parseDBString(datasources)
|
|
80
78
|
tracingHelper.setDbString(result)
|
|
81
79
|
}
|
|
82
80
|
}
|
|
81
|
+
return originalRequest.apply(this, arguments)
|
|
83
82
|
}
|
|
84
83
|
|
|
85
|
-
prisma.PrismaClient = PrismaClient
|
|
86
84
|
/*
|
|
87
85
|
* This is taking advantage of the built in tracing support from Prisma.
|
|
88
86
|
* The below variable is setting a global tracing helper that Prisma uses
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const {
|
|
4
|
+
addHook,
|
|
5
|
+
channel
|
|
6
|
+
} = require('./helpers/instrument')
|
|
7
|
+
const shimmer = require('../../datadog-shimmer')
|
|
8
|
+
|
|
9
|
+
const tracingChannel = require('dc-polyfill').tracingChannel
|
|
10
|
+
const serverCh = tracingChannel('ws:server:connect')
|
|
11
|
+
const producerCh = tracingChannel('ws:send')
|
|
12
|
+
const receiverCh = tracingChannel('ws:receive')
|
|
13
|
+
const closeCh = tracingChannel('ws:close')
|
|
14
|
+
const emitCh = channel('tracing:ws:server:connect:emit')
|
|
15
|
+
|
|
16
|
+
function wrapHandleUpgrade (handleUpgrade) {
|
|
17
|
+
return function () {
|
|
18
|
+
const [req, socket, , cb] = arguments
|
|
19
|
+
if (!serverCh.start.hasSubscribers || typeof cb !== 'function') {
|
|
20
|
+
return handleUpgrade.apply(this, arguments)
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const ctx = { req, socket }
|
|
24
|
+
|
|
25
|
+
arguments[3] = function () {
|
|
26
|
+
return serverCh.asyncStart.runStores(ctx, () => {
|
|
27
|
+
try {
|
|
28
|
+
return cb.apply(this, arguments)
|
|
29
|
+
} finally {
|
|
30
|
+
serverCh.asyncEnd.publish(ctx)
|
|
31
|
+
}
|
|
32
|
+
}, this, ...arguments)
|
|
33
|
+
}
|
|
34
|
+
return serverCh.traceSync(handleUpgrade, ctx, this, ...arguments)
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function wrapSend (send) {
|
|
39
|
+
return function wrappedSend (...args) {
|
|
40
|
+
if (!producerCh.start.hasSubscribers) return send.apply(this, arguments)
|
|
41
|
+
|
|
42
|
+
const [data, options, cb] = arguments
|
|
43
|
+
|
|
44
|
+
const ctx = { data, socket: this._sender._socket }
|
|
45
|
+
|
|
46
|
+
return typeof cb === 'function'
|
|
47
|
+
? producerCh.traceCallback(send, undefined, ctx, this, data, options, cb)
|
|
48
|
+
: producerCh.traceSync(send, ctx, this, data, options, cb)
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function createWrapEmit (emit) {
|
|
53
|
+
return function (title, headers, req) {
|
|
54
|
+
if (!serverCh.start.hasSubscribers || title !== 'headers') return emit.apply(this, arguments)
|
|
55
|
+
|
|
56
|
+
const ctx = { req }
|
|
57
|
+
ctx.req.resStatus = headers[0].split(' ')[1]
|
|
58
|
+
|
|
59
|
+
emitCh.runStores(ctx, () => {
|
|
60
|
+
try {
|
|
61
|
+
return emit.apply(this, arguments)
|
|
62
|
+
} finally {
|
|
63
|
+
emitCh.publish(ctx)
|
|
64
|
+
}
|
|
65
|
+
})
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function createWrappedHandler (handler) {
|
|
70
|
+
return function wrappedMessageHandler (data, binary) {
|
|
71
|
+
const byteLength = dataLength(data)
|
|
72
|
+
|
|
73
|
+
const ctx = { data, binary, socket: this._sender._socket, byteLength }
|
|
74
|
+
|
|
75
|
+
return receiverCh.traceSync(handler, ctx, this, data, binary)
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function wrapListener (originalOn) {
|
|
80
|
+
return function (eventName, handler) {
|
|
81
|
+
if (eventName === 'message') {
|
|
82
|
+
return originalOn.call(this, eventName, createWrappedHandler(handler))
|
|
83
|
+
}
|
|
84
|
+
return originalOn.apply(this, arguments)
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function wrapClose (close) {
|
|
89
|
+
return function (code, data) {
|
|
90
|
+
// _closeFrameReceived is set to true when receiver receives a close frame from a peer
|
|
91
|
+
// _closeFrameSent is set to true when a close frame is sent
|
|
92
|
+
// in the case that a close frame is received and not yet sent then connection is closed by peer
|
|
93
|
+
// if both are true then the self is sending the close event
|
|
94
|
+
const isPeerClose = this._closeFrameReceived === true && this._closeFrameSent === false
|
|
95
|
+
|
|
96
|
+
const ctx = { code, data, socket: this._sender._socket, isPeerClose }
|
|
97
|
+
|
|
98
|
+
return closeCh.traceSync(close, ctx, this, ...arguments)
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
addHook({
|
|
103
|
+
name: 'ws',
|
|
104
|
+
file: 'lib/websocket-server.js',
|
|
105
|
+
versions: ['>=8.0.0']
|
|
106
|
+
}, ws => {
|
|
107
|
+
shimmer.wrap(ws.prototype, 'handleUpgrade', wrapHandleUpgrade)
|
|
108
|
+
shimmer.wrap(ws.prototype, 'emit', createWrapEmit)
|
|
109
|
+
return ws
|
|
110
|
+
})
|
|
111
|
+
|
|
112
|
+
addHook({
|
|
113
|
+
name: 'ws',
|
|
114
|
+
file: 'lib/websocket.js',
|
|
115
|
+
versions: ['>=8.0.0']
|
|
116
|
+
}, ws => {
|
|
117
|
+
shimmer.wrap(ws.prototype, 'send', wrapSend)
|
|
118
|
+
shimmer.wrap(ws.prototype, 'on', wrapListener)
|
|
119
|
+
shimmer.wrap(ws.prototype, 'close', wrapClose)
|
|
120
|
+
return ws
|
|
121
|
+
})
|
|
122
|
+
|
|
123
|
+
function detectType (data) {
|
|
124
|
+
if (typeof Blob !== 'undefined' && data instanceof Blob) return 'Blob'
|
|
125
|
+
if (typeof Buffer !== 'undefined' && Buffer.isBuffer(data)) return 'Buffer'
|
|
126
|
+
if (typeof data === 'string') return 'string'
|
|
127
|
+
return 'Unknown'
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function dataLength (data) {
|
|
131
|
+
const type = detectType(data)
|
|
132
|
+
if (type === 'Blob') return data.size
|
|
133
|
+
if (type === 'Buffer') return data.length
|
|
134
|
+
if (type === 'string') return Buffer.byteLength(data)
|
|
135
|
+
return 0
|
|
136
|
+
}
|
|
@@ -149,12 +149,24 @@ function extractRequestParams (params, provider) {
|
|
|
149
149
|
})
|
|
150
150
|
}
|
|
151
151
|
case PROVIDER.ANTHROPIC: {
|
|
152
|
-
|
|
152
|
+
let prompt = requestBody.prompt
|
|
153
|
+
if (Array.isArray(requestBody.messages)) { // newer claude models
|
|
154
|
+
for (let idx = requestBody.messages.length - 1; idx >= 0; idx--) {
|
|
155
|
+
const message = requestBody.messages[idx]
|
|
156
|
+
if (message.role === 'user') {
|
|
157
|
+
prompt = message.content?.filter(block => block.type === 'text')
|
|
158
|
+
.map(block => block.text)
|
|
159
|
+
.join('')
|
|
160
|
+
break
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
153
165
|
return new RequestParams({
|
|
154
166
|
prompt,
|
|
155
167
|
temperature: requestBody.temperature,
|
|
156
168
|
topP: requestBody.top_p,
|
|
157
|
-
maxTokens: requestBody.max_tokens_to_sample,
|
|
169
|
+
maxTokens: requestBody.max_tokens_to_sample ?? requestBody.max_tokens,
|
|
158
170
|
stopSequences: requestBody.stop_sequences
|
|
159
171
|
})
|
|
160
172
|
}
|
|
@@ -253,7 +265,13 @@ function extractTextAndResponseReason (response, provider, modelName) {
|
|
|
253
265
|
break
|
|
254
266
|
}
|
|
255
267
|
case PROVIDER.ANTHROPIC: {
|
|
256
|
-
|
|
268
|
+
let message = body.completion
|
|
269
|
+
if (Array.isArray(body.content)) { // newer claude models
|
|
270
|
+
message = body.content.find(item => item.type === 'text')?.text ?? body.content
|
|
271
|
+
} else if (body.content) {
|
|
272
|
+
message = body.content
|
|
273
|
+
}
|
|
274
|
+
return new Generation({ message, finishReason: body.stop_reason })
|
|
257
275
|
}
|
|
258
276
|
case PROVIDER.COHERE: {
|
|
259
277
|
if (modelName.includes('embed')) {
|
|
@@ -262,6 +280,15 @@ function extractTextAndResponseReason (response, provider, modelName) {
|
|
|
262
280
|
return new Generation({ message: embeddings[0] })
|
|
263
281
|
}
|
|
264
282
|
}
|
|
283
|
+
|
|
284
|
+
if (body.text) {
|
|
285
|
+
return new Generation({
|
|
286
|
+
message: body.text,
|
|
287
|
+
finishReason: body.finish_reason,
|
|
288
|
+
choiceId: shouldSetChoiceIds ? body.response_id : undefined
|
|
289
|
+
})
|
|
290
|
+
}
|
|
291
|
+
|
|
265
292
|
const generations = body.generations || []
|
|
266
293
|
if (generations.length > 0) {
|
|
267
294
|
const generation = generations[0]
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
const log = require('../../../dd-trace/src/log')
|
|
4
4
|
const BaseAwsSdkPlugin = require('../base')
|
|
5
5
|
const { DsmPathwayCodec, getHeadersSize } = require('../../../dd-trace/src/datastreams')
|
|
6
|
+
const { extractQueueMetadata } = require('../util')
|
|
6
7
|
|
|
7
8
|
class Sqs extends BaseAwsSdkPlugin {
|
|
8
9
|
static id = 'sqs'
|
|
@@ -92,16 +93,18 @@ class Sqs extends BaseAwsSdkPlugin {
|
|
|
92
93
|
|
|
93
94
|
generateTags (params, operation, response) {
|
|
94
95
|
if (!params || (!params.QueueName && !params.QueueUrl)) return {}
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
queueName = params.QueueUrl.split('/').at(-1)
|
|
99
|
-
}
|
|
96
|
+
|
|
97
|
+
const queueMetadata = extractQueueMetadata(params.QueueUrl)
|
|
98
|
+
const queueName = queueMetadata?.queueName || params.QueueName
|
|
100
99
|
|
|
101
100
|
const tags = {
|
|
102
101
|
'resource.name': `${operation} ${params.QueueName || params.QueueUrl}`,
|
|
103
102
|
'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
|
|
105
108
|
}
|
|
106
109
|
|
|
107
110
|
switch (operation) {
|
|
@@ -84,8 +84,68 @@ const extractPrimaryKeys = (keyNames, keyValuePairs) => {
|
|
|
84
84
|
}
|
|
85
85
|
}
|
|
86
86
|
|
|
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
|
+
|
|
87
146
|
module.exports = {
|
|
88
147
|
generatePointerHash,
|
|
89
148
|
encodeValue,
|
|
90
|
-
extractPrimaryKeys
|
|
149
|
+
extractPrimaryKeys,
|
|
150
|
+
extractQueueMetadata
|
|
91
151
|
}
|
|
@@ -22,6 +22,17 @@ class ExpressCodeOriginForSpansPlugin extends Plugin {
|
|
|
22
22
|
if (layerTags.has(layer)) return
|
|
23
23
|
layerTags.set(layer, entryTags(topOfStackFunc))
|
|
24
24
|
})
|
|
25
|
+
|
|
26
|
+
this.addSub('apm:router:middleware:enter', ({ req, layer }) => {
|
|
27
|
+
const tags = layerTags.get(layer)
|
|
28
|
+
if (!tags) return
|
|
29
|
+
web.getContext(req).span?.addTags(tags)
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
this.addSub('apm:router:route:added', ({ topOfStackFunc, layer }) => {
|
|
33
|
+
if (layerTags.has(layer)) return
|
|
34
|
+
layerTags.set(layer, entryTags(topOfStackFunc))
|
|
35
|
+
})
|
|
25
36
|
}
|
|
26
37
|
}
|
|
27
38
|
|