dd-trace 5.99.1 → 5.101.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 -1
- package/index.d.ts +14 -0
- package/package.json +8 -8
- package/packages/datadog-instrumentations/src/cucumber.js +69 -5
- package/packages/datadog-instrumentations/src/cypress.js +5 -3
- package/packages/datadog-instrumentations/src/express.js +3 -2
- package/packages/datadog-instrumentations/src/helpers/hooks.js +1 -0
- package/packages/datadog-instrumentations/src/hono.js +15 -4
- package/packages/datadog-instrumentations/src/http/client.js +20 -3
- package/packages/datadog-instrumentations/src/jest.js +146 -90
- package/packages/datadog-instrumentations/src/mocha/common.js +4 -1
- package/packages/datadog-instrumentations/src/mocha/main.js +43 -26
- package/packages/datadog-instrumentations/src/mocha/utils.js +114 -96
- package/packages/datadog-instrumentations/src/mocha/worker.js +7 -4
- package/packages/datadog-instrumentations/src/otel-sdk-trace.js +11 -6
- package/packages/datadog-instrumentations/src/path-to-regexp.js +44 -0
- package/packages/datadog-instrumentations/src/playwright.js +108 -18
- package/packages/datadog-instrumentations/src/router.js +53 -33
- package/packages/datadog-instrumentations/src/vitest.js +76 -30
- package/packages/datadog-plugin-aws-sdk/src/base.js +1 -1
- package/packages/datadog-plugin-aws-sdk/src/services/dynamodb.js +1 -1
- package/packages/datadog-plugin-bullmq/src/consumer.js +5 -4
- package/packages/datadog-plugin-bullmq/src/producer.js +37 -29
- package/packages/datadog-plugin-cypress/src/cypress-plugin.js +49 -9
- package/packages/datadog-plugin-cypress/src/plugin.js +5 -14
- package/packages/datadog-plugin-cypress/src/support.js +22 -21
- package/packages/datadog-plugin-grpc/src/client.js +1 -1
- package/packages/datadog-plugin-grpc/src/server.js +1 -1
- package/packages/datadog-plugin-kafkajs/src/consumer.js +2 -9
- package/packages/datadog-plugin-kafkajs/src/producer.js +2 -8
- package/packages/datadog-plugin-mongodb-core/src/index.js +2 -3
- package/packages/datadog-plugin-playwright/src/index.js +6 -0
- package/packages/datadog-plugin-router/src/index.js +13 -0
- package/packages/dd-trace/index.js +4 -3
- package/packages/dd-trace/src/aiguard/sdk.js +2 -2
- package/packages/dd-trace/src/appsec/reporter.js +4 -1
- package/packages/dd-trace/src/baggage.js +10 -0
- package/packages/dd-trace/src/ci-visibility/lage.js +2 -1
- package/packages/dd-trace/src/ci-visibility/requests/request.js +11 -33
- package/packages/dd-trace/src/config/config-types.d.ts +0 -2
- package/packages/dd-trace/src/config/generated-config-types.d.ts +17 -41
- package/packages/dd-trace/src/config/index.js +7 -60
- package/packages/dd-trace/src/config/normalize-service.js +31 -0
- package/packages/dd-trace/src/config/supported-configurations.json +15 -32
- package/packages/dd-trace/src/datastreams/checkpointer.js +4 -10
- package/packages/dd-trace/src/datastreams/encoding.js +39 -28
- package/packages/dd-trace/src/datastreams/pathway.js +29 -26
- package/packages/dd-trace/src/datastreams/processor.js +17 -15
- package/packages/dd-trace/src/datastreams/size.js +6 -2
- package/packages/dd-trace/src/debugger/config.js +6 -3
- package/packages/dd-trace/src/debugger/devtools_client/index.js +2 -5
- package/packages/dd-trace/src/debugger/devtools_client/send.js +2 -1
- package/packages/dd-trace/src/dogstatsd.js +10 -7
- package/packages/dd-trace/src/encode/0.4.js +3 -3
- package/packages/dd-trace/src/encode/0.5.js +2 -2
- package/packages/dd-trace/src/encode/agentless-json.js +2 -2
- package/packages/dd-trace/src/encode/tags-processors.js +2 -27
- package/packages/dd-trace/src/exporters/common/request.js +22 -11
- package/packages/dd-trace/src/exporters/common/retry.js +104 -0
- package/packages/dd-trace/src/git_metadata.js +66 -0
- package/packages/dd-trace/src/git_metadata_tagger.js +13 -5
- package/packages/dd-trace/src/heap_snapshots.js +4 -4
- package/packages/dd-trace/src/id.js +15 -26
- package/packages/dd-trace/src/llmobs/constants/tags.js +2 -0
- package/packages/dd-trace/src/llmobs/plugins/anthropic/index.js +27 -16
- package/packages/dd-trace/src/llmobs/plugins/anthropic/util.js +3 -0
- package/packages/dd-trace/src/llmobs/plugins/genai/util.js +30 -13
- package/packages/dd-trace/src/llmobs/plugins/openai/index.js +20 -50
- package/packages/dd-trace/src/llmobs/sdk.js +5 -1
- package/packages/dd-trace/src/llmobs/span_processor.js +28 -2
- package/packages/dd-trace/src/llmobs/tagger.js +42 -0
- package/packages/dd-trace/src/llmobs/telemetry.js +29 -0
- package/packages/dd-trace/src/llmobs/util.js +80 -5
- package/packages/dd-trace/src/openfeature/eval-metrics-hook.js +2 -2
- package/packages/dd-trace/src/opentelemetry/active-span-proxy.js +42 -0
- package/packages/dd-trace/src/opentelemetry/bridge-span-base.js +106 -0
- package/packages/dd-trace/src/opentelemetry/context_manager.js +22 -10
- package/packages/dd-trace/src/opentelemetry/span-helpers.js +308 -0
- package/packages/dd-trace/src/opentelemetry/span.js +42 -108
- package/packages/dd-trace/src/opentelemetry/tracer.js +11 -36
- package/packages/dd-trace/src/opentracing/propagation/text_map.js +95 -36
- package/packages/dd-trace/src/opentracing/propagation/tracestate.js +98 -32
- package/packages/dd-trace/src/opentracing/span.js +58 -49
- package/packages/dd-trace/src/opentracing/span_context.js +1 -0
- package/packages/dd-trace/src/plugins/util/ci.js +119 -32
- package/packages/dd-trace/src/plugins/util/test.js +293 -27
- package/packages/dd-trace/src/priority_sampler.js +6 -4
- package/packages/dd-trace/src/profiling/config.js +5 -4
- package/packages/dd-trace/src/profiling/ssi-heuristics.js +2 -2
- package/packages/dd-trace/src/propagation-hash/index.js +1 -1
- package/packages/dd-trace/src/proxy.js +3 -3
- package/packages/dd-trace/src/remote_config/index.js +5 -3
- package/packages/dd-trace/src/runtime_metrics/runtime_metrics.js +1 -1
- package/packages/dd-trace/src/span_format.js +52 -5
- package/packages/dd-trace/src/span_processor.js +1 -5
- package/packages/dd-trace/src/spanleak.js +0 -1
- package/packages/dd-trace/src/telemetry/telemetry.js +7 -5
- package/packages/dd-trace/src/tracer_metadata.js +1 -1
- package/packages/dd-trace/src/util.js +17 -0
- package/vendor/dist/path-to-regexp/LICENSE +0 -21
- package/vendor/dist/path-to-regexp/index.js +0 -1
|
@@ -7,18 +7,24 @@ const crypto = require('crypto')
|
|
|
7
7
|
const { LRUCache } = require('../../../../vendor/dist/lru-cache')
|
|
8
8
|
const log = require('../log')
|
|
9
9
|
const pick = require('../../../datadog-core/src/utils/src/pick')
|
|
10
|
-
const {
|
|
10
|
+
const { encodeVarintInto, decodeVarint } = require('./encoding')
|
|
11
11
|
|
|
12
12
|
const cache = new LRUCache({ max: 500 })
|
|
13
13
|
|
|
14
14
|
const CONTEXT_PROPAGATION_KEY = 'dd-pathway-ctx'
|
|
15
15
|
const CONTEXT_PROPAGATION_KEY_BASE64 = 'dd-pathway-ctx-base64'
|
|
16
16
|
|
|
17
|
+
const PATHWAY_CONTEXT_BYTES = 20
|
|
18
|
+
|
|
19
|
+
// Reused across `encodePathwayContext` calls; the buffer is fully rewritten before each
|
|
20
|
+
// `Buffer.from(...)` copy-out so callers never observe mutation between checkpoints.
|
|
21
|
+
const pathwayScratch = Buffer.allocUnsafe(PATHWAY_CONTEXT_BYTES)
|
|
22
|
+
|
|
17
23
|
const logKeys = [CONTEXT_PROPAGATION_KEY, CONTEXT_PROPAGATION_KEY_BASE64]
|
|
18
24
|
|
|
19
25
|
function shaHash (checkpointString) {
|
|
20
|
-
|
|
21
|
-
return Buffer.from(
|
|
26
|
+
// Copy out of the 32-byte digest so the LRU cache doesn't retain it.
|
|
27
|
+
return Buffer.from(crypto.createHash('sha256').update(checkpointString).digest().subarray(0, 8))
|
|
22
28
|
}
|
|
23
29
|
|
|
24
30
|
/**
|
|
@@ -30,30 +36,25 @@ function shaHash (checkpointString) {
|
|
|
30
36
|
*/
|
|
31
37
|
function computeHash (service, env, edgeTags, parentHash, propagationHashBigInt = null) {
|
|
32
38
|
edgeTags.sort()
|
|
33
|
-
const hashableEdgeTags = edgeTags.
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
//
|
|
38
|
-
|
|
39
|
-
|
|
39
|
+
const hashableEdgeTags = edgeTags.includes('manual_checkpoint:true')
|
|
40
|
+
? edgeTags.filter(item => item !== 'manual_checkpoint:true')
|
|
41
|
+
: edgeTags
|
|
42
|
+
|
|
43
|
+
// The cache key includes parentHash so a fan-in node with different parents
|
|
44
|
+
// gets distinct cache entries; the hash input below excludes parentHash and
|
|
45
|
+
// gets combined with it via a second sha pass to produce the final hash.
|
|
46
|
+
const joinedEdgeTags = hashableEdgeTags.join('')
|
|
47
|
+
const propagationHex = propagationHashBigInt ? propagationHashBigInt.toString(16) : ''
|
|
48
|
+
const propagationPart = propagationHex ? `:${propagationHex}` : ''
|
|
49
|
+
const key = `${service}${env}${joinedEdgeTags}${parentHash}${propagationPart}`
|
|
40
50
|
|
|
41
51
|
let value = cache.get(key)
|
|
42
52
|
if (value) {
|
|
43
53
|
return value
|
|
44
54
|
}
|
|
45
55
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
// with the same node but different parents (e.g., multiple queues feeding one consumer)
|
|
49
|
-
// - 'hashInput' (below) excludes parentHash to compute only the current node's identity hash,
|
|
50
|
-
// which is then XORed with parentHash (line 54) to build the complete pathway hash
|
|
51
|
-
// This two-step approach (hash current node independently, then combine with parent) is
|
|
52
|
-
// required for proper pathway construction in the DSM protocol.
|
|
53
|
-
const baseString = `${service}${env}` + hashableEdgeTags.join('')
|
|
54
|
-
const hashInput = propagationHashBigInt
|
|
55
|
-
? `${baseString}:${propagationHashBigInt.toString(16)}`
|
|
56
|
-
: baseString
|
|
56
|
+
const baseString = `${service}${env}${joinedEdgeTags}`
|
|
57
|
+
const hashInput = propagationHex ? `${baseString}:${propagationHex}` : baseString
|
|
57
58
|
|
|
58
59
|
const currentHash = shaHash(hashInput)
|
|
59
60
|
const buf = Buffer.concat([currentHash, parentHash], 16)
|
|
@@ -70,11 +71,12 @@ function computeHash (service, env, edgeTags, parentHash, propagationHashBigInt
|
|
|
70
71
|
* @returns {Buffer}
|
|
71
72
|
*/
|
|
72
73
|
function encodePathwayContext (dataStreamsContext) {
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
74
|
+
let offset = dataStreamsContext.hash.copy(pathwayScratch, 0)
|
|
75
|
+
offset = encodeVarintInto(pathwayScratch, offset, Math.round(dataStreamsContext.pathwayStartNs / 1e6))
|
|
76
|
+
offset = encodeVarintInto(pathwayScratch, offset, Math.round(dataStreamsContext.edgeStartNs / 1e6))
|
|
77
|
+
// No-op when offset >= PATHWAY_CONTEXT_BYTES; otherwise pads stale bytes from a previous call.
|
|
78
|
+
pathwayScratch.fill(0, offset, PATHWAY_CONTEXT_BYTES)
|
|
79
|
+
return Buffer.from(pathwayScratch.subarray(0, PATHWAY_CONTEXT_BYTES))
|
|
78
80
|
}
|
|
79
81
|
|
|
80
82
|
/**
|
|
@@ -178,6 +180,7 @@ const DsmPathwayCodec = {
|
|
|
178
180
|
}
|
|
179
181
|
|
|
180
182
|
module.exports = {
|
|
183
|
+
CONTEXT_PROPAGATION_KEY_BASE64,
|
|
181
184
|
computePathwayHash: computeHash,
|
|
182
185
|
encodePathwayContext,
|
|
183
186
|
decodePathwayContext,
|
|
@@ -8,15 +8,21 @@ const { PATHWAY_HASH, DSM_TRANSACTION_ID, DSM_TRANSACTION_CHECKPOINT } = require
|
|
|
8
8
|
const log = require('../log')
|
|
9
9
|
const processTags = require('../process-tags')
|
|
10
10
|
const propagationHash = require('../propagation-hash')
|
|
11
|
-
const {
|
|
11
|
+
const { CONTEXT_PROPAGATION_KEY_BASE64, computePathwayHash } = require('./pathway')
|
|
12
12
|
const { DataStreamsWriter } = require('./writer')
|
|
13
|
-
const { computePathwayHash } = require('./pathway')
|
|
14
13
|
const { getAmqpMessageSize, getHeadersSize, getMessageSize, getSizeOrZero } = require('./size')
|
|
15
14
|
const { SchemaBuilder } = require('./schemas/schema_builder')
|
|
16
15
|
const { SchemaSampler } = require('./schemas/schema_sampler')
|
|
17
16
|
|
|
18
17
|
const ENTRY_PARENT_HASH = Buffer.from('0000000000000000', 'hex')
|
|
19
18
|
|
|
19
|
+
// A direction:out checkpoint estimates the size cost of the header the
|
|
20
|
+
// producer plugin will inject. The pathway context is always 20 binary
|
|
21
|
+
// bytes, encoded as 28 base64 chars; together with the header key and
|
|
22
|
+
// JSON framing (matching the prior `JSON.stringify({key: value})` byte
|
|
23
|
+
// count minus 1), this is a fixed value.
|
|
24
|
+
const PATHWAY_HEADER_BYTES = CONTEXT_PROPAGATION_KEY_BASE64.length + 28 + 6
|
|
25
|
+
|
|
20
26
|
class StatsPoint {
|
|
21
27
|
constructor (hash, parentHash, edgeTags) {
|
|
22
28
|
this.hash = hash.readBigUInt64LE()
|
|
@@ -271,19 +277,19 @@ class DataStreamsProcessor {
|
|
|
271
277
|
|
|
272
278
|
recordCheckpoint (checkpoint, span = null) {
|
|
273
279
|
if (!this.enabled) return
|
|
274
|
-
this.bucketFromTimestamp(checkpoint.currentTimestamp)
|
|
275
|
-
|
|
276
|
-
.addLatencies(checkpoint)
|
|
277
|
-
// set DSM pathway hash on span to enable related traces feature on DSM tab, convert from buffer to uint64
|
|
280
|
+
const statsPoint = this.bucketFromTimestamp(checkpoint.currentTimestamp).forCheckpoint(checkpoint)
|
|
281
|
+
statsPoint.addLatencies(checkpoint)
|
|
278
282
|
if (span) {
|
|
279
|
-
|
|
283
|
+
// StatsPoint already converted the 8-byte Buffer hash to a uint64 BigInt.
|
|
284
|
+
span.setTag(PATHWAY_HASH, statsPoint.hash.toString())
|
|
280
285
|
}
|
|
281
286
|
}
|
|
282
287
|
|
|
283
|
-
setCheckpoint (edgeTags, span, ctx
|
|
284
|
-
if (!this.enabled) return
|
|
288
|
+
setCheckpoint (edgeTags, span, ctx, payloadSize = 0) {
|
|
289
|
+
if (!this.enabled) return
|
|
285
290
|
const nowNs = Date.now() * 1e6
|
|
286
|
-
|
|
291
|
+
// Callers must place the direction tag at index 0.
|
|
292
|
+
const direction = edgeTags[0]
|
|
287
293
|
let pathwayStartNs = nowNs
|
|
288
294
|
let edgeStartNs = nowNs
|
|
289
295
|
let parentHash = ENTRY_PARENT_HASH
|
|
@@ -334,11 +340,7 @@ class DataStreamsProcessor {
|
|
|
334
340
|
closestOppositeDirectionEdgeStart,
|
|
335
341
|
}
|
|
336
342
|
if (direction === 'direction:out') {
|
|
337
|
-
|
|
338
|
-
// - 1 to account for extra byte for {
|
|
339
|
-
const ddInfoContinued = {}
|
|
340
|
-
DsmPathwayCodec.encode(dataStreamsContext, ddInfoContinued)
|
|
341
|
-
payloadSize += getSizeOrZero(JSON.stringify(ddInfoContinued)) - 1
|
|
343
|
+
payloadSize += PATHWAY_HEADER_BYTES
|
|
342
344
|
}
|
|
343
345
|
const checkpoint = {
|
|
344
346
|
currentTimestamp: nowNs,
|
|
@@ -4,7 +4,7 @@ const { types } = require('util')
|
|
|
4
4
|
|
|
5
5
|
function getSizeOrZero (obj) {
|
|
6
6
|
if (typeof obj === 'string') {
|
|
7
|
-
return Buffer.
|
|
7
|
+
return Buffer.byteLength(obj, 'utf8')
|
|
8
8
|
}
|
|
9
9
|
if (types.isArrayBuffer(obj)) {
|
|
10
10
|
return obj.byteLength
|
|
@@ -32,7 +32,11 @@ function getSizeOrZero (obj) {
|
|
|
32
32
|
|
|
33
33
|
function getHeadersSize (headers) {
|
|
34
34
|
if (headers === undefined) return 0
|
|
35
|
-
|
|
35
|
+
let size = 0
|
|
36
|
+
for (const key of Object.keys(headers)) {
|
|
37
|
+
size += Buffer.byteLength(key, 'utf8') + getSizeOrZero(headers[key])
|
|
38
|
+
}
|
|
39
|
+
return size
|
|
36
40
|
}
|
|
37
41
|
|
|
38
42
|
function getMessageSize (message) {
|
|
@@ -1,16 +1,19 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
+
const getGitMetadata = require('../git_metadata')
|
|
4
|
+
|
|
3
5
|
module.exports = function getDebuggerConfig (config, inputPath) {
|
|
6
|
+
const { commitSHA, repositoryUrl } = getGitMetadata(config)
|
|
4
7
|
return {
|
|
5
|
-
commitSHA
|
|
8
|
+
commitSHA,
|
|
6
9
|
debug: config.debug,
|
|
7
10
|
dynamicInstrumentation: config.dynamicInstrumentation,
|
|
8
11
|
env: config.env,
|
|
9
12
|
hostname: config.hostname,
|
|
10
13
|
logLevel: config.logLevel,
|
|
11
14
|
port: config.port,
|
|
12
|
-
propagateProcessTags: config.
|
|
13
|
-
repositoryUrl
|
|
15
|
+
propagateProcessTags: { enabled: config.DD_EXPERIMENTAL_PROPAGATE_PROCESS_TAGS_ENABLED },
|
|
16
|
+
repositoryUrl,
|
|
14
17
|
runtimeId: config.tags['runtime-id'],
|
|
15
18
|
service: config.service,
|
|
16
19
|
url: config.url?.toString(),
|
|
@@ -247,10 +247,6 @@ session.on('Debugger.paused', async ({ params }) => {
|
|
|
247
247
|
language: 'javascript',
|
|
248
248
|
}
|
|
249
249
|
|
|
250
|
-
if (config.propagateProcessTags.enabled) {
|
|
251
|
-
snapshot[processTags.DYNAMIC_INSTRUMENTATION_FIELD_NAME] = processTags.tagsObject
|
|
252
|
-
}
|
|
253
|
-
|
|
254
250
|
if (probe.captureSnapshot) {
|
|
255
251
|
if (fatalSnapshotErrors && fatalSnapshotErrors.length > 0) {
|
|
256
252
|
// There was an error collecting the snapshot for this probe, let's not try again
|
|
@@ -327,7 +323,8 @@ session.on('Debugger.paused', async ({ params }) => {
|
|
|
327
323
|
|
|
328
324
|
ackEmitting(probe)
|
|
329
325
|
|
|
330
|
-
send(message, logger, dd, snapshot
|
|
326
|
+
send(message, logger, dd, snapshot,
|
|
327
|
+
config.propagateProcessTags.enabled ? processTags.serialized : undefined)
|
|
331
328
|
}
|
|
332
329
|
})
|
|
333
330
|
|
|
@@ -40,7 +40,7 @@ const jsonBuffer = new JSONBuffer({
|
|
|
40
40
|
onFlush,
|
|
41
41
|
})
|
|
42
42
|
|
|
43
|
-
function send (message, logger, dd, snapshot) {
|
|
43
|
+
function send (message, logger, dd, snapshot, processTags) {
|
|
44
44
|
const payload = {
|
|
45
45
|
ddsource,
|
|
46
46
|
hostname,
|
|
@@ -50,6 +50,7 @@ function send (message, logger, dd, snapshot) {
|
|
|
50
50
|
: message,
|
|
51
51
|
logger,
|
|
52
52
|
dd,
|
|
53
|
+
process_tags: processTags,
|
|
53
54
|
debugger: { snapshot },
|
|
54
55
|
}
|
|
55
56
|
|
|
@@ -22,6 +22,7 @@ const TYPE_HISTOGRAM = 'h'
|
|
|
22
22
|
*/
|
|
23
23
|
class DogStatsDClient {
|
|
24
24
|
#lookup
|
|
25
|
+
#tagsPrefix
|
|
25
26
|
constructor (options) {
|
|
26
27
|
this.#lookup = options.lookup
|
|
27
28
|
if (options.metricsProxyUrl) {
|
|
@@ -36,6 +37,7 @@ class DogStatsDClient {
|
|
|
36
37
|
this._family = isIP(this._host)
|
|
37
38
|
this._port = options.port
|
|
38
39
|
this._tags = options.tags
|
|
40
|
+
this.#tagsPrefix = this._tags?.length ? `|#${this._tags.join(',')}` : ''
|
|
39
41
|
this._queue = []
|
|
40
42
|
this._buffer = ''
|
|
41
43
|
this._offset = 0
|
|
@@ -66,9 +68,9 @@ class DogStatsDClient {
|
|
|
66
68
|
flush () {
|
|
67
69
|
const queue = this._enqueue()
|
|
68
70
|
|
|
69
|
-
|
|
71
|
+
if (queue.length === 0) return
|
|
70
72
|
|
|
71
|
-
|
|
73
|
+
log.debug('Flushing %s metrics via %s', queue.length, this._httpOptions ? 'HTTP' : 'UDP')
|
|
72
74
|
|
|
73
75
|
this._queue = []
|
|
74
76
|
|
|
@@ -119,11 +121,12 @@ class DogStatsDClient {
|
|
|
119
121
|
_add (stat, value, type, tags) {
|
|
120
122
|
let message = `${stat}:${value}|${type}`
|
|
121
123
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
124
|
+
if (tags?.length) {
|
|
125
|
+
message += this.#tagsPrefix
|
|
126
|
+
? `${this.#tagsPrefix},${tags.join(',')}`
|
|
127
|
+
: `|#${tags.join(',')}`
|
|
128
|
+
} else {
|
|
129
|
+
message += this.#tagsPrefix
|
|
127
130
|
}
|
|
128
131
|
|
|
129
132
|
if (entityId) {
|
|
@@ -3,15 +3,15 @@
|
|
|
3
3
|
const getConfig = require('../config')
|
|
4
4
|
const { MsgpackChunk, MsgpackEncoder } = require('../msgpack')
|
|
5
5
|
const log = require('../log')
|
|
6
|
-
const {
|
|
6
|
+
const { normalizeSpan } = require('./tags-processors')
|
|
7
7
|
|
|
8
8
|
const SOFT_LIMIT = 8 * 1024 * 1024 // 8MB
|
|
9
9
|
|
|
10
10
|
function formatSpan (span, config) {
|
|
11
|
-
span = normalizeSpan(
|
|
11
|
+
span = normalizeSpan(span)
|
|
12
12
|
if (span.span_events) {
|
|
13
13
|
// ensure span events are encoded as tags if agent doesn't support native top level span events
|
|
14
|
-
if (config.
|
|
14
|
+
if (config.DD_TRACE_NATIVE_SPAN_EVENTS) {
|
|
15
15
|
formatSpanEvents(span)
|
|
16
16
|
} else {
|
|
17
17
|
span.meta.events = JSON.stringify(span.span_events)
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
-
const {
|
|
3
|
+
const { normalizeSpan } = require('./tags-processors')
|
|
4
4
|
const { AgentEncoder: BaseEncoder } = require('./0.4')
|
|
5
5
|
|
|
6
6
|
const ARRAY_OF_TWO = 0x92
|
|
7
7
|
const ARRAY_OF_TWELVE = 0x9C
|
|
8
8
|
|
|
9
9
|
function formatSpan (span) {
|
|
10
|
-
span = normalizeSpan(
|
|
10
|
+
span = normalizeSpan(span)
|
|
11
11
|
// ensure span events are encoded as tags
|
|
12
12
|
if (span.span_events) {
|
|
13
13
|
span.meta.events = JSON.stringify(span.span_events)
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
const log = require('../log')
|
|
4
4
|
const { TOP_LEVEL_KEY } = require('../constants')
|
|
5
|
-
const {
|
|
5
|
+
const { normalizeSpan } = require('./tags-processors')
|
|
6
6
|
|
|
7
7
|
// Soft limit for estimated payload size. Triggers an early flush to stay under intake request size limits.
|
|
8
8
|
const SOFT_LIMIT = 8 * 1024 * 1024 // 8MB
|
|
@@ -14,7 +14,7 @@ const SOFT_LIMIT = 8 * 1024 * 1024 // 8MB
|
|
|
14
14
|
* @returns {object} The formatted span
|
|
15
15
|
*/
|
|
16
16
|
function formatSpan (span, isFirstSpan) {
|
|
17
|
-
span = normalizeSpan(
|
|
17
|
+
span = normalizeSpan(span)
|
|
18
18
|
|
|
19
19
|
// Remove _dd.p.tid (the upper 64 bits of a 128-bit trace ID) since trace_id is truncated to lower 64 bits
|
|
20
20
|
delete span.meta['_dd.p.tid']
|
|
@@ -25,35 +25,10 @@ const MAX_SERVICE_LENGTH = 100
|
|
|
25
25
|
// MAX_TYPE_LENGTH the maximum length a span type can have
|
|
26
26
|
const MAX_TYPE_LENGTH = 100
|
|
27
27
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
// them yet again.
|
|
31
|
-
|
|
32
|
-
// normally the agent truncates the resource and parses it in certain scenarios (e.g. SQL Queries)
|
|
33
|
-
function truncateSpan (span, shouldTruncateResourceName = true) {
|
|
34
|
-
if (shouldTruncateResourceName && span.resource && span.resource.length > MAX_RESOURCE_NAME_LENGTH) {
|
|
28
|
+
function truncateSpan (span) {
|
|
29
|
+
if (span.resource && span.resource.length > MAX_RESOURCE_NAME_LENGTH) {
|
|
35
30
|
span.resource = `${span.resource.slice(0, MAX_RESOURCE_NAME_LENGTH)}...`
|
|
36
31
|
}
|
|
37
|
-
for (let metaKey in span.meta) {
|
|
38
|
-
const val = span.meta[metaKey]
|
|
39
|
-
if (metaKey.length > MAX_META_KEY_LENGTH) {
|
|
40
|
-
delete span.meta[metaKey]
|
|
41
|
-
metaKey = `${metaKey.slice(0, MAX_META_KEY_LENGTH)}...`
|
|
42
|
-
span.metrics[metaKey] = val
|
|
43
|
-
}
|
|
44
|
-
if (val && val.length > MAX_META_VALUE_LENGTH) {
|
|
45
|
-
span.meta[metaKey] = `${val.slice(0, MAX_META_VALUE_LENGTH)}...`
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
for (let metricsKey in span.metrics) {
|
|
49
|
-
const val = span.metrics[metricsKey]
|
|
50
|
-
if (metricsKey.length > MAX_METRIC_KEY_LENGTH) {
|
|
51
|
-
delete span.metrics[metricsKey]
|
|
52
|
-
metricsKey = `${metricsKey.slice(0, MAX_METRIC_KEY_LENGTH)}...`
|
|
53
|
-
span.metrics[metricsKey] = val
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
|
|
57
32
|
return span
|
|
58
33
|
}
|
|
59
34
|
|
|
@@ -13,6 +13,12 @@ const log = require('../../log')
|
|
|
13
13
|
const { urlToHttpOptions } = require('./url-to-http-options-polyfill')
|
|
14
14
|
const docker = require('./docker')
|
|
15
15
|
const { httpAgent, httpsAgent } = require('./agents')
|
|
16
|
+
const {
|
|
17
|
+
getMaxAttempts,
|
|
18
|
+
getRetryDelay,
|
|
19
|
+
isRetriableNetworkError,
|
|
20
|
+
markEndpointReached,
|
|
21
|
+
} = require('./retry')
|
|
16
22
|
|
|
17
23
|
const maxActiveBufferSize = 1024 * 1024 * 64
|
|
18
24
|
|
|
@@ -92,6 +98,8 @@ function request (data, options, callback) {
|
|
|
92
98
|
options.agent = isSecure ? httpsAgent : httpAgent
|
|
93
99
|
|
|
94
100
|
const onResponse = (res, finalize) => {
|
|
101
|
+
markEndpointReached(options)
|
|
102
|
+
|
|
95
103
|
const chunks = []
|
|
96
104
|
|
|
97
105
|
res.setTimeout(timeout)
|
|
@@ -142,7 +150,10 @@ function request (data, options, callback) {
|
|
|
142
150
|
})
|
|
143
151
|
}
|
|
144
152
|
|
|
145
|
-
|
|
153
|
+
// Retries always run via setTimeout so the AsyncLocalStorage store survives
|
|
154
|
+
// the gap before socket.connect(); ALS.run() does not call ALS.enterWith()
|
|
155
|
+
// outside AsyncContextFrame, so a synchronous re-entry would lose the store.
|
|
156
|
+
const attempt = attemptIndex => {
|
|
146
157
|
if (!request.writable) {
|
|
147
158
|
log.debug('Maximum number of active requests reached: payload is discarded.')
|
|
148
159
|
return callback(null)
|
|
@@ -163,9 +174,16 @@ function request (data, options, callback) {
|
|
|
163
174
|
req.once('close', finalize)
|
|
164
175
|
req.once('timeout', finalize)
|
|
165
176
|
|
|
166
|
-
req.once('error',
|
|
177
|
+
req.once('error', error => {
|
|
167
178
|
finalize()
|
|
168
|
-
|
|
179
|
+
if (attemptIndex < getMaxAttempts(options) && isRetriableNetworkError(error)) {
|
|
180
|
+
// Unref so a pending retry never keeps the host process alive past
|
|
181
|
+
// its natural exit point; long-running apps still retry because the
|
|
182
|
+
// event loop is held open by their own work.
|
|
183
|
+
setTimeout(attempt, getRetryDelay(options, attemptIndex), attemptIndex + 1).unref()
|
|
184
|
+
} else {
|
|
185
|
+
callback(error)
|
|
186
|
+
}
|
|
169
187
|
})
|
|
170
188
|
|
|
171
189
|
req.setTimeout(timeout, () => {
|
|
@@ -185,14 +203,7 @@ function request (data, options, callback) {
|
|
|
185
203
|
})
|
|
186
204
|
}
|
|
187
205
|
|
|
188
|
-
|
|
189
|
-
// request before socket.connect() is called. This is a workaround for the
|
|
190
|
-
// issue that the AsyncLocalStorage.run() method does not call the
|
|
191
|
-
// AsyncLocalStorage.enterWith() method when not using AsyncContextFrame.
|
|
192
|
-
//
|
|
193
|
-
// TODO: Test that this doesn't trace itself on retry when the diagnostics
|
|
194
|
-
// channel events are available in the agent exporter.
|
|
195
|
-
makeRequest(() => setTimeout(() => makeRequest(callback)))
|
|
206
|
+
attempt(1)
|
|
196
207
|
}
|
|
197
208
|
|
|
198
209
|
function byteLength (data) {
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const RATE_LIMIT_MAX_WAIT_MS = 30_000
|
|
4
|
+
|
|
5
|
+
const SINGLE_RETRY_BASE_MS = 5000
|
|
6
|
+
const SINGLE_RETRY_JITTER_MS = 2500
|
|
7
|
+
|
|
8
|
+
const STARTUP_GRACE_MS = 30_000
|
|
9
|
+
const STARTUP_BACKOFF_BASE_MS = 1000
|
|
10
|
+
const STARTUP_BACKOFF_MAX_MS = 8000
|
|
11
|
+
const STARTUP_BACKOFF_JITTER_MS = 500
|
|
12
|
+
const STARTUP_MAX_ATTEMPTS = 5
|
|
13
|
+
const POST_STARTUP_MAX_ATTEMPTS = 2
|
|
14
|
+
|
|
15
|
+
// `ECONNREFUSED` and `ENOENT` cover the agent-not-yet-listening cases (TCP and
|
|
16
|
+
// UDS). `EAI_AGAIN` covers transient DNS in agentless intake. `ENOTFOUND` is
|
|
17
|
+
// excluded because it usually means a misconfigured host, not a transient state.
|
|
18
|
+
const RETRIABLE_NETWORK_CODES = new Set([
|
|
19
|
+
'EAI_AGAIN',
|
|
20
|
+
'ECONNREFUSED',
|
|
21
|
+
'ECONNRESET',
|
|
22
|
+
'ENOENT',
|
|
23
|
+
'EPIPE',
|
|
24
|
+
'ETIMEDOUT',
|
|
25
|
+
])
|
|
26
|
+
|
|
27
|
+
const startedAtMs = Date.now()
|
|
28
|
+
const reachedEndpoints = new Set()
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* @typedef {object} EndpointOptions
|
|
32
|
+
* @property {string} [socketPath]
|
|
33
|
+
* @property {string} [hostname]
|
|
34
|
+
* @property {string} [host]
|
|
35
|
+
* @property {string|number} [port]
|
|
36
|
+
*/
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* @param {Error & { code?: string }} error
|
|
40
|
+
*/
|
|
41
|
+
function isRetriableNetworkError (error) {
|
|
42
|
+
return error?.code !== undefined && RETRIABLE_NETWORK_CODES.has(error.code)
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function singleJitteredDelay () {
|
|
46
|
+
return SINGLE_RETRY_BASE_MS + Math.random() * SINGLE_RETRY_JITTER_MS
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Stable key identifying the destination so the startup-phase gate is scoped
|
|
51
|
+
* per endpoint. UDS path beats host:port because both can coexist on the same
|
|
52
|
+
* options object after `parseUrl` runs.
|
|
53
|
+
*
|
|
54
|
+
* @param {EndpointOptions} options
|
|
55
|
+
*/
|
|
56
|
+
function getEndpointKey (options) {
|
|
57
|
+
if (options.socketPath) return options.socketPath
|
|
58
|
+
return `${options.hostname || options.host || ''}:${options.port || ''}`
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* @param {EndpointOptions} options
|
|
63
|
+
*/
|
|
64
|
+
function inStartupPhase (options) {
|
|
65
|
+
if ((Date.now() - startedAtMs) >= STARTUP_GRACE_MS) return false
|
|
66
|
+
return !reachedEndpoints.has(getEndpointKey(options))
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Wait time before the next attempt when the previous one just failed. Bounded
|
|
71
|
+
* exponential backoff with small jitter inside the startup grace window;
|
|
72
|
+
* single 5–7.5 s jittered retry afterwards.
|
|
73
|
+
*
|
|
74
|
+
* @param {EndpointOptions} options
|
|
75
|
+
* @param {number} previousAttempt 1-based index of the attempt that just failed.
|
|
76
|
+
*/
|
|
77
|
+
function getRetryDelay (options, previousAttempt) {
|
|
78
|
+
if (!inStartupPhase(options)) return singleJitteredDelay()
|
|
79
|
+
const exp = Math.min(STARTUP_BACKOFF_MAX_MS, STARTUP_BACKOFF_BASE_MS << (previousAttempt - 1))
|
|
80
|
+
return exp + Math.random() * STARTUP_BACKOFF_JITTER_MS
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* @param {EndpointOptions} options
|
|
85
|
+
*/
|
|
86
|
+
function getMaxAttempts (options) {
|
|
87
|
+
return inStartupPhase(options) ? STARTUP_MAX_ATTEMPTS : POST_STARTUP_MAX_ATTEMPTS
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* @param {EndpointOptions} options
|
|
92
|
+
*/
|
|
93
|
+
function markEndpointReached (options) {
|
|
94
|
+
reachedEndpoints.add(getEndpointKey(options))
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
module.exports = {
|
|
98
|
+
RATE_LIMIT_MAX_WAIT_MS,
|
|
99
|
+
getMaxAttempts,
|
|
100
|
+
getRetryDelay,
|
|
101
|
+
isRetriableNetworkError,
|
|
102
|
+
markEndpointReached,
|
|
103
|
+
singleJitteredDelay,
|
|
104
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const fs = require('node:fs')
|
|
4
|
+
const path = require('node:path')
|
|
5
|
+
|
|
6
|
+
const log = require('./log')
|
|
7
|
+
const { GIT_COMMIT_SHA, GIT_REPOSITORY_URL } = require('./plugins/util/tags')
|
|
8
|
+
const {
|
|
9
|
+
getGitMetadataFromGitProperties,
|
|
10
|
+
getRemoteOriginURL,
|
|
11
|
+
removeUserSensitiveInfo,
|
|
12
|
+
resolveGitHeadSHA,
|
|
13
|
+
} = require('./config/git_properties')
|
|
14
|
+
|
|
15
|
+
/** @type {{ commitSHA: string | undefined, repositoryUrl: string | undefined } | undefined} */
|
|
16
|
+
let cached
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* @param {import('./config/config-types').ConfigProperties} config
|
|
20
|
+
*/
|
|
21
|
+
function getGitMetadata (config) {
|
|
22
|
+
if (cached) return cached
|
|
23
|
+
|
|
24
|
+
if (!config.DD_TRACE_GIT_METADATA_ENABLED) {
|
|
25
|
+
cached = { commitSHA: undefined, repositoryUrl: undefined }
|
|
26
|
+
return cached
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
let repositoryUrl = removeUserSensitiveInfo(config.DD_GIT_REPOSITORY_URL ?? config.tags[GIT_REPOSITORY_URL])
|
|
30
|
+
let commitSHA = config.DD_GIT_COMMIT_SHA ?? config.tags[GIT_COMMIT_SHA]
|
|
31
|
+
|
|
32
|
+
if (!repositoryUrl || !commitSHA) {
|
|
33
|
+
const propertiesFile = config.DD_GIT_PROPERTIES_FILE
|
|
34
|
+
const gitPropertiesFile = propertiesFile ?? `${process.cwd()}/git.properties`
|
|
35
|
+
try {
|
|
36
|
+
const fromProperties = getGitMetadataFromGitProperties(fs.readFileSync(gitPropertiesFile, 'utf8'))
|
|
37
|
+
commitSHA ??= fromProperties.commitSHA
|
|
38
|
+
repositoryUrl ??= fromProperties.repositoryUrl
|
|
39
|
+
} catch (error) {
|
|
40
|
+
if (propertiesFile) {
|
|
41
|
+
log.error('Error reading DD_GIT_PROPERTIES_FILE: %s', gitPropertiesFile, error)
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const folderPath = config.DD_GIT_FOLDER_PATH
|
|
47
|
+
const gitFolderPath = folderPath ?? path.join(process.cwd(), '.git')
|
|
48
|
+
|
|
49
|
+
if (!repositoryUrl) {
|
|
50
|
+
const gitConfigPath = path.join(gitFolderPath, 'config')
|
|
51
|
+
try {
|
|
52
|
+
repositoryUrl = getRemoteOriginURL(fs.readFileSync(gitConfigPath, 'utf8'))
|
|
53
|
+
} catch (error) {
|
|
54
|
+
if (folderPath) {
|
|
55
|
+
log.error('Error reading git config: %s', gitConfigPath, error)
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
commitSHA ??= resolveGitHeadSHA(gitFolderPath)
|
|
61
|
+
|
|
62
|
+
cached = { commitSHA, repositoryUrl }
|
|
63
|
+
return cached
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
module.exports = getGitMetadata
|
|
@@ -1,17 +1,25 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
3
|
const { SCI_COMMIT_SHA, SCI_REPOSITORY_URL } = require('./constants')
|
|
4
|
+
const getGitMetadata = require('./git_metadata')
|
|
4
5
|
|
|
5
6
|
class GitMetadataTagger {
|
|
7
|
+
#commitSHA
|
|
8
|
+
#repositoryUrl
|
|
9
|
+
#enabled
|
|
10
|
+
|
|
6
11
|
constructor (config) {
|
|
7
|
-
this
|
|
12
|
+
this.#enabled = config.DD_TRACE_GIT_METADATA_ENABLED
|
|
13
|
+
const { commitSHA, repositoryUrl } = getGitMetadata(config)
|
|
14
|
+
this.#commitSHA = commitSHA
|
|
15
|
+
this.#repositoryUrl = repositoryUrl
|
|
8
16
|
}
|
|
9
17
|
|
|
10
18
|
tagGitMetadata (spanContext) {
|
|
11
|
-
if (this
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
19
|
+
if (this.#enabled) {
|
|
20
|
+
const tags = spanContext._trace.tags
|
|
21
|
+
tags[SCI_COMMIT_SHA] = this.#commitSHA
|
|
22
|
+
tags[SCI_REPOSITORY_URL] = this.#repositoryUrl
|
|
15
23
|
}
|
|
16
24
|
}
|
|
17
25
|
}
|