dd-trace 5.97.0 → 5.98.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 (81) hide show
  1. package/index.d.ts +26 -2
  2. package/package.json +1 -1
  3. package/packages/datadog-instrumentations/src/cucumber.js +65 -3
  4. package/packages/datadog-instrumentations/src/cypress-config.js +31 -37
  5. package/packages/datadog-instrumentations/src/jest.js +104 -12
  6. package/packages/datadog-instrumentations/src/mocha/utils.js +8 -0
  7. package/packages/datadog-instrumentations/src/redis.js +12 -6
  8. package/packages/datadog-plugin-aws-sdk/src/base.js +1 -1
  9. package/packages/datadog-plugin-aws-sdk/src/services/dynamodb.js +1 -0
  10. package/packages/datadog-plugin-aws-sdk/src/services/eventbridge.js +1 -0
  11. package/packages/datadog-plugin-aws-sdk/src/services/kinesis.js +1 -0
  12. package/packages/datadog-plugin-aws-sdk/src/services/redshift.js +1 -0
  13. package/packages/datadog-plugin-aws-sdk/src/services/sns.js +1 -0
  14. package/packages/datadog-plugin-aws-sdk/src/services/sqs.js +1 -0
  15. package/packages/datadog-plugin-cucumber/src/index.js +6 -0
  16. package/packages/datadog-plugin-cypress/src/cypress-plugin.js +109 -1
  17. package/packages/datadog-plugin-cypress/src/index.js +59 -2
  18. package/packages/datadog-plugin-fs/src/index.js +1 -1
  19. package/packages/datadog-plugin-google-cloud-pubsub/src/consumer.js +2 -1
  20. package/packages/datadog-plugin-google-cloud-pubsub/src/pubsub-push-subscription.js +2 -7
  21. package/packages/datadog-plugin-http/src/client.js +1 -1
  22. package/packages/datadog-plugin-http/src/server.js +10 -2
  23. package/packages/datadog-plugin-http2/src/client.js +1 -1
  24. package/packages/datadog-plugin-http2/src/server.js +10 -2
  25. package/packages/datadog-plugin-mongodb-core/src/index.js +3 -3
  26. package/packages/datadog-plugin-mysql/src/index.js +1 -1
  27. package/packages/datadog-plugin-next/src/index.js +8 -2
  28. package/packages/datadog-plugin-pg/src/index.js +1 -1
  29. package/packages/datadog-plugin-tedious/src/index.js +1 -1
  30. package/packages/datadog-plugin-ws/src/close.js +1 -1
  31. package/packages/datadog-plugin-ws/src/receiver.js +1 -1
  32. package/packages/dd-trace/src/aiguard/sdk.js +22 -22
  33. package/packages/dd-trace/src/appsec/blocked_templates.js +4 -3
  34. package/packages/dd-trace/src/appsec/blocking.js +62 -34
  35. package/packages/dd-trace/src/appsec/sdk/set_user.js +1 -1
  36. package/packages/dd-trace/src/appsec/sdk/track_event.js +5 -5
  37. package/packages/dd-trace/src/appsec/sdk/user_blocking.js +2 -2
  38. package/packages/dd-trace/src/appsec/sdk/utils.js +4 -2
  39. package/packages/dd-trace/src/config/defaults.js +0 -1
  40. package/packages/dd-trace/src/config/generated-config-types.d.ts +5 -0
  41. package/packages/dd-trace/src/config/index.js +55 -28
  42. package/packages/dd-trace/src/config/supported-configurations.json +61 -4
  43. package/packages/dd-trace/src/constants.js +1 -0
  44. package/packages/dd-trace/src/debugger/devtools_client/snapshot/processor.js +5 -2
  45. package/packages/dd-trace/src/encode/0.4.js +7 -6
  46. package/packages/dd-trace/src/encode/span-stats.js +4 -1
  47. package/packages/dd-trace/src/log/index.js +0 -10
  48. package/packages/dd-trace/src/openfeature/remote_config.js +6 -1
  49. package/packages/dd-trace/src/opentelemetry/context_manager.js +6 -4
  50. package/packages/dd-trace/src/opentelemetry/otlp/otlp_http_exporter_base.js +17 -2
  51. package/packages/dd-trace/src/opentelemetry/otlp/protobuf_loader.js +14 -2
  52. package/packages/dd-trace/src/opentelemetry/otlp/trace.proto +358 -0
  53. package/packages/dd-trace/src/opentelemetry/otlp/trace_service.proto +78 -0
  54. package/packages/dd-trace/src/opentelemetry/trace/index.js +75 -0
  55. package/packages/dd-trace/src/opentelemetry/trace/otlp_http_trace_exporter.js +66 -0
  56. package/packages/dd-trace/src/opentelemetry/trace/otlp_transformer.js +332 -0
  57. package/packages/dd-trace/src/opentracing/tracer.js +9 -4
  58. package/packages/dd-trace/src/plugins/log_plugin.js +3 -0
  59. package/packages/dd-trace/src/plugins/plugin.js +6 -11
  60. package/packages/dd-trace/src/plugins/storage.js +2 -2
  61. package/packages/dd-trace/src/plugins/tracing.js +22 -5
  62. package/packages/dd-trace/src/plugins/util/test.js +2 -0
  63. package/packages/dd-trace/src/plugins/util/web.js +6 -88
  64. package/packages/dd-trace/src/profiling/profiler.js +34 -77
  65. package/packages/dd-trace/src/proxy.js +8 -3
  66. package/packages/dd-trace/src/runtime_metrics/runtime_metrics.js +15 -11
  67. package/packages/dd-trace/src/service-naming/index.js +1 -1
  68. package/packages/dd-trace/src/service-naming/schemas/definition.js +4 -1
  69. package/packages/dd-trace/src/service-naming/schemas/util.js +15 -1
  70. package/packages/dd-trace/src/service-naming/schemas/v0/messaging.js +24 -1
  71. package/packages/dd-trace/src/service-naming/schemas/v0/storage.js +60 -0
  72. package/packages/dd-trace/src/service-naming/schemas/v0/web.js +17 -1
  73. package/packages/dd-trace/src/service-naming/schemas/v0/websocket.js +5 -0
  74. package/packages/dd-trace/src/service-naming/schemas/v1/storage.js +17 -0
  75. package/packages/dd-trace/src/service-naming/schemas/v1/web.js +11 -1
  76. package/packages/dd-trace/src/service-naming/schemas/v1/websocket.js +6 -0
  77. package/packages/dd-trace/src/span_stats.js +5 -1
  78. package/packages/dd-trace/src/tracer.js +2 -2
  79. package/vendor/dist/@apm-js-collab/code-transformer/index.js +28 -6
  80. package/vendor/dist/protobufjs/index.js +1 -1
  81. package/packages/dd-trace/src/log/utils.js +0 -16
