dd-trace 5.108.0 → 5.110.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/ci/init.js +4 -3
- package/index.d.ts +88 -25
- package/loader-hook.mjs +88 -2
- package/package.json +19 -14
- package/packages/datadog-esbuild/index.js +26 -0
- package/packages/datadog-esbuild/src/utils.js +46 -3
- package/packages/datadog-instrumentations/src/ai.js +43 -48
- package/packages/datadog-instrumentations/src/aws-durable-execution-sdk-js-context-methods.js +18 -0
- package/packages/datadog-instrumentations/src/aws-durable-execution-sdk-js.js +111 -0
- package/packages/datadog-instrumentations/src/aws-sdk.js +3 -1
- package/packages/datadog-instrumentations/src/connect.js +4 -3
- package/packages/datadog-instrumentations/src/electron.js +1 -1
- package/packages/datadog-instrumentations/src/fastify.js +4 -12
- package/packages/datadog-instrumentations/src/graphql.js +17 -6
- package/packages/datadog-instrumentations/src/helpers/check-require-cache.js +81 -8
- package/packages/datadog-instrumentations/src/helpers/hooks.js +1 -0
- package/packages/datadog-instrumentations/src/helpers/instrument.js +27 -2
- package/packages/datadog-instrumentations/src/helpers/optional-peer-bundler.js +65 -0
- package/packages/datadog-instrumentations/src/helpers/register.js +6 -4
- package/packages/datadog-instrumentations/src/helpers/require-optional-peer.js +17 -0
- package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/aws-durable-execution-sdk-js.js +31 -0
- package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/index.js +1 -0
- package/packages/datadog-instrumentations/src/helpers/rewriter/loader.mjs +18 -2
- package/packages/datadog-instrumentations/src/http/client.js +12 -2
- package/packages/datadog-instrumentations/src/ioredis.js +0 -1
- package/packages/datadog-instrumentations/src/iovalkey.js +1 -2
- package/packages/datadog-instrumentations/src/koa.js +3 -2
- package/packages/datadog-instrumentations/src/mariadb.js +4 -2
- package/packages/datadog-instrumentations/src/mocha/main.js +3 -3
- package/packages/datadog-instrumentations/src/next.js +44 -10
- package/packages/datadog-instrumentations/src/openai.js +77 -18
- package/packages/datadog-instrumentations/src/otel-sdk-trace.js +7 -6
- package/packages/datadog-instrumentations/src/playwright.js +1 -1
- package/packages/datadog-instrumentations/src/redis.js +0 -1
- package/packages/datadog-instrumentations/src/restify.js +4 -3
- package/packages/datadog-instrumentations/src/router.js +18 -5
- package/packages/datadog-instrumentations/src/selenium.js +1 -2
- package/packages/datadog-instrumentations/src/tedious.js +28 -0
- package/packages/datadog-instrumentations/src/vitest.js +323 -7
- package/packages/datadog-plugin-avsc/src/schema_iterator.js +2 -2
- package/packages/datadog-plugin-aws-durable-execution-sdk-js/src/checkpoint.js +31 -0
- package/packages/datadog-plugin-aws-durable-execution-sdk-js/src/client.js +55 -0
- package/packages/datadog-plugin-aws-durable-execution-sdk-js/src/context.js +114 -0
- package/packages/datadog-plugin-aws-durable-execution-sdk-js/src/handler.js +128 -0
- package/packages/datadog-plugin-aws-durable-execution-sdk-js/src/index.js +19 -0
- package/packages/datadog-plugin-aws-durable-execution-sdk-js/src/trace-checkpoint.js +224 -0
- package/packages/datadog-plugin-aws-durable-execution-sdk-js/src/util.js +43 -0
- package/packages/datadog-plugin-aws-sdk/src/base.js +14 -16
- package/packages/datadog-plugin-aws-sdk/src/services/eventbridge.js +43 -6
- package/packages/datadog-plugin-aws-sdk/src/services/kinesis.js +100 -37
- package/packages/datadog-plugin-aws-sdk/src/services/sqs.js +108 -99
- package/packages/datadog-plugin-bullmq/src/filter.js +35 -0
- package/packages/datadog-plugin-bullmq/src/producer.js +84 -4
- package/packages/datadog-plugin-cypress/src/cypress-plugin.js +9 -1
- package/packages/datadog-plugin-fs/src/index.js +1 -0
- package/packages/datadog-plugin-http/src/client.js +8 -3
- package/packages/datadog-plugin-http2/src/client.js +5 -2
- package/packages/datadog-plugin-mysql/src/index.js +7 -2
- package/packages/datadog-plugin-protobufjs/src/schema_iterator.js +2 -2
- package/packages/datadog-plugin-redis/src/index.js +1 -2
- package/packages/datadog-plugin-undici/src/index.js +5 -2
- package/packages/datadog-plugin-vitest/src/index.js +11 -2
- package/packages/datadog-webpack/index.js +17 -1
- package/packages/datadog-webpack/src/optional-peer-loader.js +17 -0
- package/packages/dd-trace/src/aiguard/channels.js +0 -1
- package/packages/dd-trace/src/aiguard/index.js +11 -49
- package/packages/dd-trace/src/aiguard/integrations/evaluate.js +46 -0
- package/packages/dd-trace/src/aiguard/integrations/openai.js +66 -0
- package/packages/dd-trace/src/aiguard/integrations/vercel-ai.js +78 -0
- package/packages/{datadog-instrumentations/src/helpers/ai-messages.js → dd-trace/src/aiguard/messages/openai.js} +85 -193
- package/packages/dd-trace/src/aiguard/messages/vercel-ai.js +185 -0
- package/packages/dd-trace/src/aiguard/sdk.js +13 -1
- package/packages/dd-trace/src/appsec/api_security/index.js +55 -0
- package/packages/dd-trace/src/appsec/api_security/sampler.js +147 -0
- package/packages/dd-trace/src/appsec/channels.js +1 -0
- package/packages/dd-trace/src/appsec/downstream_requests.js +111 -58
- package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-analyzers/ldap-sensitive-analyzer.js +54 -12
- package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-analyzers/url-sensitive-analyzer.js +5 -1
- package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-handler.js +29 -4
- package/packages/dd-trace/src/appsec/index.js +10 -6
- package/packages/dd-trace/src/appsec/rasp/ssrf.js +19 -11
- package/packages/dd-trace/src/appsec/reporter.js +1 -0
- package/packages/dd-trace/src/appsec/telemetry/api_security.js +34 -0
- package/packages/dd-trace/src/appsec/telemetry/index.js +26 -0
- package/packages/dd-trace/src/ci-visibility/requests/request.js +1 -17
- package/packages/dd-trace/src/config/defaults.js +22 -3
- package/packages/dd-trace/src/config/generated-config-types.d.ts +538 -1
- package/packages/dd-trace/src/config/helper.js +133 -33
- package/packages/dd-trace/src/config/index.js +4 -1
- package/packages/dd-trace/src/config/parsers.js +19 -1
- package/packages/dd-trace/src/config/supported-configurations.json +45 -5
- package/packages/dd-trace/src/debugger/devtools_client/breakpoints.js +49 -35
- package/packages/dd-trace/src/debugger/devtools_client/index.js +31 -66
- package/packages/dd-trace/src/debugger/devtools_client/probe_sampler.js +83 -0
- package/packages/dd-trace/src/debugger/devtools_client/state.js +1 -0
- package/packages/dd-trace/src/debugger/index.js +5 -0
- package/packages/dd-trace/src/debugger/probe_sampler.js +112 -0
- package/packages/dd-trace/src/debugger/probe_sampler_constants.js +19 -0
- package/packages/dd-trace/src/dogstatsd.js +15 -8
- package/packages/dd-trace/src/encode/agentless-ci-visibility.js +77 -26
- package/packages/dd-trace/src/exporters/agentless/index.js +7 -5
- package/packages/dd-trace/src/exporters/agentless/intake.js +43 -0
- package/packages/dd-trace/src/exporters/agentless/writer.js +5 -4
- package/packages/dd-trace/src/exporters/common/request.js +1 -20
- package/packages/dd-trace/src/exporters/common/url.js +34 -0
- package/packages/dd-trace/src/exporters/span-stats/writer.js +1 -3
- package/packages/dd-trace/src/guardrails/index.js +8 -5
- package/packages/dd-trace/src/index.js +6 -4
- package/packages/dd-trace/src/llmobs/constants/tags.js +6 -0
- package/packages/dd-trace/src/llmobs/index.js +16 -5
- package/packages/dd-trace/src/llmobs/plugins/genai/util.js +3 -3
- package/packages/dd-trace/src/llmobs/plugins/langchain/index.js +5 -1
- package/packages/dd-trace/src/llmobs/sdk.js +5 -3
- package/packages/dd-trace/src/llmobs/span_processor.js +4 -0
- package/packages/dd-trace/src/llmobs/tagger.js +55 -0
- package/packages/dd-trace/src/llmobs/telemetry.js +1 -0
- package/packages/dd-trace/src/log/index.js +18 -20
- package/packages/dd-trace/src/openfeature/flagging_provider.js +3 -1
- package/packages/dd-trace/src/opentelemetry/bridge-span-base.js +5 -4
- package/packages/dd-trace/src/opentelemetry/metrics/instruments.js +2 -1
- package/packages/dd-trace/src/opentelemetry/metrics/periodic_metric_reader.js +4 -3
- package/packages/dd-trace/src/opentelemetry/metrics/time.js +19 -0
- package/packages/dd-trace/src/opentelemetry/otlp/common.proto +2 -2
- package/packages/dd-trace/src/opentelemetry/otlp/logs.proto +1 -1
- package/packages/dd-trace/src/opentelemetry/otlp/logs_service.proto +1 -1
- package/packages/dd-trace/src/opentelemetry/otlp/metrics.proto +1 -1
- package/packages/dd-trace/src/opentelemetry/otlp/metrics_service.proto +1 -1
- package/packages/dd-trace/src/opentelemetry/otlp/otlp_http_exporter_base.js +11 -4
- package/packages/dd-trace/src/opentelemetry/span-helpers.js +29 -13
- package/packages/dd-trace/src/opentelemetry/span.js +14 -3
- package/packages/dd-trace/src/opentelemetry/span_context.js +1 -0
- package/packages/dd-trace/src/opentelemetry/trace/index.js +2 -1
- package/packages/dd-trace/src/opentelemetry/trace/otlp_http_trace_exporter.js +3 -2
- package/packages/dd-trace/src/opentelemetry/trace/otlp_transformer.js +26 -13
- package/packages/dd-trace/src/opentracing/propagation/text_map.js +3 -3
- package/packages/dd-trace/src/opentracing/span_context.js +17 -0
- package/packages/dd-trace/src/plugin_manager.js +7 -1
- package/packages/dd-trace/src/plugins/ci_plugin.js +36 -3
- package/packages/dd-trace/src/plugins/index.js +3 -0
- package/packages/dd-trace/src/plugins/util/http-otel-semantics.js +276 -0
- package/packages/dd-trace/src/plugins/util/test.js +80 -0
- package/packages/dd-trace/src/plugins/util/url.js +68 -1
- package/packages/dd-trace/src/plugins/util/web.js +19 -36
- package/packages/dd-trace/src/priority_sampler.js +1 -13
- package/packages/dd-trace/src/profiling/config.js +129 -198
- package/packages/dd-trace/src/profiling/constants.js +4 -7
- package/packages/dd-trace/src/profiling/exporter_cli.js +10 -10
- package/packages/dd-trace/src/profiling/exporters/agent.js +23 -16
- package/packages/dd-trace/src/profiling/exporters/event_serializer.js +45 -14
- package/packages/dd-trace/src/profiling/exporters/file.js +16 -5
- package/packages/dd-trace/src/profiling/oom.js +71 -0
- package/packages/dd-trace/src/profiling/profiler.js +35 -23
- package/packages/dd-trace/src/profiling/profilers/events.js +14 -7
- package/packages/dd-trace/src/profiling/profilers/space.js +27 -18
- package/packages/dd-trace/src/profiling/profilers/wall.js +35 -25
- package/packages/dd-trace/src/ritm.js +4 -2
- package/packages/dd-trace/src/serverless.js +1 -2
- package/packages/dd-trace/src/service-naming/schemas/util.js +36 -5
- package/packages/dd-trace/src/service-naming/schemas/v0/serverless.js +12 -0
- package/packages/dd-trace/src/service-naming/schemas/v1/serverless.js +12 -0
- package/packages/dd-trace/src/service-naming/schemas/v1/web.js +5 -3
- package/packages/dd-trace/src/span_format.js +2 -1
- package/packages/dd-trace/src/span_processor.js +6 -0
- package/packages/dd-trace/src/telemetry/send-data.js +12 -10
- package/packages/dd-trace/src/tracer.js +15 -1
- package/packages/dd-trace/src/util.js +14 -0
- package/register.js +58 -1
- package/vendor/dist/@apm-js-collab/code-transformer/index.js +1 -1
- package/vendor/dist/@datadog/sketches-js/index.js +1 -1
- package/vendor/dist/pprof-format/index.js +1 -1
- package/vendor/dist/protobufjs/index.js +1 -1
- package/vendor/dist/protobufjs/minimal/index.js +1 -1
- package/packages/datadog-instrumentations/src/helpers/openai-ai-guard.js +0 -284
- package/packages/dd-trace/src/appsec/api_security_sampler.js +0 -117
|
@@ -14,11 +14,13 @@ function guard (fn) {
|
|
|
14
14
|
var initBailout = false
|
|
15
15
|
var clobberBailout = false
|
|
16
16
|
var forced = isTrue(process.env.DD_INJECT_FORCE)
|
|
17
|
-
var
|
|
18
|
-
var
|
|
17
|
+
var pkg = require('../../../../package.json')
|
|
18
|
+
var engines = pkg.engines
|
|
19
|
+
var versions = engines.node.match(/^>=(\d+)$/)
|
|
19
20
|
var minMajor = versions[1]
|
|
20
|
-
var nextMajor =
|
|
21
|
+
var nextMajor = pkg.nodeMaxMajor
|
|
21
22
|
var version = process.versions.node
|
|
23
|
+
var supportedRange = engines.node + ' <' + nextMajor
|
|
22
24
|
|
|
23
25
|
if (process.env.DD_INJECTION_ENABLED) {
|
|
24
26
|
// If we're running via single-step install, and we're in the app's
|
|
@@ -45,16 +47,17 @@ function guard (fn) {
|
|
|
45
47
|
// should not initialize the tracer.
|
|
46
48
|
if (!clobberBailout && (NODE_MAJOR < minMajor || NODE_MAJOR >= nextMajor)) {
|
|
47
49
|
initBailout = true
|
|
50
|
+
var runtimeInfo = 'Incompatible runtime Node.js ' + version + ', supported runtimes: Node.js ' + supportedRange
|
|
48
51
|
telemetry([
|
|
49
52
|
{ name: 'abort', tags: ['reason:incompatible_runtime'] },
|
|
50
53
|
{ name: 'abort.runtime', tags: [] }
|
|
51
54
|
], undefined, {
|
|
52
55
|
result: 'abort',
|
|
53
56
|
result_class: 'incompatible_runtime',
|
|
54
|
-
result_reason:
|
|
57
|
+
result_reason: runtimeInfo
|
|
55
58
|
})
|
|
56
59
|
log.info('Aborting application instrumentation due to incompatible_runtime.')
|
|
57
|
-
log.info('Found incompatible runtime Node.js %s, Supported runtimes: Node.js %s.', version,
|
|
60
|
+
log.info('Found incompatible runtime Node.js %s, Supported runtimes: Node.js %s.', version, supportedRange)
|
|
58
61
|
if (forced) {
|
|
59
62
|
log.info('DD_INJECT_FORCE enabled, allowing unsupported runtimes and continuing.')
|
|
60
63
|
}
|
|
@@ -1,14 +1,16 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
3
|
const { getValueFromEnvSources } = require('./config/helper')
|
|
4
|
-
const { isFalse } = require('./util')
|
|
5
4
|
|
|
6
5
|
// Global `jest` is only present in Jest workers.
|
|
7
6
|
const inJestWorker = typeof jest !== 'undefined'
|
|
8
7
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
8
|
+
// skipDefault: distinguish an unset DD_TRACE_ENABLED (fall back to the OTel signal) from an
|
|
9
|
+
// explicit value; the registered default would otherwise mask the OTEL_TRACES_EXPORTER check.
|
|
10
|
+
const ddTraceEnabled = getValueFromEnvSources('DD_TRACE_ENABLED', true)
|
|
11
|
+
const ddTraceDisabled = ddTraceEnabled === undefined
|
|
12
|
+
? getValueFromEnvSources('OTEL_TRACES_EXPORTER') === 'none'
|
|
13
|
+
: ddTraceEnabled === false
|
|
12
14
|
|
|
13
15
|
module.exports = ddTraceDisabled || inJestWorker
|
|
14
16
|
? require('./noop/proxy')
|
|
@@ -14,6 +14,12 @@ module.exports = {
|
|
|
14
14
|
PROPAGATED_PARENT_ID_KEY: '_dd.p.llmobs_parent_id',
|
|
15
15
|
PROPAGATED_ML_APP_KEY: '_dd.p.llmobs_ml_app',
|
|
16
16
|
PARENT_ID_KEY: '_ml_obs.llmobs_parent_id',
|
|
17
|
+
PROPAGATED_SAMPLE_RATE_KEY: '_dd.p.llmobs_sr',
|
|
18
|
+
PROPAGATED_SAMPLING_DECISION_KEY: '_dd.p.llmobs_sd',
|
|
19
|
+
SAMPLE_RATE: '_ml_obs.sample_rate',
|
|
20
|
+
SAMPLING_DECISION: '_ml_obs.sampling_decision',
|
|
21
|
+
SAMPLING_DECISION_SAMPLED: '1',
|
|
22
|
+
SAMPLING_DECISION_DROPPED: '0',
|
|
17
23
|
TAGS: '_ml_obs.tags',
|
|
18
24
|
NAME: '_ml_obs.name',
|
|
19
25
|
TRACE_ID: '_ml_obs.trace_id',
|
|
@@ -9,6 +9,10 @@ const {
|
|
|
9
9
|
ML_APP,
|
|
10
10
|
PROPAGATED_ML_APP_KEY,
|
|
11
11
|
PROPAGATED_PARENT_ID_KEY,
|
|
12
|
+
SAMPLE_RATE,
|
|
13
|
+
SAMPLING_DECISION,
|
|
14
|
+
PROPAGATED_SAMPLE_RATE_KEY,
|
|
15
|
+
PROPAGATED_SAMPLING_DECISION_KEY,
|
|
12
16
|
} = require('./constants/tags')
|
|
13
17
|
const { storage } = require('./storage')
|
|
14
18
|
const telemetry = require('./telemetry')
|
|
@@ -68,7 +72,7 @@ function enable (config) {
|
|
|
68
72
|
spanFinishCh.subscribe(handleSpanProcess)
|
|
69
73
|
|
|
70
74
|
// distributed tracing for llmobs
|
|
71
|
-
injectCh.subscribe(
|
|
75
|
+
injectCh.subscribe(handleLLMObsInjection)
|
|
72
76
|
|
|
73
77
|
setAgentStrategy(config, useAgentless => {
|
|
74
78
|
if (useAgentless && !(config.apiKey && config.site)) {
|
|
@@ -92,7 +96,7 @@ function disable () {
|
|
|
92
96
|
if (evalMetricAppendCh.hasSubscribers) evalMetricAppendCh.unsubscribe(handleEvalMetricAppend)
|
|
93
97
|
if (flushCh.hasSubscribers) flushCh.unsubscribe(handleFlush)
|
|
94
98
|
if (spanFinishCh.hasSubscribers) spanFinishCh.unsubscribe(handleSpanProcess)
|
|
95
|
-
if (injectCh.hasSubscribers) injectCh.unsubscribe(
|
|
99
|
+
if (injectCh.hasSubscribers) injectCh.unsubscribe(handleLLMObsInjection)
|
|
96
100
|
if (registerUserSpanProcessorCh.hasSubscribers) registerUserSpanProcessorCh.unsubscribe(handleRegisterProcessor)
|
|
97
101
|
|
|
98
102
|
spanWriter?.destroy()
|
|
@@ -106,8 +110,8 @@ function disable () {
|
|
|
106
110
|
}
|
|
107
111
|
|
|
108
112
|
// since LLMObs traces can extend between services and be the same trace,
|
|
109
|
-
// we need to propagate the parent id and
|
|
110
|
-
function
|
|
113
|
+
// we need to propagate the parent id, mlApp, and sampling rate/decision.
|
|
114
|
+
function handleLLMObsInjection ({ carrier }) {
|
|
111
115
|
// Respect the standard propagator's gate: when trace tag propagation is
|
|
112
116
|
// disabled, don't write `x-datadog-tags` for LLMObs either.
|
|
113
117
|
if (globalTracerConfig.DD_TRACE_X_DATADOG_TAGS_MAX_LENGTH === 0) return
|
|
@@ -122,7 +126,12 @@ function handleLLMObsParentIdInjection ({ carrier }) {
|
|
|
122
126
|
parentContext?._trace?.tags?.[PROPAGATED_ML_APP_KEY] ||
|
|
123
127
|
globalTracerConfig.llmobs.mlApp
|
|
124
128
|
|
|
125
|
-
|
|
129
|
+
const sampleRate =
|
|
130
|
+
mlObsSpanTags?.[SAMPLE_RATE] ?? parentContext?._trace?.tags?.[PROPAGATED_SAMPLE_RATE_KEY]
|
|
131
|
+
const samplingDecision =
|
|
132
|
+
mlObsSpanTags?.[SAMPLING_DECISION] ?? parentContext?._trace?.tags?.[PROPAGATED_SAMPLING_DECISION_KEY]
|
|
133
|
+
|
|
134
|
+
if (!parentId && !mlApp && samplingDecision == null) return
|
|
126
135
|
|
|
127
136
|
// `_injectTags` only writes `x-datadog-tags` when the trace has `_dd.p.*`
|
|
128
137
|
// tags, so it may be undefined here — coalesce before appending.
|
|
@@ -130,6 +139,8 @@ function handleLLMObsParentIdInjection ({ carrier }) {
|
|
|
130
139
|
let tags = existing || ''
|
|
131
140
|
if (parentId) tags += `${tags ? ',' : ''}${PROPAGATED_PARENT_ID_KEY}=${parentId}`
|
|
132
141
|
if (mlApp) tags += `${tags ? ',' : ''}${PROPAGATED_ML_APP_KEY}=${mlApp}`
|
|
142
|
+
if (sampleRate != null) tags += `${tags ? ',' : ''}${PROPAGATED_SAMPLE_RATE_KEY}=${sampleRate}`
|
|
143
|
+
if (samplingDecision != null) tags += `${tags ? ',' : ''}${PROPAGATED_SAMPLING_DECISION_KEY}=${samplingDecision}`
|
|
133
144
|
if (tags !== existing) carrier['x-datadog-tags'] = tags
|
|
134
145
|
}
|
|
135
146
|
|
|
@@ -114,7 +114,7 @@ function extractMetrics (response) {
|
|
|
114
114
|
}
|
|
115
115
|
|
|
116
116
|
const totalTokens = tokenUsage.totalTokenCount ||
|
|
117
|
-
|
|
117
|
+
(tokenUsage.promptTokenCount || 0) + (tokenUsage.candidatesTokenCount || 0)
|
|
118
118
|
if (totalTokens) {
|
|
119
119
|
metrics.totalTokens = totalTokens
|
|
120
120
|
}
|
|
@@ -458,8 +458,8 @@ function formatStreamingOutput (response) {
|
|
|
458
458
|
|
|
459
459
|
// Skip special cases in streaming (handle them as non-streaming)
|
|
460
460
|
if (content.parts.some(part => part.functionCall ||
|
|
461
|
-
|
|
462
|
-
|
|
461
|
+
part.executableCode ||
|
|
462
|
+
part.codeExecutionResult)) {
|
|
463
463
|
messages.push(...formatNonStreamingCandidate(candidate))
|
|
464
464
|
continue
|
|
465
465
|
}
|
|
@@ -9,7 +9,11 @@ const ANTHROPIC_PROVIDER_NAME = 'anthropic'
|
|
|
9
9
|
const BEDROCK_PROVIDER_NAME = 'amazon_bedrock'
|
|
10
10
|
const OPENAI_PROVIDER_NAME = 'openai'
|
|
11
11
|
|
|
12
|
-
|
|
12
|
+
// Providers that ship their own LLMObs integration. When one of these is also
|
|
13
|
+
// enabled, the LangChain model span is demoted to `workflow` so the provider
|
|
14
|
+
// integration emits the single `llm` span — avoiding two llm spans (and
|
|
15
|
+
// double-counted tokens/cost) for one underlying call.
|
|
16
|
+
const SUPPORTED_INTEGRATIONS = new Set(['openai', 'anthropic'])
|
|
13
17
|
const LLM_SPAN_TYPES = new Set(['llm', 'chat_model', 'embedding'])
|
|
14
18
|
const LLM = 'llm'
|
|
15
19
|
const WORKFLOW = 'workflow'
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
const { channel } = require('dc-polyfill')
|
|
4
4
|
|
|
5
|
-
const { isError
|
|
5
|
+
const { isError } = require('../util')
|
|
6
6
|
const tracerVersion = require('../../../../package.json').version
|
|
7
7
|
const logger = require('../log')
|
|
8
8
|
const { getValueFromEnvSources } = require('../config/helper')
|
|
@@ -65,9 +65,11 @@ class LLMObs extends NoopLLMObs {
|
|
|
65
65
|
|
|
66
66
|
logger.debug('Enabling LLMObs')
|
|
67
67
|
|
|
68
|
-
|
|
68
|
+
// skipDefault: only an explicit DD_LLMOBS_ENABLED=false blocks enable(); an unset value
|
|
69
|
+
// (its default is false) must still allow this programmatic opt-in.
|
|
70
|
+
const DD_LLMOBS_ENABLED = getValueFromEnvSources('DD_LLMOBS_ENABLED', true)
|
|
69
71
|
|
|
70
|
-
if (DD_LLMOBS_ENABLED
|
|
72
|
+
if (DD_LLMOBS_ENABLED === false) {
|
|
71
73
|
logger.debug('LLMObs.enable() called when DD_LLMOBS_ENABLED is false. No action taken.')
|
|
72
74
|
return
|
|
73
75
|
}
|
|
@@ -33,6 +33,8 @@ const {
|
|
|
33
33
|
ROUTING_API_KEY,
|
|
34
34
|
ROUTING_SITE,
|
|
35
35
|
LLMOBS_SUBMITTED_TAG_KEY,
|
|
36
|
+
SAMPLE_RATE,
|
|
37
|
+
SAMPLING_DECISION,
|
|
36
38
|
} = require('./constants/tags')
|
|
37
39
|
const { UNSERIALIZABLE_VALUE_TEXT } = require('./constants/text')
|
|
38
40
|
const telemetry = require('./telemetry')
|
|
@@ -248,6 +250,8 @@ class LLMObsSpanProcessor {
|
|
|
248
250
|
_dd: {
|
|
249
251
|
span_id: span.context().toSpanId(),
|
|
250
252
|
trace_id: span.context().toTraceId(true),
|
|
253
|
+
sample_rate: mlObsTags[SAMPLE_RATE],
|
|
254
|
+
sampling_decision: mlObsTags[SAMPLING_DECISION],
|
|
251
255
|
},
|
|
252
256
|
}
|
|
253
257
|
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
3
|
const log = require('../log')
|
|
4
|
+
const Sampler = require('../sampler')
|
|
5
|
+
const { formatKnuthRate } = require('../util')
|
|
4
6
|
const {
|
|
5
7
|
MODEL_NAME,
|
|
6
8
|
MODEL_PROVIDER,
|
|
@@ -41,6 +43,12 @@ const {
|
|
|
41
43
|
ROUTING_SITE,
|
|
42
44
|
PROMPT_TRACKING_INSTRUMENTATION_METHOD,
|
|
43
45
|
INSTRUMENTATION_METHOD_ANNOTATED,
|
|
46
|
+
SAMPLE_RATE,
|
|
47
|
+
SAMPLING_DECISION,
|
|
48
|
+
SAMPLING_DECISION_SAMPLED,
|
|
49
|
+
SAMPLING_DECISION_DROPPED,
|
|
50
|
+
PROPAGATED_SAMPLE_RATE_KEY,
|
|
51
|
+
PROPAGATED_SAMPLING_DECISION_KEY,
|
|
44
52
|
} = require('./constants/tags')
|
|
45
53
|
const { storage } = require('./storage')
|
|
46
54
|
const { findGenAIAncestorSpanId, validateCostTags, writeBridgeTags, validateToolDefinitions } = require('./util')
|
|
@@ -53,12 +61,31 @@ class LLMObsTagger {
|
|
|
53
61
|
/** @type {import('../config/config-base')} */
|
|
54
62
|
#config
|
|
55
63
|
|
|
64
|
+
/** @type {import('../sampler') | null} */
|
|
65
|
+
#sampler = null
|
|
66
|
+
|
|
56
67
|
constructor (config, softFail = false) {
|
|
57
68
|
this.#config = config
|
|
58
69
|
|
|
59
70
|
this.softFail = softFail
|
|
60
71
|
}
|
|
61
72
|
|
|
73
|
+
/**
|
|
74
|
+
* The sampler reads its rate from `config.llmobs.sampleRate`, which can change
|
|
75
|
+
* at runtime (e.g. via remote config). Rebuild the sampler whenever the rate
|
|
76
|
+
* changes so decisions reflect the current config, while reusing the existing
|
|
77
|
+
* sampler when it hasn't.
|
|
78
|
+
*
|
|
79
|
+
* @returns {import('../sampler')}
|
|
80
|
+
*/
|
|
81
|
+
#getSampler () {
|
|
82
|
+
const rate = this.#config.llmobs?.sampleRate ?? 1
|
|
83
|
+
if (this.#sampler === null || rate !== this.#sampler.rate()) {
|
|
84
|
+
this.#sampler = new Sampler(rate)
|
|
85
|
+
}
|
|
86
|
+
return this.#sampler
|
|
87
|
+
}
|
|
88
|
+
|
|
62
89
|
static get tagMap () {
|
|
63
90
|
return registry
|
|
64
91
|
}
|
|
@@ -124,6 +151,8 @@ class LLMObsTagger {
|
|
|
124
151
|
ROOT_PARENT_ID
|
|
125
152
|
this._setTag(span, PARENT_ID_KEY, parentId)
|
|
126
153
|
|
|
154
|
+
this.#tagSamplingDecision(span, parent)
|
|
155
|
+
|
|
127
156
|
// apply annotation context
|
|
128
157
|
const annotationContext = storage.getStore()?.annotationContext
|
|
129
158
|
|
|
@@ -153,6 +182,32 @@ class LLMObsTagger {
|
|
|
153
182
|
}
|
|
154
183
|
}
|
|
155
184
|
|
|
185
|
+
#tagSamplingDecision (span, parent) {
|
|
186
|
+
const traceTags = span.context()._trace.tags
|
|
187
|
+
const parentTags = registry.get(parent)
|
|
188
|
+
|
|
189
|
+
let sampleRate, samplingDecision
|
|
190
|
+
if (parentTags) {
|
|
191
|
+
// Local LLMObs parent: inherit its decision.
|
|
192
|
+
sampleRate = parentTags[SAMPLE_RATE]
|
|
193
|
+
samplingDecision = parentTags[SAMPLING_DECISION]
|
|
194
|
+
} else if (traceTags[PROPAGATED_PARENT_ID_KEY]) {
|
|
195
|
+
// Distributed LLMObs parent: inherit whatever was propagated. This may be
|
|
196
|
+
// absent if the upstream service predates sampling propagation, in which
|
|
197
|
+
// case we make no decision here rather than starting a divergent one.
|
|
198
|
+
sampleRate = traceTags[PROPAGATED_SAMPLE_RATE_KEY]
|
|
199
|
+
samplingDecision = traceTags[PROPAGATED_SAMPLING_DECISION_KEY]
|
|
200
|
+
} else {
|
|
201
|
+
// Root span: make the trace's one sampling decision.
|
|
202
|
+
const sampler = this.#getSampler()
|
|
203
|
+
sampleRate = formatKnuthRate(sampler.rate())
|
|
204
|
+
samplingDecision = sampler.isSampled(span) ? SAMPLING_DECISION_SAMPLED : SAMPLING_DECISION_DROPPED
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
if (sampleRate != null) this._setTag(span, SAMPLE_RATE, sampleRate)
|
|
208
|
+
if (samplingDecision != null) this._setTag(span, SAMPLING_DECISION, samplingDecision)
|
|
209
|
+
}
|
|
210
|
+
|
|
156
211
|
// TODO: similarly for the following `tag` methods,
|
|
157
212
|
// how can we transition from a span weakmap to core API functionality
|
|
158
213
|
tagLLMIO (span, inputData, outputData) {
|
|
@@ -87,6 +87,7 @@ function recordLLMObsEnabled (startTime, config, value = 1) {
|
|
|
87
87
|
site: config.site,
|
|
88
88
|
auto: Number(autoEnabled),
|
|
89
89
|
ml_app: config.llmobs.mlApp,
|
|
90
|
+
sample_rate: config.llmobs.sampleRate,
|
|
90
91
|
}
|
|
91
92
|
llmobsMetrics.count('product_enabled', tags).inc(value)
|
|
92
93
|
llmobsMetrics.distribution('init_time', tags).track(initTimeMs)
|
|
@@ -2,19 +2,16 @@
|
|
|
2
2
|
|
|
3
3
|
const { inspect } = require('util')
|
|
4
4
|
|
|
5
|
-
const { defaults } = require('../config/defaults')
|
|
6
|
-
const { isTrue } = require('../util')
|
|
7
5
|
const { getValueFromEnvSources } = require('../config/helper')
|
|
6
|
+
// Eager require restores the original startup module-load order: pulling `config/defaults` here
|
|
7
|
+
// also installs the instrumented `dns` it transitively loads before the tracer/agent connects.
|
|
8
|
+
// `config/defaults` defers its own `dns` require until after it exports, so this no longer hits
|
|
9
|
+
// the `config/defaults` <-> log parse cycle that motivated the lazy require below.
|
|
10
|
+
const { defaults } = require('../config/defaults')
|
|
8
11
|
const { traceChannel, debugChannel, infoChannel, warnChannel, errorChannel } = require('./channels')
|
|
9
12
|
const logWriter = require('./writer')
|
|
10
13
|
const { Log, LogConfig, NoTransmitError } = require('./log')
|
|
11
14
|
|
|
12
|
-
const config = {
|
|
13
|
-
enabled: defaults.DD_TRACE_DEBUG,
|
|
14
|
-
logger: undefined,
|
|
15
|
-
logLevel: defaults.logLevel,
|
|
16
|
-
}
|
|
17
|
-
|
|
18
15
|
// In most places where we know we want to mute a log we use log.error() directly
|
|
19
16
|
const NO_TRANSMIT = new LogConfig(false)
|
|
20
17
|
|
|
@@ -77,19 +74,17 @@ const log = {
|
|
|
77
74
|
},
|
|
78
75
|
|
|
79
76
|
configure (options) {
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
getValueFromEnvSources('DD_TRACE_LOG_LEVEL') ??
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
getValueFromEnvSources('DD_TRACE_DEBUG') ??
|
|
77
|
+
const logger = options.logger
|
|
78
|
+
const logLevel = options.logLevel ??
|
|
79
|
+
getValueFromEnvSources('DD_TRACE_LOG_LEVEL', true) ??
|
|
80
|
+
defaults?.logLevel
|
|
81
|
+
const enabled = getValueFromEnvSources('DD_TRACE_DEBUG', true) ??
|
|
86
82
|
// TODO: Handle this by adding a log buffer so that configure may be called with the actual configurations.
|
|
87
83
|
// eslint-disable-next-line eslint-rules/eslint-process-env
|
|
88
|
-
(process.env.OTEL_LOG_LEVEL === 'debug' ||
|
|
89
|
-
)
|
|
90
|
-
logWriter.configure(config.enabled, config.logLevel, options.logger)
|
|
84
|
+
(process.env.OTEL_LOG_LEVEL === 'debug' || defaults?.DD_TRACE_DEBUG)
|
|
85
|
+
logWriter.configure(enabled, logLevel, logger)
|
|
91
86
|
|
|
92
|
-
return
|
|
87
|
+
return enabled
|
|
93
88
|
},
|
|
94
89
|
}
|
|
95
90
|
|
|
@@ -113,6 +108,9 @@ function getErrorLog (err) {
|
|
|
113
108
|
return err
|
|
114
109
|
}
|
|
115
110
|
|
|
116
|
-
|
|
117
|
-
|
|
111
|
+
// Assign before the bootstrap configure() call: an invalid DD_TRACE_LOG_LEVEL
|
|
112
|
+
// makes config/defaults re-require this module to warn, which must observe the
|
|
113
|
+
// fully built log object rather than a half-initialized one.
|
|
118
114
|
module.exports = log
|
|
115
|
+
|
|
116
|
+
log.configure({})
|
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
-
const { DatadogNodeServerProvider } = require('@datadog/openfeature-node-server')
|
|
4
3
|
const { channel } = require('dc-polyfill')
|
|
4
|
+
const requireOptionalPeer = require('../../../datadog-instrumentations/src/helpers/require-optional-peer')
|
|
5
5
|
const log = require('../log')
|
|
6
6
|
const { EXPOSURE_CHANNEL } = require('./constants/constants')
|
|
7
7
|
const EvalMetricsHook = require('./eval-metrics-hook')
|
|
8
8
|
const SpanEnrichmentHook = require('./span-enrichment-hook')
|
|
9
9
|
|
|
10
|
+
const { DatadogNodeServerProvider } = requireOptionalPeer('@datadog/openfeature-node-server')
|
|
11
|
+
|
|
10
12
|
/**
|
|
11
13
|
* OpenFeature provider that integrates with Datadog's feature flagging system.
|
|
12
14
|
* Extends DatadogNodeServerProvider to add tracer integration and configuration management.
|
|
@@ -30,6 +30,7 @@ class BridgeSpanBase {
|
|
|
30
30
|
*/
|
|
31
31
|
constructor (ddSpan) {
|
|
32
32
|
this._ddSpan = ddSpan
|
|
33
|
+
this._otelTraceSemanticsEnabled = false
|
|
33
34
|
}
|
|
34
35
|
|
|
35
36
|
get ended () {
|
|
@@ -45,7 +46,7 @@ class BridgeSpanBase {
|
|
|
45
46
|
* @param {import('@opentelemetry/api').AttributeValue} value
|
|
46
47
|
*/
|
|
47
48
|
setAttribute (key, value) {
|
|
48
|
-
setOtelAttribute(this._ddSpan, key, value)
|
|
49
|
+
setOtelAttribute(this._ddSpan, key, value, this._otelTraceSemanticsEnabled)
|
|
49
50
|
return this
|
|
50
51
|
}
|
|
51
52
|
|
|
@@ -53,7 +54,7 @@ class BridgeSpanBase {
|
|
|
53
54
|
* @param {import('@opentelemetry/api').Attributes} attributes
|
|
54
55
|
*/
|
|
55
56
|
setAttributes (attributes) {
|
|
56
|
-
setOtelAttributes(this._ddSpan, attributes)
|
|
57
|
+
setOtelAttributes(this._ddSpan, attributes, this._otelTraceSemanticsEnabled)
|
|
57
58
|
return this
|
|
58
59
|
}
|
|
59
60
|
|
|
@@ -91,14 +92,14 @@ class BridgeSpanBase {
|
|
|
91
92
|
* @param {import('@opentelemetry/api').TimeInput} [timeInput]
|
|
92
93
|
*/
|
|
93
94
|
recordException (exception, timeInput) {
|
|
94
|
-
recordException(this._ddSpan, exception, timeInput)
|
|
95
|
+
recordException(this._ddSpan, exception, timeInput, this._otelTraceSemanticsEnabled)
|
|
95
96
|
}
|
|
96
97
|
|
|
97
98
|
/**
|
|
98
99
|
* @param {import('@opentelemetry/api').SpanStatus} status
|
|
99
100
|
*/
|
|
100
101
|
setStatus (status) {
|
|
101
|
-
this.#statusCode = applyOtelStatus(this._ddSpan, this.#statusCode, status)
|
|
102
|
+
this.#statusCode = applyOtelStatus(this._ddSpan, this.#statusCode, status, this._otelTraceSemanticsEnabled)
|
|
102
103
|
return this
|
|
103
104
|
}
|
|
104
105
|
}
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
const { sanitizeAttributes } = require('../../../../../vendor/dist/@opentelemetry/core')
|
|
4
4
|
const { METRIC_TYPES } = require('./constants')
|
|
5
|
+
const { nowUnixNano } = require('./time')
|
|
5
6
|
|
|
6
7
|
/**
|
|
7
8
|
* @typedef {import('@opentelemetry/api').Attributes} Attributes
|
|
@@ -62,7 +63,7 @@ class Instrument {
|
|
|
62
63
|
type,
|
|
63
64
|
value,
|
|
64
65
|
attributes: sanitizeAttributes(attributes),
|
|
65
|
-
timestamp:
|
|
66
|
+
timestamp: nowUnixNano(),
|
|
66
67
|
}
|
|
67
68
|
}
|
|
68
69
|
}
|
|
@@ -6,6 +6,7 @@ const {
|
|
|
6
6
|
METRIC_TYPES, TEMPORALITY, DEFAULT_HISTOGRAM_BUCKETS, DEFAULT_MAX_MEASUREMENT_QUEUE_SIZE,
|
|
7
7
|
} = require('./constants')
|
|
8
8
|
const { ObservableInstrument } = require('./instruments')
|
|
9
|
+
const { nowUnixNano } = require('./time')
|
|
9
10
|
|
|
10
11
|
/**
|
|
11
12
|
* @typedef {import('@opentelemetry/api').Attributes} Attributes
|
|
@@ -309,7 +310,7 @@ class PeriodicMetricReader {
|
|
|
309
310
|
*
|
|
310
311
|
*/
|
|
311
312
|
class MetricAggregator {
|
|
312
|
-
#startTime =
|
|
313
|
+
#startTime = nowUnixNano()
|
|
313
314
|
#temporalityPreference
|
|
314
315
|
#maxBatchedQueueSize
|
|
315
316
|
|
|
@@ -444,8 +445,8 @@ class MetricAggregator {
|
|
|
444
445
|
*/
|
|
445
446
|
#isDeltaType (type) {
|
|
446
447
|
return type === METRIC_TYPES.COUNTER ||
|
|
447
|
-
|
|
448
|
-
|
|
448
|
+
type === METRIC_TYPES.OBSERVABLECOUNTER ||
|
|
449
|
+
type === METRIC_TYPES.HISTOGRAM
|
|
449
450
|
}
|
|
450
451
|
|
|
451
452
|
/**
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const { timeOrigin } = performance
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Current wall-clock time as UNIX epoch nanoseconds, the unit OTLP requires for
|
|
7
|
+
* `timeUnixNano` / `startTimeUnixNano`. `process.hrtime.bigint()` is a monotonic
|
|
8
|
+
* clock with an arbitrary origin, so its values decode as 1970 timestamps that
|
|
9
|
+
* the Datadog Agent silently drops. Anchoring on `performance.timeOrigin` puts
|
|
10
|
+
* the value on the epoch while `performance.now()` keeps it monotonic, so a
|
|
11
|
+
* backward wall-clock jump can't make `timeUnixNano` precede `startTimeUnixNano`.
|
|
12
|
+
*
|
|
13
|
+
* @returns {number} Nanoseconds since the UNIX epoch
|
|
14
|
+
*/
|
|
15
|
+
function nowUnixNano () {
|
|
16
|
+
return Math.round((timeOrigin + performance.now()) * 1e6)
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
module.exports = { nowUnixNano }
|
|
@@ -68,7 +68,7 @@ message KeyValue {
|
|
|
68
68
|
}
|
|
69
69
|
|
|
70
70
|
// InstrumentationScope is a message representing the instrumentation scope information
|
|
71
|
-
// such as the fully qualified name and version.
|
|
71
|
+
// such as the fully qualified name and version.
|
|
72
72
|
message InstrumentationScope {
|
|
73
73
|
// An empty instrumentation scope name means the name is unknown.
|
|
74
74
|
string name = 1;
|
|
@@ -113,4 +113,4 @@ message EntityRef {
|
|
|
113
113
|
// These attribute keys are not part of entity's identity.
|
|
114
114
|
// These keys MUST exist in the containing {message}.attributes.
|
|
115
115
|
repeated string description_keys = 4;
|
|
116
|
-
}
|
|
116
|
+
}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
-
const http = require('http')
|
|
4
|
-
const
|
|
3
|
+
const http = require('node:http')
|
|
4
|
+
const https = require('node:https')
|
|
5
|
+
const { URL } = require('node:url')
|
|
5
6
|
const log = require('../../log')
|
|
6
7
|
const telemetryMetrics = require('../../telemetry/metrics')
|
|
7
8
|
|
|
@@ -16,6 +17,8 @@ const tracerMetrics = telemetryMetrics.manager.namespace('tracers')
|
|
|
16
17
|
* @class OtlpHttpExporterBase
|
|
17
18
|
*/
|
|
18
19
|
class OtlpHttpExporterBase {
|
|
20
|
+
#transport = https
|
|
21
|
+
|
|
19
22
|
/**
|
|
20
23
|
* Creates a new OtlpHttpExporterBase instance.
|
|
21
24
|
*
|
|
@@ -45,7 +48,7 @@ class OtlpHttpExporterBase {
|
|
|
45
48
|
this.setUrl(url)
|
|
46
49
|
|
|
47
50
|
this.telemetryTags = [
|
|
48
|
-
'
|
|
51
|
+
`protocol:${this.#transport === https ? 'https' : 'http'}`,
|
|
49
52
|
`encoding:${isJson ? 'json' : 'protobuf'}`,
|
|
50
53
|
]
|
|
51
54
|
}
|
|
@@ -81,7 +84,7 @@ class OtlpHttpExporterBase {
|
|
|
81
84
|
},
|
|
82
85
|
}
|
|
83
86
|
|
|
84
|
-
const req =
|
|
87
|
+
const req = this.#transport.request(options, (res) => {
|
|
85
88
|
let data = ''
|
|
86
89
|
|
|
87
90
|
res.on('data', (chunk) => {
|
|
@@ -124,6 +127,10 @@ class OtlpHttpExporterBase {
|
|
|
124
127
|
this.options.hostname = parsedUrl.hostname
|
|
125
128
|
this.options.port = parsedUrl.port
|
|
126
129
|
this.options.path = parsedUrl.pathname + parsedUrl.search
|
|
130
|
+
this.#transport = parsedUrl.protocol === 'http:' ? http : https
|
|
131
|
+
if (this.telemetryTags !== undefined) {
|
|
132
|
+
this.telemetryTags[0] = `protocol:${this.#transport === https ? 'https' : 'http'}`
|
|
133
|
+
}
|
|
127
134
|
}
|
|
128
135
|
|
|
129
136
|
/**
|