dd-trace 5.103.0 → 5.105.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 +90 -102
- package/index.d.ts +107 -6
- package/package.json +18 -17
- package/packages/datadog-core/src/storage.js +1 -1
- package/packages/datadog-instrumentations/src/aerospike.js +1 -1
- package/packages/datadog-instrumentations/src/ai.js +8 -7
- package/packages/datadog-instrumentations/src/aws-sdk.js +15 -2
- package/packages/datadog-instrumentations/src/azure-cosmos.js +7 -0
- package/packages/datadog-instrumentations/src/azure-functions.js +3 -0
- package/packages/datadog-instrumentations/src/cassandra-driver.js +5 -2
- package/packages/datadog-instrumentations/src/cucumber.js +181 -35
- package/packages/datadog-instrumentations/src/dns.js +54 -18
- package/packages/datadog-instrumentations/src/elasticsearch.js +4 -4
- package/packages/datadog-instrumentations/src/fastify.js +142 -82
- package/packages/datadog-instrumentations/src/graphql.js +188 -67
- package/packages/datadog-instrumentations/src/grpc/client.js +48 -32
- package/packages/datadog-instrumentations/src/helpers/ai-messages.js +322 -14
- package/packages/datadog-instrumentations/src/helpers/callback-instrumentor.js +1 -1
- package/packages/datadog-instrumentations/src/helpers/hooks.js +4 -0
- package/packages/datadog-instrumentations/src/helpers/instrument.js +2 -1
- package/packages/datadog-instrumentations/src/helpers/kafka.js +17 -0
- package/packages/datadog-instrumentations/src/helpers/openai-ai-guard.js +269 -0
- package/packages/datadog-instrumentations/src/helpers/promise-instrumentor.js +42 -0
- package/packages/datadog-instrumentations/src/helpers/register.js +1 -1
- package/packages/datadog-instrumentations/src/helpers/rewriter/compiler.js +3 -2
- package/packages/datadog-instrumentations/src/helpers/rewriter/index.js +19 -6
- package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/azure-cosmos.js +50 -0
- package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/index.js +2 -0
- package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/langgraph.js +4 -2
- package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/playwright.js +85 -0
- package/packages/datadog-instrumentations/src/helpers/rewriter/transforms.js +31 -229
- package/packages/datadog-instrumentations/src/hono.js +54 -3
- package/packages/datadog-instrumentations/src/http/client.js +2 -2
- package/packages/datadog-instrumentations/src/http/server.js +9 -4
- package/packages/datadog-instrumentations/src/ioredis.js +3 -3
- package/packages/datadog-instrumentations/src/jest/coverage-backfill.js +163 -0
- package/packages/datadog-instrumentations/src/jest.js +390 -183
- package/packages/datadog-instrumentations/src/kafkajs.js +140 -17
- package/packages/datadog-instrumentations/src/mariadb.js +1 -1
- package/packages/datadog-instrumentations/src/memcached.js +2 -1
- package/packages/datadog-instrumentations/src/mocha/main.js +399 -107
- package/packages/datadog-instrumentations/src/mocha/utils.js +48 -8
- package/packages/datadog-instrumentations/src/mongodb-core.js +1 -1
- package/packages/datadog-instrumentations/src/mongoose.js +10 -12
- package/packages/datadog-instrumentations/src/mysql.js +2 -2
- package/packages/datadog-instrumentations/src/mysql2.js +1 -1
- package/packages/datadog-instrumentations/src/nats.js +182 -0
- package/packages/datadog-instrumentations/src/nyc.js +38 -1
- package/packages/datadog-instrumentations/src/openai.js +33 -18
- package/packages/datadog-instrumentations/src/oracledb.js +6 -1
- package/packages/datadog-instrumentations/src/pg.js +1 -1
- package/packages/datadog-instrumentations/src/pino.js +17 -5
- package/packages/datadog-instrumentations/src/playwright.js +537 -297
- package/packages/datadog-instrumentations/src/router.js +80 -34
- package/packages/datadog-instrumentations/src/stripe.js +1 -1
- package/packages/datadog-instrumentations/src/vitest.js +246 -149
- package/packages/datadog-plugin-avsc/src/schema_iterator.js +1 -1
- package/packages/datadog-plugin-azure-cosmos/src/index.js +144 -0
- package/packages/datadog-plugin-azure-event-hubs/src/producer.js +1 -1
- package/packages/datadog-plugin-azure-functions/src/index.js +5 -2
- package/packages/datadog-plugin-azure-service-bus/src/producer.js +1 -1
- package/packages/datadog-plugin-bunyan/src/index.js +28 -0
- package/packages/datadog-plugin-cucumber/src/index.js +17 -3
- package/packages/datadog-plugin-cypress/src/cypress-plugin.js +223 -45
- package/packages/datadog-plugin-cypress/src/support.js +69 -1
- package/packages/datadog-plugin-dns/src/lookup.js +8 -6
- package/packages/datadog-plugin-elasticsearch/src/index.js +28 -8
- package/packages/datadog-plugin-google-cloud-pubsub/src/pubsub-push-subscription.js +1 -1
- package/packages/datadog-plugin-graphql/src/execute.js +2 -0
- package/packages/datadog-plugin-graphql/src/resolve.js +64 -67
- package/packages/datadog-plugin-graphql/src/utils.js +4 -1
- package/packages/datadog-plugin-http/src/server.js +40 -15
- package/packages/datadog-plugin-jest/src/index.js +11 -3
- package/packages/datadog-plugin-jest/src/util.js +15 -8
- package/packages/datadog-plugin-kafkajs/src/batch-consumer.js +1 -1
- package/packages/datadog-plugin-kafkajs/src/producer.js +35 -0
- package/packages/datadog-plugin-langgraph/src/stream.js +1 -1
- package/packages/datadog-plugin-mocha/src/index.js +19 -4
- package/packages/datadog-plugin-mongodb-core/src/index.js +311 -35
- package/packages/datadog-plugin-nats/src/consumer.js +43 -0
- package/packages/datadog-plugin-nats/src/index.js +20 -0
- package/packages/datadog-plugin-nats/src/producer.js +62 -0
- package/packages/datadog-plugin-nats/src/util.js +33 -0
- package/packages/datadog-plugin-next/src/index.js +5 -3
- package/packages/datadog-plugin-openai/src/tracing.js +15 -2
- package/packages/datadog-plugin-oracledb/src/index.js +13 -2
- package/packages/datadog-plugin-pino/src/index.js +42 -0
- package/packages/datadog-plugin-playwright/src/index.js +4 -4
- package/packages/datadog-plugin-protobufjs/src/schema_iterator.js +1 -1
- package/packages/datadog-plugin-redis/src/index.js +37 -2
- package/packages/datadog-plugin-rhea/src/producer.js +1 -1
- package/packages/datadog-plugin-router/src/index.js +33 -44
- package/packages/datadog-plugin-selenium/src/index.js +1 -1
- package/packages/datadog-plugin-undici/src/index.js +19 -0
- package/packages/datadog-plugin-vitest/src/index.js +24 -20
- package/packages/datadog-plugin-winston/src/index.js +30 -0
- package/packages/datadog-shimmer/src/shimmer.js +49 -21
- package/packages/dd-trace/src/aiguard/index.js +1 -1
- package/packages/dd-trace/src/aiguard/sdk.js +1 -1
- package/packages/dd-trace/src/appsec/api_security_sampler.js +1 -1
- package/packages/dd-trace/src/appsec/blocking.js +2 -2
- package/packages/dd-trace/src/appsec/index.js +11 -4
- package/packages/dd-trace/src/appsec/reporter.js +24 -11
- package/packages/dd-trace/src/appsec/sdk/user_blocking.js +1 -1
- package/packages/dd-trace/src/appsec/sdk/utils.js +1 -1
- package/packages/dd-trace/src/appsec/user_tracking.js +5 -4
- package/packages/dd-trace/src/baggage.js +7 -1
- package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +0 -1
- package/packages/dd-trace/src/ci-visibility/intelligent-test-runner/get-skippable-suites.js +25 -13
- package/packages/dd-trace/src/ci-visibility/requests/request.js +3 -1
- package/packages/dd-trace/src/ci-visibility/test-api-manual/test-api-manual-plugin.js +5 -3
- package/packages/dd-trace/src/ci-visibility/test-optimization-cache.js +70 -6
- package/packages/dd-trace/src/config/generated-config-types.d.ts +7 -2
- package/packages/dd-trace/src/config/supported-configurations.json +36 -8
- package/packages/dd-trace/src/crashtracking/crashtracker.js +15 -3
- package/packages/dd-trace/src/datastreams/context.js +4 -2
- package/packages/dd-trace/src/datastreams/writer.js +2 -4
- package/packages/dd-trace/src/debugger/devtools_client/condition.js +5 -8
- package/packages/dd-trace/src/encode/0.4.js +124 -108
- package/packages/dd-trace/src/encode/0.5.js +114 -26
- package/packages/dd-trace/src/encode/agentless-ci-visibility.js +57 -42
- package/packages/dd-trace/src/encode/agentless-json.js +4 -2
- package/packages/dd-trace/src/encode/coverage-ci-visibility.js +32 -13
- package/packages/dd-trace/src/encode/span-stats.js +16 -16
- package/packages/dd-trace/src/encode/tags-processors.js +16 -0
- package/packages/dd-trace/src/exporters/common/agents.js +3 -1
- package/packages/dd-trace/src/exporters/common/request.js +3 -1
- package/packages/dd-trace/src/id.js +17 -4
- package/packages/dd-trace/src/lambda/handler.js +2 -4
- package/packages/dd-trace/src/llmobs/plugins/ai/util.js +1 -1
- package/packages/dd-trace/src/llmobs/plugins/genai/index.js +1 -1
- package/packages/dd-trace/src/llmobs/plugins/langchain/handlers/index.js +1 -1
- package/packages/dd-trace/src/llmobs/plugins/langchain/index.js +9 -7
- package/packages/dd-trace/src/llmobs/plugins/langgraph/index.js +1 -1
- package/packages/dd-trace/src/llmobs/plugins/openai/index.js +1 -1
- package/packages/dd-trace/src/llmobs/sdk.js +10 -16
- package/packages/dd-trace/src/llmobs/span_processor.js +3 -3
- package/packages/dd-trace/src/llmobs/tagger.js +9 -1
- package/packages/dd-trace/src/llmobs/telemetry.js +1 -1
- package/packages/dd-trace/src/llmobs/util.js +66 -3
- package/packages/dd-trace/src/log/index.js +1 -1
- package/packages/dd-trace/src/log/writer.js +3 -1
- package/packages/dd-trace/src/msgpack/chunk.js +394 -10
- package/packages/dd-trace/src/msgpack/index.js +96 -2
- package/packages/dd-trace/src/noop/span.js +3 -1
- package/packages/dd-trace/src/openfeature/encoding.js +70 -0
- package/packages/dd-trace/src/openfeature/flagging_provider.js +20 -0
- package/packages/dd-trace/src/openfeature/span-enrichment-hook.js +143 -0
- package/packages/dd-trace/src/openfeature/span-enrichment.js +149 -0
- package/packages/dd-trace/src/openfeature/writers/exposures.js +51 -20
- package/packages/dd-trace/src/opentelemetry/metrics/periodic_metric_reader.js +1 -1
- package/packages/dd-trace/src/opentelemetry/span-helpers.js +4 -3
- package/packages/dd-trace/src/opentelemetry/span.js +1 -1
- package/packages/dd-trace/src/opentracing/propagation/log.js +18 -7
- package/packages/dd-trace/src/opentracing/propagation/text_map.js +62 -67
- package/packages/dd-trace/src/opentracing/span.js +59 -19
- package/packages/dd-trace/src/opentracing/span_context.js +49 -0
- package/packages/dd-trace/src/plugins/apollo.js +3 -1
- package/packages/dd-trace/src/plugins/ci_plugin.js +23 -33
- package/packages/dd-trace/src/plugins/database.js +7 -6
- package/packages/dd-trace/src/plugins/index.js +4 -0
- package/packages/dd-trace/src/plugins/log_injection.js +56 -0
- package/packages/dd-trace/src/plugins/log_plugin.js +3 -46
- package/packages/dd-trace/src/plugins/outbound.js +1 -1
- package/packages/dd-trace/src/plugins/plugin.js +15 -17
- package/packages/dd-trace/src/plugins/tracing.js +48 -8
- package/packages/dd-trace/src/plugins/util/git.js +3 -1
- package/packages/dd-trace/src/plugins/util/test.js +318 -13
- package/packages/dd-trace/src/plugins/util/web.js +89 -64
- package/packages/dd-trace/src/priority_sampler.js +2 -2
- package/packages/dd-trace/src/profiling/profiler.js +2 -2
- package/packages/dd-trace/src/profiling/profilers/wall.js +10 -4
- package/packages/dd-trace/src/sampling_rule.js +7 -7
- package/packages/dd-trace/src/scope.js +7 -5
- package/packages/dd-trace/src/service-naming/extra-services.js +14 -0
- package/packages/dd-trace/src/service-naming/schemas/v0/messaging.js +10 -0
- package/packages/dd-trace/src/service-naming/schemas/v1/messaging.js +8 -0
- package/packages/dd-trace/src/service-naming/source-resolver.js +46 -0
- package/packages/dd-trace/src/span_format.js +190 -58
- package/packages/dd-trace/src/spanleak.js +1 -1
- package/packages/dd-trace/src/standalone/index.js +3 -3
- package/packages/dd-trace/src/tagger.js +0 -2
- package/vendor/dist/@apm-js-collab/code-transformer/index.js +70 -39
- package/vendor/dist/@datadog/sketches-js/LICENSE +10 -36
- package/vendor/dist/@datadog/sketches-js/index.js +1 -1
- package/vendor/dist/protobufjs/index.js +1 -1
- package/vendor/dist/protobufjs/minimal/index.js +1 -1
- package/packages/dd-trace/src/msgpack/encoder.js +0 -308
- package/packages/dd-trace/src/plugins/structured_log_plugin.js +0 -9
- package/vendor/dist/opentracing/LICENSE +0 -201
- package/vendor/dist/opentracing/binary_carrier.d.ts +0 -11
- package/vendor/dist/opentracing/constants.d.ts +0 -61
- package/vendor/dist/opentracing/examples/demo/demo.d.ts +0 -2
- package/vendor/dist/opentracing/ext/tags.d.ts +0 -90
- package/vendor/dist/opentracing/functions.d.ts +0 -20
- package/vendor/dist/opentracing/global_tracer.d.ts +0 -14
- package/vendor/dist/opentracing/index.d.ts +0 -12
- package/vendor/dist/opentracing/index.js +0 -1
- package/vendor/dist/opentracing/mock_tracer/index.d.ts +0 -5
- package/vendor/dist/opentracing/mock_tracer/mock_context.d.ts +0 -13
- package/vendor/dist/opentracing/mock_tracer/mock_report.d.ts +0 -16
- package/vendor/dist/opentracing/mock_tracer/mock_span.d.ts +0 -50
- package/vendor/dist/opentracing/mock_tracer/mock_tracer.d.ts +0 -26
- package/vendor/dist/opentracing/noop.d.ts +0 -8
- package/vendor/dist/opentracing/reference.d.ts +0 -33
- package/vendor/dist/opentracing/span.d.ts +0 -147
- package/vendor/dist/opentracing/span_context.d.ts +0 -26
- package/vendor/dist/opentracing/test/api_compatibility.d.ts +0 -16
- package/vendor/dist/opentracing/test/mocktracer_implemenation.d.ts +0 -3
- package/vendor/dist/opentracing/test/noop_implementation.d.ts +0 -4
- package/vendor/dist/opentracing/test/opentracing_api.d.ts +0 -3
- package/vendor/dist/opentracing/test/unittest.d.ts +0 -2
- package/vendor/dist/opentracing/tracer.d.ts +0 -127
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const { channel } = require('dc-polyfill')
|
|
4
|
+
const log = require('../log')
|
|
5
|
+
const { SpanEnrichmentState } = require('./span-enrichment')
|
|
6
|
+
|
|
7
|
+
const finishCh = channel('dd-trace:span:finish')
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* OpenFeature hook that enriches APM spans with feature flag evaluation data.
|
|
11
|
+
*
|
|
12
|
+
* Implements the OpenFeature `finally` hook interface to capture flag evaluations
|
|
13
|
+
* and add span tags for observability. Tags are accumulated during the span's
|
|
14
|
+
* lifetime and applied when the root span finishes.
|
|
15
|
+
*
|
|
16
|
+
* Span tags added:
|
|
17
|
+
* - `ffe_flags_enc`: Base64 delta-varint encoded serial IDs
|
|
18
|
+
* - `ffe_subjects_enc`: JSON dict of SHA256(targeting_key) → encoded serial IDs
|
|
19
|
+
* - `ffe_runtime_defaults`: JSON dict of flag_key → default value string
|
|
20
|
+
*/
|
|
21
|
+
class SpanEnrichmentHook {
|
|
22
|
+
#tracer
|
|
23
|
+
/** @type {WeakMap<object, SpanEnrichmentState>} */
|
|
24
|
+
#spanStates = new WeakMap()
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Handler for span finish channel. Applies accumulated tags to the span.
|
|
28
|
+
* Arrow function to preserve `this` binding for channel subscription.
|
|
29
|
+
*
|
|
30
|
+
* @param {object} span - The span that is finishing
|
|
31
|
+
*/
|
|
32
|
+
#onSpanFinish = (span) => {
|
|
33
|
+
const state = this.#spanStates.get(span)
|
|
34
|
+
if (!state || !state.hasData()) return
|
|
35
|
+
|
|
36
|
+
try {
|
|
37
|
+
const tags = state.toSpanTags()
|
|
38
|
+
|
|
39
|
+
for (const [key, value] of Object.entries(tags)) {
|
|
40
|
+
if (value) {
|
|
41
|
+
span.setTag(key, value)
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
} catch (err) {
|
|
45
|
+
log.warn('SpanEnrichmentHook: error applying span tags: %s', err.message)
|
|
46
|
+
} finally {
|
|
47
|
+
this.#spanStates.delete(span)
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* @param {import('../tracer')} tracer - Datadog tracer instance
|
|
53
|
+
*/
|
|
54
|
+
constructor (tracer) {
|
|
55
|
+
this.#tracer = tracer
|
|
56
|
+
finishCh.subscribe(this.#onSpanFinish)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Called by the OpenFeature SDK after every flag evaluation (success or error).
|
|
61
|
+
*
|
|
62
|
+
* @param {object} hookContext - Hook context containing the flag key and evaluation context
|
|
63
|
+
* @param {string} hookContext.flagKey - The flag key being evaluated
|
|
64
|
+
* @param {object} [hookContext.context] - Evaluation context
|
|
65
|
+
* @param {string} [hookContext.context.targetingKey] - Targeting key
|
|
66
|
+
* @param {object} evaluationDetails - Full evaluation details including flag metadata
|
|
67
|
+
* @param {object} [evaluationDetails.flagMetadata] - Metadata from the provider
|
|
68
|
+
* @param {number} [evaluationDetails.flagMetadata.__dd_split_serial_id] - Serial ID from UFC split
|
|
69
|
+
* @param {boolean} [evaluationDetails.flagMetadata.__dd_do_log] - Whether to log subject
|
|
70
|
+
* @param {string} [evaluationDetails.variant] - Variant key if flag was found in UFC
|
|
71
|
+
* @param {boolean|string|number|object} [evaluationDetails.value] - Evaluated value
|
|
72
|
+
* @returns {void}
|
|
73
|
+
*/
|
|
74
|
+
finally (hookContext, evaluationDetails) {
|
|
75
|
+
try {
|
|
76
|
+
const rootSpan = this._getRootSpan()
|
|
77
|
+
if (!rootSpan) return
|
|
78
|
+
|
|
79
|
+
const state = this._getOrCreateState(rootSpan)
|
|
80
|
+
const { flagKey, context } = hookContext || {}
|
|
81
|
+
const { flagMetadata, variant, value } = evaluationDetails || {}
|
|
82
|
+
|
|
83
|
+
const serialId = flagMetadata?.__dd_split_serial_id
|
|
84
|
+
const doLog = flagMetadata?.__dd_do_log ?? false
|
|
85
|
+
const targetingKey = context?.targetingKey
|
|
86
|
+
|
|
87
|
+
if (serialId != null) {
|
|
88
|
+
state.addSerialId(serialId)
|
|
89
|
+
|
|
90
|
+
if (doLog && targetingKey) {
|
|
91
|
+
state.addSubject(targetingKey, serialId)
|
|
92
|
+
}
|
|
93
|
+
} else if (variant === undefined) {
|
|
94
|
+
state.addDefault(flagKey, value)
|
|
95
|
+
}
|
|
96
|
+
} catch (err) {
|
|
97
|
+
log.warn('SpanEnrichmentHook: error in finally hook: %s', err.message)
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Get the root span for the current trace context.
|
|
103
|
+
* The root span is always the first span in trace.started since spans
|
|
104
|
+
* are added in creation order and the root is created first.
|
|
105
|
+
*
|
|
106
|
+
* @returns {object|null} The root span, or null if no active span
|
|
107
|
+
* @private
|
|
108
|
+
*/
|
|
109
|
+
_getRootSpan () {
|
|
110
|
+
const span = this.#tracer.scope().active()
|
|
111
|
+
if (!span) return null
|
|
112
|
+
|
|
113
|
+
const trace = span.context()._trace
|
|
114
|
+
|
|
115
|
+
return trace?.started?.[0] ?? span
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Get or create enrichment state for a span.
|
|
120
|
+
*
|
|
121
|
+
* @param {object} span - The span to get state for
|
|
122
|
+
* @returns {SpanEnrichmentState} The enrichment state
|
|
123
|
+
* @private
|
|
124
|
+
*/
|
|
125
|
+
_getOrCreateState (span) {
|
|
126
|
+
let state = this.#spanStates.get(span)
|
|
127
|
+
if (!state) {
|
|
128
|
+
state = new SpanEnrichmentState()
|
|
129
|
+
this.#spanStates.set(span, state)
|
|
130
|
+
}
|
|
131
|
+
return state
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Cleanup method to unsubscribe from channels.
|
|
136
|
+
* Should be called when the provider is shut down.
|
|
137
|
+
*/
|
|
138
|
+
destroy () {
|
|
139
|
+
finishCh.unsubscribe(this.#onSpanFinish)
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
module.exports = SpanEnrichmentHook
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const log = require('../log')
|
|
4
|
+
|
|
5
|
+
const { encodeDeltaVarint, hashTargetingKey } = require('./encoding')
|
|
6
|
+
|
|
7
|
+
const MAX_SERIAL_IDS = 200
|
|
8
|
+
const MAX_SUBJECTS = 10
|
|
9
|
+
const MAX_EXPERIMENTS_PER_SUBJECT = 20
|
|
10
|
+
const MAX_DEFAULTS = 5
|
|
11
|
+
const MAX_DEFAULT_VALUE_LENGTH = 64
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Manages feature flag enrichment state for a single root span.
|
|
15
|
+
* Accumulates serial IDs, subjects, and defaults throughout the span's lifetime.
|
|
16
|
+
*/
|
|
17
|
+
class SpanEnrichmentState {
|
|
18
|
+
constructor () {
|
|
19
|
+
/** @type {Set<number>} */
|
|
20
|
+
this._serialIds = new Set()
|
|
21
|
+
|
|
22
|
+
/** @type {Map<string, Set<number>>} hashed targeting key -> serial IDs */
|
|
23
|
+
this._subjects = new Map()
|
|
24
|
+
|
|
25
|
+
/** @type {Map<string, string>} flag key -> runtime default value */
|
|
26
|
+
this._defaults = new Map()
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Add a serial ID from a flag evaluation.
|
|
31
|
+
*
|
|
32
|
+
* @param {number} serialId - The serial ID to add
|
|
33
|
+
* @returns {boolean} True if added, false if limit reached
|
|
34
|
+
*/
|
|
35
|
+
addSerialId (serialId) {
|
|
36
|
+
if (this._serialIds.size >= MAX_SERIAL_IDS) {
|
|
37
|
+
log.debug('SpanEnrichment: MAX_SERIAL_IDS limit (%d) reached, dropping serialId %d', MAX_SERIAL_IDS, serialId)
|
|
38
|
+
return false
|
|
39
|
+
}
|
|
40
|
+
this._serialIds.add(serialId)
|
|
41
|
+
return true
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Add a subject (targeting key) with its associated serial ID.
|
|
46
|
+
* Only called when doLog=true.
|
|
47
|
+
*
|
|
48
|
+
* @param {string} targetingKey - The targeting key (will be hashed)
|
|
49
|
+
* @param {number} serialId - The serial ID associated with this evaluation
|
|
50
|
+
* @returns {boolean} True if added, false if limit reached
|
|
51
|
+
*/
|
|
52
|
+
addSubject (targetingKey, serialId) {
|
|
53
|
+
const hashedKey = hashTargetingKey(targetingKey)
|
|
54
|
+
|
|
55
|
+
if (this._subjects.has(hashedKey)) {
|
|
56
|
+
const subjectIds = this._subjects.get(hashedKey)
|
|
57
|
+
if (subjectIds.size >= MAX_EXPERIMENTS_PER_SUBJECT) {
|
|
58
|
+
log.debug('SpanEnrichment: MAX_EXPERIMENTS_PER_SUBJECT limit (%d) reached for subject',
|
|
59
|
+
MAX_EXPERIMENTS_PER_SUBJECT)
|
|
60
|
+
return false
|
|
61
|
+
}
|
|
62
|
+
subjectIds.add(serialId)
|
|
63
|
+
return true
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (this._subjects.size >= MAX_SUBJECTS) {
|
|
67
|
+
log.debug('SpanEnrichment: MAX_SUBJECTS limit (%d) reached, dropping subject', MAX_SUBJECTS)
|
|
68
|
+
return false
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
this._subjects.set(hashedKey, new Set([serialId]))
|
|
72
|
+
return true
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Add a default fallback for a flag not found in UFC.
|
|
77
|
+
*
|
|
78
|
+
* @param {string} flagKey - The flag key
|
|
79
|
+
* @param {boolean|string|number|object} defaultValue - The default value used
|
|
80
|
+
* @returns {boolean} True if added, false if limit reached
|
|
81
|
+
*/
|
|
82
|
+
addDefault (flagKey, defaultValue) {
|
|
83
|
+
if (this._defaults.has(flagKey)) {
|
|
84
|
+
return true
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
if (this._defaults.size >= MAX_DEFAULTS) {
|
|
88
|
+
log.debug('SpanEnrichment: MAX_DEFAULTS limit (%d) reached, dropping flag %s', MAX_DEFAULTS, flagKey)
|
|
89
|
+
return false
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
let valueStr = typeof defaultValue === 'object' && defaultValue !== null
|
|
93
|
+
? JSON.stringify(defaultValue)
|
|
94
|
+
: String(defaultValue)
|
|
95
|
+
|
|
96
|
+
if (valueStr.length > MAX_DEFAULT_VALUE_LENGTH) {
|
|
97
|
+
valueStr = valueStr.slice(0, MAX_DEFAULT_VALUE_LENGTH)
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
this._defaults.set(flagKey, valueStr)
|
|
101
|
+
return true
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Check if there is any enrichment data to add to the span.
|
|
106
|
+
* Note: _subjects is not checked because addSubject() is never called without first
|
|
107
|
+
* calling addSerialId(), so _subjects having data necessitates _serialIds having data.
|
|
108
|
+
*
|
|
109
|
+
* @returns {boolean} True if there is data to add
|
|
110
|
+
*/
|
|
111
|
+
hasData () {
|
|
112
|
+
return this._serialIds.size > 0 || this._defaults.size > 0
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Convert accumulated state to span tags.
|
|
117
|
+
*
|
|
118
|
+
* @returns {object} Object with ffe_flags_enc, ffe_subjects_enc, and ffe_runtime_defaults tags
|
|
119
|
+
*/
|
|
120
|
+
toSpanTags () {
|
|
121
|
+
const tags = {}
|
|
122
|
+
|
|
123
|
+
if (this._serialIds.size > 0) {
|
|
124
|
+
tags.ffe_flags_enc = encodeDeltaVarint(this._serialIds)
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
if (this._subjects.size > 0) {
|
|
128
|
+
const subjectsObj = Object.fromEntries(
|
|
129
|
+
[...this._subjects].map(([key, ids]) => [key, encodeDeltaVarint(ids)])
|
|
130
|
+
)
|
|
131
|
+
tags.ffe_subjects_enc = JSON.stringify(subjectsObj)
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
if (this._defaults.size > 0) {
|
|
135
|
+
tags.ffe_runtime_defaults = JSON.stringify(Object.fromEntries(this._defaults))
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
return tags
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
module.exports = {
|
|
143
|
+
SpanEnrichmentState,
|
|
144
|
+
MAX_SERIAL_IDS,
|
|
145
|
+
MAX_SUBJECTS,
|
|
146
|
+
MAX_EXPERIMENTS_PER_SUBJECT,
|
|
147
|
+
MAX_DEFAULTS,
|
|
148
|
+
MAX_DEFAULT_VALUE_LENGTH,
|
|
149
|
+
}
|
|
@@ -8,8 +8,14 @@ const {
|
|
|
8
8
|
EVP_PAYLOAD_SIZE_LIMIT,
|
|
9
9
|
EVP_EVENT_SIZE_LIMIT,
|
|
10
10
|
} = require('../constants/constants')
|
|
11
|
+
const log = require('../../log')
|
|
11
12
|
const BaseFFEWriter = require('./base')
|
|
12
13
|
|
|
14
|
+
// Disabled-state cap. Drops invalidate experiment results because the provider's
|
|
15
|
+
// exposure dedupe cache keeps masking dropped events after recovery. The first
|
|
16
|
+
// drop emits a warning and `droppedEventCount` accumulates the cumulative loss.
|
|
17
|
+
const PENDING_MAX_EVENTS = 1000
|
|
18
|
+
|
|
13
19
|
/**
|
|
14
20
|
* @typedef {object} ExposureEvent
|
|
15
21
|
* @property {number} timestamp - Unix timestamp in milliseconds
|
|
@@ -42,11 +48,21 @@ const BaseFFEWriter = require('./base')
|
|
|
42
48
|
* ExposuresWriter is responsible for sending exposure events to the Datadog Agent.
|
|
43
49
|
*/
|
|
44
50
|
class ExposuresWriter extends BaseFFEWriter {
|
|
51
|
+
// Disabled until the agent strategy probe resolves.
|
|
52
|
+
#enabled = false
|
|
53
|
+
|
|
54
|
+
/** @type {ExposureEvent[]} */
|
|
55
|
+
#pendingEvents = []
|
|
56
|
+
|
|
57
|
+
/** @type {ExposureContext} */
|
|
58
|
+
#context
|
|
59
|
+
|
|
60
|
+
#dropWarned = false
|
|
61
|
+
|
|
45
62
|
/**
|
|
46
63
|
* @param {import('../../config/config-base')} config - Tracer configuration object
|
|
47
64
|
*/
|
|
48
65
|
constructor (config) {
|
|
49
|
-
// Build full EVP endpoint path
|
|
50
66
|
const basePath = EVP_PROXY_AGENT_BASE_PATH.replace(/\/+$/, '')
|
|
51
67
|
const endpoint = EXPOSURES_ENDPOINT.replace(/^\/+/, '')
|
|
52
68
|
const fullEndpoint = `${basePath}/${endpoint}`
|
|
@@ -60,33 +76,33 @@ class ExposuresWriter extends BaseFFEWriter {
|
|
|
60
76
|
[EVP_SUBDOMAIN_HEADER_NAME]: EVP_SUBDOMAIN_VALUE,
|
|
61
77
|
},
|
|
62
78
|
})
|
|
63
|
-
this._enabled = false // Start disabled until agent strategy is set
|
|
64
|
-
this._pendingEvents = [] // Buffer events until enabled
|
|
65
79
|
|
|
80
|
+
/** @type {ExposureContext} */
|
|
66
81
|
const context = {
|
|
67
82
|
service: config.service,
|
|
68
83
|
}
|
|
69
|
-
|
|
84
|
+
|
|
70
85
|
if (config.version !== undefined) {
|
|
71
86
|
context.version = config.version
|
|
72
87
|
}
|
|
88
|
+
|
|
73
89
|
if (config.env !== undefined) {
|
|
74
90
|
context.env = config.env
|
|
75
91
|
}
|
|
76
92
|
|
|
77
|
-
this
|
|
93
|
+
this.#context = context
|
|
78
94
|
}
|
|
79
95
|
|
|
80
96
|
/**
|
|
81
97
|
* @param {boolean} enabled - Whether to enable the writer
|
|
82
98
|
*/
|
|
83
99
|
setEnabled (enabled) {
|
|
84
|
-
this
|
|
100
|
+
this.#enabled = enabled
|
|
85
101
|
|
|
86
|
-
if (enabled && this.
|
|
102
|
+
if (enabled && this.#pendingEvents.length > 0) {
|
|
87
103
|
// Flush all pending events as a batch
|
|
88
|
-
super.append(this
|
|
89
|
-
this
|
|
104
|
+
super.append(this.#pendingEvents)
|
|
105
|
+
this.#pendingEvents = []
|
|
90
106
|
}
|
|
91
107
|
}
|
|
92
108
|
|
|
@@ -95,24 +111,38 @@ class ExposuresWriter extends BaseFFEWriter {
|
|
|
95
111
|
* @param {ExposureEvent|ExposureEvent[]} events - Exposure event(s) to append
|
|
96
112
|
*/
|
|
97
113
|
append (events) {
|
|
98
|
-
if (
|
|
99
|
-
|
|
100
|
-
if (Array.isArray(events)) {
|
|
101
|
-
this._pendingEvents.push(...events)
|
|
102
|
-
} else {
|
|
103
|
-
this._pendingEvents.push(events)
|
|
104
|
-
}
|
|
114
|
+
if (this.#enabled) {
|
|
115
|
+
super.append(events)
|
|
105
116
|
return
|
|
106
117
|
}
|
|
107
|
-
|
|
118
|
+
|
|
119
|
+
const eventArray = Array.isArray(events) ? events : [events]
|
|
120
|
+
this.#pendingEvents.push(...eventArray)
|
|
121
|
+
if (this.#pendingEvents.length > PENDING_MAX_EVENTS) {
|
|
122
|
+
const dropped = this.#pendingEvents.length - PENDING_MAX_EVENTS
|
|
123
|
+
this.#pendingEvents.splice(0, dropped)
|
|
124
|
+
this._droppedEvents += dropped
|
|
125
|
+
if (!this.#dropWarned) {
|
|
126
|
+
this.#dropWarned = true
|
|
127
|
+
log.warn(
|
|
128
|
+
'%s dropped exposure event(s) at cap %d. This may invalidate experiment results.',
|
|
129
|
+
this.constructor.name, PENDING_MAX_EVENTS)
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* @returns {number} Cumulative number of exposure events dropped due to buffer overflow.
|
|
136
|
+
*/
|
|
137
|
+
get droppedEventCount () {
|
|
138
|
+
return this._droppedEvents
|
|
108
139
|
}
|
|
109
140
|
|
|
110
141
|
/**
|
|
111
142
|
* Flushes buffered exposure events to the agent
|
|
112
143
|
*/
|
|
113
144
|
flush () {
|
|
114
|
-
if (!this
|
|
115
|
-
// Don't flush when disabled
|
|
145
|
+
if (!this.#enabled) {
|
|
116
146
|
return
|
|
117
147
|
}
|
|
118
148
|
super.flush()
|
|
@@ -125,6 +155,7 @@ class ExposuresWriter extends BaseFFEWriter {
|
|
|
125
155
|
*/
|
|
126
156
|
makePayload (events) {
|
|
127
157
|
const formattedEvents = events.map(event => {
|
|
158
|
+
/** @type {ExposureEvent} */
|
|
128
159
|
return {
|
|
129
160
|
timestamp: event.timestamp || Date.now(),
|
|
130
161
|
allocation: {
|
|
@@ -145,7 +176,7 @@ class ExposuresWriter extends BaseFFEWriter {
|
|
|
145
176
|
})
|
|
146
177
|
|
|
147
178
|
return {
|
|
148
|
-
context: this
|
|
179
|
+
context: this.#context,
|
|
149
180
|
exposures: formattedEvents,
|
|
150
181
|
}
|
|
151
182
|
}
|
|
@@ -7,6 +7,7 @@ const { timeInputToHrTime } = require('../../../../vendor/dist/@opentelemetry/co
|
|
|
7
7
|
const { ERROR_MESSAGE, ERROR_STACK, ERROR_TYPE, IGNORE_OTEL_ERROR } = require('../constants')
|
|
8
8
|
const DatadogSpanContext = require('../opentracing/span_context')
|
|
9
9
|
const TraceState = require('../opentracing/propagation/tracestate')
|
|
10
|
+
const { DD_MAJOR } = require('../../../../version')
|
|
10
11
|
|
|
11
12
|
const id = require('../id')
|
|
12
13
|
|
|
@@ -176,8 +177,8 @@ function setOtelAttributes (ddSpan, attributes) {
|
|
|
176
177
|
function addOtelLink (ddSpan, link, attrs) {
|
|
177
178
|
if (!isWritable(ddSpan) || !link) return
|
|
178
179
|
|
|
179
|
-
//
|
|
180
|
-
const { context, attributes } = isOtelLink(link)
|
|
180
|
+
// v5 still accepts the legacy `addLink(context, attrs)` shape; v6 only takes `addLink(otel.Link)`.
|
|
181
|
+
const { context, attributes } = isOtelLink(link) || DD_MAJOR >= 6
|
|
181
182
|
? link
|
|
182
183
|
: { context: link, attributes: attrs ?? {} }
|
|
183
184
|
|
|
@@ -230,7 +231,7 @@ function recordException (ddSpan, exception, timeInput) {
|
|
|
230
231
|
[ERROR_TYPE]: exception.name,
|
|
231
232
|
[ERROR_MESSAGE]: exception.message,
|
|
232
233
|
[ERROR_STACK]: exception.stack,
|
|
233
|
-
[IGNORE_OTEL_ERROR]: ddSpan.context().
|
|
234
|
+
[IGNORE_OTEL_ERROR]: ddSpan.context().getTag(IGNORE_OTEL_ERROR) ?? true,
|
|
234
235
|
})
|
|
235
236
|
|
|
236
237
|
const attributes = {}
|
|
@@ -11,20 +11,31 @@ class LogPropagator {
|
|
|
11
11
|
inject (spanContext, carrier) {
|
|
12
12
|
if (!carrier) return
|
|
13
13
|
|
|
14
|
-
|
|
14
|
+
const dd = {}
|
|
15
|
+
let hasField = false
|
|
15
16
|
|
|
16
17
|
if (spanContext) {
|
|
17
|
-
|
|
18
|
+
dd.trace_id = this._config.traceId128BitGenerationEnabled &&
|
|
18
19
|
this._config.traceId128BitLoggingEnabled && spanContext._trace.tags['_dd.p.tid']
|
|
19
20
|
? spanContext.toTraceId(true)
|
|
20
21
|
: spanContext.toTraceId()
|
|
21
|
-
|
|
22
|
-
|
|
22
|
+
dd.span_id = spanContext.toSpanId()
|
|
23
|
+
hasField = true
|
|
24
|
+
}
|
|
25
|
+
if (this._config.service) {
|
|
26
|
+
dd.service = this._config.service
|
|
27
|
+
hasField = true
|
|
28
|
+
}
|
|
29
|
+
if (this._config.version) {
|
|
30
|
+
dd.version = this._config.version
|
|
31
|
+
hasField = true
|
|
32
|
+
}
|
|
33
|
+
if (this._config.env) {
|
|
34
|
+
dd.env = this._config.env
|
|
35
|
+
hasField = true
|
|
23
36
|
}
|
|
24
37
|
|
|
25
|
-
if (
|
|
26
|
-
if (this._config.version) carrier.dd.version = this._config.version
|
|
27
|
-
if (this._config.env) carrier.dd.env = this._config.env
|
|
38
|
+
if (hasField) carrier.dd = dd
|
|
28
39
|
}
|
|
29
40
|
|
|
30
41
|
extract (carrier) {
|