@@ -1,5 +1,9 @@
1
1
  'use strict'
2
2
 
3
+ // Capture real timers at module load, before any test can install fake timers.
4
+ const { performance } = require('perf_hooks')
5
+ const dateNow = Date.now
6
+
3
7
  const {
4
8
  TEST_STATUS,
5
9
  TEST_IS_RUM_ACTIVE,
@@ -53,6 +57,7 @@ const {
53
57
  DYNAMIC_NAME_RE,
54
58
  logDynamicNamesWarning,
55
59
  getPullRequestBaseBranch,
60
+ TEST_FINAL_STATUS,
56
61
  } = require('../../dd-trace/src/plugins/util/test')
57
62
  const { isMarkedAsUnskippable } = require('../../datadog-plugin-jest/src/util')
58
63
  const { ORIGIN_KEY, COMPONENT } = require('../../dd-trace/src/constants')
@@ -240,6 +245,58 @@ function getSuiteStatus (suiteStats) {
240
245
  return 'pass'
241
246
  }
242
247
 
248
+ const FINAL_STATUS_RETRY_KIND = {
249
+ none: 'none',
250
+ atr: 'atr',
251
+ efd: 'efd',
252
+ atf: 'atf',
253
+ }
254
+
255
+ function getFinalStatusRetryKind ({ finishedTest, finishedTestAttempts, flakyTestRetriesCount }) {
256
+ // Infer retry kind from the executions we actually saw so ATR enabled with
257
+ // a retry count of 0 is still treated as a single final execution.
258
+ if (finishedTest.isAttemptToFix) {
259
+ return FINAL_STATUS_RETRY_KIND.atf
260
+ }
261
+
262
+ if (finishedTestAttempts.some(testAttempt => testAttempt.isEfdRetry)) {
263
+ return FINAL_STATUS_RETRY_KIND.efd
264
+ }
265
+
266
+ if (finishedTestAttempts.length > 1 && flakyTestRetriesCount > 0) {
267
+ return FINAL_STATUS_RETRY_KIND.atr
268
+ }
269
+
270
+ return FINAL_STATUS_RETRY_KIND.none
271
+ }
272
+
273
+ function getFinalStatus ({
274
+ status,
275
+ retryKind,
276
+ hasFailedAllRetries,
277
+ hasPassedAllAtfRetries,
278
+ isQuarantined,
279
+ isDisabled,
280
+ }) {
281
+ // If the test is quarantined or disabled, regardless of its actual execution result or active retry features,
282
+ // the final status of its last execution should be reported as 'skip'.
283
+ if (isQuarantined || isDisabled || status === 'skip') {
284
+ return 'skip'
285
+ }
286
+
287
+ switch (retryKind) {
288
+ case FINAL_STATUS_RETRY_KIND.atr:
289
+ case FINAL_STATUS_RETRY_KIND.efd:
290
+ // These modes report the aggregate result across attempts.
291
+ return hasFailedAllRetries ? 'fail' : 'pass'
292
+ case FINAL_STATUS_RETRY_KIND.atf:
293
+ // Attempt-to-fix only passes if every execution passed.
294
+ return hasPassedAllAtfRetries ? 'pass' : 'fail'
295
+ default:
296
+ return status
297
+ }
298
+ }
299
+
243
300
  class CypressPlugin {
244
301
  _isInit = false
245
302
  testEnvironmentMetadata = getTestEnvironmentMetadata(TEST_FRAMEWORK_NAME)
@@ -349,6 +406,19 @@ class CypressPlugin {
349
406
  this.rumFlushWaitMillis = undefined
350
407
  this._pendingRequestErrorTags = []
351
408
  this.libraryConfigurationPromise = undefined
409
+ this._timeOrigin = 0
410
+ this._perfOrigin = 0
411
+ }
412
+
413
+ /**
414
+ * Returns the current time in the same coordinate system used by span
415
+ * start/finish. Captured at session span creation so it shares the same
416
+ * epoch as the trace without reaching into span internals.
417
+ *
418
+ * @returns {number}
419
+ */
420
+ _now () {
421
+ return this._timeOrigin + performance.now() - this._perfOrigin
352
422
  }
353
423
 
354
424
  // Init function returns a promise that resolves with the Cypress configuration
@@ -665,6 +735,13 @@ class CypressPlugin {
665
735
  this.tracer._tracer._exporter.addMetadataTags(metadataTags)
666
736
  }
667
737
 
738
+ // Capture time references that match what startSpan records internally
739
+ // (trace.startTime = Date.now(), trace.ticks = performance.now()).
740
+ // This lets _now() produce values in the same coordinate system as
741
+ // span._startTime without accessing span internals.
742
+ this._timeOrigin = dateNow()
743
+ this._perfOrigin = performance.now()
744
+
668
745
  this.testSessionSpan = this.tracer.startSpan(`${TEST_FRAMEWORK_NAME}.test_session`, {
669
746
  childOf,
670
747
  tags: {
@@ -797,6 +874,7 @@ class CypressPlugin {
797
874
  : spec.relative
798
875
 
799
876
  const skippedTestSpan = this.getTestSpan({ testName: cypressTestName, testSuite: spec.relative, testSourceFile })
877
+ skippedTestSpan.setTag(TEST_FINAL_STATUS, 'skip')
800
878
 
801
879
  skippedTestSpan.setTag(TEST_STATUS, 'skip')
802
880
  if (isSkippedByItr) {
@@ -886,6 +964,36 @@ class CypressPlugin {
886
964
  finishedTest.testSpan.setTag(TEST_CODE_OWNERS, codeOwners)
887
965
  }
888
966
 
967
+ // We can check if this is the last attempt regardless of the retry mechanism
968
+ const isLastAttempt = attemptIndex === finishedTestAttempts.length - 1
969
+ if (isLastAttempt) {
970
+ const testSpanTags = finishedTest.testSpan.context()._tags
971
+ const retryKind = getFinalStatusRetryKind({
972
+ finishedTest,
973
+ finishedTestAttempts,
974
+ flakyTestRetriesCount: this.flakyTestRetriesCount,
975
+ })
976
+
977
+ const hasFailedAllRetries = testSpanTags[TEST_HAS_FAILED_ALL_RETRIES] === 'true'
978
+ const hasPassedAllAtfRetries =
979
+ testSpanTags[TEST_MANAGEMENT_ATTEMPT_TO_FIX_PASSED] === 'true'
980
+ const isQuarantined = testSpanTags[TEST_MANAGEMENT_IS_QUARANTINED] === 'true'
981
+ const isDisabled = testSpanTags[TEST_MANAGEMENT_IS_DISABLED] === 'true'
982
+
983
+ const finalStatus = getFinalStatus({
984
+ status: cypressTestStatus,
985
+ retryKind,
986
+ hasFailedAllRetries,
987
+ hasPassedAllAtfRetries,
988
+ isQuarantined,
989
+ isDisabled,
990
+ })
991
+
992
+ if (finalStatus) {
993
+ finishedTest.testSpan.setTag(TEST_FINAL_STATUS, finalStatus)
994
+ }
995
+ }
996
+
889
997
  finishedTest.testSpan.finish(finishedTest.finishTime)
890
998
  }
891
999
  }
@@ -1103,7 +1211,7 @@ class CypressPlugin {
1103
1211
  const finishedTest = {
1104
1212
  testName,
1105
1213
  testStatus,
1106
- finishTime: this.activeTestSpan._getTime(), // we store the finish time here
1214
+ finishTime: this._now(),
1107
1215
  testSpan: this.activeTestSpan,
1108
1216
  isEfdRetry,
1109
1217
  isAttemptToFix,
@@ -1,11 +1,68 @@
1
1
  'use strict'
2
2
 
3
+ const NoopTracer = require('../../dd-trace/src/noop/tracer')
3
4
  const Plugin = require('../../dd-trace/src/plugins/plugin')
4
5
 
5
- // Cypress plugin does not patch any library. This is just a placeholder to
6
- // follow the structure of the plugins
6
+ /**
7
+ * Cypress plugin handles setup-node-events from the instrumentation layer
8
+ * via a diagnostic channel, keeping the instrumentation free of tracer references.
9
+ */
7
10
  class CypressPlugin extends Plugin {
8
11
  static id = 'cypress'
12
+
13
+ constructor (...args) {
14
+ super(...args)
15
+
16
+ this.addSub('ci:cypress:setup-node-events', (payload) => {
17
+ // Bail out if the tracer failed to init (e.g. invalid DD_SITE).
18
+ // Mirrors the guard in the manual plugin entrypoint (plugin.js).
19
+ if (this._tracer._tracer instanceof NoopTracer) return
20
+
21
+ const { on, config, userAfterSpecHandlers, userAfterRunHandlers, cleanupWrapper } = payload
22
+
23
+ const registerAfterRunWithCleanup = (afterRunHandler) => {
24
+ on('after:run', (results) => {
25
+ const chain = userAfterRunHandlers.reduce(
26
+ (p, h) => p.then(() => h(results)),
27
+ Promise.resolve()
28
+ )
29
+ if (afterRunHandler) {
30
+ return chain.then(() => afterRunHandler(results)).finally(cleanupWrapper)
31
+ }
32
+ return chain.finally(cleanupWrapper)
33
+ })
34
+ }
35
+
36
+ const cypressPlugin = require('./cypress-plugin')
37
+
38
+ if (cypressPlugin._isInit) {
39
+ // Already initialized by manual plugin call — just chain user handlers
40
+ for (const h of userAfterSpecHandlers) on('after:spec', h)
41
+ registerAfterRunWithCleanup()
42
+ payload.registered = true
43
+ return
44
+ }
45
+
46
+ on('before:run', cypressPlugin.beforeRun.bind(cypressPlugin))
47
+
48
+ on('after:spec', (spec, results) => {
49
+ const chain = userAfterSpecHandlers.reduce(
50
+ (p, h) => p.then(() => h(spec, results)),
51
+ Promise.resolve()
52
+ )
53
+ return chain.then(() => cypressPlugin.afterSpec(spec, results))
54
+ })
55
+
56
+ registerAfterRunWithCleanup((results) => cypressPlugin.afterRun(results))
57
+
58
+ on('task', cypressPlugin.getTasks())
59
+
60
+ payload.registered = true
61
+ // cypressPlugin.init expects the proxy tracer (with ._tracer._exporter),
62
+ // not the unwrapped internal tracer that this.tracer returns.
63
+ payload.configPromise = Promise.resolve(cypressPlugin.init(this._tracer, config)).then(() => config)
64
+ })
65
+ }
9
66
  }
10
67
 
11
68
  module.exports = CypressPlugin
@@ -11,7 +11,7 @@ class FsPlugin extends TracingPlugin {
11
11
  }
12
12
 
13
13
  bindStart (ctx) {
14
- if (!this.activeSpan) return this.skip()
14
+ if (!this.activeSpan) return { noop: true }
15
15
 
16
16
  const { operation, ...params } = ctx
17
17
  const lowerOp = operation.toLowerCase()
@@ -123,7 +123,8 @@ class GoogleCloudPubsubConsumerPlugin extends ConsumerPlugin {
123
123
  const topicName = topic?.slice(topic.lastIndexOf('/') + 1) ??
124
124
  subscription.name.slice(subscription.name.lastIndexOf('/') + 1)
125
125
  const baseService = this.tracer._service || 'unknown'
126
- const serviceName = this.config.service || `${baseService}-pubsub`
126
+ const serviceName = this.config.service || { name: `${baseService}-pubsub`, source: baseService }
127
+
127
128
  const meta = {
128
129
  'gcloud.project_id': subscription.pubsub.projectId,
129
130
  'pubsub.topic': topic,
@@ -145,17 +145,12 @@ class GoogleCloudPubsubPushSubscriptionPlugin extends TracingPlugin {
145
145
  const subscriptionName = subscription?.slice(subscription.lastIndexOf('/') + 1) ?? subscription
146
146
  const publishStartTime = attrs['x-dd-publish-start-time']
147
147
  const startTime = publishStartTime ? Number.parseInt(publishStartTime, 10) : undefined
148
-
149
- // Get the base service name and construct the pubsub service override
150
- const baseService = this.tracer._service
151
- const serviceOverride = this.config.service ?? `${baseService}-pubsub`
152
-
153
148
  // Use this.startSpan() which automatically activates the span
154
149
  const span = this.startSpan('pubsub.push.receive', {
155
150
  childOf: parentContext,
156
151
  startTime,
157
152
  kind: 'consumer',
158
- service: serviceOverride,
153
+ service: this.config.service || { name: `${this.tracer._service}-pubsub`, source: this.tracer._service },
159
154
  meta: {
160
155
  component: 'google-cloud-pubsub',
161
156
  'pubsub.method': 'receive',
@@ -163,7 +158,7 @@ class GoogleCloudPubsubPushSubscriptionPlugin extends TracingPlugin {
163
158
  'pubsub.message_id': message.messageId,
164
159
  'pubsub.subscription_type': 'push',
165
160
  'pubsub.topic': topicName,
166
- '_dd.base_service': baseService,
161
+ '_dd.base_service': this.tracer._service,
167
162
  '_dd.serviceoverride.type': 'integration',
168
163
  'resource.name': `Push Subscription ${subscriptionName}`,
169
164
  },
@@ -40,10 +40,10 @@ class HttpClientPlugin extends ClientPlugin {
40
40
  const span = this.startSpan(this.operationName(), {
41
41
  childOf,
42
42
  integrationName: this.constructor.id,
43
+ service: this.serviceName({ pluginConfig: this.config, sessionDetails: extractSessionDetails(options) }),
43
44
  meta: {
44
45
  [COMPONENT]: this.constructor.id,
45
46
  'span.kind': 'client',
46
- 'service.name': this.serviceName({ pluginConfig: this.config, sessionDetails: extractSessionDetails(options) }),
47
47
  'resource.name': method,
48
48
  'span.type': 'http',
49
49
  'http.method': method,
@@ -4,7 +4,7 @@ const ServerPlugin = require('../../dd-trace/src/plugins/server')
4
4
  const { storage } = require('../../datadog-core')
5
5
  const web = require('../../dd-trace/src/plugins/util/web')
6
6
  const { incomingHttpRequestStart, incomingHttpRequestEnd } = require('../../dd-trace/src/appsec/channels')
7
- const { COMPONENT } = require('../../dd-trace/src/constants')
7
+ const { COMPONENT, SVC_SRC_KEY } = require('../../dd-trace/src/constants')
8
8
 
9
9
  class HttpServerPlugin extends ServerPlugin {
10
10
  static id = 'http'
@@ -18,16 +18,24 @@ class HttpServerPlugin extends ServerPlugin {
18
18
 
19
19
  start ({ req, res, abortController }) {
20
20
  let store = storage('legacy').getStore()
21
+ const { name: schemaServiceName, source: schemaServiceSource } = this.serviceName()
22
+ const service = this.config.service || schemaServiceName
23
+ const serviceSource = (this.config.service && service !== this.tracer._service)
24
+ ? 'opt.plugin'
25
+ : (service === this.tracer._service ? undefined : schemaServiceSource)
21
26
  const span = web.startSpan(
22
27
  this.tracer,
23
28
  {
24
29
  ...this.config,
25
- service: this.config.service || this.serviceName(),
30
+ service,
26
31
  },
27
32
  req,
28
33
  res,
29
34
  this.operationName()
30
35
  )
36
+ if (serviceSource !== undefined) {
37
+ span.setTag(SVC_SRC_KEY, serviceSource)
38
+ }
31
39
  span.setTag(COMPONENT, this.constructor.id)
32
40
  span._integrationName = this.constructor.id
33
41
 
@@ -41,10 +41,10 @@ class Http2ClientPlugin extends ClientPlugin {
41
41
  const span = this.startSpan(this.operationName(), {
42
42
  childOf,
43
43
  integrationName: this.constructor.id,
44
+ service: this.serviceName({ pluginConfig: this.config, sessionDetails }),
44
45
  meta: {
45
46
  [COMPONENT]: this.constructor.id,
46
47
  [SPAN_KIND]: CLIENT,
47
- 'service.name': this.serviceName({ pluginConfig: this.config, sessionDetails }),
48
48
  'resource.name': method,
49
49
  'span.type': 'http',
50
50
  'http.method': method,
@@ -4,7 +4,7 @@
4
4
 
5
5
  const ServerPlugin = require('../../dd-trace/src/plugins/server')
6
6
  const web = require('../../dd-trace/src/plugins/util/web')
7
- const { COMPONENT } = require('../../dd-trace/src/constants')
7
+ const { COMPONENT, SVC_SRC_KEY } = require('../../dd-trace/src/constants')
8
8
 
9
9
  class Http2ServerPlugin extends ServerPlugin {
10
10
  constructor (tracer, config) {
@@ -19,17 +19,25 @@ class Http2ServerPlugin extends ServerPlugin {
19
19
  bindStart (ctx) {
20
20
  const { req, res } = ctx
21
21
 
22
+ const { name: schemaServiceName, source: schemaServiceSource } = this.serviceName()
23
+ const service = this.config.service || schemaServiceName
24
+ const serviceSource = (this.config.service && service !== this.tracer._service)
25
+ ? 'opt.plugin'
26
+ : (service === this.tracer._service ? undefined : schemaServiceSource)
22
27
  const span = web.startSpan(
23
28
  this.tracer,
24
29
  {
25
30
  ...this.config,
26
- service: this.config.service || this.serviceName(),
31
+ service,
27
32
  },
28
33
  req,
29
34
  res,
30
35
  this.operationName(),
31
36
  ctx
32
37
  )
38
+ if (serviceSource !== undefined) {
39
+ span.setTag(SVC_SRC_KEY, serviceSource)
40
+ }
33
41
 
34
42
  span.setTag(COMPONENT, this.constructor.id)
35
43
  span._integrationName = this.constructor.id
@@ -35,9 +35,9 @@ class MongodbCorePlugin extends DatabasePlugin {
35
35
  }
36
36
  const query = getQuery(ops)
37
37
  const resource = truncate(getResource(this, ns, query, name))
38
- const service = this.serviceName({ pluginConfig: this.config })
38
+ const serviceResult = this.serviceName({ pluginConfig: this.config })
39
39
  const span = this.startSpan(this.operationName(), {
40
- service,
40
+ service: serviceResult,
41
41
  resource,
42
42
  type: 'mongodb',
43
43
  kind: 'client',
@@ -49,7 +49,7 @@ class MongodbCorePlugin extends DatabasePlugin {
49
49
  'out.port': options.port,
50
50
  },
51
51
  }, ctx)
52
- const comment = this.injectDbmComment(span, ops.comment, service)
52
+ const comment = this.injectDbmComment(span, ops.comment, serviceResult.name)
53
53
  if (comment) {
54
54
  ops.comment = comment
55
55
  }
@@ -33,7 +33,7 @@ class MySQLPlugin extends DatabasePlugin {
33
33
  [CLIENT_PORT_KEY]: ctx.conf.port,
34
34
  },
35
35
  }, ctx)
36
- ctx.sql = this.injectDbmQuery(span, ctx.sql, service)
36
+ ctx.sql = this.injectDbmQuery(span, ctx.sql, service.name)
37
37
 
38
38
  return ctx.currentStore
39
39
  }
@@ -3,7 +3,7 @@
3
3
  const ServerPlugin = require('../../dd-trace/src/plugins/server')
4
4
  const { storage } = require('../../datadog-core')
5
5
  const analyticsSampler = require('../../dd-trace/src/analytics_sampler')
6
- const { COMPONENT } = require('../../dd-trace/src/constants')
6
+ const { COMPONENT, SVC_SRC_KEY } = require('../../dd-trace/src/constants')
7
7
  const web = require('../../dd-trace/src/plugins/util/web')
8
8
 
9
9
  const errorPages = new Set(['/404', '/500', '/_error', '/_not-found', '/_not-found/page'])
@@ -19,15 +19,21 @@ class NextPlugin extends ServerPlugin {
19
19
  bindStart ({ req, res }) {
20
20
  const store = storage('legacy').getStore()
21
21
  const childOf = store ? store.span : store
22
+ const { name: schemaServiceName, source: schemaServiceSource } = this.serviceName()
23
+ const serviceName = this.config.service || schemaServiceName
24
+ let serviceSource = this.config.service ? 'opt.plugin' : schemaServiceSource
25
+ if (!serviceName || serviceName === this.tracer._service) serviceSource = undefined
26
+
22
27
  const span = this.tracer.startSpan(this.operationName(), {
23
28
  childOf,
24
29
  tags: {
25
30
  [COMPONENT]: this.constructor.id,
26
- 'service.name': this.config.service || this.serviceName(),
31
+ 'service.name': serviceName,
27
32
  'resource.name': req.method,
28
33
  'span.type': 'web',
29
34
  'span.kind': 'server',
30
35
  'http.method': req.method,
36
+ ...(serviceSource === undefined ? {} : { [SVC_SRC_KEY]: serviceSource }),
31
37
  },
32
38
  integrationName: this.constructor.id,
33
39
  })
@@ -32,7 +32,7 @@ class PGPlugin extends DatabasePlugin {
32
32
  span.setTag('db.stream', 1)
33
33
  }
34
34
 
35
- query.__ddInjectableQuery = this.injectDbmQuery(span, query.text, service, !!query.name)
35
+ query.__ddInjectableQuery = this.injectDbmQuery(span, query.text, service.name, !!query.name)
36
36
 
37
37
  return ctx.currentStore
38
38
  }
@@ -28,7 +28,7 @@ class TediousPlugin extends DatabasePlugin {
28
28
 
29
29
  // SQL Server includes comments when caching queries
30
30
  // For that reason we allow service mode but not full mode
31
- ctx.sql = this.injectDbmQuery(span, ctx.queryOrProcedure, service, true)
31
+ ctx.sql = this.injectDbmQuery(span, ctx.queryOrProcedure, service.name, true)
32
32
  return ctx.currentStore
33
33
  }
34
34
  }
@@ -47,7 +47,7 @@ class WSClosePlugin extends TracingPlugin {
47
47
  }
48
48
 
49
49
  if (isPeerClose && traceWebsocketMessagesInheritSampling && traceWebsocketMessagesSeparateTraces) {
50
- span.setTag('_dd.dm.service', spanTags['service.name'] || service)
50
+ span.setTag('_dd.dm.service', spanTags['service.name'] || service.name)
51
51
  span.setTag('_dd.dm.resource', spanTags['resource.name'] || `websocket ${path}`)
52
52
  span.setTag('_dd.dm.inherited', 1)
53
53
  }
@@ -48,7 +48,7 @@ class WSReceiverPlugin extends TracingPlugin {
48
48
  }, ctx)
49
49
 
50
50
  if (traceWebsocketMessagesInheritSampling && traceWebsocketMessagesSeparateTraces) {
51
- span.setTag('_dd.dm.service', spanTags['service.name'] || service)
51
+ span.setTag('_dd.dm.service', spanTags['service.name'] || service.name)
52
52
  span.setTag('_dd.dm.resource', spanTags['resource.name'] || `websocket ${path}`)
53
53
  span.setTag('_dd.dm.inherited', 1)
54
54
  }
@@ -25,11 +25,12 @@ const appsecMetrics = telemetryMetrics.manager.namespace('appsec')
25
25
  const ALLOW = 'ALLOW'
26
26
 
27
27
  class AIGuardAbortError extends Error {
28
- constructor (reason, tags, sds) {
28
+ constructor (reason, tags, tagProbs, sds) {
29
29
  super(reason)
30
30
  this.name = 'AIGuardAbortError'
31
31
  this.reason = reason
32
32
  this.tags = tags
33
+ this.tagProbabilities = tagProbs
33
34
  this.sds = sds || []
34
35
  }
35
36
  }
@@ -188,38 +189,37 @@ class AIGuard extends NoopAIGuard {
188
189
  `AI Guard service call failed, status ${response.status}`,
189
190
  { errors: response.body?.errors })
190
191
  }
191
- let action, reason, tags, sdsFindings, blockingEnabled
192
- try {
193
- const attr = response.body.data.attributes
194
- if (!attr.action) {
195
- throw new Error('Action missing from response')
196
- }
197
- action = attr.action
198
- reason = attr.reason
199
- tags = attr.tags
200
- sdsFindings = attr.sds_findings || []
201
- blockingEnabled = attr.is_blocking_enabled ?? false
202
- } catch (e) {
192
+ const attr = response.body?.data?.attributes
193
+ if (!attr?.action) {
203
194
  appsecMetrics.count(AI_GUARD_TELEMETRY_REQUESTS, { error: true }).inc(1)
204
- throw new AIGuardClientError(`AI Guard service returned unexpected response : ${response.body}`, { cause: e })
195
+ throw new AIGuardClientError(`AI Guard service returned unexpected response : ${response.body}`)
196
+ }
197
+ const action = attr.action
198
+ const reason = attr.reason
199
+ const tags = attr.tags ?? []
200
+ if (tags.length > 0) {
201
+ metaStruct.attack_categories = tags
202
+ }
203
+ const sdsFindings = attr.sds_findings ?? []
204
+ if (sdsFindings.length > 0) {
205
+ metaStruct.sds = sdsFindings
206
+ }
207
+ const tagProbabilities = attr.tag_probs ?? {}
208
+ if (attr.tag_probs) {
209
+ metaStruct.tag_probs = tagProbabilities
205
210
  }
211
+ const blockingEnabled = attr.is_blocking_enabled ?? false
206
212
  const shouldBlock = block && blockingEnabled && action !== ALLOW
207
213
  appsecMetrics.count(AI_GUARD_TELEMETRY_REQUESTS, { action, error: false, block: shouldBlock }).inc(1)
208
214
  span.setTag(AI_GUARD_ACTION_TAG_KEY, action)
209
215
  if (reason) {
210
216
  span.setTag(AI_GUARD_REASON_TAG_KEY, reason)
211
217
  }
212
- if (tags?.length > 0) {
213
- metaStruct.attack_categories = tags
214
- }
215
- if (sdsFindings?.length > 0) {
216
- metaStruct.sds = sdsFindings
217
- }
218
218
  if (shouldBlock) {
219
219
  span.setTag(AI_GUARD_BLOCKED_TAG_KEY, 'true')
220
- throw new AIGuardAbortError(reason, tags, sdsFindings)
220
+ throw new AIGuardAbortError(reason, tags, tagProbabilities, sdsFindings)
221
221
  }
222
- return { action, reason, tags, sds: sdsFindings }
222
+ return { action, reason, tags, tagProbabilities, sds: sdsFindings }
223
223
  })
224
224
  }
225
225
  }
@@ -1,11 +1,12 @@
1
1
  /* eslint-disable @stylistic/max-len */
2
+ /* eslint-disable @stylistic/quotes */
2
3
  'use strict'
3
4
 
4
- const html = '<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>You\'ve been blocked</title><style>a,body,div,html,span{margin:0;padding:0;border:0;font-size:100%;font:inherit;vertical-align:baseline}body{background:-webkit-radial-gradient(26% 19%,circle,#fff,#f4f7f9);background:radial-gradient(circle at 26% 19%,#fff,#f4f7f9);display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-ms-flex-line-pack:center;align-content:center;width:100%;min-height:100vh;line-height:1;flex-direction:column}p{display:block}main{text-align:center;flex:1;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-ms-flex-line-pack:center;align-content:center;flex-direction:column}p{font-size:18px;line-height:normal;color:#646464;font-family:sans-serif;font-weight:400}a{color:#4842b7}footer{width:100%;text-align:center}footer p{font-size:16px}</style></head><body><main><p>Sorry, you cannot access this page. Please contact the customer service team.</p></main><footer><p>Security provided by <a href="https://www.datadoghq.com/product/security-platform/application-security-monitoring/" target="_blank">Datadog</a></p></footer></body></html>'
5
+ const html = `<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>You've been blocked</title><style>a,body,div,html,span{margin:0;padding:0;border:0;font-size:100%;font:inherit;vertical-align:baseline}body{background:-webkit-radial-gradient(26% 19%,circle,#fff,#f4f7f9);background:radial-gradient(circle at 26% 19%,#fff,#f4f7f9);display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-ms-flex-line-pack:center;align-content:center;width:100%;min-height:100vh;line-height:1;flex-direction:column}p{display:block}main{text-align:center;flex:1;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-ms-flex-line-pack:center;align-content:center;flex-direction:column}p{font-size:18px;line-height:normal;color:#646464;font-family:sans-serif;font-weight:400}a{color:#4842b7}footer{width:100%;text-align:center}footer p{font-size:16px}.security-response-id{font-size:14px;color:#999;margin-top:20px;font-family:monospace}</style></head><body><main><p>Sorry, you cannot access this page. Please contact the customer service team.</p><p class="security-response-id">Security Response ID: [security_response_id]</p></main><footer><p>Security provided by <a href="https://www.datadoghq.com/product/security-platform/application-security-monitoring/" target="_blank" rel="noopener noreferrer">Datadog</a></p></footer></body></html>`
5
6
 
6
- const json = '{"errors":[{"title":"You\'ve been blocked","detail":"Sorry, you cannot access this page. Please contact the customer service team. Security provided by Datadog."}]}'
7
+ const json = `{"errors":[{"title":"You've been blocked","detail":"Sorry, you cannot access this page. Please contact the customer service team. Security provided by Datadog."}],"security_response_id":"[security_response_id]"}`
7
8
 
8
- const graphqlJson = '{"errors":[{"message":"You\'ve been blocked","extensions":{"detail":"Sorry, you cannot perform this operation. Please contact the customer service team. Security provided by Datadog."}}]}'
9
+ const graphqlJson = `{"errors":[{"message":"You've been blocked","extensions":{"detail":"Sorry, you cannot perform this operation. Please contact the customer service team. Security provided by Datadog."}}],"security_response_id":"[security_response_id]"}`
9
10
 
10
11
  module.exports = {
11
12
  html,