dd-trace
Version:
Datadog APM tracing client for JavaScript
170 lines (134 loc) • 4.51 kB
JavaScript
'use strict'
const { URL, format } = require('node:url')
const path = require('node:path')
const request = require('../../exporters/common/request')
const { getEnvironmentVariable } = require('../../config-helper')
const logger = require('../../log')
const { encodeUnicode } = require('../util')
const telemetry = require('../telemetry')
const log = require('../../log')
const {
EVP_SUBDOMAIN_HEADER_NAME,
EVP_PROXY_AGENT_BASE_PATH
} = require('../constants/writers')
const { parseResponseAndLog } = require('./util')
class BaseLLMObsWriter {
constructor ({ interval, timeout, eventType, config, endpoint, intake }) {
this._interval = interval ?? getEnvironmentVariable('_DD_LLMOBS_FLUSH_INTERVAL') ?? 1000 // 1s
this._timeout = timeout ?? getEnvironmentVariable('_DD_LLMOBS_TIMEOUT') ?? 5000 // 5s
this._eventType = eventType
this._buffer = []
this._bufferLimit = 1000
this._bufferSize = 0
this._config = config
this._endpoint = endpoint
this._intake = intake
this._periodic = setInterval(() => {
this.flush()
}, this._interval).unref()
this._beforeExitHandler = () => {
this.destroy()
}
process.once('beforeExit', this._beforeExitHandler)
this._destroyed = false
}
get url () {
if (this._agentless == null) return null
const baseUrl = this._baseUrl.href
const endpoint = this._endpoint
// Split on protocol separator to preserve it
// path.join will remove some slashes unnecessarily
const [protocol, rest] = baseUrl.split('://')
return protocol + '://' + path.join(rest, endpoint)
}
append (event, byteLength) {
if (this._buffer.length >= this._bufferLimit) {
logger.warn(`${this.constructor.name} event buffer full (limit is ${this._bufferLimit}), dropping event`)
telemetry.recordDroppedPayload(1, this._eventType, 'buffer_full')
return
}
this._bufferSize += byteLength || Buffer.byteLength(JSON.stringify(event))
this._buffer.push(event)
}
flush () {
const noAgentStrategy = this._agentless == null
if (this._buffer.length === 0 || noAgentStrategy) {
return
}
const events = this._buffer
this._buffer = []
this._bufferSize = 0
const payload = this._encode(this.makePayload(events))
log.debug('Encoded LLMObs payload: %s', payload)
const options = this._getOptions()
request(payload, options, (err, resp, code) => {
parseResponseAndLog(err, code, events.length, this.url, this._eventType)
})
}
makePayload (events) {}
destroy () {
if (!this._destroyed) {
logger.debug(`Stopping ${this.constructor.name}`)
clearInterval(this._periodic)
process.removeListener('beforeExit', this._beforeExitHandler)
this.flush()
this._destroyed = true
}
}
setAgentless (agentless) {
this._agentless = agentless
const { url, endpoint } = this._getUrlAndPath()
this._baseUrl = url
this._endpoint = endpoint
logger.debug(`Configuring ${this.constructor.name} to ${this.url}`)
}
_getUrlAndPath () {
if (this._agentless) {
return {
url: new URL(format({
protocol: 'https:',
hostname: `${this._intake}.${this._config.site}`
})),
endpoint: this._endpoint
}
}
const { hostname, port } = this._config
const overrideOriginEnv = getEnvironmentVariable('_DD_LLMOBS_OVERRIDE_ORIGIN')
const overrideOriginUrl = overrideOriginEnv && new URL(overrideOriginEnv)
const base = overrideOriginUrl ?? this._config.url ?? new URL(format({
protocol: 'http:',
hostname,
port
}))
return {
url: base,
endpoint: path.join(EVP_PROXY_AGENT_BASE_PATH, this._endpoint)
}
}
_getOptions () {
const options = {
headers: {
'Content-Type': 'application/json'
},
method: 'POST',
timeout: this._timeout,
url: this._baseUrl,
path: this._endpoint
}
if (this._agentless) {
options.headers['DD-API-KEY'] = this._config.apiKey || ''
} else {
options.headers[EVP_SUBDOMAIN_HEADER_NAME] = this._intake
}
return options
}
_encode (payload) {
return JSON.stringify(payload, (key, value) => {
if (typeof value === 'string') {
return encodeUnicode(value) // serialize unicode characters
}
return value
}).replaceAll(String.raw`\\u`, String.raw`\u`) // remove double escaping
}
}
module.exports = BaseLLMObsWriter