Skip to content

Commit ac19207

Browse files
authored
update guardrails to report telemetry in old node versions (#4949)
1 parent 82c489b commit ac19207

File tree

9 files changed

+195
-171
lines changed

9 files changed

+195
-171
lines changed

Diff for: .github/workflows/project.yml

+2-2
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ jobs:
3434
integration-guardrails:
3535
strategy:
3636
matrix:
37-
version: [12.0.0, 12, 14.0.0, 14, 16.0.0, 16, 18.0.0, 18.1.0, 20.0.0, 22.0.0]
37+
version: [12, 14.0.0, 14, 16.0.0, 16, 18.0.0, 18.1.0, 20.0.0, 22.0.0]
3838
runs-on: ubuntu-latest
3939
steps:
4040
- uses: actions/checkout@v4
@@ -47,7 +47,7 @@ jobs:
4747
integration-guardrails-unsupported:
4848
strategy:
4949
matrix:
50-
version: ['0.8', '0.10', '0.12', '4', '6', '8', '10']
50+
version: ['0.8', '0.10', '0.12', '4', '6', '8', '10', '12.0.0']
5151
runs-on: ubuntu-latest
5252
env:
5353
DD_INJECTION_ENABLED: 'true'

Diff for: init.js

+4-68
Original file line numberDiff line numberDiff line change
@@ -2,72 +2,8 @@
22

33
/* eslint-disable no-var */
44

5-
var nodeVersion = require('./version')
6-
var NODE_MAJOR = nodeVersion.NODE_MAJOR
7-
var NODE_MINOR = nodeVersion.NODE_MINOR
5+
var guard = require('./packages/dd-trace/src/guardrails')
86

9-
// We use several things that are not supported by older versions of Node:
10-
// - AsyncLocalStorage
11-
// - The `semver` module
12-
// - dc-polyfill
13-
// - Mocha (for testing)
14-
// and probably others.
15-
// TODO: Remove all these dependencies so that we can report telemetry.
16-
if ((NODE_MAJOR === 12 && NODE_MINOR >= 17) || NODE_MAJOR > 12) {
17-
var path = require('path')
18-
var Module = require('module')
19-
var semver = require('semver')
20-
var log = require('./packages/dd-trace/src/log')
21-
var isTrue = require('./packages/dd-trace/src/util').isTrue
22-
var telemetry = require('./packages/dd-trace/src/telemetry/init-telemetry')
23-
24-
var initBailout = false
25-
var clobberBailout = false
26-
var forced = isTrue(process.env.DD_INJECT_FORCE)
27-
28-
if (process.env.DD_INJECTION_ENABLED) {
29-
// If we're running via single-step install, and we're not in the app's
30-
// node_modules, then we should not initialize the tracer. This prevents
31-
// single-step-installed tracer from clobbering the manually-installed tracer.
32-
var resolvedInApp
33-
var entrypoint = process.argv[1]
34-
try {
35-
resolvedInApp = Module.createRequire(entrypoint).resolve('dd-trace')
36-
} catch (e) {
37-
// Ignore. If we can't resolve the module, we assume it's not in the app.
38-
}
39-
if (resolvedInApp) {
40-
var ourselves = path.join(__dirname, 'index.js')
41-
if (ourselves !== resolvedInApp) {
42-
clobberBailout = true
43-
}
44-
}
45-
46-
// If we're running via single-step install, and the runtime doesn't match
47-
// the engines field in package.json, then we should not initialize the tracer.
48-
if (!clobberBailout) {
49-
var engines = require('./package.json').engines
50-
var version = process.versions.node
51-
if (!semver.satisfies(version, engines.node)) {
52-
initBailout = true
53-
telemetry([
54-
{ name: 'abort', tags: ['reason:incompatible_runtime'] },
55-
{ name: 'abort.runtime', tags: [] }
56-
])
57-
log.info('Aborting application instrumentation due to incompatible_runtime.')
58-
log.info('Found incompatible runtime nodejs ' + version + ', Supported runtimes: nodejs ' + engines.node + '.')
59-
if (forced) {
60-
log.info('DD_INJECT_FORCE enabled, allowing unsupported runtimes and continuing.')
61-
}
62-
}
63-
}
64-
}
65-
66-
if (!clobberBailout && (!initBailout || forced)) {
67-
var tracer = require('.')
68-
tracer.init()
69-
module.exports = tracer
70-
telemetry('complete', ['injection_forced:' + (forced && initBailout ? 'true' : 'false')])
71-
log.info('Application instrumentation bootstrapping complete')
72-
}
73-
}
7+
module.exports = guard(function () {
8+
return require('.').init()
9+
})

Diff for: integration-tests/init.spec.js

+1-25
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@ const telemetryGood = ['complete', 'injection_forced:false']
2020
const { engines } = require('../package.json')
2121
const supportedRange = engines.node
2222
const currentVersionIsSupported = semver.satisfies(process.versions.node, supportedRange)
23-
const currentVersionCanLog = semver.satisfies(process.versions.node, '>=12.17.0')
2423

2524
// These are on by default in release tests, so we'll turn them off for
2625
// more fine-grained control of these variables in these tests.
@@ -84,30 +83,7 @@ function testRuntimeVersionChecks (arg, filename) {
8483
}
8584
}
8685

