dd-trace 5.25.0 → 5.26.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 (67) hide show
  1. package/LICENSE-3rdparty.csv +2 -0
  2. package/index.d.ts +10 -8
  3. package/init.js +60 -47
  4. package/package.json +5 -2
  5. package/packages/datadog-core/index.js +1 -3
  6. package/packages/datadog-core/src/storage.js +21 -0
  7. package/packages/datadog-instrumentations/src/express.js +1 -1
  8. package/packages/datadog-instrumentations/src/handlebars.js +40 -0
  9. package/packages/datadog-instrumentations/src/helpers/hooks.js +2 -0
  10. package/packages/datadog-instrumentations/src/jest.js +6 -2
  11. package/packages/datadog-instrumentations/src/pug.js +23 -0
  12. package/packages/datadog-instrumentations/src/router.js +2 -3
  13. package/packages/datadog-plugin-amqplib/src/consumer.js +2 -1
  14. package/packages/datadog-plugin-aws-sdk/src/base.js +5 -0
  15. package/packages/datadog-plugin-aws-sdk/src/services/kinesis.js +9 -7
  16. package/packages/datadog-plugin-aws-sdk/src/services/s3.js +34 -0
  17. package/packages/datadog-plugin-aws-sdk/src/services/sqs.js +10 -9
  18. package/packages/datadog-plugin-cypress/src/cypress-plugin.js +59 -45
  19. package/packages/datadog-plugin-cypress/src/support.js +1 -0
  20. package/packages/datadog-plugin-google-cloud-pubsub/src/consumer.js +2 -1
  21. package/packages/datadog-plugin-grpc/src/server.js +2 -1
  22. package/packages/datadog-plugin-http/src/client.js +42 -1
  23. package/packages/datadog-plugin-http2/src/client.js +26 -1
  24. package/packages/datadog-plugin-jest/src/index.js +2 -1
  25. package/packages/datadog-plugin-kafkajs/src/consumer.js +2 -1
  26. package/packages/datadog-plugin-mocha/src/index.js +1 -1
  27. package/packages/datadog-plugin-moleculer/src/server.js +2 -2
  28. package/packages/datadog-plugin-rhea/src/consumer.js +2 -1
  29. package/packages/datadog-plugin-vitest/src/index.js +2 -1
  30. package/packages/dd-trace/src/appsec/api_security_sampler.js +50 -27
  31. package/packages/dd-trace/src/appsec/iast/analyzers/analyzers.js +1 -0
  32. package/packages/dd-trace/src/appsec/iast/analyzers/header-injection-analyzer.js +33 -16
  33. package/packages/dd-trace/src/appsec/iast/analyzers/template-injection-analyzer.js +18 -0
  34. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-handler.js +3 -2
  35. package/packages/dd-trace/src/appsec/iast/vulnerabilities.js +1 -0
  36. package/packages/dd-trace/src/appsec/index.js +6 -6
  37. package/packages/dd-trace/src/appsec/recommended.json +353 -155
  38. package/packages/dd-trace/src/appsec/remote_config/capabilities.js +1 -1
  39. package/packages/dd-trace/src/appsec/remote_config/index.js +0 -7
  40. package/packages/dd-trace/src/appsec/reporter.js +1 -0
  41. package/packages/dd-trace/src/config.js +13 -4
  42. package/packages/dd-trace/src/constants.js +6 -1
  43. package/packages/dd-trace/src/crashtracking/crashtracker.js +98 -0
  44. package/packages/dd-trace/src/crashtracking/index.js +15 -0
  45. package/packages/dd-trace/src/crashtracking/noop.js +8 -0
  46. package/packages/dd-trace/src/llmobs/sdk.js +1 -1
  47. package/packages/dd-trace/src/llmobs/span_processor.js +1 -1
  48. package/packages/dd-trace/src/llmobs/writers/spans/base.js +3 -0
  49. package/packages/dd-trace/src/log/index.js +10 -13
  50. package/packages/dd-trace/src/log/log.js +52 -0
  51. package/packages/dd-trace/src/log/writer.js +50 -19
  52. package/packages/dd-trace/src/noop/span.js +1 -0
  53. package/packages/dd-trace/src/opentelemetry/span.js +15 -0
  54. package/packages/dd-trace/src/opentracing/propagation/text_map.js +35 -22
  55. package/packages/dd-trace/src/opentracing/span.js +14 -0
  56. package/packages/dd-trace/src/opentracing/span_context.js +1 -0
  57. package/packages/dd-trace/src/plugins/tracing.js +3 -3
  58. package/packages/dd-trace/src/plugins/util/inferred_proxy.js +121 -0
  59. package/packages/dd-trace/src/plugins/util/ip_extractor.js +0 -1
  60. package/packages/dd-trace/src/plugins/util/web.js +39 -11
  61. package/packages/dd-trace/src/proxy.js +5 -0
  62. package/packages/dd-trace/src/telemetry/logs/index.js +16 -11
  63. package/packages/dd-trace/src/telemetry/logs/log-collector.js +3 -8
  64. package/packages/dd-trace/src/telemetry/metrics.js +6 -1
  65. package/packages/dd-trace/src/util.js +16 -1
  66. package/version.js +4 -2
  67. /package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-analyzers/{code-injection-sensitive-analyzer.js → tainted-range-based-sensitive-analyzer.js} +0 -0
