Skip to content

chore(ssi): send injection and instrumentation telemetry #5721

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jun 12, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion init.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,8 @@
var guard = require('./packages/dd-trace/src/guardrails')

module.exports = guard(function () {
return require('.').init()
var INSTRUMENTED_BY_SSI = require('./packages/dd-trace/src/constants').INSTRUMENTED_BY_SSI
var obj = {}
obj[INSTRUMENTED_BY_SSI] = 'ssi'
return require('.').init(obj)
})
13 changes: 9 additions & 4 deletions integration-tests/helpers/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ const hookFile = 'dd-trace/loader-hook.mjs'
// This is set by the setShouldKill function
let shouldKill

async function runAndCheckOutput (filename, cwd, expectedOut) {
async function runAndCheckOutput (filename, cwd, expectedOut, expectedSource) {
const proc = spawn(process.execPath, [filename], { cwd, stdio: 'pipe' })
const pid = proc.pid
let out = await new Promise((resolve, reject) => {
Expand All @@ -42,7 +42,12 @@ async function runAndCheckOutput (filename, cwd, expectedOut) {
// Debug adds this, which we don't care about in these tests
out = out.replace('Flushing 0 metrics via HTTP\n', '')
}
assert.strictEqual(out, expectedOut)
assert.match(out, new RegExp(expectedOut), `output "${out} does not contain expected output "${expectedOut}"`)
}

if (expectedSource) {
assert.match(out, new RegExp(`instrumentation source: ${expectedSource}`),
`Expected the process to output "${expectedSource}", but logs only contain: "${out}"`)
}
return pid
}
Expand All @@ -51,10 +56,10 @@ async function runAndCheckOutput (filename, cwd, expectedOut) {
let sandbox

// This _must_ be used with the useSandbox function
async function runAndCheckWithTelemetry (filename, expectedOut, ...expectedTelemetryPoints) {
async function runAndCheckWithTelemetry (filename, expectedOut, expectedTelemetryPoints, expectedSource) {
const cwd = sandbox.folder
const cleanup = telemetryForwarder(expectedTelemetryPoints)
const pid = await runAndCheckOutput(filename, cwd, expectedOut)
const pid = await runAndCheckOutput(filename, cwd, expectedOut, expectedSource)
const msgs = await cleanup()
if (expectedTelemetryPoints.length === 0) {
// assert no telemetry sent
Expand Down
46 changes: 23 additions & 23 deletions integration-tests/init.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,37 +36,37 @@ function testInjectionScenarios (arg, filename, esmWorks = false) {

if (currentVersionIsSupported) {
context('without DD_INJECTION_ENABLED', () => {
it('should initialize the tracer', () => doTest('init/trace.js', 'true\n'))
it('should initialize instrumentation', () => doTest('init/instrument.js', 'true\n'))
it('should initialize the tracer', () => doTest('init/trace.js', 'true\n', [], 'ssi'))
it('should initialize instrumentation', () => doTest('init/instrument.js', 'true\n', [], 'ssi'))
it(`should ${esmWorks ? '' : 'not '}initialize ESM instrumentation`, () =>
doTest('init/instrument.mjs', `${esmWorks}\n`))
doTest('init/instrument.mjs', `${esmWorks}\n`, []))
})
}
context('with DD_INJECTION_ENABLED', () => {
useEnv({ DD_INJECTION_ENABLED })

it('should not initialize the tracer', () => doTest('init/trace.js', 'false\n'))
it('should not initialize instrumentation', () => doTest('init/instrument.js', 'false\n'))
it('should not initialize ESM instrumentation', () => doTest('init/instrument.mjs', 'false\n'))
it('should not initialize the tracer', () => doTest('init/trace.js', 'false\n', []))
it('should not initialize instrumentation', () => doTest('init/instrument.js', 'false\n', []))
it('should not initialize ESM instrumentation', () => doTest('init/instrument.mjs', 'false\n', []))
})
})
context('when dd-trace in the app dir', () => {
const NODE_OPTIONS = `--no-warnings --${arg} dd-trace/${filename}`
useEnv({ NODE_OPTIONS })

context('without DD_INJECTION_ENABLED', () => {
it('should initialize the tracer', () => doTest('init/trace.js', 'true\n'))
it('should initialize instrumentation', () => doTest('init/instrument.js', 'true\n'))
it('should initialize the tracer', () => doTest('init/trace.js', 'true\n', [], 'ssi'))
it('should initialize instrumentation', () => doTest('init/instrument.js', 'true\n', [], 'ssi'))
it(`should ${esmWorks ? '' : 'not '}initialize ESM instrumentation`, () =>
doTest('init/instrument.mjs', `${esmWorks}\n`))
doTest('init/instrument.mjs', `${esmWorks}\n`, []))
})
context('with DD_INJECTION_ENABLED', () => {
useEnv({ DD_INJECTION_ENABLED })
useEnv({ DD_INJECTION_ENABLED, DD_TRACE_DEBUG })

it('should initialize the tracer', () => doTest('init/trace.js', 'true\n', ...telemetryGood))
it('should initialize instrumentation', () => doTest('init/instrument.js', 'true\n', ...telemetryGood))
it('should initialize the tracer', () => doTest('init/trace.js', 'true\n', telemetryGood, 'ssi'))
it('should initialize instrumentation', () => doTest('init/instrument.js', 'true\n', telemetryGood, 'ssi'))
it(`should ${esmWorks ? '' : 'not '}initialize ESM instrumentation`, () =>
doTest('init/instrument.mjs', `${esmWorks}\n`, ...telemetryGood))
doTest('init/instrument.mjs', `${esmWorks}\n`, telemetryGood, 'ssi'))
})
})
})
Expand All @@ -90,13 +90,13 @@ function testRuntimeVersionChecks (arg, filename) {
useEnv({ NODE_OPTIONS })

it('should not initialize the tracer', () =>
doTest('false\n'))
doTest('false\n', []))
context('with DD_INJECTION_ENABLED', () => {
useEnv({ DD_INJECTION_ENABLED })

context('without debug', () => {
it('should not initialize the tracer', () => doTest('false\n', ...telemetryAbort))
it('should initialize the tracer, if DD_INJECT_FORCE', () => doTestForced('true\n', ...telemetryForced))
it('should not initialize the tracer', () => doTest('false\n', telemetryAbort))
it('should initialize the tracer, if DD_INJECT_FORCE', () => doTestForced('true\n', telemetryForced))
})
context('with debug', () => {
useEnv({ DD_TRACE_DEBUG })
Expand All @@ -106,38 +106,38 @@ function testRuntimeVersionChecks (arg, filename) {
Found incompatible runtime nodejs ${process.versions.node}, Supported runtimes: nodejs \
>=18.
false
`, ...telemetryAbort))
`, telemetryAbort))
it('should initialize the tracer, if DD_INJECT_FORCE', () =>
doTestForced(`Aborting application instrumentation due to incompatible_runtime.
Found incompatible runtime nodejs ${process.versions.node}, Supported runtimes: nodejs \
>=18.
DD_INJECT_FORCE enabled, allowing unsupported runtimes and continuing.
Application instrumentation bootstrapping complete
true
`, ...telemetryForced))
`, telemetryForced))
})
})
})
} else {
context('when node version is more than engines field', () => {
useEnv({ NODE_OPTIONS })

it('should initialize the tracer, if no DD_INJECTION_ENABLED', () => doTest('true\n'))
it('should initialize the tracer, if no DD_INJECTION_ENABLED', () => doTest('true\n', [], 'ssi'))
context('with DD_INJECTION_ENABLED', () => {
useEnv({ DD_INJECTION_ENABLED })

context('without debug', () => {
it('should initialize the tracer', () => doTest('true\n', ...telemetryGood))
it('should initialize the tracer', () => doTest('true\n', telemetryGood, 'ssi'))
it('should initialize the tracer, if DD_INJECT_FORCE', () =>
doTestForced('true\n', ...telemetryGood))
doTestForced('true\n', telemetryGood, 'ssi'))
})
context('with debug', () => {
useEnv({ DD_TRACE_DEBUG })

it('should initialize the tracer', () =>
doTest('Application instrumentation bootstrapping complete\ntrue\n', ...telemetryGood))
doTest('Application instrumentation bootstrapping complete\ntrue\n', telemetryGood, 'ssi'))
it('should initialize the tracer, if DD_INJECT_FORCE', () =>
doTestForced('Application instrumentation bootstrapping complete\ntrue\n', ...telemetryGood))
doTestForced('Application instrumentation bootstrapping complete\ntrue\n', telemetryGood, 'ssi'))
})
})
})
Expand Down
2 changes: 2 additions & 0 deletions integration-tests/init/instrument.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ const server = http.createServer((req, res) => {
server.close()
// eslint-disable-next-line no-console
console.log(gotEvent)
// eslint-disable-next-line no-console
console.log('instrumentation source:', global._ddtrace._tracer._config.instrumentationSource)
process.exit()
})
})
Expand Down
2 changes: 2 additions & 0 deletions integration-tests/init/instrument.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ const server = http.createServer((req, res) => {
server.close()
// eslint-disable-next-line no-console
console.log(gotEvent)
// eslint-disable-next-line no-console
console.log('instrumentation source:', global._ddtrace._tracer._config.instrumentationSource)
process.exit()
})
})
Expand Down
2 changes: 2 additions & 0 deletions integration-tests/init/trace.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
// eslint-disable-next-line no-console
console.log(!!global._ddtrace)
// eslint-disable-next-line no-console
console.log('instrumentation source:', global._ddtrace._tracer._config.instrumentationSource)
process.exit()
25 changes: 13 additions & 12 deletions integration-tests/package-guardrails.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,44 +29,45 @@ describe('package guardrails', () => {
useEnv({ DD_INJECTION_ENABLED })
it('should not instrument the package, and send telemetry', () =>
runTest('false\n',
'complete', 'injection_forced:false',
'abort.integration', 'integration:bluebird,integration_version:1.0.0'
['complete', 'injection_forced:false',
'abort.integration', 'integration:bluebird,integration_version:1.0.0'
]
))
})
context('with logging disabled', () => {
it('should not instrument the package', () => runTest('false\n'))
it('should not instrument the package', () => runTest('false\n', []))
})
context('with logging enabled', () => {
useEnv({ DD_TRACE_DEBUG })
it('should not instrument the package', () =>
runTest(`Application instrumentation bootstrapping complete
Found incompatible integration version: bluebird@1.0.0
false
`))
`, []))
})
})

