dd-trace 5.72.0 → 5.73.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.
Files changed (35) hide show
  1. package/LICENSE-3rdparty.csv +2 -0
  2. package/index.d.ts +21 -0
  3. package/package.json +8 -6
  4. package/packages/datadog-esbuild/index.js +8 -0
  5. package/packages/datadog-instrumentations/src/azure-service-bus.js +49 -22
  6. package/packages/datadog-instrumentations/src/cookie-parser.js +2 -0
  7. package/packages/datadog-instrumentations/src/jest.js +59 -14
  8. package/packages/datadog-instrumentations/src/mocha/utils.js +3 -4
  9. package/packages/datadog-plugin-amqplib/src/consumer.js +1 -1
  10. package/packages/datadog-plugin-azure-functions/src/index.js +24 -14
  11. package/packages/datadog-plugin-azure-service-bus/src/index.js +1 -1
  12. package/packages/datadog-plugin-azure-service-bus/src/producer.js +60 -12
  13. package/packages/datadog-plugin-jest/src/index.js +53 -18
  14. package/packages/datadog-plugin-ws/src/close.js +1 -1
  15. package/packages/datadog-plugin-ws/src/producer.js +1 -1
  16. package/packages/datadog-plugin-ws/src/receiver.js +1 -1
  17. package/packages/dd-trace/src/appsec/index.js +9 -1
  18. package/packages/dd-trace/src/appsec/reporter.js +2 -3
  19. package/packages/dd-trace/src/ci-visibility/test-management/get-test-management-tests.js +5 -0
  20. package/packages/dd-trace/src/llmobs/plugins/ai/util.js +8 -3
  21. package/packages/dd-trace/src/llmobs/plugins/base.js +11 -12
  22. package/packages/dd-trace/src/llmobs/sdk.js +20 -4
  23. package/packages/dd-trace/src/llmobs/tagger.js +12 -0
  24. package/packages/dd-trace/src/opentelemetry/logs/otlp_http_log_exporter.js +7 -127
  25. package/packages/dd-trace/src/opentelemetry/logs/otlp_transformer.js +19 -134
  26. package/packages/dd-trace/src/opentelemetry/otlp/metrics.proto +720 -0
  27. package/packages/dd-trace/src/opentelemetry/otlp/metrics_service.proto +78 -0
  28. package/packages/dd-trace/src/opentelemetry/otlp/otlp_http_exporter_base.js +177 -0
  29. package/packages/dd-trace/src/opentelemetry/otlp/otlp_transformer_base.js +163 -0
  30. package/packages/dd-trace/src/opentelemetry/{protos → otlp}/protobuf_loader.js +24 -6
  31. package/packages/dd-trace/src/supported-configurations.json +1 -0
  32. /package/packages/dd-trace/src/opentelemetry/{protos → otlp}/common.proto +0 -0
  33. /package/packages/dd-trace/src/opentelemetry/{protos → otlp}/logs.proto +0 -0
  34. /package/packages/dd-trace/src/opentelemetry/{protos → otlp}/logs_service.proto +0 -0
  35. /package/packages/dd-trace/src/opentelemetry/{protos → otlp}/resource.proto +0 -0