@@ -1,4 +1,5 @@
1
1
  Component,Origin,License,Copyright
2
+ require,@datadog/libdatadog,Apache license 2.0,Copyright 2024 Datadog Inc.
2
3
  require,@datadog/native-appsec,Apache license 2.0,Copyright 2018 Datadog Inc.
3
4
  require,@datadog/native-metrics,Apache license 2.0,Copyright 2018 Datadog Inc.
4
5
  require,@datadog/native-iast-rewriter,Apache license 2.0,Copyright 2018 Datadog Inc.
@@ -7,6 +8,7 @@ require,@datadog/pprof,Apache license 2.0,Copyright 2019 Google Inc.
7
8
  require,@datadog/sketches-js,Apache license 2.0,Copyright 2020 Datadog Inc.
8
9
  require,@opentelemetry/api,Apache license 2.0,Copyright OpenTelemetry Authors
9
10
  require,@opentelemetry/core,Apache license 2.0,Copyright OpenTelemetry Authors
11
+ require,@isaacs/ttlcache,ISC,Copyright (c) 2022-2023 - Isaac Z. Schlueter and Contributors
10
12
  require,crypto-randomuuid,MIT,Copyright 2021 Node.js Foundation and contributors
11
13
  require,dc-polyfill,MIT,Copyright 2023 Datadog Inc.
12
14
  require,ignore,MIT,Copyright 2013 Kael Zhang and contributors
package/index.d.ts CHANGED
@@ -662,19 +662,13 @@ declare namespace tracer {
662
662
  mode?: 'safe' | 'extended' | 'disabled'
663
663
  },
664
664
  /**
665
- * Configuration for Api Security sampling
665
+ * Configuration for Api Security
666
666
  */
667
667
  apiSecurity?: {
668
668
  /** Whether to enable Api Security.
669
- * @default false
669
+ * @default true
670
670
  */
671
671
  enabled?: boolean,
672
-
673
- /** Controls the request sampling rate (between 0 and 1) in which Api Security is triggered.
674
- * The value will be coerced back if it's outside of the 0-1 range.
675
- * @default 0.1
676
- */
677
- requestSampling?: number
678
672
  },
679
673
  /**
680
674
  * Configuration for RASP
@@ -1043,6 +1037,14 @@ declare namespace tracer {
1043
1037
  * @default code => code < 500
1044
1038
  */
1045
1039
  validateStatus?: (code: number) => boolean;
1040
+
1041
+ /**
1042
+ * Enable injection of tracing headers into requests signed with AWS IAM headers.
1043
+ * Disable this if you get AWS signature errors (HTTP 403).
1044
+ *
1045
+ * @default false
1046
+ */
1047
+ enablePropagationWithAmazonHeaders?: boolean;
1046
1048
  }
1047
1049
 
1048
1050
  /** @hidden */
package/init.js CHANGED
@@ -1,58 +1,71 @@
1
1
  'use strict'
2
2
 
3
- const path = require('path')
4
- const Module = require('module')
5
- const semver = require('semver')
6
- const log = require('./packages/dd-trace/src/log')
7
- const { isTrue } = require('./packages/dd-trace/src/util')
8
- const telemetry = require('./packages/dd-trace/src/telemetry/init-telemetry')
3
+ /* eslint-disable no-var */
9
4
 
10
- let initBailout = false
11
- let clobberBailout = false
12
- const forced = isTrue(process.env.DD_INJECT_FORCE)
5
+ var NODE_MAJOR = require('./version').NODE_MAJOR
13
6
 