context('when package is in range', () => {
context('when bluebird is 2.9.0', () => {
useSandbox(['bluebird@2.9.0'])
it('should instrument the package', () => runTest('true\n'))
it('should instrument the package', () => runTest('true\n', [], 'ssi'))
})
context('when bluebird is 3.7.2', () => {
useSandbox(['bluebird@3.7.2'])
it('should instrument the package', () => runTest('true\n'))
it('should instrument the package', () => runTest('true\n', [], 'ssi'))
})
})

context('when package is in range (fastify)', () => {
context('when fastify is latest', () => {
useSandbox(['fastify'])
it('should instrument the package', () => runTest('true\n'))
it('should instrument the package', () => runTest('true\n', [], 'ssi'))
})
context('when fastify is latest and logging enabled', () => {
useSandbox(['fastify'])
useEnv({ DD_TRACE_DEBUG })
it('should instrument the package', () =>
runTest('Application instrumentation bootstrapping complete\ntrue\n'))
runTest('Application instrumentation bootstrapping complete\ntrue\n', [], 'ssi'))
})
})

Expand All @@ -88,13 +89,13 @@ addHook({ name: 'bluebird', versions: ['*'] }, Promise => {
useEnv({ DD_INJECTION_ENABLED })
it('should not instrument the package, and send telemetry', () =>
runTest('false\n',
'complete', 'injection_forced:false',
'error', 'error_type:ReferenceError,integration:bluebird,integration_version:3.7.2'
['complete', 'injection_forced:false',
'error', 'error_type:ReferenceError,integration:bluebird,integration_version:3.7.2']
))
})

