dd-trace 5.102.0 → 5.103.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/ext/exporters.js +1 -0
- package/package.json +12 -11
- package/packages/datadog-esbuild/src/utils.js +2 -2
- package/packages/datadog-instrumentations/src/ai.js +1 -1
- package/packages/datadog-instrumentations/src/confluentinc-kafka-javascript.js +32 -15
- package/packages/datadog-instrumentations/src/couchbase.js +69 -220
- package/packages/datadog-instrumentations/src/cucumber.js +1 -1
- package/packages/datadog-instrumentations/src/electron/preload.js +42 -0
- package/packages/datadog-instrumentations/src/electron.js +240 -0
- package/packages/datadog-instrumentations/src/fetch.js +5 -5
- package/packages/datadog-instrumentations/src/graphql.js +13 -12
- package/packages/datadog-instrumentations/src/helpers/callback-instrumentor.js +1 -1
- package/packages/datadog-instrumentations/src/helpers/hook.js +4 -1
- package/packages/datadog-instrumentations/src/helpers/hooks.js +1 -0
- package/packages/datadog-instrumentations/src/helpers/instrument.js +2 -2
- package/packages/datadog-instrumentations/src/helpers/kafka.js +41 -0
- package/packages/datadog-instrumentations/src/ioredis.js +16 -12
- package/packages/datadog-instrumentations/src/jest.js +351 -50
- package/packages/datadog-instrumentations/src/kafkajs.js +164 -173
- package/packages/datadog-instrumentations/src/mocha/main.js +73 -1
- package/packages/datadog-instrumentations/src/mongodb-core.js +33 -8
- package/packages/datadog-instrumentations/src/pg.js +24 -10
- package/packages/datadog-instrumentations/src/playwright.js +427 -55
- package/packages/datadog-instrumentations/src/redis.js +19 -10
- package/packages/datadog-plugin-apollo/src/gateway/request.js +1 -21
- package/packages/datadog-plugin-aws-sdk/src/base.js +18 -24
- package/packages/datadog-plugin-aws-sdk/src/services/cloudwatchlogs.js +1 -1
- package/packages/datadog-plugin-aws-sdk/src/services/eventbridge.js +1 -1
- package/packages/datadog-plugin-aws-sdk/src/services/kinesis.js +1 -1
- package/packages/datadog-plugin-aws-sdk/src/services/lambda.js +1 -1
- package/packages/datadog-plugin-aws-sdk/src/services/redshift.js +1 -1
- package/packages/datadog-plugin-aws-sdk/src/services/s3.js +1 -1
- package/packages/datadog-plugin-aws-sdk/src/services/sns.js +2 -2
- package/packages/datadog-plugin-aws-sdk/src/services/sqs.js +1 -1
- package/packages/datadog-plugin-aws-sdk/src/services/stepfunctions.js +1 -1
- package/packages/datadog-plugin-couchbase/src/index.js +58 -52
- package/packages/datadog-plugin-cucumber/src/index.js +1 -0
- package/packages/datadog-plugin-cypress/src/cypress-plugin.js +214 -22
- package/packages/datadog-plugin-cypress/src/support.js +13 -1
- package/packages/datadog-plugin-electron/src/index.js +17 -0
- package/packages/datadog-plugin-electron/src/ipc.js +143 -0
- package/packages/datadog-plugin-electron/src/net.js +82 -0
- package/packages/datadog-plugin-google-cloud-pubsub/src/producer.js +27 -18
- package/packages/datadog-plugin-graphql/src/execute.js +6 -28
- package/packages/datadog-plugin-graphql/src/resolve.js +30 -35
- package/packages/datadog-plugin-graphql/src/tools/signature.js +32 -7
- package/packages/datadog-plugin-graphql/src/tools/transforms.js +118 -100
- package/packages/datadog-plugin-graphql/src/utils.js +29 -0
- package/packages/datadog-plugin-grpc/src/client.js +6 -7
- package/packages/datadog-plugin-grpc/src/util.js +57 -22
- package/packages/datadog-plugin-http/src/client.js +2 -2
- package/packages/datadog-plugin-jest/src/index.js +92 -50
- package/packages/datadog-plugin-mocha/src/index.js +1 -0
- package/packages/datadog-plugin-mongodb-core/src/index.js +36 -70
- package/packages/datadog-plugin-mysql/src/index.js +1 -1
- package/packages/datadog-plugin-openai/src/services.js +2 -1
- package/packages/datadog-plugin-pg/src/index.js +3 -3
- package/packages/datadog-plugin-playwright/src/index.js +4 -0
- package/packages/datadog-plugin-redis/src/index.js +18 -23
- package/packages/dd-trace/src/aiguard/index.js +3 -1
- package/packages/dd-trace/src/aiguard/sdk.js +36 -30
- package/packages/dd-trace/src/aiguard/tags.js +20 -11
- package/packages/dd-trace/src/appsec/iast/taint-tracking/rewriter.js +2 -2
- package/packages/dd-trace/src/appsec/iast/vulnerability-reporter.js +1 -1
- package/packages/dd-trace/src/azure_metadata.js +17 -6
- package/packages/dd-trace/src/ci-visibility/dynamic-instrumentation/index.js +4 -4
- package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +4 -2
- package/packages/dd-trace/src/ci-visibility/exporters/test-worker/index.js +6 -4
- package/packages/dd-trace/src/ci-visibility/requests/fs-cache.js +1 -1
- package/packages/dd-trace/src/config/defaults.js +3 -14
- package/packages/dd-trace/src/config/generated-config-types.d.ts +3 -1
- package/packages/dd-trace/src/config/helper.js +4 -0
- package/packages/dd-trace/src/config/index.js +2 -2
- package/packages/dd-trace/src/config/major-overrides.js +98 -0
- package/packages/dd-trace/src/config/parsers.js +7 -1
- package/packages/dd-trace/src/config/supported-configurations.json +51 -38
- package/packages/dd-trace/src/datastreams/checkpointer.js +2 -2
- package/packages/dd-trace/src/datastreams/manager.js +1 -1
- package/packages/dd-trace/src/datastreams/processor.js +2 -2
- package/packages/dd-trace/src/debugger/devtools_client/snapshot/collector.js +2 -2
- package/packages/dd-trace/src/debugger/devtools_client/source-maps.js +1 -1
- package/packages/dd-trace/src/debugger/devtools_client/state.js +2 -1
- package/packages/dd-trace/src/debugger/index.js +7 -7
- package/packages/dd-trace/src/dogstatsd.js +2 -2
- package/packages/dd-trace/src/encode/0.4.js +45 -54
- package/packages/dd-trace/src/encode/0.5.js +34 -3
- package/packages/dd-trace/src/encode/agentless-json.js +1 -1
- package/packages/dd-trace/src/exporter.js +2 -0
- package/packages/dd-trace/src/exporters/agent/index.js +2 -1
- package/packages/dd-trace/src/exporters/agentless/index.js +3 -2
- package/packages/dd-trace/src/exporters/agentless/writer.js +2 -2
- package/packages/dd-trace/src/exporters/common/buffering-exporter.js +2 -1
- package/packages/dd-trace/src/exporters/common/request.js +1 -1
- package/packages/dd-trace/src/exporters/electron/index.js +49 -0
- package/packages/dd-trace/src/external-logger/src/index.js +2 -1
- package/packages/dd-trace/src/git_metadata.js +10 -8
- package/packages/dd-trace/src/lambda/handler-paths.js +52 -0
- package/packages/dd-trace/src/lambda/index.js +62 -14
- package/packages/dd-trace/src/lambda/runtime/patch.js +21 -46
- package/packages/dd-trace/src/llmobs/index.js +13 -2
- package/packages/dd-trace/src/llmobs/plugins/bedrockruntime.js +45 -15
- package/packages/dd-trace/src/llmobs/writers/base.js +2 -1
- package/packages/dd-trace/src/openfeature/writers/base.js +2 -1
- package/packages/dd-trace/src/opentelemetry/metrics/periodic_metric_reader.js +2 -1
- package/packages/dd-trace/src/opentracing/propagation/text_map.js +20 -9
- package/packages/dd-trace/src/payload-tagging/config/index.js +2 -2
- package/packages/dd-trace/src/plugins/ci_plugin.js +49 -4
- package/packages/dd-trace/src/plugins/database.js +54 -12
- package/packages/dd-trace/src/plugins/index.js +1 -0
- package/packages/dd-trace/src/plugins/plugin.js +2 -4
- package/packages/dd-trace/src/plugins/util/ci.js +8 -8
- package/packages/dd-trace/src/plugins/util/git-cache.js +20 -18
- package/packages/dd-trace/src/plugins/util/stacktrace.js +2 -2
- package/packages/dd-trace/src/plugins/util/test.js +37 -5
- package/packages/dd-trace/src/plugins/util/user-provided-git.js +17 -15
- package/packages/dd-trace/src/priority_sampler.js +1 -1
- package/packages/dd-trace/src/profiling/profiler.js +1 -1
- package/packages/dd-trace/src/profiling/profilers/wall.js +1 -1
- package/packages/dd-trace/src/profiling/ssi-heuristics.js +1 -1
- package/packages/dd-trace/src/rate_limiter.js +1 -1
- package/packages/dd-trace/src/remote_config/scheduler.js +1 -1
- package/packages/dd-trace/src/ritm.js +2 -1
- package/packages/dd-trace/src/runtime_metrics/runtime_metrics.js +5 -8
- package/packages/dd-trace/src/serverless.js +5 -2
- package/packages/dd-trace/src/service-naming/schemas/v0/messaging.js +20 -0
- package/packages/dd-trace/src/service-naming/schemas/v0/web.js +4 -0
- package/packages/dd-trace/src/service-naming/schemas/v1/messaging.js +20 -0
- package/packages/dd-trace/src/service-naming/schemas/v1/web.js +4 -0
- package/packages/dd-trace/src/span_stats.js +1 -1
- package/packages/dd-trace/src/telemetry/dependencies.js +1 -1
- package/packages/dd-trace/src/telemetry/endpoints.js +1 -1
- package/packages/dd-trace/src/telemetry/telemetry.js +2 -2
- package/packages/dd-trace/src/lambda/runtime/ritm.js +0 -133
|
@@ -7,6 +7,10 @@ const {
|
|
|
7
7
|
channel,
|
|
8
8
|
addHook,
|
|
9
9
|
} = require('./helpers/instrument')
|
|
10
|
+
const {
|
|
11
|
+
clientToCluster,
|
|
12
|
+
cloneMessages,
|
|
13
|
+
} = require('./helpers/kafka')
|
|
10
14
|
|
|
11
15
|
const producerStartCh = channel('apm:kafkajs:produce:start')
|
|
12
16
|
const producerCommitCh = channel('apm:kafkajs:produce:commit')
|
|
@@ -24,6 +28,26 @@ const batchConsumerErrorCh = channel('apm:kafkajs:consume-batch:error')
|
|
|
24
28
|
|
|
25
29
|
const disabledHeaderWeakSet = new WeakSet()
|
|
26
30
|
|
|
31
|
+
addHook({ name: 'kafkajs', file: 'src/producer/index.js', versions: ['>=1.4'] }, (createProducer) =>
|
|
32
|
+
shimmer.wrapFunction(createProducer, original => function wrappedCreateProducer (params) {
|
|
33
|
+
const producer = original(params)
|
|
34
|
+
if (params?.cluster) {
|
|
35
|
+
clientToCluster.set(producer, params.cluster)
|
|
36
|
+
}
|
|
37
|
+
return producer
|
|
38
|
+
})
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
addHook({ name: 'kafkajs', file: 'src/consumer/index.js', versions: ['>=1.4'] }, (createConsumer) =>
|
|
42
|
+
shimmer.wrapFunction(createConsumer, original => function wrappedCreateConsumer (params) {
|
|
43
|
+
const consumer = original(params)
|
|
44
|
+
if (params?.cluster) {
|
|
45
|
+
clientToCluster.set(consumer, params.cluster)
|
|
46
|
+
}
|
|
47
|
+
return consumer
|
|
48
|
+
})
|
|
49
|
+
)
|
|
50
|
+
|
|
27
51
|
addHook({ name: 'kafkajs', file: 'src/index.js', versions: ['>=1.4'] }, (BaseKafka) => {
|
|
28
52
|
class Kafka extends BaseKafka {
|
|
29
53
|
constructor (options) {
|
|
@@ -34,79 +58,95 @@ addHook({ name: 'kafkajs', file: 'src/index.js', versions: ['>=1.4'] }, (BaseKaf
|
|
|
34
58
|
}
|
|
35
59
|
}
|
|
36
60
|
|
|
37
|
-
shimmer.wrap(Kafka.prototype, 'producer', createProducer => function (
|
|
38
|
-
const producer = createProducer.apply(this,
|
|
39
|
-
const
|
|
61
|
+
shimmer.wrap(Kafka.prototype, 'producer', createProducer => function () {
|
|
62
|
+
const producer = createProducer.apply(this, arguments)
|
|
63
|
+
const originalSend = producer.send
|
|
40
64
|
const bootstrapServers = this._brokers
|
|
41
|
-
|
|
42
|
-
const kafkaClusterIdPromise = getKafkaClusterId(this)
|
|
65
|
+
const cluster = clientToCluster.get(producer)
|
|
43
66
|
|
|
44
67
|
producer.send = function (...args) {
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
const ctx = {
|
|
49
|
-
bootstrapServers,
|
|
50
|
-
clusterId,
|
|
51
|
-
disableHeaderInjection: disabledHeaderWeakSet.has(producer),
|
|
52
|
-
messages,
|
|
53
|
-
topic,
|
|
54
|
-
}
|
|
68
|
+
if (!producerStartCh.hasSubscribers) {
|
|
69
|
+
return originalSend.apply(this, args)
|
|
70
|
+
}
|
|
55
71
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
72
|
+
// Fast path: kafkajs has fetched metadata, so clusterId is already on
|
|
73
|
+
// the broker pool.
|
|
74
|
+
const metadata = cluster?.brokerPool?.metadata
|
|
75
|
+
if (metadata) {
|
|
76
|
+
return runSend.call(this, args, metadata.clusterId)
|
|
77
|
+
}
|
|
61
78
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
// Fixes bug where we would inject message headers for kafka brokers that don't support headers
|
|
75
|
-
// (version <0.11). On the error, we disable header injection.
|
|
76
|
-
// Unfortunately the error name / type is not more specific.
|
|
77
|
-
// This approach is implemented by other tracers as well.
|
|
78
|
-
if (err.name === 'KafkaJSProtocolError' && err.type === 'UNKNOWN') {
|
|
79
|
-
disabledHeaderWeakSet.add(producer)
|
|
80
|
-
log.error(
|
|
81
|
-
// eslint-disable-next-line @stylistic/max-len
|
|
82
|
-
'Kafka Broker responded with UNKNOWN_SERVER_ERROR (-1). Please look at broker logs for more information. Tracer message header injection for Kafka is disabled.'
|
|
83
|
-
)
|
|
84
|
-
}
|
|
85
|
-
producerErrorCh.publish(err)
|
|
86
|
-
}
|
|
87
|
-
producerFinishCh.publish(ctx)
|
|
88
|
-
})
|
|
79
|
+
// Slow path, taken at most once per producer connect cycle. Prime the
|
|
80
|
+
// metadata fetch kafkajs's send would do internally a few stack frames
|
|
81
|
+
// later. `sharedPromiseTo` collapses our call and kafkajs's call into a
|
|
82
|
+
// single round trip, so total latency is unchanged.
|
|
83
|
+
if (typeof cluster?.refreshMetadataIfNecessary !== 'function') {
|
|
84
|
+
return runSend.call(this, args)
|
|
85
|
+
}
|
|
86
|
+
return cluster.refreshMetadataIfNecessary().then(
|
|
87
|
+
() => runSend.call(this, args, cluster.brokerPool?.metadata?.clusterId),
|
|
88
|
+
() => runSend.call(this, args)
|
|
89
|
+
)
|
|
90
|
+
}
|
|
89
91
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
92
|
+
function runSend (args, clusterId) {
|
|
93
|
+
const arg0 = args[0]
|
|
94
|
+
const topic = arg0?.topic
|
|
95
|
+
const inputMessages = Array.isArray(arg0?.messages) ? arg0.messages : []
|
|
96
|
+
const disableHeaderInjection = disabledHeaderWeakSet.has(producer)
|
|
97
|
+
|
|
98
|
+
// Hand kafkajs and the plugin a shallow clone so injection writes to
|
|
99
|
+
// tracer-owned objects instead of the caller's. With injection
|
|
100
|
+
// disabled the clone must not seed `headers: {}` either: brokers that
|
|
101
|
+
// reject any header field cannot recover otherwise.
|
|
102
|
+
let messages = inputMessages
|
|
103
|
+
if (inputMessages.length > 0) {
|
|
104
|
+
messages = cloneMessages(inputMessages, !disableHeaderInjection)
|
|
105
|
+
args[0] = { ...arg0, messages }
|
|
98
106
|
}
|
|
99
107
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
108
|
+
const ctx = {
|
|
109
|
+
bootstrapServers,
|
|
110
|
+
clusterId,
|
|
111
|
+
disableHeaderInjection,
|
|
112
|
+
messages,
|
|
113
|
+
topic,
|
|
105
114
|
}
|
|
106
115
|
|
|
107
|
-
|
|
108
|
-
|
|
116
|
+
return producerStartCh.runStores(ctx, () => {
|
|
117
|
+
try {
|
|
118
|
+
const result = originalSend.apply(this, args)
|
|
119
|
+
result.then(
|
|
120
|
+
(res) => {
|
|
121
|
+
ctx.result = res
|
|
122
|
+
producerFinishCh.publish(ctx)
|
|
123
|
+
producerCommitCh.publish(ctx)
|
|
124
|
+
},
|
|
125
|
+
(error) => {
|
|
126
|
+
ctx.error = error
|
|
127
|
+
if (error) {
|
|
128
|
+
if (error.name === 'KafkaJSProtocolError' && error.type === 'UNKNOWN') {
|
|
129
|
+
disabledHeaderWeakSet.add(producer)
|
|
130
|
+
log.error(
|
|
131
|
+
// eslint-disable-next-line @stylistic/max-len
|
|
132
|
+
'Kafka Broker responded with UNKNOWN_SERVER_ERROR (-1). Please look at broker logs for more information. Tracer message header injection for Kafka is disabled.'
|
|
133
|
+
)
|
|
134
|
+
}
|
|
135
|
+
producerErrorCh.publish(error)
|
|
136
|
+
}
|
|
137
|
+
producerFinishCh.publish(ctx)
|
|
138
|
+
}
|
|
139
|
+
)
|
|
140
|
+
return result
|
|
141
|
+
} catch (error) {
|
|
142
|
+
ctx.error = error
|
|
143
|
+
producerErrorCh.publish(ctx)
|
|
144
|
+
producerFinishCh.publish(ctx)
|
|
145
|
+
throw error
|
|
146
|
+
}
|
|
147
|
+
})
|
|
109
148
|
}
|
|
149
|
+
|
|
110
150
|
return producer
|
|
111
151
|
})
|
|
112
152
|
|
|
@@ -115,24 +155,26 @@ addHook({ name: 'kafkajs', file: 'src/index.js', versions: ['>=1.4'] }, (BaseKaf
|
|
|
115
155
|
return createConsumer.apply(this, args)
|
|
116
156
|
}
|
|
117
157
|
|
|
118
|
-
const
|
|
119
|
-
|
|
158
|
+
const consumer = createConsumer.apply(this, arguments)
|
|
159
|
+
const cluster = clientToCluster.get(consumer)
|
|
160
|
+
const groupId = arguments[0].groupId
|
|
161
|
+
|
|
162
|
+
const readClusterId = () => cluster?.brokerPool?.metadata?.clusterId
|
|
120
163
|
|
|
121
|
-
const eachMessageExtractor = (args
|
|
164
|
+
const eachMessageExtractor = (args) => {
|
|
122
165
|
const { topic, partition, message } = args[0]
|
|
123
|
-
return { topic, partition, message, groupId, clusterId }
|
|
166
|
+
return { topic, partition, message, groupId, clusterId: readClusterId() }
|
|
124
167
|
}
|
|
125
168
|
|
|
126
|
-
const eachBatchExtractor = (args
|
|
169
|
+
const eachBatchExtractor = (args) => {
|
|
127
170
|
const { batch } = args[0]
|
|
128
171
|
const { topic, partition, messages } = batch
|
|
129
|
-
return { topic, partition, messages, groupId, clusterId }
|
|
172
|
+
return { topic, partition, messages, groupId, clusterId: readClusterId() }
|
|
130
173
|
}
|
|
131
174
|
|
|
132
|
-
const consumer = createConsumer.apply(this, args)
|
|
133
|
-
|
|
134
175
|
consumer.on(consumer.events.COMMIT_OFFSETS, (event) => {
|
|
135
176
|
const { payload: { groupId: commitGroupId, topics } } = event
|
|
177
|
+
const clusterId = readClusterId()
|
|
136
178
|
const commitList = []
|
|
137
179
|
for (const { topic, partitions } of topics) {
|
|
138
180
|
for (const { partition, offset } of partitions) {
|
|
@@ -141,7 +183,7 @@ addHook({ name: 'kafkajs', file: 'src/index.js', versions: ['>=1.4'] }, (BaseKaf
|
|
|
141
183
|
partition,
|
|
142
184
|
offset,
|
|
143
185
|
topic,
|
|
144
|
-
clusterId
|
|
186
|
+
clusterId,
|
|
145
187
|
})
|
|
146
188
|
}
|
|
147
189
|
}
|
|
@@ -149,118 +191,67 @@ addHook({ name: 'kafkajs', file: 'src/index.js', versions: ['>=1.4'] }, (BaseKaf
|
|
|
149
191
|
})
|
|
150
192
|
|
|
151
193
|
const run = consumer.run
|
|
152
|
-
const groupId = args[0].groupId
|
|
153
194
|
|
|
154
195
|
consumer.run = function ({ eachMessage, eachBatch, ...runArgs }) {
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
batchConsumerErrorCh,
|
|
173
|
-
eachBatchExtractor,
|
|
174
|
-
clusterId
|
|
175
|
-
),
|
|
176
|
-
...runArgs,
|
|
177
|
-
})
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
if (isPromise(kafkaClusterIdPromise)) {
|
|
181
|
-
// promise is not resolved
|
|
182
|
-
return kafkaClusterIdPromise.then((clusterId) => {
|
|
183
|
-
return wrapConsume(clusterId)
|
|
184
|
-
})
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
// promise is already resolved
|
|
188
|
-
return wrapConsume(kafkaClusterIdPromise)
|
|
196
|
+
return run({
|
|
197
|
+
eachMessage: wrappedCallback(
|
|
198
|
+
eachMessage,
|
|
199
|
+
consumerStartCh,
|
|
200
|
+
consumerFinishCh,
|
|
201
|
+
consumerErrorCh,
|
|
202
|
+
eachMessageExtractor
|
|
203
|
+
),
|
|
204
|
+
eachBatch: wrappedCallback(
|
|
205
|
+
eachBatch,
|
|
206
|
+
batchConsumerStartCh,
|
|
207
|
+
batchConsumerFinishCh,
|
|
208
|
+
batchConsumerErrorCh,
|
|
209
|
+
eachBatchExtractor
|
|
210
|
+
),
|
|
211
|
+
...runArgs,
|
|
212
|
+
})
|
|
189
213
|
}
|
|
214
|
+
|
|
190
215
|
return consumer
|
|
191
216
|
})
|
|
217
|
+
|
|
192
218
|
return Kafka
|
|
193
219
|
})
|
|
194
220
|
|
|
195
|
-
const wrappedCallback = (fn, startCh, finishCh, errorCh, extractArgs
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
}
|
|
221
|
+
const wrappedCallback = (fn, startCh, finishCh, errorCh, extractArgs) => {
|
|
222
|
+
if (typeof fn !== 'function') return fn
|
|
223
|
+
return function (...args) {
|
|
224
|
+
const ctx = {
|
|
225
|
+
extractedArgs: extractArgs(args),
|
|
226
|
+
}
|
|
202
227
|
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
}
|
|
222
|
-
return result
|
|
223
|
-
} catch (e) {
|
|
224
|
-
ctx.error = e
|
|
225
|
-
errorCh.publish(ctx)
|
|
228
|
+
return startCh.runStores(ctx, () => {
|
|
229
|
+
try {
|
|
230
|
+
const result = fn.apply(this, args)
|
|
231
|
+
if (result && typeof result.then === 'function') {
|
|
232
|
+
result.then(
|
|
233
|
+
(res) => {
|
|
234
|
+
ctx.result = res
|
|
235
|
+
finishCh.publish(ctx)
|
|
236
|
+
},
|
|
237
|
+
(error) => {
|
|
238
|
+
ctx.error = error
|
|
239
|
+
if (error) {
|
|
240
|
+
errorCh.publish(ctx)
|
|
241
|
+
}
|
|
242
|
+
finishCh.publish(ctx)
|
|
243
|
+
}
|
|
244
|
+
)
|
|
245
|
+
} else {
|
|
226
246
|
finishCh.publish(ctx)
|
|
227
|
-
throw e
|
|
228
247
|
}
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
return kafka._ddKafkaClusterId
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
if (!kafka.admin) {
|
|
240
|
-
return null
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
const admin = kafka.admin()
|
|
244
|
-
|
|
245
|
-
if (!admin.describeCluster) {
|
|
246
|
-
return null
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
return admin.connect()
|
|
250
|
-
.then(() => {
|
|
251
|
-
return admin.describeCluster()
|
|
252
|
-
})
|
|
253
|
-
.then((clusterInfo) => {
|
|
254
|
-
const clusterId = clusterInfo?.clusterId
|
|
255
|
-
kafka._ddKafkaClusterId = clusterId
|
|
256
|
-
admin.disconnect()
|
|
257
|
-
return clusterId
|
|
258
|
-
})
|
|
259
|
-
.catch((error) => {
|
|
260
|
-
throw error
|
|
248
|
+
return result
|
|
249
|
+
} catch (error) {
|
|
250
|
+
ctx.error = error
|
|
251
|
+
errorCh.publish(ctx)
|
|
252
|
+
finishCh.publish(ctx)
|
|
253
|
+
throw error
|
|
254
|
+
}
|
|
261
255
|
})
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
function isPromise (obj) {
|
|
265
|
-
return !!obj && (typeof obj === 'object' || typeof obj === 'function') && typeof obj.then === 'function'
|
|
256
|
+
}
|
|
266
257
|
}
|
|
@@ -469,6 +469,10 @@ addHook({
|
|
|
469
469
|
}
|
|
470
470
|
|
|
471
471
|
const { suitesByTestFile, numSuitesByTestFile } = getSuitesByTestFile(this.suite)
|
|
472
|
+
// Root-level tests (direct children of root, no describe wrapper) keyed by file.
|
|
473
|
+
// Populated during the root 'suite' event so the normal finish path can include them
|
|
474
|
+
// in mixed-file status calculation.
|
|
475
|
+
const rootTestsByFile = new Map()
|
|
472
476
|
|
|
473
477
|
this.once('start', getOnStartHandler(frameworkVersion))
|
|
474
478
|
|
|
@@ -489,6 +493,30 @@ addHook({
|
|
|
489
493
|
|
|
490
494
|
this.on('suite', function (suite) {
|
|
491
495
|
if (suite.root || !suite.tests.length) {
|
|
496
|
+
// This branch can be triggered when we have top level it(...) inside test files.
|
|
497
|
+
// In that case, they all (even if they are from different files) are going to be
|
|
498
|
+
// children of the root suite.
|
|
499
|
+
// Note: We could have suites that contain top level it(...) and also it(...) nested
|
|
500
|
+
// inside describe(...) ("mixed case"). Duplication is avoided by the context guard
|
|
501
|
+
// below. Since 'suite' fires for root first, in the mixed case the ctx is created
|
|
502
|
+
// here and the describe-based handler finds it already set.
|
|
503
|
+
if (suite.root && suite.tests.length > 0) {
|
|
504
|
+
const files = new Set(suite.tests.map(test => test.file).filter(Boolean))
|
|
505
|
+
for (const file of files) {
|
|
506
|
+
rootTestsByFile.set(file, suite.tests.filter(t => t.file === file))
|
|
507
|
+
if (testFileToSuiteCtx.get(file)) continue
|
|
508
|
+
const isUnskippable = unskippableSuites.includes(file)
|
|
509
|
+
isForcedToRun = isUnskippable && suitesToSkip.includes(getTestSuitePath(file, process.cwd()))
|
|
510
|
+
const ctx = {
|
|
511
|
+
testSuiteAbsolutePath: file,
|
|
512
|
+
isUnskippable,
|
|
513
|
+
isForcedToRun,
|
|
514
|
+
itrCorrelationId,
|
|
515
|
+
}
|
|
516
|
+
testFileToSuiteCtx.set(file, ctx)
|
|
517
|
+
testSuiteStartCh.runStores(ctx, () => {})
|
|
518
|
+
}
|
|
519
|
+
}
|
|
492
520
|
return
|
|
493
521
|
}
|
|
494
522
|
let ctx = testFileToSuiteCtx.get(suite.file)
|
|
@@ -508,6 +536,44 @@ addHook({
|
|
|
508
536
|
|
|
509
537
|
this.on('suite end', function (suite) {
|
|
510
538
|
if (suite.root) {
|
|
539
|
+
// Symmetric to the suite start fix
|
|
540
|
+
const fileToTests = new Map()
|
|
541
|
+
for (const test of suite.tests) {
|
|
542
|
+
if (!test.file) continue
|
|
543
|
+
if (!fileToTests.has(test.file)) fileToTests.set(test.file, [])
|
|
544
|
+
fileToTests.get(test.file).push(test)
|
|
545
|
+
}
|
|
546
|
+
for (const [file, tests] of fileToTests) {
|
|
547
|
+
// Mixed case: if a file appears in suitesByTestFile (pre-populated before the run),
|
|
548
|
+
// its numSuitesByTestFile counter hits zero when its last describe-based suite ends
|
|
549
|
+
// and the normal path below fires testSuiteFinishCh. Since root is last when
|
|
550
|
+
// 'suite end' fires, any such file has already been handled — skipping it here
|
|
551
|
+
// avoids duplication.
|
|
552
|
+
if (suitesByTestFile[file]) continue
|
|
553
|
+
let status = 'pass'
|
|
554
|
+
if (tests.every(test => test.isPending())) {
|
|
555
|
+
status = 'skip'
|
|
556
|
+
} else {
|
|
557
|
+
for (const test of tests) {
|
|
558
|
+
if (test.state === 'failed' || test.timedOut) {
|
|
559
|
+
status = 'fail'
|
|
560
|
+
break
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
if (global.__coverage__) {
|
|
565
|
+
const coverageFiles = getCoveredFilenamesFromCoverage(global.__coverage__)
|
|
566
|
+
testSuiteCodeCoverageCh.publish({ coverageFiles, suiteFile: file })
|
|
567
|
+
mergeCoverage(global.__coverage__, originalCoverageMap)
|
|
568
|
+
resetCoverage(global.__coverage__)
|
|
569
|
+
}
|
|
570
|
+
const ctx = testFileToSuiteCtx.get(file)
|
|
571
|
+
if (ctx) {
|
|
572
|
+
testSuiteFinishCh.publish({ status, ...ctx.currentStore }, () => {})
|
|
573
|
+
} else {
|
|
574
|
+
log.warn('No ctx found for suite', file)
|
|
575
|
+
}
|
|
576
|
+
}
|
|
511
577
|
return
|
|
512
578
|
}
|
|
513
579
|
const suitesInTestFile = suitesByTestFile[suite.file]
|
|
@@ -517,8 +583,9 @@ addHook({
|
|
|
517
583
|
return
|
|
518
584
|
}
|
|
519
585
|
|
|
586
|
+
const rootTests = rootTestsByFile.get(suite.file) || []
|
|
520
587
|
let status = 'pass'
|
|
521
|
-
if (suitesInTestFile.every(suite => suite.pending)) {
|
|
588
|
+
if (suitesInTestFile.every(suite => suite.pending) && rootTests.every(test => test.isPending())) {
|
|
522
589
|
status = 'skip'
|
|
523
590
|
} else {
|
|
524
591
|
// has to check every test in the test file
|
|
@@ -530,6 +597,11 @@ addHook({
|
|
|
530
597
|
}
|
|
531
598
|
})
|
|
532
599
|
})
|
|
600
|
+
for (const test of rootTests) {
|
|
601
|
+
if (test.state === 'failed' || test.timedOut) {
|
|
602
|
+
status = 'fail'
|
|
603
|
+
}
|
|
604
|
+
}
|
|
533
605
|
}
|
|
534
606
|
|
|
535
607
|
if (global.__coverage__) {
|
|
@@ -11,6 +11,14 @@ const startCh = channel('apm:mongodb:query:start')
|
|
|
11
11
|
const finishCh = channel('apm:mongodb:query:finish')
|
|
12
12
|
const errorCh = channel('apm:mongodb:query:error')
|
|
13
13
|
|
|
14
|
+
// Per-Connection cached topology shape (mongodb >= 4). The Connection's `address` is immutable
|
|
15
|
+
// for the lifetime of the connection, so we synthesize the `{ s: { options } }` envelope the
|
|
16
|
+
// plugin expects only once per connection. A WeakMap keeps the cache off the foreign Connection
|
|
17
|
+
// instance — no extra own-key visible to `Reflect.ownKeys`, `Object.freeze`, or another tracer's
|
|
18
|
+
// instrumentation walking the connection.
|
|
19
|
+
/** @type {WeakMap<object, { s: { options: { host?: string, port?: string } } }>} */
|
|
20
|
+
const topologyCache = new WeakMap()
|
|
21
|
+
|
|
14
22
|
addHook({ name: 'mongodb-core', versions: ['2 - 3.1.9'] }, Server => {
|
|
15
23
|
const serverProto = Server.Server.prototype
|
|
16
24
|
shimmer.wrap(serverProto, 'command', command => wrapCommand(command, 'command'))
|
|
@@ -88,21 +96,36 @@ function wrapUnifiedCommand (command, operation, name) {
|
|
|
88
96
|
}
|
|
89
97
|
|
|
90
98
|
function wrapConnectionCommand (command, operation, name, instrumentFn = instrument) {
|
|
99
|
+
const opts = { name }
|
|
91
100
|
return function (ns, ops) {
|
|
92
101
|
if (!startCh.hasSubscribers) {
|
|
93
102
|
return command.apply(this, arguments)
|
|
94
103
|
}
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
ns = `${ns.db}.${ns.collection}`
|
|
102
|
-
return instrumentFn(operation, command, this, arguments, topology, ns, ops, { name })
|
|
104
|
+
let topology = topologyCache.get(this)
|
|
105
|
+
if (topology === undefined) {
|
|
106
|
+
topology = synthesizeTopology(this.address)
|
|
107
|
+
topologyCache.set(this, topology)
|
|
108
|
+
}
|
|
109
|
+
return instrumentFn(operation, command, this, arguments, topology, `${ns.db}.${ns.collection}`, ops, opts)
|
|
103
110
|
}
|
|
104
111
|
}
|
|
105
112
|
|
|
113
|
+
/**
|
|
114
|
+
* @param {string} address
|
|
115
|
+
* @returns {{ s: { options: { host?: string, port?: string } } }}
|
|
116
|
+
*/
|
|
117
|
+
function synthesizeTopology (address) {
|
|
118
|
+
if (typeof address === 'string') {
|
|
119
|
+
const colon = address.indexOf(':')
|
|
120
|
+
// Match the previous `.split(':')` length-2 check: exactly one colon with non-empty parts on both sides.
|
|
121
|
+
if (colon > 0 && colon < address.length - 1 && !address.includes(':', colon + 1)) {
|
|
122
|
+
return { s: { options: { host: address.slice(0, colon), port: address.slice(colon + 1) } } }
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
// No port means the address is a random UUID, an IPv6 form, or otherwise unparseable, so no host either.
|
|
126
|
+
return { s: { options: {} } }
|
|
127
|
+
}
|
|
128
|
+
|
|
106
129
|
function wrapQuery (query, operation, name) {
|
|
107
130
|
return function (...args) {
|
|
108
131
|
if (!startCh.hasSubscribers) {
|
|
@@ -171,6 +194,8 @@ function instrument (operation, command, instance, args, server, ns, ops, option
|
|
|
171
194
|
})
|
|
172
195
|
}
|
|
173
196
|
|
|
197
|
+
module.exports = { synthesizeTopology }
|
|
198
|
+
|
|
174
199
|
function instrumentPromise (operation, command, instance, args, server, ns, ops, options = {}) {
|
|
175
200
|
const name = options.name || (ops && Object.keys(ops)[0])
|
|
176
201
|
|
|
@@ -15,6 +15,10 @@ const errorCh = channel('apm:pg:query:error')
|
|
|
15
15
|
const startPoolQueryCh = channel('datadog:pg:pool:query:start')
|
|
16
16
|
const finishPoolQueryCh = channel('datadog:pg:pool:query:finish')
|
|
17
17
|
|
|
18
|
+
// Drivers like pg-promise reuse the same prepared-statement query object across executions; cache
|
|
19
|
+
// the un-injected `text` so the wrap doesn't capture a previous DBM injection as the new original.
|
|
20
|
+
const originalTextCache = new WeakMap()
|
|
21
|
+
|
|
18
22
|
addHook({ name: 'pg', versions: ['>=8.0.3'], file: 'lib/native/client.js' }, Client => {
|
|
19
23
|
shimmer.wrap(Client.prototype, 'query', query => wrapQuery(query))
|
|
20
24
|
return Client
|
|
@@ -43,20 +47,14 @@ function wrapQuery (query) {
|
|
|
43
47
|
: { text: args[0] }
|
|
44
48
|
|
|
45
49
|
const textPropObj = pgQuery.cursor ?? pgQuery
|
|
46
|
-
const textProp = Object.getOwnPropertyDescriptor(textPropObj, 'text')
|
|
47
50
|
const stream = typeof textPropObj.read === 'function'
|
|
48
51
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
if (!textProp || textProp.configurable) {
|
|
52
|
+
let originalText = originalTextCache.get(textPropObj)
|
|
53
|
+
if (originalText === undefined) {
|
|
52
54
|
originalText = textPropObj.text
|
|
53
|
-
|
|
54
|
-
Object.defineProperty(textPropObj, 'text', {
|
|
55
|
-
get () {
|
|
56
|
-
return this?.__ddInjectableQuery || originalText
|
|
57
|
-
},
|
|
58
|
-
})
|
|
55
|
+
originalTextCache.set(textPropObj, originalText)
|
|
59
56
|
}
|
|
57
|
+
|
|
60
58
|
const abortController = new AbortController()
|
|
61
59
|
const ctx = {
|
|
62
60
|
params: this.connectionParameters,
|
|
@@ -109,6 +107,22 @@ function wrapQuery (query) {
|
|
|
109
107
|
return Promise.reject(error)
|
|
110
108
|
}
|
|
111
109
|
|
|
110
|
+
const injected = ctx.injected
|
|
111
|
+
if (injected !== undefined) {
|
|
112
|
+
// Skip the per-read getter trampoline when `text` is a configurable, writable data
|
|
113
|
+
// property (the pg / pg-cursor common shape). Accessor descriptors and read-only data
|
|
114
|
+
// still go through `defineProperty(get)` so `get text ()` query objects keep working.
|
|
115
|
+
const textProp = Object.getOwnPropertyDescriptor(textPropObj, 'text')
|
|
116
|
+
if (textProp?.configurable === true && textProp.writable === true) {
|
|
117
|
+
textPropObj.text = injected
|
|
118
|
+
} else if (textProp === undefined || textProp.configurable === true) {
|
|
119
|
+
Object.defineProperty(textPropObj, 'text', {
|
|
120
|
+
configurable: true,
|
|
121
|
+
get () { return injected },
|
|
122
|
+
})
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
112
126
|
args[0] = pgQuery
|
|
113
127
|
|
|
114
128
|
const retval = query.apply(this, args)
|