dd-trace 5.67.0 → 5.68.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/LICENSE-3rdparty.csv +0 -3
- package/README.md +0 -2
- package/ci/init.js +52 -54
- package/ext/exporters.d.ts +2 -1
- package/ext/exporters.js +2 -1
- package/index.d.ts +47 -2
- package/initialize.mjs +1 -1
- package/package.json +8 -11
- package/packages/datadog-esbuild/index.js +56 -0
- package/packages/datadog-instrumentations/src/aws-sdk.js +42 -4
- package/packages/datadog-instrumentations/src/azure-functions.js +1 -1
- package/packages/datadog-instrumentations/src/azure-service-bus.js +1 -1
- package/packages/datadog-instrumentations/src/cassandra-driver.js +2 -2
- package/packages/datadog-instrumentations/src/connect.js +6 -2
- package/packages/datadog-instrumentations/src/cucumber.js +31 -6
- package/packages/datadog-instrumentations/src/express.js +5 -6
- package/packages/datadog-instrumentations/src/fastify.js +3 -3
- package/packages/datadog-instrumentations/src/helpers/hook.js +28 -15
- package/packages/datadog-instrumentations/src/helpers/hooks.js +2 -0
- package/packages/datadog-instrumentations/src/helpers/instrument.js +11 -2
- package/packages/datadog-instrumentations/src/helpers/register.js +10 -3
- package/packages/datadog-instrumentations/src/http2/client.js +1 -0
- package/packages/datadog-instrumentations/src/http2/server.js +0 -1
- package/packages/datadog-instrumentations/src/ioredis.js +12 -1
- package/packages/datadog-instrumentations/src/jest.js +48 -36
- package/packages/datadog-instrumentations/src/limitd-client.js +2 -1
- package/packages/datadog-instrumentations/src/mocha/main.js +15 -7
- package/packages/datadog-instrumentations/src/mocha/utils.js +3 -0
- package/packages/datadog-instrumentations/src/mongoose.js +2 -1
- package/packages/datadog-instrumentations/src/oracledb.js +19 -13
- package/packages/datadog-instrumentations/src/pg.js +9 -5
- package/packages/datadog-instrumentations/src/pino.js +18 -6
- package/packages/datadog-instrumentations/src/playwright.js +15 -1
- package/packages/datadog-instrumentations/src/sequelize.js +1 -1
- package/packages/datadog-instrumentations/src/vitest.js +155 -62
- package/packages/datadog-plugin-ai/src/tracing.js +3 -3
- package/packages/datadog-plugin-aws-sdk/src/base.js +23 -8
- package/packages/datadog-plugin-aws-sdk/src/services/bedrockruntime/tracing.js +2 -2
- package/packages/datadog-plugin-aws-sdk/src/services/bedrockruntime/utils.js +101 -2
- package/packages/datadog-plugin-aws-sdk/src/util.js +1 -1
- package/packages/datadog-plugin-cucumber/src/index.js +4 -56
- package/packages/datadog-plugin-cypress/src/cypress-plugin.js +6 -2
- package/packages/datadog-plugin-cypress/src/support.js +4 -0
- package/packages/datadog-plugin-express/src/code_origin.js +2 -2
- package/packages/datadog-plugin-fastify/src/code_origin.js +1 -2
- package/packages/datadog-plugin-jest/src/index.js +0 -21
- package/packages/datadog-plugin-mocha/src/index.js +3 -57
- package/packages/datadog-plugin-mongodb-core/src/index.js +20 -7
- package/packages/datadog-plugin-playwright/src/index.js +11 -5
- package/packages/datadog-plugin-vitest/src/index.js +5 -1
- package/packages/datadog-plugin-ws/src/close.js +1 -1
- package/packages/datadog-plugin-ws/src/producer.js +6 -1
- package/packages/datadog-plugin-ws/src/receiver.js +6 -1
- package/packages/dd-trace/src/appsec/iast/security-controls/parser.js +1 -1
- package/packages/dd-trace/src/appsec/telemetry/waf.js +2 -2
- package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +4 -4
- package/packages/dd-trace/src/ci-visibility/exporters/test-worker/index.js +11 -3
- package/packages/dd-trace/src/ci-visibility/exporters/test-worker/writer.js +10 -1
- package/packages/dd-trace/src/config.js +69 -304
- package/packages/dd-trace/src/config_defaults.js +186 -0
- package/packages/dd-trace/src/crashtracking/crashtracker.js +2 -1
- package/packages/dd-trace/src/datastreams/fnv.js +2 -2
- package/packages/dd-trace/src/datastreams/writer.js +3 -2
- package/packages/dd-trace/src/debugger/devtools_client/config.js +2 -1
- package/packages/dd-trace/src/dogstatsd.js +4 -3
- package/packages/dd-trace/src/encode/0.4.js +1 -5
- package/packages/dd-trace/src/exporter.js +1 -0
- package/packages/dd-trace/src/exporters/agent/index.js +3 -2
- package/packages/dd-trace/src/exporters/agent/writer.js +1 -1
- package/packages/dd-trace/src/exporters/common/agent-info-exporter.js +3 -2
- package/packages/dd-trace/src/exporters/common/request.js +2 -1
- package/packages/dd-trace/src/exporters/span-stats/index.js +3 -2
- package/packages/dd-trace/src/llmobs/constants/tags.js +2 -0
- package/packages/dd-trace/src/llmobs/plugins/ai/index.js +4 -3
- package/packages/dd-trace/src/llmobs/plugins/ai/util.js +12 -1
- package/packages/dd-trace/src/llmobs/plugins/bedrockruntime.js +40 -13
- package/packages/dd-trace/src/llmobs/plugins/openai.js +7 -1
- package/packages/dd-trace/src/llmobs/tagger.js +8 -0
- package/packages/dd-trace/src/llmobs/telemetry.js +2 -1
- package/packages/dd-trace/src/log/index.js +28 -17
- package/packages/dd-trace/src/log/log.js +29 -5
- package/packages/dd-trace/src/log/writer.js +5 -5
- package/packages/dd-trace/src/noop/span.js +1 -0
- package/packages/dd-trace/src/opentelemetry/span.js +14 -3
- package/packages/dd-trace/src/opentracing/span.js +18 -4
- package/packages/dd-trace/src/plugin_manager.js +20 -2
- package/packages/dd-trace/src/plugins/ci_plugin.js +97 -3
- package/packages/dd-trace/src/plugins/index.js +2 -0
- package/packages/dd-trace/src/plugins/util/git-cache.js +129 -0
- package/packages/dd-trace/src/plugins/util/git.js +40 -26
- package/packages/dd-trace/src/plugins/util/test.js +37 -27
- package/packages/dd-trace/src/plugins/util/web.js +1 -1
- package/packages/dd-trace/src/profiler.js +4 -1
- package/packages/dd-trace/src/profiling/config.js +73 -42
- package/packages/dd-trace/src/profiling/profiler.js +3 -1
- package/packages/dd-trace/src/profiling/profilers/events.js +3 -8
- package/packages/dd-trace/src/profiling/profilers/space.js +1 -0
- package/packages/dd-trace/src/profiling/profilers/wall.js +196 -117
- package/packages/dd-trace/src/remote_config/capabilities.js +5 -0
- package/packages/dd-trace/src/remote_config/manager.js +3 -2
- package/packages/dd-trace/src/startup-log.js +2 -1
- package/packages/dd-trace/src/supported-configurations.json +3 -0
- package/packages/dd-trace/src/telemetry/logs/index.js +2 -2
- package/register.js +1 -1
|
@@ -1,11 +1,9 @@
|
|
|
1
1
|
'use strict'
|
|
2
|
-
|
|
3
|
-
const coalesce = require('koalas')
|
|
4
2
|
const { inspect } = require('util')
|
|
5
3
|
const { isTrue } = require('../util')
|
|
6
4
|
const { traceChannel, debugChannel, infoChannel, warnChannel, errorChannel } = require('./channels')
|
|
7
5
|
const logWriter = require('./writer')
|
|
8
|
-
const { Log } = require('./log')
|
|
6
|
+
const { Log, LogConfig, NoTransmitError } = require('./log')
|
|
9
7
|
const { memoize } = require('./utils')
|
|
10
8
|
const { getEnvironmentVariable } = require('../config-helper')
|
|
11
9
|
|
|
@@ -15,7 +13,14 @@ const config = {
|
|
|
15
13
|
logLevel: 'debug'
|
|
16
14
|
}
|
|
17
15
|
|
|
16
|
+
// in most places where we know we want to mute a log we use log.error() directly
|
|
17
|
+
const NO_TRANSMIT = new LogConfig(false)
|
|
18
|
+
|
|
18
19
|
const log = {
|
|
20
|
+
LogConfig,
|
|
21
|
+
NO_TRANSMIT,
|
|
22
|
+
NoTransmitError,
|
|
23
|
+
|
|
19
24
|
/**
|
|
20
25
|
* @returns Read-only version of logging config. To modify config, call `log.use` and `log.toggle`
|
|
21
26
|
*/
|
|
@@ -92,18 +97,26 @@ const log = {
|
|
|
92
97
|
return this
|
|
93
98
|
},
|
|
94
99
|
|
|
100
|
+
errorWithoutTelemetry (...args) {
|
|
101
|
+
args.push(NO_TRANSMIT)
|
|
102
|
+
if (errorChannel.hasSubscribers) {
|
|
103
|
+
errorChannel.publish(Log.parse(...args))
|
|
104
|
+
}
|
|
105
|
+
return this
|
|
106
|
+
},
|
|
107
|
+
|
|
95
108
|
deprecate (code, message) {
|
|
96
109
|
return this._deprecate(code, message)
|
|
97
110
|
},
|
|
98
111
|
|
|
99
112
|
isEnabled (fleetStableConfigValue, localStableConfigValue) {
|
|
100
|
-
return isTrue(
|
|
101
|
-
fleetStableConfigValue
|
|
102
|
-
getEnvironmentVariable('DD_TRACE_DEBUG')
|
|
103
|
-
getEnvironmentVariable('OTEL_LOG_LEVEL') === 'debug' ||
|
|
104
|
-
localStableConfigValue
|
|
105
|
-
config.enabled
|
|
106
|
-
)
|
|
113
|
+
return isTrue(
|
|
114
|
+
(fleetStableConfigValue ??
|
|
115
|
+
getEnvironmentVariable('DD_TRACE_DEBUG') ??
|
|
116
|
+
getEnvironmentVariable('OTEL_LOG_LEVEL') === 'debug') ||
|
|
117
|
+
(localStableConfigValue ??
|
|
118
|
+
config.enabled)
|
|
119
|
+
)
|
|
107
120
|
},
|
|
108
121
|
|
|
109
122
|
getLogLevel (
|
|
@@ -111,14 +124,12 @@ const log = {
|
|
|
111
124
|
fleetStableConfigValue,
|
|
112
125
|
localStableConfigValue
|
|
113
126
|
) {
|
|
114
|
-
return
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
getEnvironmentVariable('
|
|
118
|
-
|
|
119
|
-
localStableConfigValue,
|
|
127
|
+
return optionsValue ??
|
|
128
|
+
fleetStableConfigValue ??
|
|
129
|
+
getEnvironmentVariable('DD_TRACE_LOG_LEVEL') ??
|
|
130
|
+
getEnvironmentVariable('OTEL_LOG_LEVEL') ??
|
|
131
|
+
localStableConfigValue ??
|
|
120
132
|
config.logLevel
|
|
121
|
-
)
|
|
122
133
|
}
|
|
123
134
|
}
|
|
124
135
|
|
|
@@ -2,12 +2,16 @@
|
|
|
2
2
|
|
|
3
3
|
const { format } = require('util')
|
|
4
4
|
|
|
5
|
+
// other times we produce an Error in a central location and log it several other places
|
|
6
|
+
class NoTransmitError extends Error {}
|
|
7
|
+
|
|
5
8
|
class Log {
|
|
6
|
-
constructor (message, args, cause, delegate) {
|
|
9
|
+
constructor (message, args, cause, delegate, sendViaTelemetry = true) {
|
|
7
10
|
this.message = message
|
|
8
11
|
this.args = args
|
|
9
12
|
this.cause = cause
|
|
10
13
|
this.delegate = delegate
|
|
14
|
+
this.sendViaTelemetry = sendViaTelemetry
|
|
11
15
|
}
|
|
12
16
|
|
|
13
17
|
get formatted () {
|
|
@@ -22,10 +26,18 @@ class Log {
|
|
|
22
26
|
|
|
23
27
|
static parse (...args) {
|
|
24
28
|
let message, cause, delegate
|
|
29
|
+
let sendViaTelemetry = true
|
|
30
|
+
|
|
31
|
+
const maybeLogConfig = args.at(-1)
|
|
32
|
+
if (maybeLogConfig instanceof LogConfig) {
|
|
33
|
+
args.pop()
|
|
34
|
+
sendViaTelemetry = maybeLogConfig.transmit
|
|
35
|
+
}
|
|
25
36
|
|
|
26
|
-
const
|
|
27
|
-
if (
|
|
37
|
+
const maybeError = args.at(-1)
|
|
38
|
+
if (maybeError && typeof maybeError === 'object' && maybeError.stack) { // maybeError instanceof Error?
|
|
28
39
|
cause = args.pop()
|
|
40
|
+
if (cause instanceof NoTransmitError) sendViaTelemetry = false
|
|
29
41
|
}
|
|
30
42
|
|
|
31
43
|
const firstArg = args.shift()
|
|
@@ -43,10 +55,22 @@ class Log {
|
|
|
43
55
|
message = String(firstArg)
|
|
44
56
|
}
|
|
45
57
|
|
|
46
|
-
return new Log(message, args, cause, delegate)
|
|
58
|
+
return new Log(message, args, cause, delegate, sendViaTelemetry)
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Pass instances of this class to logger methods when fine-grain control is needed
|
|
64
|
+
* @property {boolean} transmit - Whether to send the log via telemetry.
|
|
65
|
+
*/
|
|
66
|
+
class LogConfig {
|
|
67
|
+
constructor (transmit = true) {
|
|
68
|
+
this.transmit = transmit
|
|
47
69
|
}
|
|
48
70
|
}
|
|
49
71
|
|
|
50
72
|
module.exports = {
|
|
51
|
-
Log
|
|
73
|
+
Log,
|
|
74
|
+
LogConfig,
|
|
75
|
+
NoTransmitError,
|
|
52
76
|
}
|
|
@@ -75,12 +75,12 @@ function onError (err) {
|
|
|
75
75
|
// TODO: replace it with Error(message, { cause }) when cause has broad support
|
|
76
76
|
if (formatted) {
|
|
77
77
|
withNoop(() => {
|
|
78
|
-
const
|
|
78
|
+
const stackTraceLimitBackup = Error.stackTraceLimit
|
|
79
79
|
Error.stackTraceLimit = 0
|
|
80
|
-
const
|
|
81
|
-
Error.stackTraceLimit =
|
|
82
|
-
Error.captureStackTrace(
|
|
83
|
-
logger.error(
|
|
80
|
+
const newError = new Error(formatted)
|
|
81
|
+
Error.stackTraceLimit = stackTraceLimitBackup
|
|
82
|
+
Error.captureStackTrace(newError, stackTraceLimitFunction)
|
|
83
|
+
logger.error(newError)
|
|
84
84
|
})
|
|
85
85
|
}
|
|
86
86
|
if (cause) withNoop(() => logger.error(cause))
|
|
@@ -22,6 +22,7 @@ class NoopSpan {
|
|
|
22
22
|
setTag (key, value) { return this }
|
|
23
23
|
addTags (keyValueMap) { return this }
|
|
24
24
|
addLink (link) { return this }
|
|
25
|
+
addLinks (links) { return this }
|
|
25
26
|
addSpanPointer (ptrKind, ptrDir, ptrHash) { return this }
|
|
26
27
|
log () { return this }
|
|
27
28
|
logEvent () {}
|
|
@@ -211,10 +211,21 @@ class Span {
|
|
|
211
211
|
return this
|
|
212
212
|
}
|
|
213
213
|
|
|
214
|
-
addLink (
|
|
215
|
-
//
|
|
214
|
+
addLink (link, attrs) {
|
|
215
|
+
// TODO: Remove this once we remove addLink(context, attrs) in v6.0.0
|
|
216
|
+
if (link instanceof SpanContext) {
|
|
217
|
+
link = { context: link, attributes: attrs ?? {} }
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
const { context, attributes } = link
|
|
221
|
+
// Extract dd context
|
|
216
222
|
const ddSpanContext = context._ddContext
|
|
217
|
-
this._ddSpan.addLink(ddSpanContext, attributes)
|
|
223
|
+
this._ddSpan.addLink({ context: ddSpanContext, attributes })
|
|
224
|
+
return this
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
addLinks (links) {
|
|
228
|
+
links.forEach(link => this.addLink(link))
|
|
218
229
|
return this
|
|
219
230
|
}
|
|
220
231
|
|
|
@@ -86,8 +86,10 @@ class DatadogSpan {
|
|
|
86
86
|
|
|
87
87
|
this._startTime = fields.startTime || this._getTime()
|
|
88
88
|
|
|
89
|
-
this._links =
|
|
90
|
-
|
|
89
|
+
this._links = fields.links?.map(link => ({
|
|
90
|
+
context: link.context._ddContext ?? link.context,
|
|
91
|
+
attributes: this._sanitizeAttributes(link.attributes)
|
|
92
|
+
})) ?? []
|
|
91
93
|
|
|
92
94
|
if (DD_TRACE_EXPERIMENTAL_SPAN_COUNTS && finishedRegistry) {
|
|
93
95
|
runtimeMetrics.increment('runtime.node.spans.unfinished')
|
|
@@ -196,13 +198,25 @@ class DatadogSpan {
|
|
|
196
198
|
|
|
197
199
|
logEvent () {}
|
|
198
200
|
|
|
199
|
-
addLink (
|
|
201
|
+
addLink (link, attrs) {
|
|
202
|
+
// TODO: Remove this once we remove addLink(context, attrs) in v6.0.0
|
|
203
|
+
if (link instanceof SpanContext) {
|
|
204
|
+
link = { context: link, attributes: attrs ?? {} }
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
const { context, attributes } = link
|
|
208
|
+
|
|
200
209
|
this._links.push({
|
|
201
210
|
context: context._ddContext ?? context,
|
|
202
211
|
attributes: this._sanitizeAttributes(attributes)
|
|
203
212
|
})
|
|
204
213
|
}
|
|
205
214
|
|
|
215
|
+
addLinks (links) {
|
|
216
|
+
links.forEach(link => this.addLink(link))
|
|
217
|
+
return this
|
|
218
|
+
}
|
|
219
|
+
|
|
206
220
|
addSpanPointer (ptrKind, ptrDir, ptrHash) {
|
|
207
221
|
const zeroContext = new SpanContext({
|
|
208
222
|
traceId: id('0'),
|
|
@@ -214,7 +228,7 @@ class DatadogSpan {
|
|
|
214
228
|
'ptr.hash': ptrHash,
|
|
215
229
|
'link.kind': 'span-pointer'
|
|
216
230
|
}
|
|
217
|
-
this.addLink(zeroContext, attributes)
|
|
231
|
+
this.addLink({ context: zeroContext, attributes })
|
|
218
232
|
}
|
|
219
233
|
|
|
220
234
|
addEvent (name, attributesOrStartTime, startTime) {
|
|
@@ -6,6 +6,15 @@ const plugins = require('./plugins')
|
|
|
6
6
|
const log = require('./log')
|
|
7
7
|
const { getEnvironmentVariable } = require('../../dd-trace/src/config-helper')
|
|
8
8
|
|
|
9
|
+
// Test optimization plugins that should only be enabled when isCiVisibility is true
|
|
10
|
+
const TEST_OPTIMIZATION_PLUGINS = new Set([
|
|
11
|
+
'jest',
|
|
12
|
+
'vitest',
|
|
13
|
+
'cucumber',
|
|
14
|
+
'mocha',
|
|
15
|
+
'playwright'
|
|
16
|
+
])
|
|
17
|
+
|
|
9
18
|
const loadChannel = channel('dd-trace:instrumentation:load')
|
|
10
19
|
|
|
11
20
|
// instrument everything that needs Plugin System V2 instrumentation
|
|
@@ -74,6 +83,13 @@ module.exports = class PluginManager {
|
|
|
74
83
|
|
|
75
84
|
if (!Plugin) return
|
|
76
85
|
if (!this._tracerConfig) return // TODO: don't wait for tracer to be initialized
|
|
86
|
+
|
|
87
|
+
// Check if this is a Test Optimization plugin and Test Optimization is not enabled
|
|
88
|
+
if (TEST_OPTIMIZATION_PLUGINS.has(name) && !this._tracerConfig.isCiVisibility) {
|
|
89
|
+
log.debug('Plugin "%s" is not initialized because Test Optimization mode is not enabled.', name)
|
|
90
|
+
return
|
|
91
|
+
}
|
|
92
|
+
|
|
77
93
|
if (!this._pluginsByName[name]) {
|
|
78
94
|
this._pluginsByName[name] = new Plugin(this._tracer, this._tracerConfig)
|
|
79
95
|
}
|
|
@@ -148,7 +164,8 @@ module.exports = class PluginManager {
|
|
|
148
164
|
middlewareTracingEnabled,
|
|
149
165
|
traceWebsocketMessagesEnabled,
|
|
150
166
|
traceWebsocketMessagesInheritSampling,
|
|
151
|
-
traceWebsocketMessagesSeparateTraces
|
|
167
|
+
traceWebsocketMessagesSeparateTraces,
|
|
168
|
+
experimental
|
|
152
169
|
} = this._tracerConfig
|
|
153
170
|
|
|
154
171
|
const sharedConfig = {
|
|
@@ -166,7 +183,8 @@ module.exports = class PluginManager {
|
|
|
166
183
|
isServiceUserProvided,
|
|
167
184
|
traceWebsocketMessagesEnabled,
|
|
168
185
|
traceWebsocketMessagesInheritSampling,
|
|
169
|
-
traceWebsocketMessagesSeparateTraces
|
|
186
|
+
traceWebsocketMessagesSeparateTraces,
|
|
187
|
+
experimental
|
|
170
188
|
}
|
|
171
189
|
|
|
172
190
|
if (logInjection !== undefined) {
|
|
@@ -36,6 +36,7 @@ const {
|
|
|
36
36
|
getModifiedTestsFromDiff,
|
|
37
37
|
getPullRequestBaseBranch
|
|
38
38
|
} = require('./util/test')
|
|
39
|
+
const { getRepositoryRoot } = require('./util/git')
|
|
39
40
|
const Plugin = require('./plugin')
|
|
40
41
|
const { COMPONENT } = require('../constants')
|
|
41
42
|
const log = require('../log')
|
|
@@ -61,6 +62,7 @@ const {
|
|
|
61
62
|
const { OS_VERSION, OS_PLATFORM, OS_ARCHITECTURE, RUNTIME_NAME, RUNTIME_VERSION } = require('./util/env')
|
|
62
63
|
const getDiClient = require('../ci-visibility/dynamic-instrumentation')
|
|
63
64
|
const { DD_MAJOR } = require('../../../../version')
|
|
65
|
+
const id = require('../id')
|
|
64
66
|
|
|
65
67
|
const FRAMEWORK_TO_TRIMMED_COMMAND = {
|
|
66
68
|
vitest: 'vitest run',
|
|
@@ -70,12 +72,44 @@ const FRAMEWORK_TO_TRIMMED_COMMAND = {
|
|
|
70
72
|
jest: 'jest'
|
|
71
73
|
}
|
|
72
74
|
|
|
75
|
+
const WORKER_EXPORTER_TO_TEST_FRAMEWORK = {
|
|
76
|
+
vitest_worker: 'vitest',
|
|
77
|
+
jest_worker: 'jest',
|
|
78
|
+
cucumber_worker: 'cucumber',
|
|
79
|
+
mocha_worker: 'mocha',
|
|
80
|
+
playwright_worker: 'playwright'
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const TEST_FRAMEWORKS_TO_SKIP_GIT_METADATA_EXTRACTION = new Set([
|
|
84
|
+
'vitest',
|
|
85
|
+
'jest',
|
|
86
|
+
'mocha',
|
|
87
|
+
'cucumber',
|
|
88
|
+
])
|
|
89
|
+
|
|
90
|
+
function getTestSuiteLevelVisibilityTags (testSuiteSpan, testFramework) {
|
|
91
|
+
const testSuiteSpanContext = testSuiteSpan.context()
|
|
92
|
+
|
|
93
|
+
const suiteTags = {
|
|
94
|
+
[TEST_SUITE_ID]: testSuiteSpanContext.toSpanId(),
|
|
95
|
+
[TEST_SESSION_ID]: testSuiteSpanContext.toTraceId(),
|
|
96
|
+
[TEST_COMMAND]: testSuiteSpanContext._tags[TEST_COMMAND],
|
|
97
|
+
[TEST_MODULE]: testFramework
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
if (testSuiteSpanContext._parentId) {
|
|
101
|
+
suiteTags[TEST_MODULE_ID] = testSuiteSpanContext._parentId.toString(10)
|
|
102
|
+
}
|
|
103
|
+
return suiteTags
|
|
104
|
+
}
|
|
105
|
+
|
|
73
106
|
module.exports = class CiPlugin extends Plugin {
|
|
74
107
|
constructor (...args) {
|
|
75
108
|
super(...args)
|
|
76
109
|
|
|
77
110
|
this.fileLineToProbeId = new Map()
|
|
78
111
|
this.rootDir = process.cwd() // fallback in case :session:start events are not emitted
|
|
112
|
+
this._testSuiteSpansByTestSuite = new Map()
|
|
79
113
|
|
|
80
114
|
this.addSub(`ci:${this.constructor.id}:library-configuration`, (ctx) => {
|
|
81
115
|
const { onDone, isParallel, frameworkVersion } = ctx
|
|
@@ -265,6 +299,53 @@ module.exports = class CiPlugin extends Plugin {
|
|
|
265
299
|
// TODO: Add telemetry for this type of error
|
|
266
300
|
return onDone({ err: new Error('No modified tests could have been retrieved') })
|
|
267
301
|
})
|
|
302
|
+
|
|
303
|
+
this.addSub(`ci:${this.constructor.id}:worker-report:trace`, traces => {
|
|
304
|
+
const formattedTraces = JSON.parse(traces)
|
|
305
|
+
|
|
306
|
+
for (const trace of formattedTraces) {
|
|
307
|
+
for (const span of trace) {
|
|
308
|
+
span.span_id = id(span.span_id)
|
|
309
|
+
span.trace_id = id(span.trace_id)
|
|
310
|
+
span.parent_id = id(span.parent_id)
|
|
311
|
+
|
|
312
|
+
if (span.name?.startsWith(`${this.constructor.id}.`)) {
|
|
313
|
+
// augment with git information (since it will not be available in the worker)
|
|
314
|
+
for (const key in this.testEnvironmentMetadata) {
|
|
315
|
+
// CAREFUL: this bypasses the metadata/metrics distinction
|
|
316
|
+
// Be careful not to pass numbers in `meta`
|
|
317
|
+
if (key.startsWith('git.')) {
|
|
318
|
+
span.meta[key] = this.testEnvironmentMetadata[key]
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
// Only test hooks run in the cucumber worker, so the test events do not have the
|
|
324
|
+
// test session, test module and test suite ids. We have to update them here.
|
|
325
|
+
if (span.name === 'cucumber.test' || span.name === 'mocha.test') {
|
|
326
|
+
const testSuite = span.meta[TEST_SUITE]
|
|
327
|
+
const testSuiteSpan = this._testSuiteSpansByTestSuite.get(testSuite)
|
|
328
|
+
if (!testSuiteSpan) {
|
|
329
|
+
log.warn(`Test suite span not found for test span with test suite ${testSuite}`)
|
|
330
|
+
continue
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
const testSuiteTags = getTestSuiteLevelVisibilityTags(testSuiteSpan, this.constructor.id)
|
|
334
|
+
span.meta = {
|
|
335
|
+
...span.meta,
|
|
336
|
+
...testSuiteTags
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
this.tracer._exporter.export(trace)
|
|
341
|
+
}
|
|
342
|
+
})
|
|
343
|
+
|
|
344
|
+
this.addSub(`ci:${this.constructor.id}:worker-report:logs`, (logsPayloads) => {
|
|
345
|
+
JSON.parse(logsPayloads).forEach(({ logMessage }) => {
|
|
346
|
+
this.tracer._exporter.exportDiLogs(this.testEnvironmentMetadata, logMessage)
|
|
347
|
+
})
|
|
348
|
+
})
|
|
268
349
|
}
|
|
269
350
|
|
|
270
351
|
get telemetry () {
|
|
@@ -298,7 +379,20 @@ module.exports = class CiPlugin extends Plugin {
|
|
|
298
379
|
this.di = getDiClient()
|
|
299
380
|
}
|
|
300
381
|
|
|
301
|
-
|
|
382
|
+
if (this.testConfiguration) { // no need to recalculate as it's constant
|
|
383
|
+
return
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
const exporter = this.config.experimental?.exporter
|
|
387
|
+
const workerTestFramework = WORKER_EXPORTER_TO_TEST_FRAMEWORK[exporter]
|
|
388
|
+
this.shouldSkipGitMetadataExtraction = workerTestFramework &&
|
|
389
|
+
TEST_FRAMEWORKS_TO_SKIP_GIT_METADATA_EXTRACTION.has(workerTestFramework)
|
|
390
|
+
|
|
391
|
+
this.testEnvironmentMetadata = getTestEnvironmentMetadata(
|
|
392
|
+
this.constructor.id,
|
|
393
|
+
this.config,
|
|
394
|
+
this.shouldSkipGitMetadataExtraction
|
|
395
|
+
)
|
|
302
396
|
|
|
303
397
|
const {
|
|
304
398
|
[GIT_REPOSITORY_URL]: repositoryUrl,
|
|
@@ -318,9 +412,9 @@ module.exports = class CiPlugin extends Plugin {
|
|
|
318
412
|
[GIT_COMMIT_HEAD_MESSAGE]: commitHeadMessage
|
|
319
413
|
} = this.testEnvironmentMetadata
|
|
320
414
|
|
|
321
|
-
this.repositoryRoot = repositoryRoot || process.cwd()
|
|
415
|
+
this.repositoryRoot = repositoryRoot || getRepositoryRoot() || process.cwd()
|
|
322
416
|
|
|
323
|
-
this.codeOwnersEntries = getCodeOwnersFileEntries(repositoryRoot)
|
|
417
|
+
this.codeOwnersEntries = getCodeOwnersFileEntries(this.repositoryRoot)
|
|
324
418
|
|
|
325
419
|
this.ciProviderName = ciProviderName
|
|
326
420
|
|
|
@@ -13,6 +13,7 @@ module.exports = {
|
|
|
13
13
|
get '@google-cloud/vertexai' () { return require('../../../datadog-plugin-google-cloud-vertexai/src') },
|
|
14
14
|
get '@grpc/grpc-js' () { return require('../../../datadog-plugin-grpc/src') },
|
|
15
15
|
get '@hapi/hapi' () { return require('../../../datadog-plugin-hapi/src') },
|
|
16
|
+
get '@happy-dom/jest-environment' () { return require('../../../datadog-plugin-jest/src') },
|
|
16
17
|
get '@jest/core' () { return require('../../../datadog-plugin-jest/src') },
|
|
17
18
|
get '@jest/test-sequencer' () { return require('../../../datadog-plugin-jest/src') },
|
|
18
19
|
get '@jest/transform' () { return require('../../../datadog-plugin-jest/src') },
|
|
@@ -73,6 +74,7 @@ module.exports = {
|
|
|
73
74
|
get 'mocha-each' () { return require('../../../datadog-plugin-mocha/src') },
|
|
74
75
|
get vitest () { return require('../../../datadog-plugin-vitest/src') },
|
|
75
76
|
get workerpool () { return require('../../../datadog-plugin-mocha/src') },
|
|
77
|
+
get tinypool () { return require('../../../datadog-plugin-vitest/src') },
|
|
76
78
|
get moleculer () { return require('../../../datadog-plugin-moleculer/src') },
|
|
77
79
|
get mongodb () { return require('../../../datadog-plugin-mongodb-core/src') },
|
|
78
80
|
get 'mongodb-core' () { return require('../../../datadog-plugin-mongodb-core/src') },
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const os = require('os')
|
|
4
|
+
const path = require('path')
|
|
5
|
+
const fs = require('fs')
|
|
6
|
+
const crypto = require('crypto')
|
|
7
|
+
const cp = require('child_process')
|
|
8
|
+
|
|
9
|
+
const log = require('../../log')
|
|
10
|
+
const { getEnvironmentVariable } = require('../../config-helper')
|
|
11
|
+
const { isTrue } = require('../../util')
|
|
12
|
+
|
|
13
|
+
let isGitEnabled = isTrue(getEnvironmentVariable('DD_EXPERIMENTAL_TEST_OPT_GIT_CACHE_ENABLED'))
|
|
14
|
+
const GIT_CACHE_DIR = getEnvironmentVariable('DD_EXPERIMENTAL_TEST_OPT_GIT_CACHE_DIR') ||
|
|
15
|
+
path.join(os.tmpdir(), 'dd-trace-git-cache')
|
|
16
|
+
|
|
17
|
+
function ensureCacheDir () {
|
|
18
|
+
if (!isGitEnabled) return false
|
|
19
|
+
|
|
20
|
+
try {
|
|
21
|
+
if (fs.existsSync(GIT_CACHE_DIR)) {
|
|
22
|
+
const stats = fs.statSync(GIT_CACHE_DIR)
|
|
23
|
+
if (!stats.isDirectory()) {
|
|
24
|
+
throw new Error(`Cache directory path exists but is not a directory: ${GIT_CACHE_DIR}`)
|
|
25
|
+
}
|
|
26
|
+
} else {
|
|
27
|
+
fs.mkdirSync(GIT_CACHE_DIR, { recursive: true })
|
|
28
|
+
}
|
|
29
|
+
return true
|
|
30
|
+
} catch (err) {
|
|
31
|
+
log.error('Failed to create git cache directory, disabling cache', err)
|
|
32
|
+
isGitEnabled = false
|
|
33
|
+
return false
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Initialize cache directory at module load time
|
|
38
|
+
ensureCacheDir()
|
|
39
|
+
|
|
40
|
+
function getCacheKey (cmd, flags) {
|
|
41
|
+
// Create a hash of the command and flags to use as cache key
|
|
42
|
+
const commandString = `${cmd} ${flags.join(' ')}`
|
|
43
|
+
return crypto.createHash('sha256').update(commandString).digest('hex')
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function getCacheFilePath (cacheKey) {
|
|
47
|
+
return path.join(GIT_CACHE_DIR, `${cacheKey}.cache`)
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function getCache (cacheKey) {
|
|
51
|
+
if (!isGitEnabled) return null
|
|
52
|
+
|
|
53
|
+
try {
|
|
54
|
+
const cacheFilePath = getCacheFilePath(cacheKey)
|
|
55
|
+
if (!fs.existsSync(cacheFilePath)) {
|
|
56
|
+
return null
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const content = fs.readFileSync(cacheFilePath, 'utf8')
|
|
60
|
+
return content
|
|
61
|
+
} catch (err) {
|
|
62
|
+
log.error('Failed to read git cache', err)
|
|
63
|
+
return null
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function setCache (cacheKey, result) {
|
|
68
|
+
if (!isGitEnabled) return
|
|
69
|
+
|
|
70
|
+
// Ensure cache directory exists
|
|
71
|
+
if (!ensureCacheDir()) return
|
|
72
|
+
|
|
73
|
+
try {
|
|
74
|
+
const cacheFilePath = getCacheFilePath(cacheKey)
|
|
75
|
+
fs.writeFileSync(cacheFilePath, result, 'utf8')
|
|
76
|
+
} catch (err) {
|
|
77
|
+
log.error('Failed to write git cache', err)
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function cachedExec (cmd, flags, options) {
|
|
82
|
+
if (options === undefined) {
|
|
83
|
+
options = { stdio: 'pipe' }
|
|
84
|
+
}
|
|
85
|
+
if (!isGitEnabled) {
|
|
86
|
+
return cp.execFileSync(cmd, flags, options)
|
|
87
|
+
}
|
|
88
|
+
const cacheKey = getCacheKey(cmd, flags)
|
|
89
|
+
const cachedResult = getCache(cacheKey)
|
|
90
|
+
if (cachedResult !== null) {
|
|
91
|
+
if (cachedResult.startsWith('__GIT_COMMAND_FAILED__')) {
|
|
92
|
+
let error
|
|
93
|
+
try {
|
|
94
|
+
const errorData = cachedResult.replace('__GIT_COMMAND_FAILED__', '')
|
|
95
|
+
const { message, code, status, errno } = JSON.parse(errorData)
|
|
96
|
+
error = new Error(message)
|
|
97
|
+
error.code = code
|
|
98
|
+
error.status = status
|
|
99
|
+
error.errno = errno
|
|
100
|
+
} catch {
|
|
101
|
+
// we couldn't parse the error data, so we'll throw a generic error
|
|
102
|
+
throw new Error('Git command failed')
|
|
103
|
+
}
|
|
104
|
+
throw error
|
|
105
|
+
}
|
|
106
|
+
return cachedResult
|
|
107
|
+
}
|
|
108
|
+
try {
|
|
109
|
+
const result = cp.execFileSync(cmd, flags, options)
|
|
110
|
+
setCache(cacheKey, result)
|
|
111
|
+
return result
|
|
112
|
+
} catch (err) {
|
|
113
|
+
const cacheValue = '__GIT_COMMAND_FAILED__' +
|
|
114
|
+
JSON.stringify({
|
|
115
|
+
code: err.code,
|
|
116
|
+
status: err.status,
|
|
117
|
+
errno: err.errno,
|
|
118
|
+
message: err.message
|
|
119
|
+
})
|
|
120
|
+
setCache(cacheKey, cacheValue)
|
|
121
|
+
throw err
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
module.exports = {
|
|
126
|
+
getCacheKey,
|
|
127
|
+
getCacheFilePath,
|
|
128
|
+
cachedExec
|
|
129
|
+
}
|