context('with logging disabled', () => {
it('should not instrument the package', () => runTest('false\n'))
it('should not instrument the package', () => runTest('false\n', []))
})

context('with logging enabled', () => {
Expand All @@ -107,7 +108,7 @@ Error during ddtrace instrumentation of application, aborting.
ReferenceError: this is a test error
at `))
assert.ok(log.includes('\nfalse\n'))
}))
}, []))
})
})
})
8 changes: 6 additions & 2 deletions integration-tests/package-guardrails/index.js
Original file line number Diff line number Diff line change
@@ -1,15 +1,19 @@
'use strict'

/* eslint-disable no-console */

try {
const P = require('bluebird')

const isWrapped = P.prototype._then.toString().includes('AsyncResource')

// eslint-disable-next-line no-console
console.log(isWrapped)
} catch (e) {
const fastify = require('fastify')

// eslint-disable-next-line no-console
console.log(fastify.toString().startsWith('function fastifyWithTrace'))
}
if (global._ddtrace) {
// eslint-disable-next-line no-console
console.log('instrumentation source:', global._ddtrace._tracer._config.instrumentationSource)
}
11 changes: 10 additions & 1 deletion packages/dd-trace/src/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,9 @@ const { getGitMetadataFromGitProperties, removeUserSensitiveInfo } = require('./
const { updateConfig } = require('./telemetry')
const telemetryMetrics = require('./telemetry/metrics')
const { isInServerlessEnvironment, getIsGCPFunction, getIsAzureFunction } = require('./serverless')
const { ORIGIN_KEY, GRPC_CLIENT_ERROR_STATUSES, GRPC_SERVER_ERROR_STATUSES } = require('./constants')
const {
ORIGIN_KEY, GRPC_CLIENT_ERROR_STATUSES, GRPC_SERVER_ERROR_STATUSES, INSTRUMENTED_BY_SSI
} = require('./constants')
const { appendRules } = require('./payload-tagging/config')
const { getEnvironmentVariable, getEnvironmentVariables } = require('./config-helper')

Expand Down Expand Up @@ -522,6 +524,8 @@ class Config {
this._setValue(defaults, 'iast.telemetryVerbosity', 'INFORMATION')
this._setValue(defaults, 'iast.stackTrace.enabled', true)
this._setValue(defaults, 'injectionEnabled', [])
this._setValue(defaults, 'instrumentationSource', 'manual')
this._setValue(defaults, 'injectForce', null)
this._setValue(defaults, 'isAzureFunction', false)
this._setValue(defaults, 'isCiVisibility', false)
this._setValue(defaults, 'isEarlyFlakeDetectionEnabled', false)
Expand Down Expand Up @@ -701,6 +705,7 @@ class Config {
DD_IAST_TELEMETRY_VERBOSITY,
DD_IAST_STACK_TRACE_ENABLED,
DD_INJECTION_ENABLED,
DD_INJECT_FORCE,
DD_INSTRUMENTATION_TELEMETRY_ENABLED,
DD_INSTRUMENTATION_CONFIG_ID,
DD_LOGS_INJECTION,
Expand Down Expand Up @@ -892,6 +897,7 @@ class Config {
this._setString(env, 'iast.telemetryVerbosity', DD_IAST_TELEMETRY_VERBOSITY)
this._setBoolean(env, 'iast.stackTrace.enabled', DD_IAST_STACK_TRACE_ENABLED)
this._setArray(env, 'injectionEnabled', DD_INJECTION_ENABLED)
this._setBoolean(env, 'injectForce', DD_INJECT_FORCE)
this._setBoolean(env, 'isAzureFunction', getIsAzureFunction())
this._setBoolean(env, 'isGCPFunction', getIsGCPFunction())
this._setValue(env, 'langchain.spanCharLimit', maybeInt(DD_LANGCHAIN_SPAN_CHAR_LIMIT))
Expand Down Expand Up @@ -1127,6 +1133,9 @@ class Config {
this._setValue(opts, 'iast.securityControlsConfiguration', options.iast?.securityControlsConfiguration)
this._setBoolean(opts, 'iast.stackTrace.enabled', options.iast?.stackTrace?.enabled)
this._setString(opts, 'iast.telemetryVerbosity', options.iast && options.iast.telemetryVerbosity)
if (options[INSTRUMENTED_BY_SSI]) {
this._setString(opts, 'instrumentationSource', options[INSTRUMENTED_BY_SSI])
}
this._setBoolean(opts, 'isCiVisibility', options.isCiVisibility)
this._setBoolean(opts, 'legacyBaggageEnabled', options.legacyBaggageEnabled)
this._setBoolean(opts, 'llmobs.agentlessEnabled', options.llmobs?.agentlessEnabled)
Expand Down
3 changes: 2 additions & 1 deletion packages/dd-trace/src/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -53,5 +53,6 @@ module.exports = {
SPAN_POINTER_DIRECTION: Object.freeze({
UPSTREAM: 'u',
DOWNSTREAM: 'd'
})
}),
INSTRUMENTED_BY_SSI: Symbol('_dd.instrumented.by.ssi')
}
2 changes: 2 additions & 0 deletions packages/dd-trace/src/guardrails/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,8 @@ function guard (fn) {
}

if (!clobberBailout && (!initBailout || forced)) {
// Ensure the instrumentation source is set for the current process and potential
// child processes.
var result = fn()
telemetry('complete', ['injection_forced:' + (forced && initBailout ? 'true' : 'false')])
log.info('Application instrumentation bootstrapping complete')
Expand Down
5 changes: 4 additions & 1 deletion packages/dd-trace/src/telemetry/telemetry.js
Original file line number Diff line number Diff line change
Expand Up @@ -321,7 +321,10 @@ const nameMapping = {
clientIpHeader: 'DD_TRACE_CLIENT_IP_HEADER',
'grpc.client.error.statuses': 'DD_GRPC_CLIENT_ERROR_STATUSES',
'grpc.server.error.statuses': 'DD_GRPC_SERVER_ERROR_STATUSES',
traceId128BitLoggingEnabled: 'DD_TRACE_128_BIT_TRACEID_LOGGING_ENABLED'
traceId128BitLoggingEnabled: 'DD_TRACE_128_BIT_TRACEID_LOGGING_ENABLED',
instrumentationSource: 'instrumentation_source',
injectionEnabled: 'ssi_injection_enabled',
injectForce: 'ssi_forced_injection_enabled'
}

const namesNeedFormatting = new Set(['DD_TAGS', 'peerServiceMapping', 'serviceMapping'])
Expand Down
7 changes: 7 additions & 0 deletions packages/dd-trace/test/config.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -341,6 +341,9 @@ describe('Config', () => {
expect(config).to.have.nested.property('llmobs.mlApp', undefined)
expect(config).to.have.nested.property('llmobs.agentlessEnabled', undefined)
expect(config).to.have.nested.property('llmobs.enabled', false)
expect(config).to.have.nested.deep.property('injectionEnabled', [])
expect(config).to.have.nested.property('instrumentationSource', 'manual')
expect(config).to.have.nested.property('injectForce', null)

expect(updateConfig).to.be.calledOnce

Expand Down Expand Up @@ -406,7 +409,9 @@ describe('Config', () => {
{ name: 'iast.securityControlsConfiguration', value: null, origin: 'default' },
{ name: 'iast.telemetryVerbosity', value: 'INFORMATION', origin: 'default' },
{ name: 'iast.stackTrace.enabled', value: true, origin: 'default' },
{ name: 'instrumentationSource', value: 'manual', origin: 'default' },
{ name: 'injectionEnabled', value: [], origin: 'default' },
{ name: 'injectForce', value: null, origin: 'default' },
{ name: 'isCiVisibility', value: false, origin: 'default' },
{ name: 'isEarlyFlakeDetectionEnabled', value: false, origin: 'default' },
{ name: 'isFlakyTestRetriesEnabled', value: false, origin: 'default' },
Expand Down Expand Up @@ -601,6 +606,7 @@ describe('Config', () => {
process.env.DD_TRACE_128_BIT_TRACEID_LOGGING_ENABLED = 'true'
process.env.DD_PROFILING_ENABLED = 'true'
process.env.DD_INJECTION_ENABLED = 'profiler'
process.env.DD_INJECT_FORCE = 'false'
process.env.DD_API_SECURITY_ENABLED = 'true'
process.env.DD_API_SECURITY_SAMPLE_DELAY = '25'
process.env.DD_INSTRUMENTATION_INSTALL_ID = '68e75c48-57ca-4a12-adfc-575c4b05fcbe'
Expand Down Expand Up @@ -787,6 +793,7 @@ describe('Config', () => {
{ name: 'iast.stackTrace.enabled', value: false, origin: 'env_var' },
{ name: 'instrumentation_config_id', value: 'abcdef123', origin: 'env_var' },
{ name: 'injectionEnabled', value: ['profiler'], origin: 'env_var' },
{ name: 'injectForce', value: false, origin: 'env_var' },
{ name: 'isGCPFunction', value: false, origin: 'env_var' },
{ name: 'middlewareTracingEnabled', value: false, origin: 'env_var' },
{ name: 'peerServiceMapping', value: process.env.DD_TRACE_PEER_SERVICE_MAPPING, origin: 'env_var' },
Expand Down