@@ -64,6 +64,8 @@ dev,globals,MIT,Copyright (c) Sindre Sorhus <sindresorhus@gmail.com> (https://si
64
64
  dev,graphql,MIT,Copyright 2015 Facebook Inc.
65
65
  dev,jszip,MIT,Copyright 2015-2016 Stuart Knightley and contributors
66
66
  dev,mocha,MIT,Copyright 2011-2018 JS Foundation and contributors https://js.foundation
67
+ dev,mocha-multi-reporters,MIT,Copyright 2015-2019 Yousaf Nabi and Stanley Ng
68
+ dev,mocha-junit-reporter,MIT, Copyright 2015 Michael Allen
67
69
  dev,multer,MIT,Copyright 2014 Hage Yaapa
68
70
  dev,nock,MIT,Copyright 2017 Pedro Teixeira and other contributors
69
71
  dev,nyc,ISC,Copyright 2015 Contributors
package/index.d.ts CHANGED
@@ -2938,6 +2938,15 @@ declare namespace tracer {
2938
2938
  */
2939
2939
  submitEvaluation (spanContext: llmobs.ExportedLLMObsSpan, options: llmobs.EvaluationOptions): void
2940
2940
 
2941
+
2942
+ /**
2943
+ * Annotates all spans, including auto-instrumented spans, with the provided tags created in the context of the callback function.
2944
+ * @param options The annotation context options.
2945
+ * @param fn The callback over which to apply the annotation context options.
2946
+ * @returns The result of the function.
2947
+ */
2948
+ annotationContext<T> (options: llmobs.AnnotationContextOptions, fn: () => T): T
2949
+
2941
2950
  /**
2942
2951
  * Flushes any remaining spans and evaluation metrics to LLM Observability.
2943
2952
  */
@@ -3099,6 +3108,18 @@ declare namespace tracer {
3099
3108
  tags?: { [key: string]: any }
3100
3109
  }
3101
3110
 
3111
+ interface AnnotationContextOptions {
3112
+ /**
3113
+ * Dictionary of JSON serializable key-value tag pairs to set or update on the LLMObs span regarding the span's context.
3114
+ */
3115
+ tags?: { [key: string]: any },
3116
+
3117
+ /**
3118
+ * Set to override the span name for any spans annotated within the returned context.
3119
+ */
3120
+ name?: string,
3121
+ }
3122
+
3102
3123
  /**
3103
3124
  * An object containing the span ID and trace ID of interest
3104
3125
  */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dd-trace",
3
- "version": "5.72.0",
3
+ "version": "5.73.0",
4
4
  "description": "Datadog APM tracing client for JavaScript",
5
5
  "main": "index.js",
6
6
  "typings": "index.d.ts",
@@ -44,7 +44,7 @@
44
44
  "test:llmobs:sdk:ci": "nyc --no-clean --include \"packages/dd-trace/src/llmobs/**/*.js\" -- npm run test:llmobs:sdk",
45
45
  "test:llmobs:plugins": "mocha -r \"packages/dd-trace/test/setup/mocha.js\" \"packages/dd-trace/test/llmobs/plugins/@($(echo $PLUGINS))/*.spec.js\"",
46
46
  "test:llmobs:plugins:ci": "yarn services && nyc --no-clean --include \"packages/dd-trace/src/llmobs/**/*.js\" -- npm run test:llmobs:plugins",
47
- "test:plugins": "mocha -r \"packages/dd-trace/test/setup/mocha.js\" \"packages/datadog-plugin-@($(echo $PLUGINS))/test/**/*.spec.js\"",
47
+ "test:plugins": "mocha -r \"packages/dd-trace/test/setup/mocha.js\" \"packages/datadog-plugin-@($(echo $PLUGINS))/test/**/@($(echo ${SPEC:-'*'})).spec.js\"",
48
48
  "test:plugins:ci": "yarn services && nyc --no-clean --include \"packages/datadog-plugin-@($(echo $PLUGINS))/src/**/*.js\" -- npm run test:plugins",
49
49
  "test:plugins:ci:flaky": "yarn services && nyc --no-clean --include \"packages/datadog-plugin-@($(echo $PLUGINS))/src/**/*.js\" -- npm run test:plugins -- --bail --retries 2",
50
50
  "test:plugins:upstream": "node ./packages/dd-trace/test/plugins/suite.js",
@@ -120,10 +120,10 @@
120
120
  ],
121
121
  "dependencies": {
122
122
  "@datadog/libdatadog": "0.7.0",
123
- "@datadog/native-appsec": "10.2.1",
123
+ "@datadog/native-appsec": "10.3.0",
124
124
  "@datadog/native-iast-taint-tracking": "4.0.0",
125
125
  "@datadog/native-metrics": "3.1.1",
126
- "@datadog/openfeature-node-server": "0.1.0-preview.10",
126
+ "@datadog/openfeature-node-server": "0.1.0-preview.12",
127
127
  "@datadog/pprof": "5.11.1",
128
128
  "@datadog/sketches-js": "2.1.1",
129
129
  "@datadog/wasm-js-rewriter": "4.0.1",
@@ -158,7 +158,7 @@
158
158
  },
159
159
  "peerDependencies": {
160
160
  "@openfeature/core": "^1.9.0",
161
- "@openfeature/server-sdk": "~1.18.0"
161
+ "@openfeature/server-sdk": "~1.19.0"
162
162
  },
163
163
  "peerDependenciesMeta": {
164
164
  "@openfeature/core": {
@@ -174,7 +174,7 @@
174
174
  "@eslint/js": "^9.29.0",
175
175
  "@msgpack/msgpack": "^3.1.2",
176
176
  "@openfeature/core": "^1.8.1",
177
- "@openfeature/server-sdk": "~1.18.0",
177
+ "@openfeature/server-sdk": "~1.19.0",
178
178
  "@stylistic/eslint-plugin": "^5.0.0",
179
179
  "@types/chai": "^4.3.16",
180
180
  "@types/mocha": "^10.0.10",
@@ -198,6 +198,8 @@
198
198
  "graphql": "*",
199
199
  "jszip": "^3.10.1",
200
200
  "mocha": "^11.6.0",
201
+ "mocha-junit-reporter": "^2.2.1",
202
+ "mocha-multi-reporters": "^1.5.1",
201
203
  "multer": "^2.0.2",
202
204
  "nock": "^13.5.6",
203
205
  "nyc": "^15.1.0",
@@ -115,6 +115,14 @@ module.exports.setup = function (build) {
115
115
  ${isSourceMapEnabled ? `globalThis.__DD_ESBUILD_BASEPATH = '${require('../dd-trace/src/util').ddBasePath}';` : ''}
116
116
  ${build.initialOptions.banner.js}`
117
117
  }
118
+
119
+ try {
120
+ require.resolve('@openfeature/core')
121
+ } catch (error) {
122
+ build.initialOptions.external ??= []
123
+ build.initialOptions.external.push('@openfeature/core')
124
+ }
125
+
118
126
  if (isESMBuild(build)) {
119
127
  if (!build.initialOptions.banner.js.includes('import { createRequire as $dd_createRequire } from \'module\'')) {
120
128
  build.initialOptions.banner.js = `import { createRequire as $dd_createRequire } from 'module';
@@ -1,38 +1,65 @@
1
1
  'use strict'
2
2
 
3
3
  const {
4
- channel,
5
4
  addHook
6
5
  } = require('./helpers/instrument')
7
6
 
8
7
  const shimmer = require('../../datadog-shimmer')
8
+ const dc = require('dc-polyfill')
9
9
 
10
- const producerStartCh = channel('apm:azure-service-bus:send:start')
11
- const producerErrorCh = channel('apm:azure-service-bus:send:error')
12
- const producerFinishCh = channel('apm:azure-service-bus:send:finish')
10
+ const producerCh = dc.tracingChannel('apm:azure-service-bus:send')
13
11
 
14
12
  addHook({ name: '@azure/service-bus', versions: ['>=7.9.2'], patchDefault: false }, (obj) => {
15
13
  const ServiceBusClient = obj.ServiceBusClient
16
- shimmer.wrap(ServiceBusClient.prototype, 'createSender', createSender => function (queueOrTopicName) {
17
- const sender = createSender.apply(this, arguments)
18
- shimmer.wrap(sender._sender, 'send', send => function (msg) {
19
- const ctx = { sender, msg }
20
- return producerStartCh.runStores(ctx, () => {
21
- return send.apply(this, arguments)
22
- .then(
23
- response => {
24
- producerFinishCh.publish(ctx)
25
- },
26
- error => {
27
- ctx.error = error
28
- producerErrorCh.publish(ctx)
29
- producerFinishCh.publish(ctx)
30
- throw error
31
- }
14
+ let didItShim = false
15
+ shimmer.wrap(ServiceBusClient.prototype, 'createSender',
16
+ createSender => function (queueOrTopicName) {
17
+ const sender = createSender.apply(this, arguments)
18
+ if (didItShim) return sender
19
+ const senderPrototype = sender.constructor.prototype
20
+ const senderSenderPrototype = sender._sender.constructor.prototype
21
+ shimmer.wrap(senderPrototype, 'scheduleMessages', scheduleMessages =>
22
+ function (msg, scheduledEnqueueTimeUtc) {
23
+ const functionName = scheduleMessages.name
24
+ const config = this._context.config
25
+ const entityPath = this._entityPath
26
+ return producerCh.tracePromise(
27
+ scheduleMessages,
28
+ { config, entityPath, functionName, msg, scheduledEnqueueTimeUtc },
29
+ this, ...arguments
32
30
  )
31
+ })
32
+
33
+ shimmer.wrap(senderPrototype, 'createMessageBatch', createMessageBatch => async function () {
34
+ const batch = await createMessageBatch.apply(this, arguments)
35
+ shimmer.wrap(batch.constructor.prototype, 'tryAddMessage', tryAddMessage => function (msg) {
36
+ const functionName = tryAddMessage.name
37
+ const config = this._context.config
38
+ return producerCh.tracePromise(
39
+ tryAddMessage, { config, functionName, batch, msg }, this, ...arguments)
40
+ })
41
+ return batch
42
+ })
43
+
44
+ shimmer.wrap(senderSenderPrototype, 'send', send => function (msg) {
45
+ const functionName = send.name
46
+ const config = this._context.config
47
+ const entityPath = this.entityPath
48
+ return producerCh.tracePromise(
49
+ send, { config, entityPath, functionName, msg }, this, ...arguments
50
+ )
51
+ })
52
+
53
+ shimmer.wrap(senderSenderPrototype, 'sendBatch', sendBatch => function (msg) {
54
+ const functionName = sendBatch.name
55
+ const config = this._context.config
56
+ const entityPath = this.entityPath
57
+ return producerCh.tracePromise(
58
+ sendBatch, { config, entityPath, functionName, msg }, this, ...arguments
59
+ )
33
60
  })
61
+ didItShim = true
62
+ return sender
34
63
  })
35
- return sender
36
- })
37
64
  return obj
38
65
  })
@@ -25,6 +25,8 @@ addHook({
25
25
  name: 'cookie-parser',
26
26
  versions: ['>=1.0.0']
27
27
  }, cookieParser => {
28
+ // This prevents the non default export from entering the wrapping process
29
+ if (cookieParser.default) return cookieParser
28
30
  return shimmer.wrapFunction(cookieParser, cookieParser => function () {
29
31
  const cookieMiddleware = cookieParser.apply(this, arguments)
30
32
 
@@ -29,6 +29,7 @@ const testSessionConfigurationCh = channel('ci:jest:session:configuration')
29
29
 
30
30
  const testSuiteStartCh = channel('ci:jest:test-suite:start')
31
31
  const testSuiteFinishCh = channel('ci:jest:test-suite:finish')
32
+ const testSuiteErrorCh = channel('ci:jest:test-suite:error')
32
33
 
33
34
  const workerReportTraceCh = channel('ci:jest:worker-report:trace')
34
35
  const workerReportCoverageCh = channel('ci:jest:worker-report:coverage')
@@ -397,7 +398,8 @@ function getWrappedEnvironment (BaseEnvironment, jestVersion) {
397
398
  isJestRetry,
398
399
  isDisabled,
399
400
  isQuarantined,
400
- isModified
401
+ isModified,
402
+ testSuiteAbsolutePath: this.testSuiteAbsolutePath
401
403
  }
402
404
  testContexts.set(event.test, ctx)
403
405
 
@@ -809,11 +811,15 @@ function getCliWrapper (isNewJestVersion) {
809
811
 
810
812
  try {
811
813
  const { err, testManagementTests: receivedTestManagementTests } = await testManagementTestsPromise
812
- if (!err) {
814
+ if (err) {
815
+ isTestManagementTestsEnabled = false
816
+ testManagementTests = {}
817
+ } else {
813
818
  testManagementTests = receivedTestManagementTests
814
819
  }
815
820
  } catch (err) {
816
821
  log.error('Jest test management tests error', err)
822
+ isTestManagementTestsEnabled = false
817
823
  }
818
824
  }
819
825
 
@@ -1092,7 +1098,8 @@ function jestAdapterWrapper (jestAdapter, jestVersion) {
1092
1098
  testEnvironmentOptions: environment.testEnvironmentOptions,
1093
1099
  testSourceFile: environment.testSourceFile,
1094
1100
  displayName: environment.displayName,
1095
- frameworkVersion: jestVersion
1101
+ frameworkVersion: jestVersion,
1102
+ testSuiteAbsolutePath: environment.testSuiteAbsolutePath
1096
1103
  })
1097
1104
  return adapter.apply(this, arguments).then(suiteResults => {
1098
1105
  const { numFailingTests, skipped, failureMessage: errorMessage } = suiteResults
@@ -1116,12 +1123,17 @@ function jestAdapterWrapper (jestAdapter, jestVersion) {
1116
1123
  const coverageFiles = getFilesWithPath(getCoveredFilenamesFromCoverage(environment.global.__coverage__))
1117
1124
  const mockedFiles = getFilesWithPath(testSuiteMockedFiles.get(environment.testSuiteAbsolutePath) || [])
1118
1125
 
1119
- testSuiteCodeCoverageCh.publish({ coverageFiles, testSuite: environment.testSourceFile, mockedFiles })
1126
+ testSuiteCodeCoverageCh.publish({
1127
+ coverageFiles,
1128
+ testSuite: environment.testSourceFile,
1129
+ mockedFiles,
1130
+ testSuiteAbsolutePath: environment.testSuiteAbsolutePath
1131
+ })
1120
1132
  }
1121
- testSuiteFinishCh.publish({ status, errorMessage })
1133
+ testSuiteFinishCh.publish({ status, errorMessage, testSuiteAbsolutePath: environment.testSuiteAbsolutePath })
1122
1134
  return suiteResults
1123
1135
  }).catch(error => {
1124
- testSuiteFinishCh.publish({ status: 'fail', error })
1136
+ testSuiteFinishCh.publish({ status: 'fail', error, testSuiteAbsolutePath: environment.testSuiteAbsolutePath })
1125
1137
  throw error
1126
1138
  })
1127
1139
  })
@@ -1301,16 +1313,49 @@ addHook({
1301
1313
  })
1302
1314
 
1303
1315
  shimmer.wrap(Runtime.prototype, 'requireModuleOrMock', requireModuleOrMock => function (from, moduleName) {
1304
- // TODO: do this for every library that we instrument
1305
- if (LIBRARIES_BYPASSING_JEST_REQUIRE_ENGINE.has(moduleName)) {
1306
- // To bypass jest's own require engine
1307
- return this._requireCoreModule(moduleName)
1316
+ // `requireModuleOrMock` may log errors to the console. If we don't remove ourselves
1317
+ // from the stack trace, the user might see a useless stack trace rather than the error
1318
+ // that `jest` tries to show.
1319
+ const originalPrepareStackTrace = Error.prepareStackTrace
1320
+ Error.prepareStackTrace = function (error, structuredStackTrace) {
1321
+ const filteredStackTrace = structuredStackTrace
1322
+ .filter(callSite => !callSite.getFileName()?.includes('datadog-instrumentations/src/jest.js'))
1323
+
1324
+ return originalPrepareStackTrace(error, filteredStackTrace)
1308
1325
  }
1309
- // This means that `@fast-check/jest` is used in the test file.
1310
- if (moduleName === '@fast-check/jest') {
1311
- testSuiteAbsolutePathsWithFastCheck.add(this._testPath)
1326
+ try {
1327
+ // TODO: do this for every library that we instrument
1328
+ if (LIBRARIES_BYPASSING_JEST_REQUIRE_ENGINE.has(moduleName)) {
1329
+ // To bypass jest's own require engine
1330
+ return this._requireCoreModule(moduleName)
1331
+ }
1332
+ // This means that `@fast-check/jest` is used in the test file.
1333
+ if (moduleName === '@fast-check/jest') {
1334
+ testSuiteAbsolutePathsWithFastCheck.add(this._testPath)
1335
+ }
1336
+ const returnedValue = requireModuleOrMock.apply(this, arguments)
1337
+ if (process.exitCode === 1) {
1338
+ if (this.loggedReferenceErrors.size > 0) {
1339
+ const errorMessage = [...this.loggedReferenceErrors][0]
1340
+ testSuiteErrorCh.publish({
1341
+ errorMessage,
1342
+ testSuiteAbsolutePath: this._testPath
1343
+ })
1344
+ } else {
1345
+ testSuiteErrorCh.publish({
1346
+ errorMessage: 'An error occurred while importing a module',
1347
+ testSuiteAbsolutePath: this._testPath
1348
+ })
1349
+ }
1350
+ }
1351
+ return returnedValue
1352
+ } catch (error) {
1353
+ testSuiteErrorCh.publish({ error, testSuiteAbsolutePath: this._testPath })
1354
+ throw error
1355
+ } finally {
1356
+ // Restore original prepareStackTrace
1357
+ Error.prepareStackTrace = originalPrepareStackTrace
1312
1358
  }
1313
- return requireModuleOrMock.apply(this, arguments)
1314
1359
  })
1315
1360
 
1316
1361
  return runtimePackage
@@ -163,14 +163,13 @@ function runnableWrapper (RunnablePackage, libraryConfig) {
163
163
  const ctx = getTestContext(test)
164
164
 
165
165
  if (ctx) {
166
- const originalFn = this.fn
167
166
  // we bind the test fn to the correct context
168
- const newFn = function () {
167
+ const newFn = shimmer.wrapFunction(this.fn, originalFn => function () {
169
168
  return testFnCh.runStores(ctx, () => originalFn.apply(this, arguments))
170
- }
169
+ })
171
170
 
172
171
  // we store the original function, not to lose it
173
- originalFns.set(newFn, originalFn)
172
+ originalFns.set(newFn, this.fn)
174
173
  this.fn = newFn
175
174
 
176
175
  wrappedFunctions.add(this.fn)
@@ -10,7 +10,7 @@ class AmqplibConsumerPlugin extends ConsumerPlugin {
10
10
  static operation = 'consume'
11
11
 
12
12
  bindStart (ctx) {
13
- const { method, fields, message, queue } = ctx
13
+ const { method, fields = {}, message, queue } = ctx
14
14
 
15
15
  if (method !== 'basic.deliver' && method !== 'basic.get') return
16
16
 
@@ -24,10 +24,10 @@ class AzureFunctionsPlugin extends TracingPlugin {
24
24
  static prefix = 'tracing:datadog:azure:functions:invoke'
25
25
 
26
26
  bindStart (ctx) {
27
- const childOf = extractTraceContext(this._tracer, ctx)
28
27
  const meta = getMetaForTrigger(ctx)
29
28
  const triggerType = triggerMap[ctx.methodName]
30
29
  const isMessagingService = (triggerType === 'ServiceBus' || triggerType === 'EventHubs')
30
+ const childOf = isMessagingService ? null : extractTraceContext(this._tracer, ctx)
31
31
  const span = this.startSpan(this.operationName(), {
32
32
  childOf,
33
33
  service: this.serviceName(),
@@ -36,7 +36,7 @@ class AzureFunctionsPlugin extends TracingPlugin {
36
36
  }, ctx)
37
37
 
38
38
  if (isMessagingService) {
39
- setSpanLinks(this.tracer, span, ctx)
39
+ setSpanLinks(triggerType, this.tracer, span, ctx)
40
40
  }
41
41
 
42
42
  ctx.span = span
@@ -116,29 +116,39 @@ function extractTraceContext (tracer, ctx) {
116
116
  switch (String(triggerMap[ctx.methodName])) {
117
117
  case 'Http':
118
118
  return tracer.extract('http_headers', Object.fromEntries(ctx.httpRequest.headers))
119
- case 'ServiceBus':
120
- return tracer.extract('text_map', ctx.invocationContext.triggerMetadata.applicationProperties)
121
119
  default:
122
120
  null
123
121
  }
124
122
  }
125
123
 
126
- function setSpanLinks (tracer, span, ctx) {
124
+ // message & messages & batch with cardinality of 1 == applicationProperties
125
+ // messages with cardinality of many == applicationPropertiesArray
126
+ function setSpanLinks (triggerType, tracer, span, ctx) {
127
127
  const cardinality = ctx.invocationContext.options.trigger.cardinality
128
128
  const triggerMetadata = ctx.invocationContext.triggerMetadata
129
- if (cardinality === 'many' && triggerMetadata.propertiesArray.length > 0) {
130
- triggerMetadata.propertiesArray.forEach(event => {
131
- // Check for possible empty event when span links are disabled
132
- if (Object.keys(event).length > 0) {
133
- span.addLink(tracer.extract('text_map', event))
134
- }
135
- })
136
- } else if (cardinality === 'one') {
137
- const spanContext = tracer.extract('text_map', triggerMetadata.properties)
129
+ const isServiceBus = triggerType === 'ServiceBus'
130
+
131
+ const properties = isServiceBus
132
+ ? triggerMetadata.applicationProperties
133
+ : triggerMetadata.properties
134
+
135
+ const propertiesArray = isServiceBus
136
+ ? triggerMetadata.applicationPropertiesArray
137
+ : triggerMetadata.propertiesArray
138
+
139
+ const addLinkFromProperties = (props) => {
140
+ if (!props || Object.keys(props).length === 0) return
141
+ const spanContext = tracer.extract('text_map', props)
138
142
  if (spanContext) {
139
143
  span.addLink(spanContext)
140
144
  }
141
145
  }
146
+
147
+ if (cardinality === 'many' && propertiesArray?.length > 0) {
148
+ propertiesArray.forEach(addLinkFromProperties)
149
+ } else if (cardinality === 'one') {
150
+ addLinkFromProperties(properties)
151
+ }
142
152
  }
143
153
 
144
154
  module.exports = AzureFunctionsPlugin
@@ -4,7 +4,7 @@ const ProducerPlugin = require('./producer')
4
4
  const CompositePlugin = require('../../dd-trace/src/plugins/composite')
5
5
 
6
6
  class AzureServiceBusPlugin extends CompositePlugin {
7
- static id = 'azure-service-bus'
7
+ static get id () { return 'azure-service-bus' }
8
8
  static get plugins () {
9
9
  return {
10
10
  producer: ProducerPlugin
@@ -1,36 +1,84 @@
1
1
  'use strict'
2
2
 
3
+ const { getEnvironmentVariable } = require('../../dd-trace/src/config-helper')
3
4
  const ProducerPlugin = require('../../dd-trace/src/plugins/producer')
4
5
 
5
6
  class AzureServiceBusProducerPlugin extends ProducerPlugin {
6
- static id = 'azure-service-bus'
7
- static operation = 'send'
7
+ static get id () { return 'azure-service-bus' }
8
+ static get operation () { return 'send' }
9
+ static get prefix () { return 'tracing:apm:azure-service-bus:send' }
8
10
 
9
11
  bindStart (ctx) {
10
- const { sender, msg } = ctx
11
- const qualifiedSenderNamespace = sender._sender.audience.replace('sb://', '')
12
+ // we do not want to make these spans when batch linking is disabled.
13
+ if (!batchLinksAreEnabled() && ctx.functionName === 'tryAddMessage') {
14
+ return ctx.currentStore
15
+ }
16
+
17
+ const qualifiedSenderNamespace = ctx.config.host
12
18
  const span = this.startSpan({
13
- resource: sender.entityPath,
19
+ resource: ctx.entityPath,
14
20
  type: 'messaging',
15
21
  meta: {
16
22
  component: 'azure-service-bus',
17
- 'messaging.destination.name': sender.entityPath,
23
+ 'messaging.destination.name': ctx.entityPath,
18
24
  'messaging.operation': 'send',
19
25
  'messaging.system': 'servicebus',
20
26
  'network.destination.name': qualifiedSenderNamespace,
21
27
  }
22
28
  }, ctx)
23
29
 
24
- // This is the correct key for injecting trace context into Azure Service Bus messages
25
- // It may not be present in the message properties, so we ensure it exists
26
- if (!msg.applicationProperties) {
27
- msg.applicationProperties = {}
28
- }
30
+ if (ctx.functionName === 'tryAddMessage') {
31
+ span._spanContext._name = 'azure.servicebus.create'
32
+ span.setTag('messaging.operation', 'create')
29
33
 
30
- this.tracer.inject(span, 'text_map', msg.applicationProperties)
34
+ if (ctx.msg.messageID !== undefined) {
35
+ span.setTag('message.id', ctx.msg)
36
+ }
31
37
 
38
+ if (batchLinksAreEnabled()) {
39
+ ctx.batch._spanContexts.push(span.context())
40
+ injectTraceContext(this.tracer, span, ctx.msg)
41
+ }
42
+ }
43
+
44
+ if (ctx.functionName === 'send' || ctx.functionName === 'sendBatch' || ctx.functionName === 'scheduleMessages') {
45
+ const messages = ctx.msg
46
+ const isBatch = messages.constructor?.name === 'ServiceBusMessageBatchImpl'
47
+ if (isBatch) {
48
+ span.setTag('messaging.batch.message_count', messages.count)
49
+ if (batchLinksAreEnabled()) {
50
+ messages._spanContexts.forEach(spanContext => {
51
+ span.addLink(spanContext)
52
+ })
53
+ }
54
+ } else if (Array.isArray(messages)) {
55
+ span.setTag('messaging.batch.message_count', messages.length)
56
+ messages.forEach(event => {
57
+ injectTraceContext(this.tracer, span, event)
58
+ })
59
+ } else {
60
+ injectTraceContext(this.tracer, span, messages)
61
+ }
62
+ }
32
63
  return ctx.currentStore
33
64
  }
65
+
66
+ asyncEnd (ctx) {
67
+ super.finish()
68
+ }
69
+ }
70
+
71
+ function injectTraceContext (tracer, span, msg) {
72
+ if (!msg.applicationProperties) {
73
+ msg.applicationProperties = {}
74
+ }
75
+
76
+ tracer.inject(span, 'text_map', msg.applicationProperties)
77
+ }
78
+
79
+ function batchLinksAreEnabled () {
80
+ const sb = getEnvironmentVariable('DD_TRACE_AZURE_SERVICEBUS_BATCH_LINKS_ENABLED')
81
+ return sb !== 'false'
34
82
  }
35
83
 
36
84
  module.exports = AzureServiceBusProducerPlugin