dd-trace
Version:
Datadog APM tracing client for JavaScript
130 lines (118 loc) • 4.52 kB
JavaScript
var fs = require('fs')
// Capture the child_process functions at load time, before the tracer wraps the module.
// Reaching through require('child_process') at send time would route the forwarder through
// the tracer's own child_process instrumentation once the tracer is initialized.
var childProcess = require('child_process')
var spawn = childProcess.spawn
// eslint-disable-next-line n/no-unsupported-features/node-builtins
var spawnSync = childProcess.spawnSync
var tracerVersion = require('../../../../package.json').version
var log = require('./log')
module.exports = sendTelemetry
if (!process.env.DD_INJECTION_ENABLED) {
module.exports = function noop () {}
}
var telemetryForwarderPath = process.env.DD_TELEMETRY_FORWARDER_PATH
if (typeof telemetryForwarderPath !== 'string' || !fs.existsSync(telemetryForwarderPath)) {
module.exports = function noop () {}
}
var metadata = {
language_name: 'nodejs',
language_version: process.versions.node,
runtime_name: 'nodejs',
runtime_version: process.versions.node,
tracer_version: tracerVersion,
pid: process.pid,
result: 'unknown',
result_reason: 'unknown',
result_class: 'unknown'
}
var seen = {}
function shouldSend (point) {
if (point.name === 'abort') {
// This one can only be sent once, regardless of tags
if (seen.abort) {
return false
}
seen.abort = true
} else if (point.name === 'abort.integration') {
// For now, this is the only other one we want to dedupe
var compiledPoint = point.name + point.tags.join('')
if (seen[compiledPoint]) {
return false
}
seen[compiledPoint] = true
}
return true
}
function sendTelemetry (name, tags, resultMetadata, synchronous) {
var points = name
if (typeof name === 'string') {
points = [{ name: name, tags: tags || [] }]
}
if (['1', 'true', 'True'].indexOf(process.env.DD_INJECT_FORCE) !== -1) {
points = points.filter(function (p) { return ['error', 'complete'].indexOf(p.name) !== -1 })
}
points = points.filter(function (p) { return shouldSend(p) })
for (var i = 0; i < points.length; i++) {
points[i].name = 'library_entrypoint.' + points[i].name
}
if (points.length === 0) {
return
}
// Update metadata with provided result metadata
var currentMetadata = {}
for (var key in metadata) {
currentMetadata[key] = metadata[key]
}
if (resultMetadata) {
for (var resultKey in resultMetadata) {
currentMetadata[resultKey] = resultMetadata[resultKey]
}
}
var payload = JSON.stringify({ metadata: currentMetadata, points: points })
// A forwarder spawned asynchronously can still be tearing down its stdio pipes when the
// injected app calls process.exit(); on Node 24.0.0/24.1.x that deadlocks the exit
// (fixed upstream in 24.2), hanging short-lived single-step-install processes. On the
// bailout path the caller passes synchronous=true, so the child is fully reaped before we
// return and nothing survives to race the exit. spawnSync is only reached on that path,
// before any instrumentation is active, so it never traces the forwarder. It exists since
// Node 0.11.12; the guardrails still target >=0.8, which predates the exit bug anyway.
if (synchronous && spawnSync) {
// Bound the blocking send: this telemetry is best-effort and the whole point of the
// synchronous path is to avoid a hung exit, so a forwarder that wedges must not become a
// new hard hang. On timeout spawnSync kills the child and returns error.code ETIMEDOUT.
var result = spawnSync(telemetryForwarderPath, ['library_entrypoint'], {
input: payload,
stdio: ['pipe', 'ignore', 'ignore'],
timeout: 1000,
killSignal: 'SIGKILL'
})
if (result.error) {
if (result.error.code === 'ETIMEDOUT') {
log.error('Telemetry forwarder timed out')
} else {
log.error('Failed to spawn telemetry forwarder')
}
} else if (result.status) {
log.error('Telemetry forwarder exited with code', result.status)
}
return
}
var proc = spawn(telemetryForwarderPath, ['library_entrypoint'], {
stdio: 'pipe'
})
proc.on('error', function () {
log.error('Failed to spawn telemetry forwarder')
})
proc.once('exit', function (code) {
if (code !== 0) {
log.error('Telemetry forwarder exited with code', code)
}
})
proc.stdin.on('error', function () {
log.error('Failed to write telemetry data to telemetry forwarder')
})
proc.stdin.end(payload)
}