dd-trace 5.106.0 → 5.107.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/index.d.ts +20 -1
- package/package.json +5 -7
- package/packages/datadog-core/src/storage.js +47 -48
- package/packages/datadog-esbuild/index.js +6 -1
- package/packages/datadog-instrumentations/src/ai.js +12 -3
- package/packages/datadog-instrumentations/src/body-parser.js +5 -2
- package/packages/datadog-instrumentations/src/connect.js +3 -2
- package/packages/datadog-instrumentations/src/cookie-parser.js +3 -2
- package/packages/datadog-instrumentations/src/cucumber.js +7 -0
- package/packages/datadog-instrumentations/src/express-mongo-sanitize.js +7 -5
- package/packages/datadog-instrumentations/src/express-session.js +12 -11
- package/packages/datadog-instrumentations/src/express.js +24 -20
- package/packages/datadog-instrumentations/src/fastify.js +18 -6
- package/packages/datadog-instrumentations/src/helpers/openai-ai-guard.js +27 -12
- package/packages/datadog-instrumentations/src/http/client.js +9 -12
- package/packages/datadog-instrumentations/src/http/server.js +30 -16
- package/packages/datadog-instrumentations/src/http2/client.js +15 -12
- package/packages/datadog-instrumentations/src/http2/server.js +15 -8
- package/packages/datadog-instrumentations/src/jest/bail-reporter.js +42 -0
- package/packages/datadog-instrumentations/src/jest.js +143 -73
- package/packages/datadog-instrumentations/src/mocha/main.js +43 -8
- package/packages/datadog-instrumentations/src/mocha/utils.js +128 -17
- package/packages/datadog-instrumentations/src/multer.js +3 -2
- package/packages/datadog-instrumentations/src/mysql2.js +34 -0
- package/packages/datadog-instrumentations/src/net.js +8 -6
- package/packages/datadog-instrumentations/src/openai.js +19 -7
- package/packages/datadog-instrumentations/src/pg.js +19 -0
- package/packages/datadog-instrumentations/src/router.js +12 -10
- package/packages/datadog-instrumentations/src/vitest.js +29 -4
- package/packages/datadog-plugin-aws-sdk/src/base.js +0 -3
- package/packages/datadog-plugin-aws-sdk/src/services/sqs.js +62 -11
- package/packages/datadog-plugin-cucumber/src/index.js +2 -0
- package/packages/datadog-plugin-cypress/src/support.js +31 -1
- package/packages/datadog-plugin-http/src/client.js +0 -3
- package/packages/datadog-plugin-http/src/server.js +11 -1
- package/packages/datadog-plugin-mocha/src/index.js +2 -0
- package/packages/datadog-plugin-pg/src/index.js +10 -0
- package/packages/dd-trace/src/aiguard/index.js +34 -15
- package/packages/dd-trace/src/aiguard/sdk.js +34 -3
- package/packages/dd-trace/src/aiguard/tags.js +6 -0
- package/packages/dd-trace/src/ci-visibility/exporters/agentless/index.js +1 -1
- package/packages/dd-trace/src/config/defaults.js +14 -0
- package/packages/dd-trace/src/config/generated-config-types.d.ts +1 -1
- package/packages/dd-trace/src/config/helper.js +1 -0
- package/packages/dd-trace/src/config/index.js +5 -9
- package/packages/dd-trace/src/config/parsers.js +8 -0
- package/packages/dd-trace/src/config/supported-configurations.json +13 -6
- package/packages/dd-trace/src/crashtracking/crashtracker.js +2 -2
- package/packages/dd-trace/src/datastreams/writer.js +1 -2
- package/packages/dd-trace/src/debugger/config.js +1 -1
- package/packages/dd-trace/src/debugger/devtools_client/config.js +3 -2
- package/packages/dd-trace/src/debugger/index.js +1 -2
- package/packages/dd-trace/src/dogstatsd.js +2 -3
- package/packages/dd-trace/src/encode/0.4.js +49 -41
- package/packages/dd-trace/src/encode/agentless-json.js +5 -1
- package/packages/dd-trace/src/encode/tags-processors.js +14 -0
- package/packages/dd-trace/src/exporters/agent/index.js +1 -2
- package/packages/dd-trace/src/exporters/agentless/index.js +6 -10
- package/packages/dd-trace/src/exporters/common/buffering-exporter.js +1 -2
- package/packages/dd-trace/src/exporters/common/request.js +26 -0
- package/packages/dd-trace/src/exporters/span-stats/index.js +1 -2
- package/packages/dd-trace/src/llmobs/plugins/genai/index.js +4 -0
- package/packages/dd-trace/src/llmobs/plugins/genai/util.js +45 -0
- package/packages/dd-trace/src/llmobs/sdk.js +4 -1
- package/packages/dd-trace/src/llmobs/span_processor.js +17 -1
- package/packages/dd-trace/src/llmobs/tagger.js +5 -3
- package/packages/dd-trace/src/llmobs/util.js +54 -0
- package/packages/dd-trace/src/llmobs/writers/base.js +1 -2
- package/packages/dd-trace/src/llmobs/writers/util.js +1 -2
- package/packages/dd-trace/src/openfeature/writers/base.js +1 -10
- package/packages/dd-trace/src/openfeature/writers/util.js +1 -2
- package/packages/dd-trace/src/opentelemetry/metrics/instruments.js +26 -13
- package/packages/dd-trace/src/opentelemetry/metrics/meter.js +7 -10
- package/packages/dd-trace/src/opentelemetry/metrics/periodic_metric_reader.js +92 -0
- package/packages/dd-trace/src/opentelemetry/trace/otlp_transformer.js +3 -2
- package/packages/dd-trace/src/opentracing/span.js +23 -18
- package/packages/dd-trace/src/opentracing/tracer.js +16 -12
- package/packages/dd-trace/src/plugins/ci_plugin.js +131 -46
- package/packages/dd-trace/src/priority_sampler.js +6 -5
- package/packages/dd-trace/src/profiling/config.js +1 -2
- package/packages/dd-trace/src/proxy.js +13 -10
- package/packages/dd-trace/src/remote_config/index.js +1 -2
- package/packages/dd-trace/src/runtime_metrics/client.js +30 -0
- package/packages/dd-trace/src/runtime_metrics/index.js +12 -2
- package/packages/dd-trace/src/runtime_metrics/otlp_runtime_metrics.js +284 -0
- package/packages/dd-trace/src/runtime_metrics/runtime_metrics.js +2 -11
- package/packages/dd-trace/src/service-naming/source-resolver.js +5 -1
- package/packages/dd-trace/src/span_format.js +33 -25
- package/packages/dd-trace/src/span_stats.js +1 -1
- package/packages/dd-trace/src/startup-log.js +1 -2
- package/packages/dd-trace/src/telemetry/send-data.js +1 -1
- package/packages/dd-trace/src/tracer.js +1 -1
- package/vendor/dist/@apm-js-collab/code-transformer/index.js +2 -2
- package/vendor/dist/shell-quote/index.js +1 -1
- package/packages/dd-trace/src/agent/url.js +0 -28
- package/scripts/preinstall.js +0 -34
|
@@ -44,27 +44,46 @@ function disable () {
|
|
|
44
44
|
/**
|
|
45
45
|
* Handles channel messages with pre-converted messages.
|
|
46
46
|
*
|
|
47
|
-
* @param {
|
|
47
|
+
* @param {object} ctx
|
|
48
|
+
* @param {Array<object>} ctx.messages
|
|
49
|
+
* @param {string} [ctx.integration]
|
|
50
|
+
* @param {object} [ctx.parentSpan] - LLM span to parent the `ai_guard` span under.
|
|
51
|
+
* @param {AbortController} ctx.abortController
|
|
52
|
+
* @param {Array<Promise<void>>} ctx.pending - Subscribers push only when they evaluate.
|
|
48
53
|
*/
|
|
49
54
|
function onEvaluate (ctx) {
|
|
55
|
+
// Decline to evaluate empty payloads by not pushing to pending.
|
|
50
56
|
if (!ctx.messages?.length) {
|
|
51
|
-
ctx.resolve()
|
|
52
57
|
return
|
|
53
58
|
}
|
|
54
59
|
|
|
55
|
-
const opts = {
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
60
|
+
const opts = {
|
|
61
|
+
block,
|
|
62
|
+
source: SOURCE_AUTO,
|
|
63
|
+
integration: ctx.integration || INTEGRATION_NONE,
|
|
64
|
+
childOf: ctx.parentSpan,
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
try {
|
|
68
|
+
ctx.pending.push(aiguard.evaluate(ctx.messages, opts).catch(handleEvaluationError.bind(null, ctx)))
|
|
69
|
+
} catch (err) {
|
|
70
|
+
ctx.pending.push(Promise.resolve().then(() => handleEvaluationError(ctx, err)))
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Handles an AI Guard evaluation failure.
|
|
76
|
+
*
|
|
77
|
+
* @param {object} ctx
|
|
78
|
+
* @param {AbortController} ctx.abortController
|
|
79
|
+
* @param {Error} err
|
|
80
|
+
*/
|
|
81
|
+
function handleEvaluationError (ctx, err) {
|
|
82
|
+
if (err.name === 'AIGuardAbortError') {
|
|
83
|
+
ctx.abortController.abort(err)
|
|
84
|
+
} else {
|
|
85
|
+
log.error('AIGuard: unexpected error during evaluation: %s', err.message)
|
|
86
|
+
}
|
|
68
87
|
}
|
|
69
88
|
|
|
70
89
|
module.exports = { enable, disable }
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
3
|
const rfdc = require('../../../../vendor/dist/rfdc')({ proto: false, circles: false })
|
|
4
|
-
const { HTTP_CLIENT_IP, NETWORK_CLIENT_IP } = require('../../../../ext/tags')
|
|
4
|
+
const { HTTP_CLIENT_IP, NETWORK_CLIENT_IP, HTTP_USERAGENT } = require('../../../../ext/tags')
|
|
5
|
+
const { USER_ID, USER_SESSION_ID } = require('../appsec/addresses')
|
|
5
6
|
const { getActiveRequest } = require('../appsec/store')
|
|
6
7
|
const log = require('../log')
|
|
7
8
|
const { extractIp } = require('../plugins/util/ip_extractor')
|
|
@@ -17,6 +18,16 @@ const aiguardMetrics = telemetryMetrics.manager.namespace('ai_guard')
|
|
|
17
18
|
|
|
18
19
|
const ALLOW = 'ALLOW'
|
|
19
20
|
|
|
21
|
+
// Tags from the service entry span that must be mirrored onto every AI Guard span
|
|
22
|
+
// so anomaly detection pipelines can process each span independently.
|
|
23
|
+
const SERVICE_ENTRY_TAG_MAPPINGS = [
|
|
24
|
+
[HTTP_USERAGENT, TAGS.HTTP_USERAGENT_TAG_KEY],
|
|
25
|
+
[HTTP_CLIENT_IP, TAGS.HTTP_CLIENT_IP_TAG_KEY],
|
|
26
|
+
[NETWORK_CLIENT_IP, TAGS.NETWORK_CLIENT_IP_TAG_KEY],
|
|
27
|
+
[USER_ID, TAGS.USR_ID_TAG_KEY],
|
|
28
|
+
[USER_SESSION_ID, TAGS.USR_SESSION_ID_TAG_KEY],
|
|
29
|
+
]
|
|
30
|
+
|
|
20
31
|
/**
|
|
21
32
|
* Reports a telemetry error
|
|
22
33
|
*
|
|
@@ -181,13 +192,32 @@ class AIGuard extends NoopAIGuard {
|
|
|
181
192
|
}
|
|
182
193
|
}
|
|
183
194
|
|
|
195
|
+
/**
|
|
196
|
+
* Copies service entry span tags needed by anomaly detection to the AI Guard span.
|
|
197
|
+
*
|
|
198
|
+
* @param {import('../opentracing/span')} guardSpan
|
|
199
|
+
* @param {import('../opentracing/span')} rootSpan
|
|
200
|
+
*/
|
|
201
|
+
#copyServiceEntryTagsToGuardSpan (guardSpan, rootSpan) {
|
|
202
|
+
const rootTags = rootSpan.context().getTags()
|
|
203
|
+
for (const [sourceTag, destTag] of SERVICE_ENTRY_TAG_MAPPINGS) {
|
|
204
|
+
const value = rootTags[sourceTag]
|
|
205
|
+
if (value !== undefined && value !== null) {
|
|
206
|
+
guardSpan.setTag(destTag, value)
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
184
211
|
evaluate (messages, opts) {
|
|
185
212
|
if (!this.#initialized) {
|
|
186
213
|
return super.evaluate(messages, opts)
|
|
187
214
|
}
|
|
188
|
-
const { block = true, source = TAGS.SOURCE_SDK, integration = TAGS.INTEGRATION_NONE } = opts ?? {}
|
|
215
|
+
const { block = true, source = TAGS.SOURCE_SDK, integration = TAGS.INTEGRATION_NONE, childOf } = opts ?? {}
|
|
189
216
|
const telemetryTags = { source, integration }
|
|
190
|
-
|
|
217
|
+
// Only pass `childOf` when truthy so `tracer.trace`'s default (`scope().active()`)
|
|
218
|
+
// still applies for SDK callers that don't supply an explicit parent.
|
|
219
|
+
const traceOpts = childOf ? { childOf } : {}
|
|
220
|
+
return this.#tracer.trace(TAGS.RESOURCE, traceOpts, async (span) => {
|
|
191
221
|
const last = messages[messages.length - 1]
|
|
192
222
|
const target = this.#isToolCall(last) ? 'tool' : 'prompt'
|
|
193
223
|
span.setTag(TAGS.TARGET_TAG_KEY, target)
|
|
@@ -206,6 +236,7 @@ class AIGuard extends NoopAIGuard {
|
|
|
206
236
|
const rootSpan = span.context()?._trace?.started?.[0]
|
|
207
237
|
if (rootSpan) {
|
|
208
238
|
this.#setRootSpanClientIpTags(rootSpan)
|
|
239
|
+
this.#copyServiceEntryTagsToGuardSpan(span, rootSpan)
|
|
209
240
|
// keepTrace must be called before executeRequest so the sampling decision
|
|
210
241
|
// is propagated correctly to outgoing HTTP client calls.
|
|
211
242
|
keepTrace(rootSpan, AI_GUARD)
|
|
@@ -10,6 +10,12 @@ module.exports = {
|
|
|
10
10
|
EVENT_TAG_KEY: 'ai_guard.event',
|
|
11
11
|
META_STRUCT_KEY: 'ai_guard',
|
|
12
12
|
|
|
13
|
+
HTTP_USERAGENT_TAG_KEY: 'ai_guard.http.useragent',
|
|
14
|
+
HTTP_CLIENT_IP_TAG_KEY: 'ai_guard.http.client_ip',
|
|
15
|
+
NETWORK_CLIENT_IP_TAG_KEY: 'ai_guard.network.client.ip',
|
|
16
|
+
USR_ID_TAG_KEY: 'ai_guard.usr.id',
|
|
17
|
+
USR_SESSION_ID_TAG_KEY: 'ai_guard.usr.session_id',
|
|
18
|
+
|
|
13
19
|
TELEMETRY_REQUESTS: 'requests',
|
|
14
20
|
TELEMETRY_TRUNCATED: 'truncated',
|
|
15
21
|
TELEMETRY_ERROR: 'error',
|
|
@@ -9,7 +9,7 @@ const CoverageWriter = require('./coverage-writer')
|
|
|
9
9
|
class AgentlessCiVisibilityExporter extends CiVisibilityExporter {
|
|
10
10
|
constructor (config) {
|
|
11
11
|
super(config)
|
|
12
|
-
const { tags, site, url, isTestDynamicInstrumentationEnabled } = config
|
|
12
|
+
const { tags, site, DD_CIVISIBILITY_AGENTLESS_URL: url, isTestDynamicInstrumentationEnabled } = config
|
|
13
13
|
// we don't need to request /info because we are using agentless by configuration
|
|
14
14
|
this._isInitialized = true
|
|
15
15
|
this._resolveCanUseCiVisProtocol(true)
|
|
@@ -72,6 +72,12 @@ for (const [name, value] of Object.entries(defaults)) {
|
|
|
72
72
|
function generateTelemetry (value = null, origin, optionName) {
|
|
73
73
|
const tableEntry = configurationsTable[optionName]
|
|
74
74
|
const { type, canonicalName = optionName } = tableEntry ?? { type: typeof value }
|
|
75
|
+
// Sensitive configurations are excluded from configuration telemetry: their
|
|
76
|
+
// entry is never added to `configWithOrigin`, the single source for every
|
|
77
|
+
// telemetry path (app-started and app-client-configuration-change).
|
|
78
|
+
if (sensitiveConfigurations.has(canonicalName)) {
|
|
79
|
+
return
|
|
80
|
+
}
|
|
75
81
|
// TODO: Should we not send defaults to telemetry to reduce size?
|
|
76
82
|
// TODO: How to handle aliases/actual names in the future? Optional fields? Normalize the name at intake?
|
|
77
83
|
// TODO: Validate that space separated tags are parsed by the backend. Optimizations would be possible with that.
|
|
@@ -196,6 +202,11 @@ const fallbackConfigurations = new Map()
|
|
|
196
202
|
|
|
197
203
|
const regExps = {}
|
|
198
204
|
|
|
205
|
+
// Canonical names of configurations whose value is excluded from configuration
|
|
206
|
+
// telemetry. Driven by the `sensitive: true` attribute in
|
|
207
|
+
// `supported-configurations.json` so new entries opt in without code changes.
|
|
208
|
+
const sensitiveConfigurations = new Set()
|
|
209
|
+
|
|
199
210
|
for (const [canonicalName, entries] of Object.entries(supportedConfigurations)) {
|
|
200
211
|
if (entries.length !== 1) {
|
|
201
212
|
// TODO: Determine if we really want to support multiple entries for a canonical name.
|
|
@@ -207,6 +218,9 @@ for (const [canonicalName, entries] of Object.entries(supportedConfigurations))
|
|
|
207
218
|
)
|
|
208
219
|
}
|
|
209
220
|
for (const entry of entries) {
|
|
221
|
+
if (entry.sensitive) {
|
|
222
|
+
sensitiveConfigurations.add(canonicalName)
|
|
223
|
+
}
|
|
210
224
|
const configurationNames = entry.internalPropertyName ? [entry.internalPropertyName] : entry.configurationNames
|
|
211
225
|
const fullPropertyName = configurationNames?.[0] ?? canonicalName
|
|
212
226
|
const type = entry.type.toUpperCase()
|
|
@@ -73,7 +73,7 @@ export interface GeneratedConfig {
|
|
|
73
73
|
DD_APP_KEY: string | undefined;
|
|
74
74
|
DD_AZURE_RESOURCE_GROUP: string | undefined;
|
|
75
75
|
DD_CIVISIBILITY_AGENTLESS_ENABLED: boolean;
|
|
76
|
-
DD_CIVISIBILITY_AGENTLESS_URL:
|
|
76
|
+
DD_CIVISIBILITY_AGENTLESS_URL: URL | undefined;
|
|
77
77
|
DD_CIVISIBILITY_AUTO_INSTRUMENTATION_PROVIDER: string | undefined;
|
|
78
78
|
DD_CIVISIBILITY_DANGEROUSLY_FORCE_COVERAGE: boolean;
|
|
79
79
|
DD_CIVISIBILITY_DANGEROUSLY_FORCE_TEST_SKIPPING: boolean;
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
const fs = require('node:fs')
|
|
4
4
|
const os = require('node:os')
|
|
5
|
-
const { URL } = require('node:url')
|
|
5
|
+
const { URL, format } = require('node:url')
|
|
6
6
|
|
|
7
7
|
const rfdc = require('../../../../vendor/dist/rfdc')({ proto: false, circles: false })
|
|
8
8
|
const uuid = require('../../../../vendor/dist/crypto-randomuuid') // we need to keep the old uuid dep because of cypress
|
|
@@ -322,18 +322,14 @@ class Config extends ConfigBase {
|
|
|
322
322
|
#applyCalculated () {
|
|
323
323
|
undo(this, 'calculated')
|
|
324
324
|
|
|
325
|
-
if (this.
|
|
326
|
-
this.url ||
|
|
325
|
+
if (this.url ||
|
|
327
326
|
os.type() !== 'Windows_NT' &&
|
|
328
327
|
!trackedConfigOrigins.has('hostname') &&
|
|
329
328
|
!trackedConfigOrigins.has('port') &&
|
|
330
|
-
!this.DD_CIVISIBILITY_AGENTLESS_ENABLED &&
|
|
331
329
|
fs.existsSync('/var/run/datadog/apm.socket')) {
|
|
332
|
-
setAndTrack(
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
new URL(this.DD_CIVISIBILITY_AGENTLESS_URL || this.url || 'unix:///var/run/datadog/apm.socket')
|
|
336
|
-
)
|
|
330
|
+
setAndTrack(this, 'url', new URL(this.url || 'unix:///var/run/datadog/apm.socket'))
|
|
331
|
+
} else {
|
|
332
|
+
setAndTrack(this, 'url', new URL(format({ protocol: 'http:', hostname: this.hostname, port: this.port })))
|
|
337
333
|
}
|
|
338
334
|
|
|
339
335
|
if (this.isCiVisibility) {
|
|
@@ -145,6 +145,14 @@ const transformers = {
|
|
|
145
145
|
}
|
|
146
146
|
return value.replaceAll(/\s*:\s*/g, ':')
|
|
147
147
|
},
|
|
148
|
+
/**
|
|
149
|
+
* @param {string} value
|
|
150
|
+
*/
|
|
151
|
+
toURL (value) {
|
|
152
|
+
try {
|
|
153
|
+
return new URL(value)
|
|
154
|
+
} catch {}
|
|
155
|
+
},
|
|
148
156
|
validatePropagationStyles (value, optionName) {
|
|
149
157
|
value = transformers.toLowerCase(value)
|
|
150
158
|
for (let index = 0; index < value.length; index++) {
|
|
@@ -111,7 +111,8 @@
|
|
|
111
111
|
"aliases": [
|
|
112
112
|
"DATADOG_API_KEY"
|
|
113
113
|
],
|
|
114
|
-
"internalPropertyName": "apiKey"
|
|
114
|
+
"internalPropertyName": "apiKey",
|
|
115
|
+
"sensitive": true
|
|
115
116
|
}
|
|
116
117
|
],
|
|
117
118
|
"DD_API_SECURITY_ENABLED": [
|
|
@@ -423,7 +424,8 @@
|
|
|
423
424
|
{
|
|
424
425
|
"implementation": "A",
|
|
425
426
|
"type": "string",
|
|
426
|
-
"default": null
|
|
427
|
+
"default": null,
|
|
428
|
+
"sensitive": true
|
|
427
429
|
}
|
|
428
430
|
],
|
|
429
431
|
"DD_AZURE_RESOURCE_GROUP": [
|
|
@@ -444,6 +446,7 @@
|
|
|
444
446
|
{
|
|
445
447
|
"implementation": "A",
|
|
446
448
|
"type": "string",
|
|
449
|
+
"transform": "toURL",
|
|
447
450
|
"default": null
|
|
448
451
|
}
|
|
449
452
|
],
|
|
@@ -3913,7 +3916,8 @@
|
|
|
3913
3916
|
{
|
|
3914
3917
|
"implementation": "B",
|
|
3915
3918
|
"type": "map",
|
|
3916
|
-
"default": null
|
|
3919
|
+
"default": null,
|
|
3920
|
+
"sensitive": true
|
|
3917
3921
|
}
|
|
3918
3922
|
],
|
|
3919
3923
|
"OTEL_EXPORTER_OTLP_TRACES_ENDPOINT": [
|
|
@@ -3930,7 +3934,8 @@
|
|
|
3930
3934
|
"default": null,
|
|
3931
3935
|
"aliases": [
|
|
3932
3936
|
"OTEL_EXPORTER_OTLP_HEADERS"
|
|
3933
|
-
]
|
|
3937
|
+
],
|
|
3938
|
+
"sensitive": true
|
|
3934
3939
|
}
|
|
3935
3940
|
],
|
|
3936
3941
|
"OTEL_EXPORTER_OTLP_TRACES_PROTOCOL": [
|
|
@@ -3968,7 +3973,8 @@
|
|
|
3968
3973
|
"default": null,
|
|
3969
3974
|
"aliases": [
|
|
3970
3975
|
"OTEL_EXPORTER_OTLP_HEADERS"
|
|
3971
|
-
]
|
|
3976
|
+
],
|
|
3977
|
+
"sensitive": true
|
|
3972
3978
|
}
|
|
3973
3979
|
],
|
|
3974
3980
|
"OTEL_EXPORTER_OTLP_LOGS_PROTOCOL": [
|
|
@@ -4006,7 +4012,8 @@
|
|
|
4006
4012
|
"default": null,
|
|
4007
4013
|
"aliases": [
|
|
4008
4014
|
"OTEL_EXPORTER_OTLP_HEADERS"
|
|
4009
|
-
]
|
|
4015
|
+
],
|
|
4016
|
+
"sensitive": true
|
|
4010
4017
|
}
|
|
4011
4018
|
],
|
|
4012
4019
|
"OTEL_EXPORTER_OTLP_METRICS_PROTOCOL": [
|
|
@@ -7,7 +7,6 @@ const libdatadog = require('@datadog/libdatadog')
|
|
|
7
7
|
const binding = libdatadog.load('crashtracker')
|
|
8
8
|
|
|
9
9
|
const log = require('../log')
|
|
10
|
-
const { getAgentUrl } = require('../agent/url')
|
|
11
10
|
const pkg = require('../../../../package.json')
|
|
12
11
|
const processTags = require('../process-tags')
|
|
13
12
|
|
|
@@ -68,7 +67,7 @@ class Crashtracker {
|
|
|
68
67
|
* @param {import('../config/config-base')} config - Tracer configuration
|
|
69
68
|
*/
|
|
70
69
|
#getConfig (config) {
|
|
71
|
-
const url =
|
|
70
|
+
const url = config.url
|
|
72
71
|
|
|
73
72
|
// Out-of-process symbolication currently works on
|
|
74
73
|
// Linux only, does not work on Mac.
|
|
@@ -78,6 +77,7 @@ class Crashtracker {
|
|
|
78
77
|
|
|
79
78
|
return {
|
|
80
79
|
additional_files: [],
|
|
80
|
+
collect_all_threads: true,
|
|
81
81
|
create_alt_stack: true,
|
|
82
82
|
use_alt_stack: true,
|
|
83
83
|
endpoint: {
|
|
@@ -5,7 +5,6 @@ const pkg = require('../../../../package.json')
|
|
|
5
5
|
const log = require('../log')
|
|
6
6
|
const request = require('../exporters/common/request')
|
|
7
7
|
const { encode: encodeMsgpack } = require('../msgpack')
|
|
8
|
-
const { getAgentUrl } = require('../agent/url')
|
|
9
8
|
|
|
10
9
|
function makeRequest (data, url, cb) {
|
|
11
10
|
const options = {
|
|
@@ -29,7 +28,7 @@ function makeRequest (data, url, cb) {
|
|
|
29
28
|
|
|
30
29
|
class DataStreamsWriter {
|
|
31
30
|
constructor (config) {
|
|
32
|
-
this._url =
|
|
31
|
+
this._url = config.url
|
|
33
32
|
}
|
|
34
33
|
|
|
35
34
|
flush (payload) {
|
|
@@ -16,7 +16,7 @@ module.exports = function getDebuggerConfig (config, inputPath) {
|
|
|
16
16
|
repositoryUrl,
|
|
17
17
|
runtimeId: config.tags['runtime-id'],
|
|
18
18
|
service: config.service,
|
|
19
|
-
url: config.url
|
|
19
|
+
url: config.url.toString(),
|
|
20
20
|
version: config.version,
|
|
21
21
|
inputPath,
|
|
22
22
|
}
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
3
|
const { workerData: { config: parentConfig, parentThreadId, configPort } } = require('node:worker_threads')
|
|
4
|
-
const { getAgentUrl } = require('../../agent/url')
|
|
5
4
|
const processTags = require('../../process-tags')
|
|
6
5
|
const log = require('./log')
|
|
7
6
|
|
|
@@ -21,6 +20,8 @@ configPort.on('messageerror', (err) =>
|
|
|
21
20
|
)
|
|
22
21
|
|
|
23
22
|
function updateConfig (updates) {
|
|
24
|
-
config.url
|
|
23
|
+
// The worker receives a serialized config (see ../config.js) where `url` is a string, so it is
|
|
24
|
+
// reconstructed into a URL here rather than read directly off a Config instance.
|
|
25
|
+
config.url = new URL(updates.url)
|
|
25
26
|
config.dynamicInstrumentation.captureTimeoutNs = BigInt(updates.dynamicInstrumentation.captureTimeoutMs) * 1_000_000n
|
|
26
27
|
}
|
|
@@ -6,7 +6,6 @@ const { join } = require('path')
|
|
|
6
6
|
const { Worker, MessageChannel, threadId: parentThreadId } = require('worker_threads')
|
|
7
7
|
const log = require('../log')
|
|
8
8
|
const { fetchAgentInfo } = require('../agent/info')
|
|
9
|
-
const { getAgentUrl } = require('../agent/url')
|
|
10
9
|
const getDebuggerConfig = require('./config')
|
|
11
10
|
const { DEBUGGER_DIAGNOSTICS_V1, DEBUGGER_INPUT_V2 } = require('./constants')
|
|
12
11
|
|
|
@@ -211,7 +210,7 @@ function cleanup (error) {
|
|
|
211
210
|
function detectDebuggerEndpoint (config, cb) {
|
|
212
211
|
log.debug('[debugger] Detecting available debugger endpoints...')
|
|
213
212
|
|
|
214
|
-
fetchAgentInfo(
|
|
213
|
+
fetchAgentInfo(config.url, (err, agentInfo) => {
|
|
215
214
|
if (err) {
|
|
216
215
|
log.warn('[debugger] Failed to query agent %s endpoint, falling back to %s',
|
|
217
216
|
DEBUGGER_INPUT_V2,
|
|
@@ -6,7 +6,6 @@ const isIP = require('net').isIP
|
|
|
6
6
|
const request = require('./exporters/common/request')
|
|
7
7
|
const log = require('./log')
|
|
8
8
|
const Histogram = require('./histogram')
|
|
9
|
-
const { getAgentUrl } = require('./agent/url')
|
|
10
9
|
const { entityId } = require('./exporters/common/docker')
|
|
11
10
|
|
|
12
11
|
const MAX_BUFFER_SIZE = 1024 // limit from the agent
|
|
@@ -191,8 +190,8 @@ class DogStatsDClient {
|
|
|
191
190
|
lookup: config.lookup,
|
|
192
191
|
}
|
|
193
192
|
|
|
194
|
-
if (config.url
|
|
195
|
-
clientConfig.metricsProxyUrl =
|
|
193
|
+
if (config.url) {
|
|
194
|
+
clientConfig.metricsProxyUrl = config.url
|
|
196
195
|
}
|
|
197
196
|
|
|
198
197
|
return clientConfig
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
const getConfig = require('../config')
|
|
4
4
|
const { MsgpackChunk } = require('../msgpack')
|
|
5
5
|
const log = require('../log')
|
|
6
|
-
const { normalizeSpan } = require('./tags-processors')
|
|
6
|
+
const { normalizeSpan, eventTimeNano } = require('./tags-processors')
|
|
7
7
|
|
|
8
8
|
const SOFT_LIMIT = 8 * 1024 * 1024 // 8MB
|
|
9
9
|
// Values longer than this byte threshold skip the `_stringMap` lookup and
|
|
@@ -114,8 +114,10 @@ const ATTR_PAYLOAD_BOOL_FALSE = Buffer.concat([ATTR_PREFIX_BOOL, Buffer.from([0x
|
|
|
114
114
|
function formatSpanWithLegacyEvents (span) {
|
|
115
115
|
span = normalizeSpan(span)
|
|
116
116
|
if (span.span_events) {
|
|
117
|
-
//
|
|
118
|
-
//
|
|
117
|
+
// Reads the raw `_events` array directly (no formatter pre-reshape) and
|
|
118
|
+
// serializes to the legacy meta.events JSON string. The serialization is
|
|
119
|
+
// still the main cost on the legacy path; the native span_events slot
|
|
120
|
+
// (`#encodeSpanEvents`) avoids it entirely.
|
|
119
121
|
span.meta.events = stringifySpanEvents(span.span_events)
|
|
120
122
|
// `= undefined` over `delete` to keep the span's hidden class — `delete`
|
|
121
123
|
// would push every event-bearing span into V8 dictionary mode.
|
|
@@ -125,14 +127,15 @@ function formatSpanWithLegacyEvents (span) {
|
|
|
125
127
|
}
|
|
126
128
|
|
|
127
129
|
/**
|
|
128
|
-
* Hand-written stringifier for `span.span_events`.
|
|
129
|
-
* `
|
|
130
|
-
*
|
|
131
|
-
*
|
|
132
|
-
*
|
|
133
|
-
*
|
|
130
|
+
* Hand-written stringifier for `span.span_events`. Events arrive in their raw
|
|
131
|
+
* `{ name, startTime, attributes? }` shape; `time_unix_nano` is derived per
|
|
132
|
+
* event via `eventTimeNano` and empty attribute objects are dropped, matching
|
|
133
|
+
* what the formatter used to precompute. Attribute values are pre-sanitized to
|
|
134
|
+
* primitives or arrays of primitives, so we skip everything `JSON.stringify`
|
|
135
|
+
* does for the generic case (toJSON probing, prototype-chain key iteration,
|
|
136
|
+
* replacer hooks).
|
|
134
137
|
*
|
|
135
|
-
* @param {Array<{ name:
|
|
138
|
+
* @param {Array<{ name: unknown, startTime: number, attributes?: object }>} spanEvents
|
|
136
139
|
* @returns {string}
|
|
137
140
|
*/
|
|
138
141
|
function stringifySpanEvents (spanEvents) {
|
|
@@ -140,17 +143,21 @@ function stringifySpanEvents (spanEvents) {
|
|
|
140
143
|
for (let index = 0; index < spanEvents.length; index++) {
|
|
141
144
|
if (index > 0) result += ','
|
|
142
145
|
const event = spanEvents[index]
|
|
146
|
+
// `_sanitizeEventAttributes` leaves `attributes` undefined when empty, so a
|
|
147
|
+
// present value always has entries — no emptiness probe here.
|
|
148
|
+
const attributes = event.attributes
|
|
143
149
|
// `addEvent` does not type-check `name`; defer the unusual cases to
|
|
144
|
-
// `JSON.stringify` so non-string names match the prior behaviour
|
|
145
|
-
//
|
|
150
|
+
// `JSON.stringify` so non-string names match the prior behaviour instead
|
|
151
|
+
// of throwing in `escapeJsonString`. Build the wire-shaped object so the
|
|
152
|
+
// emitted key stays `time_unix_nano`, not the raw `startTime`.
|
|
146
153
|
if (typeof event.name !== 'string') {
|
|
147
|
-
result += JSON.stringify(event)
|
|
154
|
+
result += JSON.stringify({ name: event.name, time_unix_nano: eventTimeNano(event), attributes })
|
|
148
155
|
continue
|
|
149
156
|
}
|
|
150
157
|
result += '{"name":' + escapeJsonString(event.name) +
|
|
151
|
-
',"time_unix_nano":' + jsonNumber(event
|
|
152
|
-
if (
|
|
153
|
-
result += ',"attributes":' + stringifyAttributes(
|
|
158
|
+
',"time_unix_nano":' + jsonNumber(eventTimeNano(event))
|
|
159
|
+
if (attributes) {
|
|
160
|
+
result += ',"attributes":' + stringifyAttributes(attributes)
|
|
154
161
|
}
|
|
155
162
|
result += '}'
|
|
156
163
|
}
|
|
@@ -241,8 +248,8 @@ class AgentEncoder {
|
|
|
241
248
|
this.#config = getConfig()
|
|
242
249
|
this.#debugEncoding = this.#config.DD_TRACE_ENCODING_DEBUG
|
|
243
250
|
// Pick the per-span formatter once so the hot loop pays no per-span
|
|
244
|
-
// config check. The native path
|
|
245
|
-
//
|
|
251
|
+
// config check. The native path keeps the raw `span_events` slot for
|
|
252
|
+
// `#encodeSpanEvents`; the legacy path serializes it into meta.events.
|
|
246
253
|
this.#formatSpan = this.#config.DD_TRACE_NATIVE_SPAN_EVENTS
|
|
247
254
|
? normalizeSpan
|
|
248
255
|
: formatSpanWithLegacyEvents
|
|
@@ -320,16 +327,18 @@ class AgentEncoder {
|
|
|
320
327
|
const resourceLen = resourceEntry.length
|
|
321
328
|
const serviceLen = serviceEntry.length
|
|
322
329
|
|
|
323
|
-
//
|
|
324
|
-
//
|
|
325
|
-
//
|
|
326
|
-
//
|
|
327
|
-
//
|
|
328
|
-
//
|
|
329
|
-
// each integer
|
|
330
|
-
|
|
331
|
-
const
|
|
332
|
-
|
|
330
|
+
// `error` is `0` or `1` on nearly every span, and `start` is a
|
|
331
|
+
// nanosecond timestamp ≥ 2³² (always a msgpack u64). Decide the fused
|
|
332
|
+
// error key+value up front (`KEY_ERROR_0` / `KEY_ERROR_1`, or `undefined`
|
|
333
|
+
// for the rare non-binary flag) so the tail fuses without re-deciding the
|
|
334
|
+
// error shape twice. The fused tail also needs `start` as a u64; when
|
|
335
|
+
// either misses (synthetic small `start`, non-binary error) the tail
|
|
336
|
+
// routes each integer through `writeIntOrFloat` for the shortest
|
|
337
|
+
// encoding.
|
|
338
|
+
const errorEntry = span.error === 0
|
|
339
|
+
? KEY_ERROR_0
|
|
340
|
+
: span.error === 1 ? KEY_ERROR_1 : undefined
|
|
341
|
+
const fuseTail = errorEntry !== undefined && span.start >= 0x1_00_00_00_00
|
|
333
342
|
|
|
334
343
|
let blockSize = 1 +
|
|
335
344
|
KEY_TRACE_ID_PREFIX.length + 8 +
|
|
@@ -340,7 +349,7 @@ class AgentEncoder {
|
|
|
340
349
|
KEY_SERVICE.length + serviceLen
|
|
341
350
|
if (typeEntry) blockSize += KEY_TYPE.length + typeEntry.length
|
|
342
351
|
if (fuseTail) {
|
|
343
|
-
blockSize +=
|
|
352
|
+
blockSize += errorEntry.length + KEY_START_PREFIX.length + 8 + KEY_DURATION.length
|
|
344
353
|
}
|
|
345
354
|
|
|
346
355
|
const blockOffset = bytes.length
|
|
@@ -377,8 +386,8 @@ class AgentEncoder {
|
|
|
377
386
|
cursor += serviceLen
|
|
378
387
|
|
|
379
388
|
if (fuseTail) {
|
|
380
|
-
target.set(
|
|
381
|
-
cursor +=
|
|
389
|
+
target.set(errorEntry, cursor)
|
|
390
|
+
cursor += errorEntry.length
|
|
382
391
|
|
|
383
392
|
target.set(KEY_START_PREFIX, cursor)
|
|
384
393
|
cursor += KEY_START_PREFIX.length
|
|
@@ -389,15 +398,14 @@ class AgentEncoder {
|
|
|
389
398
|
cursor += 8
|
|
390
399
|
|
|
391
400
|
target.set(KEY_DURATION, cursor)
|
|
401
|
+
} else if (errorEntry) {
|
|
402
|
+
bytes.set(errorEntry)
|
|
403
|
+
bytes.set(KEY_START)
|
|
404
|
+
bytes.writeIntOrFloat(span.start)
|
|
405
|
+
bytes.set(KEY_DURATION)
|
|
392
406
|
} else {
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
} else if (span.error === 1) {
|
|
396
|
-
bytes.set(KEY_ERROR_1)
|
|
397
|
-
} else {
|
|
398
|
-
bytes.set(KEY_ERROR)
|
|
399
|
-
bytes.writeIntOrFloat(span.error)
|
|
400
|
-
}
|
|
407
|
+
bytes.set(KEY_ERROR)
|
|
408
|
+
bytes.writeIntOrFloat(span.error)
|
|
401
409
|
bytes.set(KEY_START)
|
|
402
410
|
bytes.writeIntOrFloat(span.start)
|
|
403
411
|
bytes.set(KEY_DURATION)
|
|
@@ -763,7 +771,7 @@ class AgentEncoder {
|
|
|
763
771
|
* values — no `formatSpanEvents` pre-pass and no recursive generic walk.
|
|
764
772
|
*
|
|
765
773
|
* @param {MsgpackChunk} bytes
|
|
766
|
-
* @param {Array<{ name:
|
|
774
|
+
* @param {Array<{ name: unknown, startTime: number, attributes?: object }>} spanEvents
|
|
767
775
|
*/
|
|
768
776
|
#encodeSpanEvents (bytes, spanEvents) {
|
|
769
777
|
const offset = bytes.length
|
|
@@ -784,7 +792,7 @@ class AgentEncoder {
|
|
|
784
792
|
bytes.set(KEY_NAME)
|
|
785
793
|
this._encodeString(bytes, event.name)
|
|
786
794
|
bytes.set(KEY_EVENT_TIME)
|
|
787
|
-
bytes.writeFloat(event
|
|
795
|
+
bytes.writeFloat(eventTimeNano(event))
|
|
788
796
|
|
|
789
797
|
const attributes = event.attributes
|
|
790
798
|
if (attributes !== null && typeof attributes === 'object') {
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
const log = require('../log')
|
|
4
4
|
const { TOP_LEVEL_KEY } = require('../constants')
|
|
5
5
|
const { normalizeSpan } = require('./tags-processors')
|
|
6
|
+
const { stringifySpanEvents } = require('./0.4')
|
|
6
7
|
|
|
7
8
|
// Soft limit for estimated payload size. Triggers an early flush to stay under intake request size limits.
|
|
8
9
|
const SOFT_LIMIT = 8 * 1024 * 1024 // 8MB
|
|
@@ -20,7 +21,10 @@ function formatSpan (span, isFirstSpan) {
|
|
|
20
21
|
delete span.meta['_dd.p.tid']
|
|
21
22
|
|
|
22
23
|
if (span.span_events) {
|
|
23
|
-
|
|
24
|
+
// Events arrive raw (`{ name, startTime, attributes? }`); stringifySpanEvents
|
|
25
|
+
// derives `time_unix_nano` and drops empty attributes, matching the JSON the
|
|
26
|
+
// reshaped array used to produce.
|
|
27
|
+
span.meta.events = stringifySpanEvents(span.span_events)
|
|
24
28
|
delete span.span_events
|
|
25
29
|
}
|
|
26
30
|
|
|
@@ -46,6 +46,19 @@ function truncateSpanTestOpt (span) {
|
|
|
46
46
|
return span
|
|
47
47
|
}
|
|
48
48
|
|
|
49
|
+
/**
|
|
50
|
+
* Convert a raw span event's `startTime` (milliseconds, sub-millisecond
|
|
51
|
+
* precision) to the wire `time_unix_nano`. Single source of truth for the
|
|
52
|
+
* formula so the four encoders that consume `span_events` stay in lockstep;
|
|
53
|
+
* the formatter no longer reshapes events, it hands the raw array through.
|
|
54
|
+
*
|
|
55
|
+
* @param {{ startTime: number }} event
|
|
56
|
+
* @returns {number}
|
|
57
|
+
*/
|
|
58
|
+
function eventTimeNano (event) {
|
|
59
|
+
return Math.round(event.startTime * 1e6)
|
|
60
|
+
}
|
|
61
|
+
|
|
49
62
|
function normalizeSpan (span) {
|
|
50
63
|
span.service = span.service || DEFAULT_SERVICE_NAME
|
|
51
64
|
if (span.service.length > MAX_SERVICE_LENGTH) {
|
|
@@ -69,6 +82,7 @@ module.exports = {
|
|
|
69
82
|
truncateSpan,
|
|
70
83
|
truncateSpanTestOpt,
|
|
71
84
|
normalizeSpan,
|
|
85
|
+
eventTimeNano,
|
|
72
86
|
MAX_META_KEY_LENGTH,
|
|
73
87
|
MAX_META_VALUE_LENGTH,
|
|
74
88
|
MAX_META_VALUE_LENGTH_TEST_OPTIMIZATION,
|