14
- if (process.env.DD_INJECTION_ENABLED) {
15
- // If we're running via single-step install, and we're not in the app's
16
- // node_modules, then we should not initialize the tracer. This prevents
17
- // single-step-installed tracer from clobbering the manually-installed tracer.
18
- let resolvedInApp
19
- const entrypoint = process.argv[1]
20
- try {
21
- resolvedInApp = Module.createRequire(entrypoint).resolve('dd-trace')
22
- } catch (e) {
23
- // Ignore. If we can't resolve the module, we assume it's not in the app.
24
- }
25
- if (resolvedInApp) {
26
- const ourselves = path.join(__dirname, 'index.js')
27
- if (ourselves !== resolvedInApp) {
28
- clobberBailout = true
7
+ // We use several things that are not supported by older versions of Node:
8
+ // - AsyncLocalStorage
9
+ // - The `semver` module
10
+ // - dc-polyfill
11
+ // - Mocha (for testing)
12
+ // and probably others.
13
+ // TODO: Remove all these dependencies so that we can report telemetry.
14
+ if (NODE_MAJOR >= 12) {
15
+ var path = require('path')
16
+ var Module = require('module')
17
+ var semver = require('semver')
18
+ var log = require('./packages/dd-trace/src/log')
19
+ var isTrue = require('./packages/dd-trace/src/util').isTrue
20
+ var telemetry = require('./packages/dd-trace/src/telemetry/init-telemetry')
21
+
22
+ var initBailout = false
23
+ var clobberBailout = false
24
+ var forced = isTrue(process.env.DD_INJECT_FORCE)
25
+
26
+ if (process.env.DD_INJECTION_ENABLED) {
27
+ // If we're running via single-step install, and we're not in the app's
28
+ // node_modules, then we should not initialize the tracer. This prevents
29
+ // single-step-installed tracer from clobbering the manually-installed tracer.
30
+ var resolvedInApp
31
+ var entrypoint = process.argv[1]
32
+ try {
33
+ resolvedInApp = Module.createRequire(entrypoint).resolve('dd-trace')
34
+ } catch (e) {
35
+ // Ignore. If we can't resolve the module, we assume it's not in the app.
36
+ }
37
+ if (resolvedInApp) {
38
+ var ourselves = path.join(__dirname, 'index.js')
39
+ if (ourselves !== resolvedInApp) {
40
+ clobberBailout = true
41
+ }
29
42
  }
30
- }
31
43
 
32
- // If we're running via single-step install, and the runtime doesn't match
33
- // the engines field in package.json, then we should not initialize the tracer.
34
- if (!clobberBailout) {
35
- const { engines } = require('./package.json')
36
- const version = process.versions.node
37
- if (!semver.satisfies(version, engines.node)) {
38
- initBailout = true
39
- telemetry([
40
- { name: 'abort', tags: ['reason:incompatible_runtime'] },
41
- { name: 'abort.runtime', tags: [] }
42
- ])
43
- log.info('Aborting application instrumentation due to incompatible_runtime.')
44
- log.info(`Found incompatible runtime nodejs ${version}, Supported runtimes: nodejs ${engines.node}.`)
45
- if (forced) {
46
- log.info('DD_INJECT_FORCE enabled, allowing unsupported runtimes and continuing.')
44
+ // If we're running via single-step install, and the runtime doesn't match
45
+ // the engines field in package.json, then we should not initialize the tracer.
46
+ if (!clobberBailout) {
47
+ var engines = require('./package.json').engines
48
+ var version = process.versions.node
49
+ if (!semver.satisfies(version, engines.node)) {
50
+ initBailout = true
51
+ telemetry([
52
+ { name: 'abort', tags: ['reason:incompatible_runtime'] },
53
+ { name: 'abort.runtime', tags: [] }
54
+ ])
55
+ log.info('Aborting application instrumentation due to incompatible_runtime.')
56
+ log.info('Found incompatible runtime nodejs ' + version + ', Supported runtimes: nodejs ' + engines.node + '.')
57
+ if (forced) {
58
+ log.info('DD_INJECT_FORCE enabled, allowing unsupported runtimes and continuing.')
59
+ }
47
60
  }
48
61
  }
49
62
  }
50
- }
51
63
 
52
- if (!clobberBailout && (!initBailout || forced)) {
53
- const tracer = require('.')
54
- tracer.init()
55
- module.exports = tracer
56
- telemetry('complete', [`injection_forced:${forced && initBailout ? 'true' : 'false'}`])
57
- log.info('Application instrumentation bootstrapping complete')
64
+ if (!clobberBailout && (!initBailout || forced)) {
65
+ var tracer = require('.')
66
+ tracer.init()
67
+ module.exports = tracer
68
+ telemetry('complete', ['injection_forced:' + (forced && initBailout ? 'true' : 'false')])
69
+ log.info('Application instrumentation bootstrapping complete')
70
+ }
58
71
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dd-trace",
3
- "version": "5.25.0",
3
+ "version": "5.26.0",
4
4
  "description": "Datadog APM tracing client for JavaScript",
5
5
  "main": "index.js",
6
6
  "typings": "index.d.ts",
@@ -15,6 +15,7 @@
15
15
  "type:test": "cd docs && yarn && yarn test",
16
16
  "lint": "node scripts/check_licenses.js && eslint . && yarn audit",
17
17
  "lint-fix": "node scripts/check_licenses.js && eslint . --fix && yarn audit",
18
+ "release:proposal": "node scripts/release/proposal",
18
19
  "services": "node ./scripts/install_plugin_modules && node packages/dd-trace/test/setup/services",
19
20
  "test": "SERVICES=* yarn services && mocha --expose-gc 'packages/dd-trace/test/setup/node.js' 'packages/*/test/**/*.spec.js'",
20
21
  "test:appsec": "mocha -r \"packages/dd-trace/test/setup/mocha.js\" --exclude \"packages/dd-trace/test/appsec/**/*.plugin.spec.js\" \"packages/dd-trace/test/appsec/**/*.spec.js\"",
@@ -81,12 +82,14 @@
81
82
  "node": ">=18"
82
83
  },
83
84
  "dependencies": {
84
- "@datadog/native-appsec": "8.2.1",
85
+ "@datadog/libdatadog": "^0.2.2",
86
+ "@datadog/native-appsec": "8.3.0",
85
87
  "@datadog/native-iast-rewriter": "2.5.0",
86
88
  "@datadog/native-iast-taint-tracking": "3.2.0",
87
89
  "@datadog/native-metrics": "^3.0.1",
88
90
  "@datadog/pprof": "5.4.1",
89
91
  "@datadog/sketches-js": "^2.1.0",
92
+ "@isaacs/ttlcache": "^1.4.1",
90
93
  "@opentelemetry/api": ">=1.0.0 <1.9.0",
91
94
  "@opentelemetry/core": "^1.14.0",
92
95
  "crypto-randomuuid": "^1.0.0",
@@ -1,7 +1,5 @@
1
1
  'use strict'
2
2
 
3
- const { AsyncLocalStorage } = require('async_hooks')
4
-
5
- const storage = new AsyncLocalStorage()
3
+ const storage = require('./src/storage')
6
4
 
7
5
  module.exports = { storage }
@@ -0,0 +1,21 @@
1
+ 'use strict'
2
+
3
+ const { AsyncLocalStorage } = require('async_hooks')
4
+
5
+ const storages = Object.create(null)
6
+ const legacyStorage = new AsyncLocalStorage()
7
+
8
+ const storage = function (namespace) {
9
+ if (!storages[namespace]) {
10
+ storages[namespace] = new AsyncLocalStorage()
11
+ }
12
+ return storages[namespace]
13
+ }
14
+
15
+ storage.disable = legacyStorage.disable.bind(legacyStorage)
16
+ storage.enterWith = legacyStorage.enterWith.bind(legacyStorage)
17
+ storage.exit = legacyStorage.exit.bind(legacyStorage)
18
+ storage.getStore = legacyStorage.getStore.bind(legacyStorage)
19
+ storage.run = legacyStorage.run.bind(legacyStorage)
20
+
21
+ module.exports = storage
@@ -29,7 +29,7 @@ function wrapResponseJson (json) {
29
29
  obj = arguments[1]
30
30
  }
31
31
 
32
- responseJsonChannel.publish({ req: this.req, body: obj })
32
+ responseJsonChannel.publish({ req: this.req, res: this, body: obj })
33
33
  }
34
34
 
35
35
  return json.apply(this, arguments)
@@ -0,0 +1,40 @@
1
+ 'use strict'
2
+
3
+ const shimmer = require('../../datadog-shimmer')
4
+ const { channel, addHook } = require('./helpers/instrument')
5
+
6
+ const handlebarsCompileCh = channel('datadog:handlebars:compile:start')
7
+ const handlebarsRegisterPartialCh = channel('datadog:handlebars:register-partial:start')
8
+
9
+ function wrapCompile (compile) {
10
+ return function wrappedCompile (source) {
11
+ if (handlebarsCompileCh.hasSubscribers) {
12
+ handlebarsCompileCh.publish({ source })
13
+ }
14
+
15
+ return compile.apply(this, arguments)
16
+ }
17
+ }
18
+
19
+ function wrapRegisterPartial (registerPartial) {
20
+ return function wrappedRegisterPartial (name, partial) {
21
+ if (handlebarsRegisterPartialCh.hasSubscribers) {
22
+ handlebarsRegisterPartialCh.publish({ partial })
23
+ }
24
+
25
+ return registerPartial.apply(this, arguments)
26
+ }
27
+ }
28
+
29
+ addHook({ name: 'handlebars', file: 'dist/cjs/handlebars/compiler/compiler.js', versions: ['>=4.0.0'] }, compiler => {
30
+ shimmer.wrap(compiler, 'compile', wrapCompile)
31
+ shimmer.wrap(compiler, 'precompile', wrapCompile)
32
+
33
+ return compiler
34
+ })
35
+
36
+ addHook({ name: 'handlebars', file: 'dist/cjs/handlebars/base.js', versions: ['>=4.0.0'] }, base => {
37
+ shimmer.wrap(base.HandlebarsEnvironment.prototype, 'registerPartial', wrapRegisterPartial)
38
+
39
+ return base
40
+ })
@@ -51,6 +51,7 @@ module.exports = {
51
51
  'generic-pool': () => require('../generic-pool'),
52
52
  graphql: () => require('../graphql'),
53
53
  grpc: () => require('../grpc'),
54
+ handlebars: () => require('../handlebars'),
54
55
  hapi: () => require('../hapi'),
55
56
  http: () => require('../http'),
56
57
  http2: () => require('../http2'),
@@ -105,6 +106,7 @@ module.exports = {
105
106
  'promise-js': () => require('../promise-js'),
106
107
  promise: () => require('../promise'),
107
108
  protobufjs: () => require('../protobufjs'),
109
+ pug: () => require('../pug'),
108
110
  q: () => require('../q'),
109
111
  qs: () => require('../qs'),
110
112
  redis: () => require('../redis'),
@@ -127,6 +127,7 @@ function getWrappedEnvironment (BaseEnvironment, jestVersion) {
127
127
 
128
128
  if (repositoryRoot) {
129
129
  this.testSourceFile = getTestSuitePath(context.testPath, repositoryRoot)
130
+ this.repositoryRoot = repositoryRoot
130
131
  }
131
132
 
132
133
  this.isEarlyFlakeDetectionEnabled = this.testEnvironmentOptions._ddIsEarlyFlakeDetectionEnabled
@@ -667,10 +668,13 @@ function jestAdapterWrapper (jestAdapter, jestVersion) {
667
668
  * controls whether coverage is reported.
668
669
  */
669
670
  if (environment.testEnvironmentOptions?._ddTestCodeCoverageEnabled) {
671
+ const root = environment.repositoryRoot || environment.rootDir
672
+
670
673
  const coverageFiles = getCoveredFilenamesFromCoverage(environment.global.__coverage__)
671
- .map(filename => getTestSuitePath(filename, environment.rootDir))
674
+ .map(filename => getTestSuitePath(filename, root))
675
+
672
676
  asyncResource.runInAsyncScope(() => {
673
- testSuiteCodeCoverageCh.publish({ coverageFiles, testSuite: environment.testSuite })
677
+ testSuiteCodeCoverageCh.publish({ coverageFiles, testSuite: environment.testSourceFile })
674
678
  })
675
679
  }
676
680
  testSuiteFinishCh.publish({ status, errorMessage })
@@ -0,0 +1,23 @@
1
+ 'use strict'
2
+
3
+ const shimmer = require('../../datadog-shimmer')
4
+ const { channel, addHook } = require('./helpers/instrument')
5
+
6
+ const pugCompileCh = channel('datadog:pug:compile:start')
7
+
8
+ function wrapCompile (compile) {
9
+ return function wrappedCompile (source) {
10
+ if (pugCompileCh.hasSubscribers) {
11
+ pugCompileCh.publish({ source })
12
+ }
13
+
14
+ return compile.apply(this, arguments)
15
+ }
16
+ }
17
+
18
+ addHook({ name: 'pug', versions: ['>=2.0.4'] }, compiler => {
19
+ shimmer.wrap(compiler, 'compile', wrapCompile)
20
+ shimmer.wrap(compiler, 'compileClientWithDependenciesTracked', wrapCompile)
21
+
22
+ return compiler
23
+ })
@@ -112,7 +112,6 @@ function createWrapRouterMethod (name) {
112
112
  path: pattern instanceof RegExp ? `(${pattern})` : pattern,
113
113
  test: layer => {
114
114
  const matchers = layerMatchers.get(layer)
115
-
116
115
  return !isFastStar(layer, matchers) &&
117
116
  !isFastSlash(layer, matchers) &&
118
117
  cachedPathToRegExp(pattern).test(layer.path)
@@ -121,7 +120,7 @@ function createWrapRouterMethod (name) {
121
120
  }
122
121
 
123
122
  function isFastStar (layer, matchers) {
124
- if (layer.regexp.fast_star !== undefined) {
123
+ if (layer.regexp?.fast_star !== undefined) {
125
124
  return layer.regexp.fast_star
126
125
  }
127
126
 
@@ -129,7 +128,7 @@ function createWrapRouterMethod (name) {
129
128
  }
130
129
 
131
130
  function isFastSlash (layer, matchers) {
132
- if (layer.regexp.fast_slash !== undefined) {
131
+ if (layer.regexp?.fast_slash !== undefined) {
133
132
  return layer.regexp.fast_slash
134
133
  }
135
134
 
@@ -26,7 +26,8 @@ class AmqplibConsumerPlugin extends ConsumerPlugin {
26
26
  'amqp.consumerTag': fields.consumerTag,
27
27
  'amqp.source': fields.source,
28
28
  'amqp.destination': fields.destination
29
- }
29
+ },
30
+ extractedLinks: childOf?._links
30
31
  })
31
32
 
32
33
  if (
@@ -93,6 +93,7 @@ class BaseAwsSdkPlugin extends ClientPlugin {
93
93
  this.responseExtractDSMContext(operation, params, response.data ?? response, span)
94
94
  }
95
95
  this.addResponseTags(span, response)
96
+ this.addSpanPointers(span, response)
96
97
  this.finish(span, response, response.error)
97
98
  })
98
99
  }
@@ -101,6 +102,10 @@ class BaseAwsSdkPlugin extends ClientPlugin {
101
102
  // implemented by subclasses, or not
102
103
  }
103
104
 
105
+ addSpanPointers (span, response) {
106
+ // Optionally implemented by subclasses, for services where we're unable to inject trace context
107
+ }
108
+
104
109
  operationFromRequest (request) {
105
110
  // can be overriden by subclasses
106
111
  return this.operationName({
@@ -42,7 +42,8 @@ class Kinesis extends BaseAwsSdkPlugin {
42
42
  {},
43
43
  this.requestTags.get(request) || {},
44
44
  { 'span.kind': 'server' }
45
- )
45
+ ),
46
+ extractedLinks: responseExtraction.maybeChildOf._links
46
47
  }
47
48
  span = plugin.tracer.startSpan('aws.response', options)
48
49
  this.enter(span, store)
@@ -113,14 +114,15 @@ class Kinesis extends BaseAwsSdkPlugin {
113
114
  response.Records.forEach(record => {
114
115
  const parsedAttributes = JSON.parse(Buffer.from(record.Data).toString())
115
116
 
116
- if (
117
- parsedAttributes?._datadog && streamName
118
- ) {
119
- const payloadSize = getSizeOrZero(record.Data)
117
+ const payloadSize = getSizeOrZero(record.Data)
118
+ if (parsedAttributes?._datadog) {
120
119
  this.tracer.decodeDataStreamsContext(parsedAttributes._datadog)
121
- this.tracer
122
- .setCheckpoint(['direction:in', `topic:${streamName}`, 'type:kinesis'], span, payloadSize)
123
120
  }
121
+ const tags = streamName
122
+ ? ['direction:in', `topic:${streamName}`, 'type:kinesis']
123
+ : ['direction:in', 'type:kinesis']
124
+ this.tracer
125
+ .setCheckpoint(tags, span, payloadSize)
124
126
  })
125
127
  }
126
128
 
@@ -1,6 +1,9 @@
1
1
  'use strict'
2
2
 
3
3
  const BaseAwsSdkPlugin = require('../base')
4
+ const log = require('../../../dd-trace/src/log')
5
+ const { generatePointerHash } = require('../../../dd-trace/src/util')
6
+ const { S3_PTR_KIND, SPAN_POINTER_DIRECTION } = require('../../../dd-trace/src/constants')
4
7
 
5
8
  class S3 extends BaseAwsSdkPlugin {
6
9
  static get id () { return 's3' }
@@ -18,6 +21,37 @@ class S3 extends BaseAwsSdkPlugin {
18
21
  bucketname: params.Bucket
19
22
  })
20
23
  }
24
+
25
+ addSpanPointers (span, response) {
26
+ const request = response?.request
27
+ const operationName = request?.operation
28
+ if (!['putObject', 'copyObject', 'completeMultipartUpload'].includes(operationName)) {
29
+ // We don't create span links for other S3 operations.
30
+ return
31
+ }
32
+
33
+ // AWS v2: https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/S3.html
34
+ // AWS v3: https://docs.aws.amazon.com/AWSJavaScriptSDK/v3/latest/client/s3/
35
+ const bucketName = request?.params?.Bucket
36
+ const objectKey = request?.params?.Key
37
+ let eTag =
38
+ response?.ETag || // v3 PutObject & CompleteMultipartUpload
39
+ response?.CopyObjectResult?.ETag || // v3 CopyObject
40
+ response?.data?.ETag || // v2 PutObject & CompleteMultipartUpload
41
+ response?.data?.CopyObjectResult?.ETag // v2 CopyObject
42
+
43
+ if (!bucketName || !objectKey || !eTag) {
44
+ log.debug('Unable to calculate span pointer hash because of missing parameters.')
45
+ return
46
+ }
47
+
48
+ // https://github.com/DataDog/dd-span-pointer-rules/blob/main/AWS/S3/Object/README.md
49
+ if (eTag.startsWith('"') && eTag.endsWith('"')) {
50
+ eTag = eTag.slice(1, -1)
51
+ }
52
+ const pointerHash = generatePointerHash([bucketName, objectKey, eTag])
53
+ span.addSpanPointer(S3_PTR_KIND, SPAN_POINTER_DIRECTION.DOWNSTREAM, pointerHash)
54
+ }
21
55
  }
22
56
 
23
57
  module.exports = S3
@@ -33,7 +33,8 @@ class Sqs extends BaseAwsSdkPlugin {
33
33
  {},
34
34
  this.requestTags.get(request) || {},
35
35
  { 'span.kind': 'server' }
36
- )
36
+ ),
37
+ extractedLinks: contextExtraction.datadogContext._links
37
38
  }
38
39
  parsedMessageAttributes = contextExtraction.parsedAttributes
39
40
  span = plugin.tracer.startSpan('aws.response', options)
@@ -42,7 +43,7 @@ class Sqs extends BaseAwsSdkPlugin {
42
43
  // extract DSM context after as we might not have a parent-child but may have a DSM context
43
44
 
44
45
  this.responseExtractDSMContext(
45
- request.operation, request.params, response, span || null, { parsedMessageAttributes }
46
+ request.operation, request.params, response, span || null, { parsedAttributes: parsedMessageAttributes }
46
47
  )
47
48
  })
48
49
 
@@ -195,16 +196,16 @@ class Sqs extends BaseAwsSdkPlugin {
195
196
  parsedAttributes = this.parseDatadogAttributes(message.MessageAttributes._datadog)
196
197
  }
197
198
  }
199
+ const payloadSize = getHeadersSize({
200
+ Body: message.Body,
201
+ MessageAttributes: message.MessageAttributes
202
+ })
203
+ const queue = params.QueueUrl.split('/').pop()
198
204
  if (parsedAttributes) {
199
- const payloadSize = getHeadersSize({
200
- Body: message.Body,
201
- MessageAttributes: message.MessageAttributes
202
- })
203
- const queue = params.QueueUrl.split('/').pop()
204
205
  this.tracer.decodeDataStreamsContext(parsedAttributes)
205
- this.tracer
206
- .setCheckpoint(['direction:in', `topic:${queue}`, 'type:sqs'], span, payloadSize)
207
206
  }
207
+ this.tracer
208
+ .setCheckpoint(['direction:in', `topic:${queue}`, 'type:sqs'], span, payloadSize)
208
209
  })
209
210
  }
210
211
 
@@ -654,55 +654,69 @@ class CypressPlugin {
654
654
  return this.activeTestSpan ? { traceId: this.activeTestSpan.context().toTraceId() } : {}
655
655
  },
656
656
  'dd:afterEach': ({ test, coverage }) => {
657
- const { state, error, isRUMActive, testSourceLine, testSuite, testName, isNew, isEfdRetry } = test
658
- if (this.activeTestSpan) {
659
- if (coverage && this.isCodeCoverageEnabled && this.tracer._tracer._exporter?.exportCoverage) {
660
- const coverageFiles = getCoveredFilenamesFromCoverage(coverage)
661
- const relativeCoverageFiles = coverageFiles.map(file => getTestSuitePath(file, this.rootDir))
662
- if (!relativeCoverageFiles.length) {
663
- incrementCountMetric(TELEMETRY_CODE_COVERAGE_EMPTY)
664
- }
665
- distributionMetric(TELEMETRY_CODE_COVERAGE_NUM_FILES, {}, relativeCoverageFiles.length)
666
- const { _traceId, _spanId } = this.testSuiteSpan.context()
667
- const formattedCoverage = {
668
- sessionId: _traceId,
669
- suiteId: _spanId,
670
- testId: this.activeTestSpan.context()._spanId,
671
- files: relativeCoverageFiles
672
- }
673
- this.tracer._tracer._exporter.exportCoverage(formattedCoverage)
674
- }
675
- const testStatus = CYPRESS_STATUS_TO_TEST_STATUS[state]
676
- this.activeTestSpan.setTag(TEST_STATUS, testStatus)
677
-
678
- if (error) {
679
- this.activeTestSpan.setTag('error', error)
680
- }
681
- if (isRUMActive) {
682
- this.activeTestSpan.setTag(TEST_IS_RUM_ACTIVE, 'true')
683
- }
684
- if (testSourceLine) {
685
- this.activeTestSpan.setTag(TEST_SOURCE_START, testSourceLine)
686
- }
687
- if (isNew) {
688
- this.activeTestSpan.setTag(TEST_IS_NEW, 'true')
689
- if (isEfdRetry) {
690
- this.activeTestSpan.setTag(TEST_IS_RETRY, 'true')
691
- }
657
+ if (!this.activeTestSpan) {
658
+ log.warn('There is no active test span in dd:afterEach handler')
659
+ return null
660
+ }
661
+ const {
662
+ state,
663
+ error,
664
+ isRUMActive,
665
+ testSourceLine,
666
+ testSuite,
667
+ testSuiteAbsolutePath,
668
+ testName,
669
+ isNew,
670
+ isEfdRetry
671
+ } = test
672
+ if (coverage && this.isCodeCoverageEnabled && this.tracer._tracer._exporter?.exportCoverage) {
673
+ const coverageFiles = getCoveredFilenamesFromCoverage(coverage)
674
+ const relativeCoverageFiles = [...coverageFiles, testSuiteAbsolutePath].map(
675
+ file => getTestSuitePath(file, this.repositoryRoot || this.rootDir)
676
+ )
677
+ if (!relativeCoverageFiles.length) {
678
+ incrementCountMetric(TELEMETRY_CODE_COVERAGE_EMPTY)
692
679
  }
693
- const finishedTest = {
694
- testName,
695
- testStatus,
696
- finishTime: this.activeTestSpan._getTime(), // we store the finish time here
697
- testSpan: this.activeTestSpan
680
+ distributionMetric(TELEMETRY_CODE_COVERAGE_NUM_FILES, {}, relativeCoverageFiles.length)
681
+ const { _traceId, _spanId } = this.testSuiteSpan.context()
682
+ const formattedCoverage = {
683
+ sessionId: _traceId,
684
+ suiteId: _spanId,
685
+ testId: this.activeTestSpan.context()._spanId,
686
+ files: relativeCoverageFiles
698
687
  }
699
- if (this.finishedTestsByFile[testSuite]) {
700
- this.finishedTestsByFile[testSuite].push(finishedTest)
701
- } else {
702
- this.finishedTestsByFile[testSuite] = [finishedTest]
688
+ this.tracer._tracer._exporter.exportCoverage(formattedCoverage)
689
+ }
690
+ const testStatus = CYPRESS_STATUS_TO_TEST_STATUS[state]
691
+ this.activeTestSpan.setTag(TEST_STATUS, testStatus)
692
+
693
+ if (error) {
694
+ this.activeTestSpan.setTag('error', error)
695
+ }
696
+ if (isRUMActive) {
697
+ this.activeTestSpan.setTag(TEST_IS_RUM_ACTIVE, 'true')
698
+ }
699
+ if (testSourceLine) {
700
+ this.activeTestSpan.setTag(TEST_SOURCE_START, testSourceLine)
701
+ }
702
+ if (isNew) {
703
+ this.activeTestSpan.setTag(TEST_IS_NEW, 'true')
704
+ if (isEfdRetry) {
705
+ this.activeTestSpan.setTag(TEST_IS_RETRY, 'true')
703
706
  }
704
- // test spans are finished at after:spec
705
707
  }
708
+ const finishedTest = {
709
+ testName,
710
+ testStatus,
711
+ finishTime: this.activeTestSpan._getTime(), // we store the finish time here
712
+ testSpan: this.activeTestSpan
713
+ }
714
+ if (this.finishedTestsByFile[testSuite]) {
715
+ this.finishedTestsByFile[testSuite].push(finishedTest)
716
+ } else {
717
+ this.finishedTestsByFile[testSuite] = [finishedTest]
718
+ }
719
+ // test spans are finished at after:spec
706
720
  this.ciVisEvent(TELEMETRY_EVENT_FINISHED, 'test', {
707
721
  hasCodeOwners: !!this.activeTestSpan.context()._tags[TEST_CODE_OWNERS],
708
722
  isNew,