87-
if (!currentVersionCanLog) {
88-
context('when node version is too low for AsyncLocalStorage', () => {
89-
useEnv({ NODE_OPTIONS })
90-
91-
it('should initialize the tracer, if no DD_INJECTION_ENABLED', () =>
92-
doTest('false\n'))
93-
context('with DD_INJECTION_ENABLED', () => {
94-
useEnv({ DD_INJECTION_ENABLED })
95-
96-
context('without debug', () => {
97-
it('should not initialize the tracer', () => doTest('false\n'))
98-
it('should not, if DD_INJECT_FORCE', () => doTestForced('false\n'))
99-
})
100-
context('with debug', () => {
101-
useEnv({ DD_TRACE_DEBUG })
102-
103-
it('should not initialize the tracer', () =>
104-
doTest('false\n'))
105-
it('should initialize the tracer, if DD_INJECT_FORCE', () =>
106-
doTestForced('false\n'))
107-
})
108-
})
109-
})
110-
} else if (!currentVersionIsSupported) {
86+
if (!currentVersionIsSupported) {
11187
context('when node version is less than engines field', () => {
11288
useEnv({ NODE_OPTIONS })
11389

Diff for: packages/datadog-instrumentations/src/helpers/register.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ const Hook = require('./hook')
77
const requirePackageJson = require('../../../dd-trace/src/require-package-json')
88
const log = require('../../../dd-trace/src/log')
99
const checkRequireCache = require('../check_require_cache')
10-
const telemetry = require('../../../dd-trace/src/telemetry/init-telemetry')
10+
const telemetry = require('../../../dd-trace/src/guardrails/telemetry')
1111

1212
const {
1313
DD_TRACE_DISABLED_INSTRUMENTATIONS = '',

Diff for: packages/dd-trace/src/guardrails/index.js

+67
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
'use strict'
2+
3+
/* eslint-disable no-var */
4+
5+
var path = require('path')
6+
var Module = require('module')
7+
var isTrue = require('./util').isTrue
8+
var log = require('./log')
9+
var telemetry = require('./telemetry')
10+
var nodeVersion = require('../../../../version')
11+
12+
var NODE_MAJOR = nodeVersion.NODE_MAJOR
13+
14+
// TODO: Test telemetry for Node <12. For now only bailout is tested for those.
15+
function guard (fn) {
16+
var initBailout = false
17+
var clobberBailout = false
18+
var forced = isTrue(process.env.DD_INJECT_FORCE)
19+
20+
if (process.env.DD_INJECTION_ENABLED) {
21+
// If we're running via single-step install, and we're not in the app's
22+
// node_modules, then we should not initialize the tracer. This prevents
23+
// single-step-installed tracer from clobbering the manually-installed tracer.
24+
var resolvedInApp
25+
var entrypoint = process.argv[1]
26+
try {
27+
resolvedInApp = Module.createRequire(entrypoint).resolve('dd-trace')
28+
} catch (e) {
29+
// Ignore. If we can't resolve the module, we assume it's not in the app.
30+
}
31+
if (resolvedInApp) {
32+
var ourselves = path.normalize(path.join(__dirname, '..', '..', '..', '..', 'index.js'))
33+
if (ourselves !== resolvedInApp) {
34+
clobberBailout = true
35+
}
36+
}
37+
38+
// If we're running via single-step install, and the runtime doesn't match
39+
// the engines field in package.json, then we should not initialize the tracer.
40+
if (!clobberBailout) {
41+
var engines = require('../../../../package.json').engines
42+
var minMajor = parseInt(engines.node.replace(/[^0-9]/g, ''))
43+
var version = process.versions.node
44+
if (NODE_MAJOR < minMajor) {
45+
initBailout = true
46+
telemetry([
47+
{ name: 'abort', tags: ['reason:incompatible_runtime'] },
48+
{ name: 'abort.runtime', tags: [] }
49+
])
50+
log.info('Aborting application instrumentation due to incompatible_runtime.')
51+
log.info('Found incompatible runtime nodejs ' + version + ', Supported runtimes: nodejs ' + engines.node + '.')
52+
if (forced) {
53+
log.info('DD_INJECT_FORCE enabled, allowing unsupported runtimes and continuing.')
54+
}
55+
}
56+
}
57+
}
58+
59+
if (!clobberBailout && (!initBailout || forced)) {
60+
var result = fn()
61+
telemetry('complete', ['injection_forced:' + (forced && initBailout ? 'true' : 'false')])
62+
log.info('Application instrumentation bootstrapping complete')
63+
return result
64+
}
65+
}
66+
67+
module.exports = guard

Diff for: packages/dd-trace/src/guardrails/log.js

+32
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
'use strict'
2+
3+
/* eslint-disable no-var */
4+
/* eslint-disable no-console */
5+
6+
var isTrue = require('./util').isTrue
7+
8+
var DD_TRACE_DEBUG = process.env.DD_TRACE_DEBUG
9+
var DD_TRACE_LOG_LEVEL = process.env.DD_TRACE_LOG_LEVEL
10+
11+
var logLevels = {
12+
trace: 20,
13+
debug: 20,
14+
info: 30,
15+
warn: 40,
16+
error: 50,
17+
critical: 50,
18+
off: 100
19+
}
20+
21+
var logLevel = isTrue(DD_TRACE_DEBUG)
22+
? Number(DD_TRACE_LOG_LEVEL) || logLevels.debug
23+
: logLevels.off
24+
25+
var log = {
26+
debug: logLevel <= 20 ? console.debug.bind(console) : function () {},
27+
info: logLevel <= 30 ? console.info.bind(console) : function () {},
28+
warn: logLevel <= 40 ? console.warn.bind(console) : function () {},
29+
error: logLevel <= 50 ? console.error.bind(console) : function () {}
30+
}
31+
32+
module.exports = log

Diff for: packages/dd-trace/src/guardrails/telemetry.js

+78
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
'use strict'
2+
3+
/* eslint-disable no-var */
4+
/* eslint-disable object-shorthand */
5+
6+
var fs = require('fs')
7+
var spawn = require('child_process').spawn
8+
var tracerVersion = require('../../../../package.json').version
9+
var log = require('./log')
10+
11+
module.exports = sendTelemetry
12+
13+
if (!process.env.DD_INJECTION_ENABLED) {
14+
module.exports = function () {}
15+
}
16+
17+
if (!process.env.DD_TELEMETRY_FORWARDER_PATH) {
18+
module.exports = function () {}
19+
}
20+
21+
if (!fs.existsSync(process.env.DD_TELEMETRY_FORWARDER_PATH)) {
22+
module.exports = function () {}
23+
}
24+
25+
var metadata = {
26+
language_name: 'nodejs',
27+
language_version: process.versions.node,
28+
runtime_name: 'nodejs',
29+
runtime_version: process.versions.node,
30+
tracer_version: tracerVersion,
31+
pid: process.pid
32+
}
33+
34+
var seen = []
35+
function hasSeen (point) {
36+
if (point.name === 'abort') {
37+
// This one can only be sent once, regardless of tags
38+
return seen.includes('abort')
39+
}
40+
if (point.name === 'abort.integration') {
41+
// For now, this is the only other one we want to dedupe
42+
var compiledPoint = point.name + point.tags.join('')
43+
return seen.includes(compiledPoint)
44+
}
45+
return false
46+
}
47+
48+
function sendTelemetry (name, tags) {
49+
var points = name
50+
if (typeof name === 'string') {
51+
points = [{ name: name, tags: tags || [] }]
52+
}
53+
if (['1', 'true', 'True'].indexOf(process.env.DD_INJECT_FORCE) !== -1) {
54+
points = points.filter(function (p) { return ['error', 'complete'].includes(p.name) })
55+
}
56+
points = points.filter(function (p) { return !hasSeen(p) })
57+
for (var i = 0; i < points.length; i++) {
58+
points[i].name = 'library_entrypoint.' + points[i].name
59+
}
60+
if (points.length === 0) {
61+
return
62+
}
63+
var proc = spawn(process.env.DD_TELEMETRY_FORWARDER_PATH, ['library_entrypoint'], {
64+
stdio: 'pipe'
65+
})
66+
proc.on('error', function () {
67+
log.error('Failed to spawn telemetry forwarder')
68+
})
69+
proc.on('exit', function (code) {
70+
if (code !== 0) {
71+
log.error('Telemetry forwarder exited with code ' + code)
72+
}
73+
})
74+
proc.stdin.on('error', function () {
75+
log.error('Failed to write telemetry data to telemetry forwarder')
76+
})
77+
proc.stdin.end(JSON.stringify({ metadata: metadata, points: points }))
78+
}

Diff for: packages/dd-trace/src/guardrails/util.js

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
'use strict'
2+
3+
/* eslint-disable object-shorthand */
4+
5+
function isTrue (str) {
6+
str = String(str).toLowerCase()
7+
return str === 'true' || str === '1'
8+
}
9+
10+
module.exports = { isTrue: isTrue }

0 commit comments

Comments
 (0)