dd-trace 5.71.0 → 5.73.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 +7 -0
- package/index.d.ts +114 -1
- package/package.json +25 -4
- package/packages/datadog-esbuild/index.js +8 -0
- package/packages/datadog-instrumentations/src/azure-event-hubs.js +37 -0
- package/packages/datadog-instrumentations/src/azure-functions.js +3 -0
- package/packages/datadog-instrumentations/src/azure-service-bus.js +49 -22
- package/packages/datadog-instrumentations/src/cookie-parser.js +2 -0
- package/packages/datadog-instrumentations/src/cucumber.js +7 -7
- package/packages/datadog-instrumentations/src/helpers/hooks.js +1 -0
- package/packages/datadog-instrumentations/src/jest.js +85 -47
- package/packages/datadog-instrumentations/src/mocha/main.js +8 -9
- package/packages/datadog-instrumentations/src/mocha/utils.js +4 -5
- package/packages/datadog-instrumentations/src/mocha/worker.js +2 -2
- package/packages/datadog-instrumentations/src/pg.js +1 -1
- package/packages/datadog-instrumentations/src/playwright.js +5 -5
- package/packages/datadog-instrumentations/src/vitest.js +8 -8
- package/packages/datadog-plugin-amqplib/src/consumer.js +1 -1
- package/packages/datadog-plugin-aws-sdk/src/services/bedrockruntime/utils.js +73 -27
- package/packages/datadog-plugin-azure-event-hubs/src/index.js +15 -0
- package/packages/datadog-plugin-azure-event-hubs/src/producer.js +82 -0
- package/packages/datadog-plugin-azure-functions/src/index.js +50 -3
- package/packages/datadog-plugin-azure-service-bus/src/index.js +1 -1
- package/packages/datadog-plugin-azure-service-bus/src/producer.js +60 -12
- package/packages/datadog-plugin-cucumber/src/index.js +3 -3
- package/packages/datadog-plugin-cypress/src/cypress-plugin.js +9 -9
- package/packages/datadog-plugin-jest/src/index.js +53 -18
- package/packages/datadog-plugin-jest/src/util.js +10 -2
- package/packages/datadog-plugin-mocha/src/index.js +2 -2
- package/packages/datadog-plugin-playwright/src/index.js +2 -2
- package/packages/datadog-plugin-vitest/src/index.js +2 -2
- package/packages/datadog-plugin-ws/src/close.js +1 -1
- package/packages/datadog-plugin-ws/src/producer.js +1 -1
- package/packages/datadog-plugin-ws/src/receiver.js +1 -1
- package/packages/datadog-plugin-ws/src/server.js +5 -3
- package/packages/dd-trace/src/appsec/index.js +9 -1
- package/packages/dd-trace/src/appsec/reporter.js +2 -3
- package/packages/dd-trace/src/ci-visibility/test-management/get-test-management-tests.js +5 -0
- package/packages/dd-trace/src/config.js +108 -26
- package/packages/dd-trace/src/config_defaults.js +12 -0
- package/packages/dd-trace/src/git_properties.js +90 -5
- package/packages/dd-trace/src/llmobs/plugins/ai/util.js +8 -3
- package/packages/dd-trace/src/llmobs/plugins/base.js +11 -12
- package/packages/dd-trace/src/llmobs/sdk.js +20 -4
- package/packages/dd-trace/src/llmobs/tagger.js +12 -0
- package/packages/dd-trace/src/noop/proxy.js +3 -0
- package/packages/dd-trace/src/openfeature/constants/constants.js +51 -0
- package/packages/dd-trace/src/openfeature/flagging_provider.js +45 -0
- package/packages/dd-trace/src/openfeature/index.js +77 -0
- package/packages/dd-trace/src/openfeature/noop.js +101 -0
- package/packages/dd-trace/src/openfeature/writers/base.js +181 -0
- package/packages/dd-trace/src/openfeature/writers/exposures.js +173 -0
- package/packages/dd-trace/src/openfeature/writers/util.js +43 -0
- package/packages/dd-trace/src/opentelemetry/logs/batch_log_processor.js +100 -0
- package/packages/dd-trace/src/opentelemetry/logs/index.js +87 -0
- package/packages/dd-trace/src/opentelemetry/logs/logger.js +77 -0
- package/packages/dd-trace/src/opentelemetry/logs/logger_provider.js +126 -0
- package/packages/dd-trace/src/opentelemetry/logs/otlp_http_log_exporter.js +53 -0
- package/packages/dd-trace/src/opentelemetry/logs/otlp_transformer.js +252 -0
- package/packages/dd-trace/src/opentelemetry/otlp/common.proto +116 -0
- package/packages/dd-trace/src/opentelemetry/otlp/logs.proto +226 -0
- package/packages/dd-trace/src/opentelemetry/otlp/logs_service.proto +78 -0
- package/packages/dd-trace/src/opentelemetry/otlp/metrics.proto +720 -0
- package/packages/dd-trace/src/opentelemetry/otlp/metrics_service.proto +78 -0
- package/packages/dd-trace/src/opentelemetry/otlp/otlp_http_exporter_base.js +177 -0
- package/packages/dd-trace/src/opentelemetry/otlp/otlp_transformer_base.js +163 -0
- package/packages/dd-trace/src/opentelemetry/otlp/protobuf_loader.js +66 -0
- package/packages/dd-trace/src/opentelemetry/otlp/resource.proto +45 -0
- package/packages/dd-trace/src/plugins/ci_plugin.js +7 -6
- package/packages/dd-trace/src/plugins/index.js +1 -0
- package/packages/dd-trace/src/plugins/util/test.js +6 -5
- package/packages/dd-trace/src/profiling/config.js +21 -1
- package/packages/dd-trace/src/profiling/exporters/event_serializer.js +3 -2
- package/packages/dd-trace/src/profiling/profiler.js +44 -22
- package/packages/dd-trace/src/profiling/profilers/events.js +12 -3
- package/packages/dd-trace/src/profiling/profilers/space.js +35 -24
- package/packages/dd-trace/src/profiling/profilers/wall.js +14 -6
- package/packages/dd-trace/src/proxy.js +22 -1
- package/packages/dd-trace/src/remote_config/capabilities.js +1 -0
- package/packages/dd-trace/src/remote_config/index.js +1 -0
- package/packages/dd-trace/src/service-naming/schemas/v0/messaging.js +4 -0
- package/packages/dd-trace/src/service-naming/schemas/v1/messaging.js +8 -0
- package/packages/dd-trace/src/supported-configurations.json +18 -0
- package/packages/dd-trace/src/telemetry/telemetry.js +13 -1
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const log = require('../log')
|
|
4
|
+
const ExposuresWriter = require('./writers/exposures')
|
|
5
|
+
const { setAgentStrategy } = require('./writers/util')
|
|
6
|
+
const { channel } = require('dc-polyfill')
|
|
7
|
+
|
|
8
|
+
const exposureSubmitCh = channel('ffe:exposure:submit')
|
|
9
|
+
const flushCh = channel('ffe:writers:flush')
|
|
10
|
+
|
|
11
|
+
let exposuresWriter = null
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* @private
|
|
15
|
+
* @param {Object|Array<Object>} exposureEvents - Exposure events channel subscriber
|
|
16
|
+
* @returns {void}
|
|
17
|
+
*/
|
|
18
|
+
function _handleExposureSubmit (exposureEvents) {
|
|
19
|
+
if (!exposuresWriter) return
|
|
20
|
+
exposuresWriter.append(exposureEvents)
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Channel subscriber for manually flushing the exposures writer
|
|
25
|
+
* @private
|
|
26
|
+
* @returns {void}
|
|
27
|
+
*/
|
|
28
|
+
function _handleFlush () {
|
|
29
|
+
exposuresWriter?.flush()
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Enables the OpenFeature module and sets up FF&E writer and channel subscribers
|
|
34
|
+
* @param {import('../config')} config - Tracer configuration object
|
|
35
|
+
* @returns {void}
|
|
36
|
+
*/
|
|
37
|
+
function enable (config) {
|
|
38
|
+
if (exposuresWriter) {
|
|
39
|
+
log.warn(exposuresWriter.constructor.name + ' already enabled')
|
|
40
|
+
return
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
exposuresWriter = new ExposuresWriter(config)
|
|
44
|
+
exposureSubmitCh.subscribe(_handleExposureSubmit)
|
|
45
|
+
flushCh.subscribe(_handleFlush)
|
|
46
|
+
|
|
47
|
+
setAgentStrategy(config, hasAgent => {
|
|
48
|
+
exposuresWriter?.setEnabled(hasAgent)
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
log.debug('OpenFeature module enabled')
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Disables the OpenFeature module and cleans up resources
|
|
56
|
+
* @returns {void}
|
|
57
|
+
*/
|
|
58
|
+
function disable () {
|
|
59
|
+
if (!exposuresWriter) return
|
|
60
|
+
|
|
61
|
+
if (exposureSubmitCh.hasSubscribers) {
|
|
62
|
+
exposureSubmitCh.unsubscribe(_handleExposureSubmit)
|
|
63
|
+
}
|
|
64
|
+
if (flushCh.hasSubscribers) {
|
|
65
|
+
flushCh.unsubscribe(_handleFlush)
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
exposuresWriter.destroy?.()
|
|
69
|
+
exposuresWriter = null
|
|
70
|
+
|
|
71
|
+
log.debug('OpenFeature module disabled')
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
module.exports = {
|
|
75
|
+
enable,
|
|
76
|
+
disable
|
|
77
|
+
}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const { NOOP_REASON } = require('./constants/constants')
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* No-op implementation of OpenFeature provider that always returns default values.
|
|
7
|
+
* Used when the OpenFeature provider is not initialized or disabled.
|
|
8
|
+
* https://openfeature.dev/docs/reference/concepts/provider/
|
|
9
|
+
*/
|
|
10
|
+
class NoopFlaggingProvider {
|
|
11
|
+
/**
|
|
12
|
+
* @param {Object} [noopTracer] - Optional noop tracer instance
|
|
13
|
+
*/
|
|
14
|
+
constructor (noopTracer) {
|
|
15
|
+
this._tracer = noopTracer
|
|
16
|
+
this._config = {}
|
|
17
|
+
this.metadata = { name: 'NoopFlaggingProvider' }
|
|
18
|
+
this.status = 'NOT_READY'
|
|
19
|
+
this.runsOn = 'server'
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* @param {string} flagKey - Flag key
|
|
24
|
+
* @param {boolean} defaultValue - Default value to return
|
|
25
|
+
* @param {Object} context - Evaluation context
|
|
26
|
+
* @param {Object} logger - Logger instance
|
|
27
|
+
* @returns {Promise<{value: boolean, reason: string}>} Resolution details
|
|
28
|
+
*/
|
|
29
|
+
resolveBooleanEvaluation (flagKey, defaultValue, context, logger) {
|
|
30
|
+
return Promise.resolve({
|
|
31
|
+
value: defaultValue,
|
|
32
|
+
reason: NOOP_REASON
|
|
33
|
+
})
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* @param {string} flagKey - Flag key
|
|
38
|
+
* @param {string} defaultValue - Default value to return
|
|
39
|
+
* @param {Object} context - Evaluation context
|
|
40
|
+
* @param {Object} logger - Logger instance
|
|
41
|
+
* @returns {Promise<{value: string, reason: string}>} Resolution details
|
|
42
|
+
*/
|
|
43
|
+
resolveStringEvaluation (flagKey, defaultValue, context, logger) {
|
|
44
|
+
return Promise.resolve({
|
|
45
|
+
value: defaultValue,
|
|
46
|
+
reason: NOOP_REASON
|
|
47
|
+
})
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* @param {string} flagKey - Flag key
|
|
52
|
+
* @param {number} defaultValue - Default value to return
|
|
53
|
+
* @param {Object} context - Evaluation context
|
|
54
|
+
* @param {Object} logger - Logger instance
|
|
55
|
+
* @returns {Promise<{value: number, reason: string}>} Resolution details
|
|
56
|
+
*/
|
|
57
|
+
resolveNumberEvaluation (flagKey, defaultValue, context, logger) {
|
|
58
|
+
return Promise.resolve({
|
|
59
|
+
value: defaultValue,
|
|
60
|
+
reason: NOOP_REASON,
|
|
61
|
+
})
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* @param {string} flagKey - Flag key
|
|
66
|
+
* @param {Object} defaultValue - Default value to return
|
|
67
|
+
* @param {Object} context - Evaluation context
|
|
68
|
+
* @param {Object} logger - Logger instance
|
|
69
|
+
* @returns {Promise<{value: Object, reason: string}>} Resolution details
|
|
70
|
+
*/
|
|
71
|
+
resolveObjectEvaluation (flagKey, defaultValue, context, logger) {
|
|
72
|
+
return Promise.resolve({
|
|
73
|
+
value: defaultValue,
|
|
74
|
+
reason: NOOP_REASON
|
|
75
|
+
})
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* @returns {Object} Current configuration
|
|
80
|
+
*/
|
|
81
|
+
getConfiguration () {
|
|
82
|
+
return this._config
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* @param {Object} config - Configuration to set
|
|
87
|
+
*/
|
|
88
|
+
setConfiguration (config) {
|
|
89
|
+
this._config = config
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* @internal
|
|
94
|
+
* @param {Object} ufc - Universal Flag Configuration object
|
|
95
|
+
*/
|
|
96
|
+
_setConfiguration (ufc) {
|
|
97
|
+
this.setConfiguration(ufc)
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
module.exports = NoopFlaggingProvider
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const request = require('../../exporters/common/request')
|
|
4
|
+
const { safeJSONStringify } = require('../../exporters/common/util')
|
|
5
|
+
const { URL, format } = require('node:url')
|
|
6
|
+
|
|
7
|
+
const log = require('../../log')
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* @typedef {Object} BaseFFEWriterOptions
|
|
11
|
+
* @property {number} [interval] - Flush interval in milliseconds
|
|
12
|
+
* @property {number} [timeout] - Request timeout in milliseconds
|
|
13
|
+
* @property {Object} config - Tracer configuration object
|
|
14
|
+
* @property {string} endpoint - API endpoint path
|
|
15
|
+
* @property {URL} [agentUrl] - Base URL for the agent
|
|
16
|
+
* @property {number} [payloadSizeLimit] - Maximum payload size in bytes
|
|
17
|
+
* @property {number} [eventSizeLimit] - Maximum individual event size in bytes
|
|
18
|
+
* @property {Object} [headers] - Additional HTTP headers
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* BaseFFEWriter is the base class for sending Feature Flagging & Exposure Events payloads to the Datadog Agent.
|
|
23
|
+
* @class BaseFFEWriter
|
|
24
|
+
*/
|
|
25
|
+
class BaseFFEWriter {
|
|
26
|
+
/**
|
|
27
|
+
* @param {BaseFFEWriterOptions} options - Writer configuration options
|
|
28
|
+
*/
|
|
29
|
+
constructor ({ interval, timeout, config, endpoint, agentUrl, payloadSizeLimit, eventSizeLimit, headers }) {
|
|
30
|
+
this._interval = interval ?? 1000
|
|
31
|
+
this._timeout = timeout ?? 5000
|
|
32
|
+
|
|
33
|
+
this._buffer = []
|
|
34
|
+
this._bufferLimit = 1000
|
|
35
|
+
this._bufferSize = 0
|
|
36
|
+
|
|
37
|
+
this._config = config
|
|
38
|
+
this._endpoint = endpoint
|
|
39
|
+
this._baseUrl = agentUrl ?? this._getAgentUrl()
|
|
40
|
+
this._payloadSizeLimit = payloadSizeLimit
|
|
41
|
+
this._eventSizeLimit = eventSizeLimit
|
|
42
|
+
this._headers = headers || {}
|
|
43
|
+
|
|
44
|
+
this._requestOptions = {
|
|
45
|
+
headers: {
|
|
46
|
+
...this._headers,
|
|
47
|
+
'Content-Type': 'application/json'
|
|
48
|
+
},
|
|
49
|
+
method: 'POST',
|
|
50
|
+
timeout: this._timeout,
|
|
51
|
+
url: this._baseUrl,
|
|
52
|
+
path: this._endpoint
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
this._periodic = setInterval(() => {
|
|
56
|
+
this.flush()
|
|
57
|
+
}, this._interval).unref()
|
|
58
|
+
|
|
59
|
+
this._beforeExitHandler = () => {
|
|
60
|
+
this.destroy()
|
|
61
|
+
}
|
|
62
|
+
process.once('beforeExit', this._beforeExitHandler)
|
|
63
|
+
|
|
64
|
+
this._destroyed = false
|
|
65
|
+
this._droppedEvents = 0
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Appends an event array to the buffer
|
|
70
|
+
* @param {Array|Object} events - Event object(s) to append to buffer
|
|
71
|
+
*/
|
|
72
|
+
append (events) {
|
|
73
|
+
const eventArray = Array.isArray(events) ? events : [events]
|
|
74
|
+
|
|
75
|
+
for (const event of eventArray) {
|
|
76
|
+
if (this._buffer.length >= this._bufferLimit) {
|
|
77
|
+
log.warn(`${this.constructor.name} event buffer full (limit is ${this._bufferLimit}), dropping event`)
|
|
78
|
+
this._droppedEvents++
|
|
79
|
+
continue
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const eventSizeBytes = Buffer.byteLength(JSON.stringify(event))
|
|
83
|
+
|
|
84
|
+
// Check individual event size limit if configured
|
|
85
|
+
if (this._eventSizeLimit && eventSizeBytes > this._eventSizeLimit) {
|
|
86
|
+
log.warn(`${this.constructor.name} event size
|
|
87
|
+
${eventSizeBytes} bytes exceeds limit ${this._eventSizeLimit}, dropping event`)
|
|
88
|
+
this._droppedEvents++
|
|
89
|
+
continue
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Check if adding this event would exceed payload size limit if configured
|
|
93
|
+
if (this._payloadSizeLimit && this._bufferSize + eventSizeBytes > this._payloadSizeLimit) {
|
|
94
|
+
log.debug(() => `${this.constructor.name}
|
|
95
|
+
buffer size would exceed ${this._payloadSizeLimit} bytes, flushing first`)
|
|
96
|
+
this.flush()
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
this._bufferSize += eventSizeBytes
|
|
100
|
+
this._buffer.push(event)
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Flushes all buffered events to the agent
|
|
106
|
+
*/
|
|
107
|
+
flush () {
|
|
108
|
+
if (this._buffer.length === 0) {
|
|
109
|
+
return
|
|
110
|
+
}
|
|
111
|
+
const events = this._buffer
|
|
112
|
+
this._buffer = []
|
|
113
|
+
this._bufferSize = 0
|
|
114
|
+
|
|
115
|
+
const payload = this._encode(this.makePayload(events))
|
|
116
|
+
|
|
117
|
+
log.debug(() => `${this.constructor.name} flushing payload: ${safeJSONStringify(payload)}`)
|
|
118
|
+
|
|
119
|
+
request(payload, this._requestOptions, (err, resp, code) => {
|
|
120
|
+
if (err) {
|
|
121
|
+
log.error(`Failed to send events to ${this._baseUrl.href}${this._endpoint}: ${err.message}`)
|
|
122
|
+
} else if (code >= 200 && code < 300) {
|
|
123
|
+
log.debug(() => `Successfully sent ${events.length} events`)
|
|
124
|
+
} else {
|
|
125
|
+
log.warn(`Events request returned status ${code}`)
|
|
126
|
+
}
|
|
127
|
+
})
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Override in subclass to customize payload structure
|
|
132
|
+
* @param {Array} events - Array of events to be sent
|
|
133
|
+
* @returns {object} Formatted payload
|
|
134
|
+
*/
|
|
135
|
+
makePayload (events) {
|
|
136
|
+
// Override in subclass
|
|
137
|
+
return events
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Cleans up resources and flushes remaining events
|
|
142
|
+
*/
|
|
143
|
+
destroy () {
|
|
144
|
+
if (!this._destroyed) {
|
|
145
|
+
log.debug(() => `Stopping ${this.constructor.name}`)
|
|
146
|
+
clearInterval(this._periodic)
|
|
147
|
+
process.removeListener('beforeExit', this._beforeExitHandler)
|
|
148
|
+
this.flush()
|
|
149
|
+
this._destroyed = true
|
|
150
|
+
|
|
151
|
+
if (this._droppedEvents > 0) {
|
|
152
|
+
log.warn(`${this.constructor.name} dropped ${this._droppedEvents} events due to buffer overflow`)
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* @private
|
|
159
|
+
* @returns {URL} Constructs agent URL from config
|
|
160
|
+
*/
|
|
161
|
+
_getAgentUrl () {
|
|
162
|
+
const { hostname, port } = this._config
|
|
163
|
+
|
|
164
|
+
return this._config.url ?? new URL(format({
|
|
165
|
+
protocol: 'http:',
|
|
166
|
+
hostname: hostname || 'localhost',
|
|
167
|
+
port: port || 8126
|
|
168
|
+
}))
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* @private
|
|
173
|
+
* @param {Array<object>} payload - Payload to encode
|
|
174
|
+
* @returns {string} JSON-stringified payload
|
|
175
|
+
*/
|
|
176
|
+
_encode (payload) {
|
|
177
|
+
return JSON.stringify(payload)
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
module.exports = BaseFFEWriter
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const BaseFFEWriter = require('./base')
|
|
4
|
+
const {
|
|
5
|
+
EXPOSURES_ENDPOINT,
|
|
6
|
+
EVP_PROXY_AGENT_BASE_PATH,
|
|
7
|
+
EVP_SUBDOMAIN_HEADER_NAME,
|
|
8
|
+
EVP_SUBDOMAIN_VALUE,
|
|
9
|
+
EVP_PAYLOAD_SIZE_LIMIT,
|
|
10
|
+
EVP_EVENT_SIZE_LIMIT
|
|
11
|
+
} = require('../constants/constants')
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* @typedef {Object} ExposureEvent
|
|
15
|
+
* @property {number} timestamp - Unix timestamp in milliseconds
|
|
16
|
+
* @property {Object} allocation - Allocation information
|
|
17
|
+
* @property {string} allocation.key - Allocation key
|
|
18
|
+
* @property {Object} flag - Flag information
|
|
19
|
+
* @property {string} flag.key - Flag key
|
|
20
|
+
* @property {Object} variant - Variant information
|
|
21
|
+
* @property {string} variant.key - Variant key
|
|
22
|
+
* @property {Object} subject - Subject (user/entity) information
|
|
23
|
+
* @property {string} subject.id - Subject identifier
|
|
24
|
+
* @property {string} [subject.type] - Subject type
|
|
25
|
+
* @property {Object} [subject.attributes] - Additional subject attributes
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* @typedef {Object} ExposureContext
|
|
30
|
+
* @property {string} service_name - Service name
|
|
31
|
+
* @property {string} [version] - Service version
|
|
32
|
+
* @property {string} [env] - Service environment
|
|
33
|
+
*/
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* @typedef {Object} ExposureEventPayload
|
|
37
|
+
* @property {ExposureContext} context - Service context metadata
|
|
38
|
+
* @property {ExposureEvent[]} exposures - Formatted exposure events
|
|
39
|
+
*/
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* ExposuresWriter is responsible for sending exposure events to the Datadog Agent.
|
|
43
|
+
*/
|
|
44
|
+
class ExposuresWriter extends BaseFFEWriter {
|
|
45
|
+
/**
|
|
46
|
+
* @param {import('../../config')} config - Tracer configuration object
|
|
47
|
+
*/
|
|
48
|
+
constructor (config) {
|
|
49
|
+
// Build full EVP endpoint path
|
|
50
|
+
const basePath = EVP_PROXY_AGENT_BASE_PATH.replace(/\/+$/, '')
|
|
51
|
+
const endpoint = EXPOSURES_ENDPOINT.replace(/^\/+/, '')
|
|
52
|
+
const fullEndpoint = `${basePath}/${endpoint}`
|
|
53
|
+
|
|
54
|
+
super({
|
|
55
|
+
config,
|
|
56
|
+
endpoint: fullEndpoint,
|
|
57
|
+
payloadSizeLimit: EVP_PAYLOAD_SIZE_LIMIT,
|
|
58
|
+
eventSizeLimit: EVP_EVENT_SIZE_LIMIT,
|
|
59
|
+
headers: {
|
|
60
|
+
[EVP_SUBDOMAIN_HEADER_NAME]: EVP_SUBDOMAIN_VALUE
|
|
61
|
+
}
|
|
62
|
+
})
|
|
63
|
+
this._enabled = false // Start disabled until agent strategy is set
|
|
64
|
+
this._pendingEvents = [] // Buffer events until enabled
|
|
65
|
+
this._context = this._buildContext()
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* @param {boolean} enabled - Whether to enable the writer
|
|
70
|
+
*/
|
|
71
|
+
setEnabled (enabled) {
|
|
72
|
+
this._enabled = enabled
|
|
73
|
+
|
|
74
|
+
if (enabled && this._pendingEvents.length > 0) {
|
|
75
|
+
// Flush all pending events as a batch
|
|
76
|
+
super.append(this._pendingEvents)
|
|
77
|
+
this._pendingEvents = []
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Appends exposure event(s) to the buffer
|
|
83
|
+
* @param {ExposureEvent|ExposureEvent[]} events - Exposure event(s) to append
|
|
84
|
+
*/
|
|
85
|
+
append (events) {
|
|
86
|
+
if (!this._enabled) {
|
|
87
|
+
// Buffer events until writer is ready
|
|
88
|
+
if (Array.isArray(events)) {
|
|
89
|
+
this._pendingEvents.push(...events)
|
|
90
|
+
} else {
|
|
91
|
+
this._pendingEvents.push(events)
|
|
92
|
+
}
|
|
93
|
+
return
|
|
94
|
+
}
|
|
95
|
+
super.append(events)
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Flushes buffered exposure events to the agent
|
|
100
|
+
*/
|
|
101
|
+
flush () {
|
|
102
|
+
if (!this._enabled) {
|
|
103
|
+
// Don't flush when disabled
|
|
104
|
+
return
|
|
105
|
+
}
|
|
106
|
+
super.flush()
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Formats exposure events with service context metadata
|
|
111
|
+
* @param {Array<ExposureEvent>} events - Array of exposure events
|
|
112
|
+
* @returns {ExposureEventPayload} Formatted payload with service context
|
|
113
|
+
*/
|
|
114
|
+
makePayload (events) {
|
|
115
|
+
const formattedEvents = events.map(event => this._formatExposureEvent(event))
|
|
116
|
+
|
|
117
|
+
return {
|
|
118
|
+
context: this._context,
|
|
119
|
+
exposures: formattedEvents
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Builds service context metadata
|
|
125
|
+
* @private
|
|
126
|
+
* @returns {ExposureContext} Service context
|
|
127
|
+
*/
|
|
128
|
+
_buildContext () {
|
|
129
|
+
const context = {
|
|
130
|
+
service_name: this._config.service || 'unknown'
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// Only include version and env if they are defined
|
|
134
|
+
if (this._config.version !== undefined) {
|
|
135
|
+
context.version = this._config.version
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
if (this._config.env !== undefined) {
|
|
139
|
+
context.env = this._config.env
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
return context
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* @private
|
|
147
|
+
* @param {ExposureEvent} event - Raw exposure event
|
|
148
|
+
* @returns {ExposureEvent} Formatted exposure event
|
|
149
|
+
*/
|
|
150
|
+
_formatExposureEvent (event) {
|
|
151
|
+
// Ensure the event matches the expected schema
|
|
152
|
+
const formattedEvent = {
|
|
153
|
+
timestamp: event.timestamp || Date.now(),
|
|
154
|
+
allocation: {
|
|
155
|
+
key: event.allocation?.key || event['allocation.key']
|
|
156
|
+
},
|
|
157
|
+
flag: {
|
|
158
|
+
key: event.flag?.key || event['flag.key']
|
|
159
|
+
},
|
|
160
|
+
variant: {
|
|
161
|
+
key: event.variant?.key || event['variant.key']
|
|
162
|
+
},
|
|
163
|
+
subject: {
|
|
164
|
+
id: event.subject?.id || event['subject.id'],
|
|
165
|
+
type: event.subject?.type,
|
|
166
|
+
attributes: event.subject?.attributes
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
return formattedEvent
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
module.exports = ExposuresWriter
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const logger = require('../../log')
|
|
4
|
+
const { EVP_PROXY_AGENT_BASE_PATH } = require('../constants/constants')
|
|
5
|
+
|
|
6
|
+
const AgentInfoExporter = require('../../exporters/common/agent-info-exporter')
|
|
7
|
+
let agentInfoExporter
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Determines if the agent supports EVP proxy and sets the writer enabled state accordingly
|
|
11
|
+
* @param {import('../../config')} config - Tracer configuration object
|
|
12
|
+
* @param {Function} setWriterEnabledValue - Callback to set the writer enabled state
|
|
13
|
+
*/
|
|
14
|
+
function setAgentStrategy (config, setWriterEnabledValue) {
|
|
15
|
+
if (!agentInfoExporter) {
|
|
16
|
+
agentInfoExporter = new AgentInfoExporter(config)
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
agentInfoExporter.getAgentInfo((err, agentInfo) => {
|
|
20
|
+
if (err) {
|
|
21
|
+
logger.debug('FFE Writer disabled - error getting agent info:', err.message)
|
|
22
|
+
setWriterEnabledValue(false)
|
|
23
|
+
return
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const endpoints = agentInfo.endpoints
|
|
27
|
+
const normalizedPath = EVP_PROXY_AGENT_BASE_PATH.replace(/\/+$/, '')
|
|
28
|
+
const hasEndpoint = Array.isArray(endpoints) &&
|
|
29
|
+
endpoints.includes(normalizedPath) || endpoints.includes(normalizedPath + '/')
|
|
30
|
+
|
|
31
|
+
if (hasEndpoint) {
|
|
32
|
+
logger.debug('FFE Writer enabled - agent has EVP proxy support')
|
|
33
|
+
setWriterEnabledValue(true)
|
|
34
|
+
} else {
|
|
35
|
+
logger.debug('FFE Writer disabled - agent does not have EVP proxy support')
|
|
36
|
+
setWriterEnabledValue(false)
|
|
37
|
+
}
|
|
38
|
+
})
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
module.exports = {
|
|
42
|
+
setAgentStrategy
|
|
43
|
+
}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @typedef {import('@opentelemetry/api-logs').LogRecord} LogRecord
|
|
5
|
+
* @typedef {import('@opentelemetry/core').InstrumentationScope} InstrumentationScope
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* BatchLogRecordProcessor processes log records in batches for efficient export to Datadog Agent.
|
|
10
|
+
*
|
|
11
|
+
* This implementation follows the OpenTelemetry JavaScript SDK BatchLogRecordProcessor:
|
|
12
|
+
* https://open-telemetry.github.io/opentelemetry-js/classes/_opentelemetry_sdk-logs.BatchLogRecordProcessor.html
|
|
13
|
+
*
|
|
14
|
+
* @class BatchLogRecordProcessor
|
|
15
|
+
*/
|
|
16
|
+
class BatchLogRecordProcessor {
|
|
17
|
+
#logRecords
|
|
18
|
+
#timer
|
|
19
|
+
#batchTimeout
|
|
20
|
+
#maxExportBatchSize
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Creates a new BatchLogRecordProcessor instance.
|
|
24
|
+
*
|
|
25
|
+
* @param {OtlpHttpLogExporter} exporter - Log processor for exporting batches to Datadog Agent
|
|
26
|
+
* @param {number} batchTimeout - Timeout in milliseconds for batch processing
|
|
27
|
+
* @param {number} maxExportBatchSize - Maximum number of log records per batch
|
|
28
|
+
*/
|
|
29
|
+
constructor (exporter, batchTimeout, maxExportBatchSize) {
|
|
30
|
+
this.exporter = exporter
|
|
31
|
+
this.#batchTimeout = batchTimeout
|
|
32
|
+
this.#maxExportBatchSize = maxExportBatchSize
|
|
33
|
+
this.#logRecords = []
|
|
34
|
+
this.#timer = null
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Processes a single log record.
|
|
39
|
+
*
|
|
40
|
+
* @param {LogRecord} logRecord - The enriched log record with trace correlation and metadata
|
|
41
|
+
* @param {InstrumentationScope} instrumentationScope - The instrumentation library
|
|
42
|
+
*/
|
|
43
|
+
onEmit (logRecord, instrumentationScope) {
|
|
44
|
+
// Store the log record (already enriched by Logger.emit)
|
|
45
|
+
this.#logRecords.push({ ...logRecord, instrumentationScope })
|
|
46
|
+
|
|
47
|
+
if (this.#logRecords.length >= this.#maxExportBatchSize) {
|
|
48
|
+
this.#export()
|
|
49
|
+
} else if (this.#logRecords.length === 1) {
|
|
50
|
+
this.#startTimer()
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Forces an immediate flush of all pending log records.
|
|
56
|
+
* @returns {undefined} Promise that resolves when flush is complete
|
|
57
|
+
*/
|
|
58
|
+
forceFlush () {
|
|
59
|
+
this.#export()
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Starts the batch timeout timer.
|
|
64
|
+
* @private
|
|
65
|
+
*/
|
|
66
|
+
#startTimer () {
|
|
67
|
+
if (this.#timer) {
|
|
68
|
+
return
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
this.#timer = setTimeout(() => {
|
|
72
|
+
this.#export()
|
|
73
|
+
}, this.#batchTimeout)
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Exports the current batch of log records.
|
|
78
|
+
* @private
|
|
79
|
+
*/
|
|
80
|
+
#export () {
|
|
81
|
+
const logRecords = this.#logRecords.slice(0, this.#maxExportBatchSize)
|
|
82
|
+
this.#logRecords = this.#logRecords.slice(this.#maxExportBatchSize)
|
|
83
|
+
|
|
84
|
+
this.#clearTimer()
|
|
85
|
+
this.exporter.export(logRecords, () => {})
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Clears the batch timeout timer.
|
|
90
|
+
* @private
|
|
91
|
+
*/
|
|
92
|
+
#clearTimer () {
|
|
93
|
+
if (this.#timer) {
|
|
94
|
+
clearTimeout(this.#timer)
|
|
95
|
+
this.#timer = null
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
module.exports = BatchLogRecordProcessor
|