dd-trace 5.67.0 → 5.69.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 +6 -4
- 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 +240 -3
- package/initialize.mjs +1 -1
- package/package.json +17 -11
- package/packages/datadog-core/src/storage.js +14 -13
- package/packages/datadog-esbuild/index.js +118 -26
- 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 +15 -5
- 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-confluentinc-kafka-javascript/src/index.js +6 -0
- package/packages/datadog-plugin-cucumber/src/index.js +4 -56
- package/packages/datadog-plugin-cypress/src/cypress-plugin.js +6 -3
- 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 +38 -12
- 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/aiguard/client.js +25 -0
- package/packages/dd-trace/src/aiguard/noop.js +9 -0
- package/packages/dd-trace/src/aiguard/sdk.js +173 -0
- package/packages/dd-trace/src/aiguard/tags.js +11 -0
- package/packages/dd-trace/src/appsec/iast/path-line.js +21 -4
- package/packages/dd-trace/src/appsec/iast/security-controls/parser.js +1 -1
- package/packages/dd-trace/src/appsec/iast/taint-tracking/rewriter.js +6 -3
- package/packages/dd-trace/src/appsec/stack_trace.js +20 -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-helper.js +8 -1
- package/packages/dd-trace/src/config.js +92 -304
- package/packages/dd-trace/src/config_defaults.js +191 -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/index.js +23 -1
- 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 +15 -4
- package/packages/dd-trace/src/llmobs/plugins/ai/util.js +20 -7
- 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 +27 -16
- 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/proxy.js +4 -0
- 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 +19 -5
- package/packages/dd-trace/src/payload-tagging/config/index.js +16 -0
- package/packages/dd-trace/src/payload-tagging/index.js +26 -15
- package/packages/dd-trace/src/payload-tagging/tagging.js +17 -8
- package/packages/dd-trace/src/pkg.js +3 -1
- 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/composite.js +3 -0
- package/packages/dd-trace/src/plugins/index.js +2 -0
- package/packages/dd-trace/src/plugins/plugin.js +67 -0
- package/packages/dd-trace/src/plugins/util/git-cache.js +129 -0
- package/packages/dd-trace/src/plugins/util/git.js +41 -27
- package/packages/dd-trace/src/plugins/util/test.js +56 -27
- package/packages/dd-trace/src/plugins/util/web.js +1 -1
- package/packages/dd-trace/src/priority_sampler.js +70 -46
- 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/proxy.js +15 -0
- package/packages/dd-trace/src/rate_limiter.js +26 -1
- 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/sampling_rule.js +124 -2
- package/packages/dd-trace/src/span_sampler.js +19 -0
- package/packages/dd-trace/src/standalone/product.js +9 -0
- package/packages/dd-trace/src/standalone/tracesource.js +16 -1
- package/packages/dd-trace/src/standalone/tracesource_priority_sampler.js +13 -0
- package/packages/dd-trace/src/startup-log.js +21 -2
- package/packages/dd-trace/src/supported-configurations.json +9 -0
- package/packages/dd-trace/src/telemetry/logs/index.js +2 -2
- package/packages/dd-trace/src/util.js +1 -1
- package/register.js +1 -1
- package/version.js +4 -2
|
@@ -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') },
|
|
@@ -6,6 +6,24 @@ const dc = require('dc-polyfill')
|
|
|
6
6
|
const logger = require('../log')
|
|
7
7
|
const { storage } = require('../../../datadog-core')
|
|
8
8
|
|
|
9
|
+
/**
|
|
10
|
+
* Base class for all Datadog plugins.
|
|
11
|
+
*
|
|
12
|
+
* Subclasses MUST define a static field `id` with the integration identifier
|
|
13
|
+
* used across channels, span names, tags and telemetry.
|
|
14
|
+
*
|
|
15
|
+
* Example:
|
|
16
|
+
* ```js
|
|
17
|
+
* class MyPlugin extends Plugin {
|
|
18
|
+
* static id = 'myframework'
|
|
19
|
+
* }
|
|
20
|
+
* ```
|
|
21
|
+
*
|
|
22
|
+
* Notes about the tracer instance:
|
|
23
|
+
* - In some contexts the tracer may be wrapped and available as `{ _tracer: Tracer }`.
|
|
24
|
+
* Use the `tracer` getter which normalizes access.
|
|
25
|
+
*/
|
|
26
|
+
|
|
9
27
|
class Subscription {
|
|
10
28
|
constructor (event, handler) {
|
|
11
29
|
this._channel = dc.channel(event)
|
|
@@ -50,6 +68,12 @@ class StoreBinding {
|
|
|
50
68
|
}
|
|
51
69
|
|
|
52
70
|
module.exports = class Plugin {
|
|
71
|
+
/**
|
|
72
|
+
* Create a new plugin instance.
|
|
73
|
+
*
|
|
74
|
+
* @param {object} tracer Tracer instance or wrapper containing it under `_tracer`.
|
|
75
|
+
* @param {object} tracerConfig Global tracer configuration object.
|
|
76
|
+
*/
|
|
53
77
|
constructor (tracer, tracerConfig) {
|
|
54
78
|
this._subscriptions = []
|
|
55
79
|
this._bindings = []
|
|
@@ -59,10 +83,22 @@ module.exports = class Plugin {
|
|
|
59
83
|
this._tracerConfig = tracerConfig // global tracer configuration
|
|
60
84
|
}
|
|
61
85
|
|
|
86
|
+
/**
|
|
87
|
+
* Normalized tracer access. Returns the underlying tracer even if wrapped.
|
|
88
|
+
*
|
|
89
|
+
* @returns {object}
|
|
90
|
+
*/
|
|
62
91
|
get tracer () {
|
|
63
92
|
return this._tracer?._tracer || this._tracer
|
|
64
93
|
}
|
|
65
94
|
|
|
95
|
+
/**
|
|
96
|
+
* Enter a context with the provided span bound in storage.
|
|
97
|
+
*
|
|
98
|
+
* @param {object} span The span to bind as current.
|
|
99
|
+
* @param {object=} store Optional existing store to extend; if omitted, uses current store.
|
|
100
|
+
* @returns {void}
|
|
101
|
+
*/
|
|
66
102
|
enter (span, store) {
|
|
67
103
|
store = store || storage('legacy').getStore()
|
|
68
104
|
storage('legacy').enterWith({ ...store, span })
|
|
@@ -74,8 +110,19 @@ module.exports = class Plugin {
|
|
|
74
110
|
storage('legacy').enterWith({ noop: true })
|
|
75
111
|
}
|
|
76
112
|
|
|
113
|
+
/**
|
|
114
|
+
* Subscribe to a diagnostic channel with automatic error handling and enable/disable lifecycle.
|
|
115
|
+
*
|
|
116
|
+
* @param {string} channelName Diagnostic channel name.
|
|
117
|
+
* @param {(...args: unknown[]) => unknown} handler Handler invoked on messages.
|
|
118
|
+
* @returns {void}
|
|
119
|
+
*/
|
|
77
120
|
addSub (channelName, handler) {
|
|
78
121
|
const plugin = this
|
|
122
|
+
/**
|
|
123
|
+
* @this {unknown}
|
|
124
|
+
* @returns {unknown}
|
|
125
|
+
*/
|
|
79
126
|
const wrappedHandler = function () {
|
|
80
127
|
try {
|
|
81
128
|
return handler.apply(this, arguments)
|
|
@@ -88,10 +135,23 @@ module.exports = class Plugin {
|
|
|
88
135
|
this._subscriptions.push(new Subscription(channelName, wrappedHandler))
|
|
89
136
|
}
|
|
90
137
|
|
|
138
|
+
/**
|
|
139
|
+
* Bind the tracer store to a diagnostic channel with a transform function.
|
|
140
|
+
*
|
|
141
|
+
* @param {string} channelName Diagnostic channel name.
|
|
142
|
+
* @param {(data: unknown) => object} transform Transform to compute the bound store.
|
|
143
|
+
* @returns {void}
|
|
144
|
+
*/
|
|
91
145
|
addBind (channelName, transform) {
|
|
92
146
|
this._bindings.push(new StoreBinding(channelName, transform))
|
|
93
147
|
}
|
|
94
148
|
|
|
149
|
+
/**
|
|
150
|
+
* Attach an error to the current active span (if any).
|
|
151
|
+
*
|
|
152
|
+
* @param {unknown} error Error object or sentinel value.
|
|
153
|
+
* @returns {void}
|
|
154
|
+
*/
|
|
95
155
|
addError (error) {
|
|
96
156
|
const store = storage('legacy').getStore()
|
|
97
157
|
|
|
@@ -102,6 +162,13 @@ module.exports = class Plugin {
|
|
|
102
162
|
}
|
|
103
163
|
}
|
|
104
164
|
|
|
165
|
+
/**
|
|
166
|
+
* Enable or disable the plugin and (re)apply its configuration.
|
|
167
|
+
*
|
|
168
|
+
* @param {boolean|object} config Either a boolean to enable/disable or a configuration object
|
|
169
|
+
* containing at least `{ enabled: boolean }`.
|
|
170
|
+
* @returns {void}
|
|
171
|
+
*/
|
|
105
172
|
configure (config) {
|
|
106
173
|
if (typeof config === 'boolean') {
|
|
107
174
|
config = { enabled: config }
|
|
@@ -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
|
+
}
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
-
const cp = require('child_process')
|
|
4
3
|
const os = require('os')
|
|
5
4
|
const path = require('path')
|
|
6
5
|
const fs = require('fs')
|
|
@@ -36,6 +35,7 @@ const {
|
|
|
36
35
|
} = require('../../ci-visibility/telemetry')
|
|
37
36
|
const { filterSensitiveInfoFromRepository } = require('./url')
|
|
38
37
|
const { storage } = require('../../../../datadog-core')
|
|
38
|
+
const { cachedExec } = require('./git-cache')
|
|
39
39
|
|
|
40
40
|
const GIT_REV_LIST_MAX_BUFFER = 12 * 1024 * 1024 // 12MB
|
|
41
41
|
|
|
@@ -58,10 +58,12 @@ function sanitizedExec (
|
|
|
58
58
|
startTime = Date.now()
|
|
59
59
|
}
|
|
60
60
|
try {
|
|
61
|
-
let result =
|
|
61
|
+
let result = cachedExec(cmd, flags, { stdio: 'pipe' }).toString()
|
|
62
|
+
|
|
62
63
|
if (shouldTrim) {
|
|
63
64
|
result = result.replaceAll(/(\r\n|\n|\r)/gm, '')
|
|
64
65
|
}
|
|
66
|
+
|
|
65
67
|
if (durationMetric) {
|
|
66
68
|
distributionMetric(durationMetric.name, durationMetric.tags, Date.now() - startTime)
|
|
67
69
|
}
|
|
@@ -94,7 +96,7 @@ function isGitAvailable () {
|
|
|
94
96
|
const isWindows = os.platform() === 'win32'
|
|
95
97
|
const command = isWindows ? 'where' : 'which'
|
|
96
98
|
try {
|
|
97
|
-
|
|
99
|
+
cachedExec(command, ['git'])
|
|
98
100
|
return true
|
|
99
101
|
} catch {
|
|
100
102
|
incrementCountMetric(TELEMETRY_GIT_COMMAND_ERRORS, { command: 'check_git', exitCode: 'missing' })
|
|
@@ -114,7 +116,7 @@ function isShallowRepository () {
|
|
|
114
116
|
|
|
115
117
|
function getGitVersion () {
|
|
116
118
|
const gitVersionString = sanitizedExec('git', ['version'])
|
|
117
|
-
const gitVersionMatches = gitVersionString.match(/git version (\d+)\.(\d+)\.(\d+)/)
|
|
119
|
+
const gitVersionMatches = /** @type {RegExpMatchArray} */ (gitVersionString.match(/git version (\d+)\.(\d+)\.(\d+)/))
|
|
118
120
|
try {
|
|
119
121
|
return {
|
|
120
122
|
major: Number.parseInt(gitVersionMatches[1]),
|
|
@@ -150,27 +152,32 @@ function unshallowRepository (parentOnly = false) {
|
|
|
150
152
|
|
|
151
153
|
incrementCountMetric(TELEMETRY_GIT_COMMAND, { command: 'unshallow' })
|
|
152
154
|
const start = Date.now()
|
|
155
|
+
let flags = [
|
|
156
|
+
...baseGitOptions,
|
|
157
|
+
revParseHead
|
|
158
|
+
]
|
|
153
159
|
try {
|
|
154
|
-
|
|
155
|
-
...baseGitOptions,
|
|
156
|
-
revParseHead
|
|
157
|
-
], { stdio: 'pipe' })
|
|
160
|
+
cachedExec('git', flags)
|
|
158
161
|
} catch (err) {
|
|
159
162
|
// If the local HEAD is a commit that has not been pushed to the remote, the above command will fail.
|
|
160
|
-
log.
|
|
163
|
+
log.warn(`Git unshallow failed: ${flags.join(' ')}`)
|
|
161
164
|
incrementCountMetric(
|
|
162
165
|
TELEMETRY_GIT_COMMAND_ERRORS,
|
|
163
166
|
{ command: 'unshallow', errorType: err.code, exitCode: err.status || err.errno }
|
|
164
167
|
)
|
|
165
|
-
const upstreamRemote = sanitizedExec(
|
|
168
|
+
const upstreamRemote = sanitizedExec(
|
|
169
|
+
'git',
|
|
170
|
+
['rev-parse', '--abbrev-ref', '--symbolic-full-name', '@{upstream}']
|
|
171
|
+
)
|
|
172
|
+
flags = [
|
|
173
|
+
...baseGitOptions,
|
|
174
|
+
upstreamRemote
|
|
175
|
+
]
|
|
166
176
|
try {
|
|
167
|
-
|
|
168
|
-
...baseGitOptions,
|
|
169
|
-
upstreamRemote
|
|
170
|
-
], { stdio: 'pipe' })
|
|
177
|
+
cachedExec('git', flags)
|
|
171
178
|
} catch (err) {
|
|
172
|
-
// If the CI is working on a detached HEAD or branch tracking hasn
|
|
173
|
-
log.
|
|
179
|
+
// If the CI is working on a detached HEAD or branch tracking hasn't been set up, the above command will fail.
|
|
180
|
+
log.warn(`Git unshallow failed again: ${flags.join(' ')}`)
|
|
174
181
|
incrementCountMetric(
|
|
175
182
|
TELEMETRY_GIT_COMMAND_ERRORS,
|
|
176
183
|
{ command: 'unshallow', errorType: err.code, exitCode: err.status || err.errno }
|
|
@@ -202,7 +209,7 @@ function getLatestCommits () {
|
|
|
202
209
|
incrementCountMetric(TELEMETRY_GIT_COMMAND, { command: 'get_local_commits' })
|
|
203
210
|
const startTime = Date.now()
|
|
204
211
|
try {
|
|
205
|
-
const result =
|
|
212
|
+
const result = cachedExec('git', ['log', '--format=%H', '-n 1000', '--since="1 month ago"'])
|
|
206
213
|
.toString()
|
|
207
214
|
.split('\n')
|
|
208
215
|
.filter(Boolean)
|
|
@@ -272,10 +279,9 @@ function checkAndFetchBranch (branch, remoteName) {
|
|
|
272
279
|
try {
|
|
273
280
|
// `git show-ref --verify --quiet refs/remotes/${remoteName}/${branch}` will exit 0 if the branch exists
|
|
274
281
|
// Otherwise it will exit 1
|
|
275
|
-
|
|
282
|
+
cachedExec(
|
|
276
283
|
'git',
|
|
277
284
|
['show-ref', '--verify', '--quiet', `refs/remotes/${remoteName}/${branch}`],
|
|
278
|
-
{ stdio: 'pipe' }
|
|
279
285
|
)
|
|
280
286
|
// branch exists locally, so we finish
|
|
281
287
|
} catch {
|
|
@@ -285,22 +291,22 @@ function checkAndFetchBranch (branch, remoteName) {
|
|
|
285
291
|
// `git ls-remote --heads origin my-branch` will exit 0 even if the branch doesn't exist.
|
|
286
292
|
// The piece of information we need is whether the command outputs anything.
|
|
287
293
|
// `git ls-remote --heads origin my-branch` could exit an error code if the remote does not exist.
|
|
288
|
-
const remoteHeads =
|
|
294
|
+
const remoteHeads = cachedExec(
|
|
289
295
|
'git',
|
|
290
296
|
['ls-remote', '--heads', remoteName, branch],
|
|
291
297
|
{ stdio: 'pipe', timeout: 2000 }
|
|
292
298
|
)
|
|
293
299
|
if (remoteHeads) {
|
|
294
300
|
// branch exists, so we'll fetch it
|
|
295
|
-
|
|
301
|
+
cachedExec(
|
|
296
302
|
'git',
|
|
297
303
|
['fetch', '--depth', '1', remoteName, branch],
|
|
298
304
|
{ stdio: 'pipe', timeout: 5000 }
|
|
299
305
|
)
|
|
300
306
|
}
|
|
301
|
-
} catch (
|
|
307
|
+
} catch (err) {
|
|
302
308
|
// branch does not exist or couldn't be fetched, so we can't do anything
|
|
303
|
-
log.
|
|
309
|
+
log.debug('Git plugin error checking and fetching branch', err)
|
|
304
310
|
}
|
|
305
311
|
}
|
|
306
312
|
}
|
|
@@ -358,7 +364,7 @@ function getCommitsRevList (commitsToExclude, commitsToInclude) {
|
|
|
358
364
|
incrementCountMetric(TELEMETRY_GIT_COMMAND, { command: 'get_objects' })
|
|
359
365
|
const startTime = Date.now()
|
|
360
366
|
try {
|
|
361
|
-
result =
|
|
367
|
+
result = cachedExec(
|
|
362
368
|
'git',
|
|
363
369
|
[
|
|
364
370
|
'rev-list',
|
|
@@ -403,7 +409,7 @@ function generatePackFilesForCommits (commitsToUpload) {
|
|
|
403
409
|
// Generates pack files to upload and
|
|
404
410
|
// returns the ordered list of packfiles' paths
|
|
405
411
|
function execGitPackObjects (targetPath) {
|
|
406
|
-
return
|
|
412
|
+
return cachedExec(
|
|
407
413
|
'git',
|
|
408
414
|
[
|
|
409
415
|
'pack-objects',
|
|
@@ -450,6 +456,13 @@ function generatePackFilesForCommits (commitsToUpload) {
|
|
|
450
456
|
return result
|
|
451
457
|
}
|
|
452
458
|
|
|
459
|
+
function getRepositoryRoot () {
|
|
460
|
+
return sanitizedExec(
|
|
461
|
+
'git',
|
|
462
|
+
['rev-parse', '--show-toplevel']
|
|
463
|
+
)
|
|
464
|
+
}
|
|
465
|
+
|
|
453
466
|
// If there is ciMetadata, it takes precedence.
|
|
454
467
|
function getGitMetadata (ciMetadata) {
|
|
455
468
|
const {
|
|
@@ -480,7 +493,7 @@ function getGitMetadata (ciMetadata) {
|
|
|
480
493
|
commitMessage || sanitizedExec('git', ['show', '-s', '--format=%B'], null, null, null, false),
|
|
481
494
|
[GIT_BRANCH]: branch || sanitizedExec('git', ['rev-parse', '--abbrev-ref', 'HEAD']),
|
|
482
495
|
[GIT_COMMIT_SHA]: commitSHA || sanitizedExec('git', ['rev-parse', 'HEAD']),
|
|
483
|
-
[CI_WORKSPACE_PATH]: ciWorkspacePath ||
|
|
496
|
+
[CI_WORKSPACE_PATH]: ciWorkspacePath || getRepositoryRoot(),
|
|
484
497
|
}
|
|
485
498
|
|
|
486
499
|
if (headCommitSha) {
|
|
@@ -596,5 +609,6 @@ module.exports = {
|
|
|
596
609
|
getLocalBranches,
|
|
597
610
|
getMergeBase,
|
|
598
611
|
getCounts,
|
|
599
|
-
fetchHeadCommitSha
|
|
612
|
+
fetchHeadCommitSha,
|
|
613
|
+
getRepositoryRoot
|
|
600
614
|
}
|