dd-trace
Version:
Datadog APM tracing client for JavaScript
174 lines (155 loc) • 4.74 kB
JavaScript
'use strict'
const {
EXPOSURES_ENDPOINT,
EVP_PROXY_AGENT_BASE_PATH,
EVP_SUBDOMAIN_HEADER_NAME,
EVP_SUBDOMAIN_VALUE,
EVP_PAYLOAD_SIZE_LIMIT,
EVP_EVENT_SIZE_LIMIT,
} = require('../constants/constants')
const BaseFFEWriter = require('./base')
/**
* @typedef {object} ExposureEvent
* @property {number} timestamp - Unix timestamp in milliseconds
* @property {object} allocation - Allocation information
* @property {string} allocation.key - Allocation key
* @property {object} flag - Flag information
* @property {string} flag.key - Flag key
* @property {object} variant - Variant information
* @property {string} variant.key - Variant key
* @property {object} subject - Subject (user/entity) information
* @property {string} subject.id - Subject identifier
* @property {string} [subject.type] - Subject type
* @property {object} [subject.attributes] - Additional subject attributes
*/
/**
* @typedef {object} ExposureContext
* @property {string} service - Service name
* @property {string} [version] - Service version
* @property {string} [env] - Service environment
*/
/**
* @typedef {object} ExposureEventPayload
* @property {ExposureContext} context - Service context metadata
* @property {ExposureEvent[]} exposures - Formatted exposure events
*/
/**
* ExposuresWriter is responsible for sending exposure events to the Datadog Agent.
*/
class ExposuresWriter extends BaseFFEWriter {
/**
* @param {import('../../config')} config - Tracer configuration object
*/
constructor (config) {
// Build full EVP endpoint path
const basePath = EVP_PROXY_AGENT_BASE_PATH.replace(/\/+$/, '')
const endpoint = EXPOSURES_ENDPOINT.replace(/^\/+/, '')
const fullEndpoint = `${basePath}/${endpoint}`
super({
config,
endpoint: fullEndpoint,
payloadSizeLimit: EVP_PAYLOAD_SIZE_LIMIT,
eventSizeLimit: EVP_EVENT_SIZE_LIMIT,
headers: {
[EVP_SUBDOMAIN_HEADER_NAME]: EVP_SUBDOMAIN_VALUE,
},
})
this._enabled = false // Start disabled until agent strategy is set
this._pendingEvents = [] // Buffer events until enabled
this._context = this._buildContext()
}
/**
* @param {boolean} enabled - Whether to enable the writer
*/
setEnabled (enabled) {
this._enabled = enabled
if (enabled && this._pendingEvents.length > 0) {
// Flush all pending events as a batch
super.append(this._pendingEvents)
this._pendingEvents = []
}
}
/**
* Appends exposure event(s) to the buffer
* @param {ExposureEvent|ExposureEvent[]} events - Exposure event(s) to append
*/
append (events) {
if (!this._enabled) {
// Buffer events until writer is ready
if (Array.isArray(events)) {
this._pendingEvents.push(...events)
} else {
this._pendingEvents.push(events)
}
return
}
super.append(events)
}
/**
* Flushes buffered exposure events to the agent
*/
flush () {
if (!this._enabled) {
// Don't flush when disabled
return
}
super.flush()
}
/**
* Formats exposure events with service context metadata
* @param {Array<ExposureEvent>} events - Array of exposure events
* @returns {ExposureEventPayload} Formatted payload with service context
*/
makePayload (events) {
const formattedEvents = events.map(event => this._formatExposureEvent(event))
return {
context: this._context,
exposures: formattedEvents,
}
}
/**
* Builds service context metadata
* @private
* @returns {ExposureContext} Service context
*/
_buildContext () {
const context = {
service: this._config.service || 'unknown',
}
// Only include version and env if they are defined
if (this._config.version !== undefined) {
context.version = this._config.version
}
if (this._config.env !== undefined) {
context.env = this._config.env
}
return context
}
/**
* @private
* @param {ExposureEvent} event - Raw exposure event
* @returns {ExposureEvent} Formatted exposure event
*/
_formatExposureEvent (event) {
// Ensure the event matches the expected schema
const formattedEvent = {
timestamp: event.timestamp || Date.now(),
allocation: {
key: event.allocation?.key || event['allocation.key'],
},
flag: {
key: event.flag?.key || event['flag.key'],
},
variant: {
key: event.variant?.key || event['variant.key'],
},
subject: {
id: event.subject?.id || event['subject.id'],
type: event.subject?.type,
attributes: event.subject?.attributes,
},
}
return formattedEvent
}
}
module.exports = ExposuresWriter