dd-trace 5.71.0 → 5.72.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 +5 -0
- package/index.d.ts +93 -1
- package/package.json +21 -2
- 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/cucumber.js +7 -7
- package/packages/datadog-instrumentations/src/helpers/hooks.js +1 -0
- package/packages/datadog-instrumentations/src/jest.js +29 -36
- package/packages/datadog-instrumentations/src/mocha/main.js +8 -9
- package/packages/datadog-instrumentations/src/mocha/utils.js +1 -1
- 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-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 +37 -0
- 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/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/server.js +5 -3
- 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/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 +173 -0
- package/packages/dd-trace/src/opentelemetry/logs/otlp_transformer.js +367 -0
- package/packages/dd-trace/src/opentelemetry/protos/common.proto +116 -0
- package/packages/dd-trace/src/opentelemetry/protos/logs.proto +226 -0
- package/packages/dd-trace/src/opentelemetry/protos/logs_service.proto +78 -0
- package/packages/dd-trace/src/opentelemetry/protos/protobuf_loader.js +48 -0
- package/packages/dd-trace/src/opentelemetry/protos/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 +17 -0
- package/packages/dd-trace/src/telemetry/telemetry.js +13 -1
|
@@ -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
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const os = require('os')
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* @typedef {import('../../config')} Config
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* @fileoverview OpenTelemetry Logs Implementation for dd-trace-js
|
|
11
|
+
*
|
|
12
|
+
* This package provides a custom OpenTelemetry Logs implementation that integrates
|
|
13
|
+
* with the Datadog tracing library. It includes all necessary components for
|
|
14
|
+
* emitting, processing, and exporting log records via OTLP (OpenTelemetry Protocol).
|
|
15
|
+
*
|
|
16
|
+
* Key Components:
|
|
17
|
+
* - LoggerProvider: Main entry point for creating loggers
|
|
18
|
+
* - Logger: Provides methods to emit log records
|
|
19
|
+
* - BatchLogRecordProcessor: Processes log records in batches for efficient export
|
|
20
|
+
* - OtlpHttpLogExporter: Exports log records via OTLP over HTTP
|
|
21
|
+
* - OtlpTransformer: Transforms log records to OTLP format
|
|
22
|
+
*
|
|
23
|
+
* This is a custom implementation to avoid pulling in the full OpenTelemetry SDK,
|
|
24
|
+
* based on OTLP Protocol v1.7.0. It supports both protobuf and JSON serialization
|
|
25
|
+
* formats and integrates with Datadog's configuration system.
|
|
26
|
+
*
|
|
27
|
+
* @package
|
|
28
|
+
*/
|
|
29
|
+
|
|
30
|
+
const LoggerProvider = require('./logger_provider')
|
|
31
|
+
const BatchLogRecordProcessor = require('./batch_log_processor')
|
|
32
|
+
const OtlpHttpLogExporter = require('./otlp_http_log_exporter')
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Initializes OpenTelemetry Logs support
|
|
36
|
+
* @param {Config} config - Tracer configuration instance
|
|
37
|
+
*/
|
|
38
|
+
function initializeOpenTelemetryLogs (config) {
|
|
39
|
+
// Build resource attributes
|
|
40
|
+
const resourceAttributes = {
|
|
41
|
+
'service.name': config.service,
|
|
42
|
+
'service.version': config.version,
|
|
43
|
+
'deployment.environment': config.env
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Add all tracer tags (includes DD_TAGS, OTEL_RESOURCE_ATTRIBUTES, DD_TRACE_TAGS, etc.)
|
|
47
|
+
// Exclude Datadog-style keys that duplicate OpenTelemetry standard keys
|
|
48
|
+
if (config.tags) {
|
|
49
|
+
const filteredTags = { ...config.tags }
|
|
50
|
+
delete filteredTags.service
|
|
51
|
+
delete filteredTags.version
|
|
52
|
+
delete filteredTags.env
|
|
53
|
+
Object.assign(resourceAttributes, filteredTags)
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Add host.name if reportHostname is enabled
|
|
57
|
+
if (config.reportHostname) {
|
|
58
|
+
resourceAttributes['host.name'] = os.hostname()
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Create OTLP exporter using resolved config values
|
|
62
|
+
const exporter = new OtlpHttpLogExporter(
|
|
63
|
+
config.otelLogsUrl,
|
|
64
|
+
config.otelLogsHeaders,
|
|
65
|
+
config.otelLogsTimeout,
|
|
66
|
+
config.otelLogsProtocol,
|
|
67
|
+
resourceAttributes
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
// Create batch processor for exporting logs to Datadog Agent
|
|
71
|
+
const processor = new BatchLogRecordProcessor(
|
|
72
|
+
exporter,
|
|
73
|
+
config.otelLogsBatchTimeout,
|
|
74
|
+
config.otelLogsMaxExportBatchSize
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
// Create logger provider with processor for Datadog Agent export
|
|
78
|
+
const loggerProvider = new LoggerProvider({ processor })
|
|
79
|
+
|
|
80
|
+
// Register the logger provider globally with OpenTelemetry API
|
|
81
|
+
loggerProvider.register()
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
module.exports = {
|
|
85
|
+
LoggerProvider,
|
|
86
|
+
initializeOpenTelemetryLogs
|
|
87
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const { sanitizeAttributes } = require('@opentelemetry/core')
|
|
4
|
+
const { context } = require('@opentelemetry/api')
|
|
5
|
+
const packageVersion = require('../../../../../package.json').version
|
|
6
|
+
/**
|
|
7
|
+
* @typedef {import('@opentelemetry/api-logs').LogRecord} LogRecord
|
|
8
|
+
* @typedef {import('@opentelemetry/api').SpanContext} SpanContext
|
|
9
|
+
* @typedef {import('@opentelemetry/api').Attributes} Attributes
|
|
10
|
+
* @typedef {import('@opentelemetry/resources').Resource} Resource
|
|
11
|
+
* @typedef {import('@opentelemetry/core').InstrumentationScope} InstrumentationScope
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Logger provides methods to emit log records.
|
|
16
|
+
*
|
|
17
|
+
* This implementation follows the OpenTelemetry JavaScript API Logger:
|
|
18
|
+
* https://open-telemetry.github.io/opentelemetry-js/interfaces/_opentelemetry_api-logs.Logger.html
|
|
19
|
+
*
|
|
20
|
+
* @class Logger
|
|
21
|
+
*/
|
|
22
|
+
class Logger {
|
|
23
|
+
#instrumentationScope
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Creates a new Logger instance.
|
|
27
|
+
*
|
|
28
|
+
* @param {LoggerProvider} loggerProvider - Parent logger provider
|
|
29
|
+
* @param {InstrumentationScope} [instrumentationScope] - Instrumentation scope information (newer API)
|
|
30
|
+
* @param {Object} [instrumentationLibrary] - Instrumentation library information (legacy API) [DEPRECATED in v1.3.0]
|
|
31
|
+
* @param {InstrumentationScope} [instrumentationScope.name] - Library name (defaults to 'dd-trace-js')
|
|
32
|
+
* @param {InstrumentationScope} [instrumentationScope.version] - Library version (defaults to tracer version)
|
|
33
|
+
* @param {string} [instrumentationLibrary.name] - Library name (legacy, defaults to 'dd-trace-js')
|
|
34
|
+
* @param {string} [instrumentationLibrary.version] - Library version (legacy, defaults to tracer version)
|
|
35
|
+
*/
|
|
36
|
+
constructor (loggerProvider, instrumentationScope, instrumentationLibrary) {
|
|
37
|
+
this.loggerProvider = loggerProvider
|
|
38
|
+
|
|
39
|
+
// Support both newer instrumentationScope and legacy instrumentationLibrary
|
|
40
|
+
const scope = instrumentationScope || instrumentationLibrary
|
|
41
|
+
this.#instrumentationScope = {
|
|
42
|
+
name: scope?.name || 'dd-trace-js',
|
|
43
|
+
version: scope?.version || packageVersion,
|
|
44
|
+
schemaUrl: scope?.schemaUrl || '',
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Emits a log record.
|
|
50
|
+
*
|
|
51
|
+
* @param {LogRecord} logRecord - The log record to emit
|
|
52
|
+
* @returns {void}
|
|
53
|
+
*/
|
|
54
|
+
emit (logRecord) {
|
|
55
|
+
if (this.loggerProvider.isShutdown || !this.loggerProvider.processor) {
|
|
56
|
+
return
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (logRecord.attributes) {
|
|
60
|
+
logRecord.attributes = sanitizeAttributes(logRecord.attributes)
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Note: timestamp is in nanoseconds (as defined by OpenTelemetry LogRecord API)
|
|
64
|
+
if (!logRecord.timestamp) {
|
|
65
|
+
logRecord.timestamp = Number(process.hrtime.bigint())
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (!logRecord.context) {
|
|
69
|
+
// Store span context in the log record context for trace correlation
|
|
70
|
+
logRecord.context = context.active()
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
this.loggerProvider.processor.onEmit(logRecord, this.#instrumentationScope)
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
module.exports = Logger
|