Skip to content

Commit 7addced

Browse files
authored
add crashtracking with libdatadog native binding (#4692)
1 parent 36903cc commit 7addced

13 files changed

Lines changed: 385 additions & 1 deletion

File tree

LICENSE-3rdparty.csv

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
Component,Origin,License,Copyright
2+
require,@datadog/libdatadog,Apache license 2.0,Copyright 2024 Datadog Inc.
23
require,@datadog/native-appsec,Apache license 2.0,Copyright 2018 Datadog Inc.
34
require,@datadog/native-metrics,Apache license 2.0,Copyright 2018 Datadog Inc.
45
require,@datadog/native-iast-rewriter,Apache license 2.0,Copyright 2018 Datadog Inc.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@
8181
"node": ">=18"
8282
},
8383
"dependencies": {
84+
"@datadog/libdatadog": "^0.2.2",
8485
"@datadog/native-appsec": "8.2.1",
8586
"@datadog/native-iast-rewriter": "2.5.0",
8687
"@datadog/native-iast-taint-tracking": "3.2.0",

packages/dd-trace/src/config.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -467,6 +467,7 @@ class Config {
467467
this._setValue(defaults, 'ciVisibilityTestSessionName', '')
468468
this._setValue(defaults, 'clientIpEnabled', false)
469469
this._setValue(defaults, 'clientIpHeader', null)
470+
this._setValue(defaults, 'crashtracking.enabled', false)
470471
this._setValue(defaults, 'codeOriginForSpans.enabled', false)
471472
this._setValue(defaults, 'dbmPropagationMode', 'disabled')
472473
this._setValue(defaults, 'dogstatsd.hostname', '127.0.0.1')
@@ -586,6 +587,7 @@ class Config {
586587
DD_APPSEC_RASP_ENABLED,
587588
DD_APPSEC_TRACE_RATE_LIMIT,
588589
DD_APPSEC_WAF_TIMEOUT,
590+
DD_CRASHTRACKING_ENABLED,
589591
DD_CODE_ORIGIN_FOR_SPANS_ENABLED,
590592
DD_DATA_STREAMS_ENABLED,
591593
DD_DBM_PROPAGATION_MODE,
@@ -730,6 +732,7 @@ class Config {
730732
this._setValue(env, 'baggageMaxItems', DD_TRACE_BAGGAGE_MAX_ITEMS)
731733
this._setBoolean(env, 'clientIpEnabled', DD_TRACE_CLIENT_IP_ENABLED)
732734
this._setString(env, 'clientIpHeader', DD_TRACE_CLIENT_IP_HEADER)
735+
this._setBoolean(env, 'crashtracking.enabled', DD_CRASHTRACKING_ENABLED)
733736
this._setBoolean(env, 'codeOriginForSpans.enabled', DD_CODE_ORIGIN_FOR_SPANS_ENABLED)
734737
this._setString(env, 'dbmPropagationMode', DD_DBM_PROPAGATION_MODE)
735738
this._setString(env, 'dogstatsd.hostname', DD_DOGSTATSD_HOSTNAME)
@@ -1138,6 +1141,9 @@ class Config {
11381141
if (iastEnabled || ['auto', 'true'].includes(profilingEnabled) || injectionIncludesProfiler) {
11391142
this._setBoolean(calc, 'telemetry.logCollection', true)
11401143
}
1144+
if (this._env.injectionEnabled?.length > 0) {
1145+
this._setBoolean(calc, 'crashtracking.enabled', true)
1146+
}
11411147
}
11421148

11431149
_applyRemote (options) {
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
'use strict'
2+
3+
// Load binding first to not import other modules if it throws
4+
const libdatadog = require('@datadog/libdatadog')
5+
const binding = libdatadog.load('crashtracker')
6+
7+
const log = require('../log')
8+
const { URL } = require('url')
9+
const pkg = require('../../../../package.json')
10+
11+
class Crashtracker {
12+
constructor () {
13+
this._started = false
14+
}
15+
16+
configure (config) {
17+
if (!this._started) return
18+
19+
try {
20+
binding.updateConfig(this._getConfig(config))
21+
binding.updateMetadata(this._getMetadata(config))
22+
} catch (e) {
23+
log.error(e)
24+
}
25+
}
26+
27+
start (config) {
28+
if (this._started) return this.configure(config)
29+
30+
this._started = true
31+
32+
try {
33+
binding.init(
34+
this._getConfig(config),
35+
this._getReceiverConfig(config),
36+
this._getMetadata(config)
37+
)
38+
} catch (e) {
39+
log.error(e)
40+
}
41+
}
42+
43+
// TODO: Send only configured values when defaults are fixed.
44+
_getConfig (config) {
45+
const { hostname = '127.0.0.1', port = 8126 } = config
46+
const url = config.url || new URL(`http://${hostname}:${port}`)
47+
48+
return {
49+
additional_files: [],
50+
create_alt_stack: true,
51+
use_alt_stack: true,
52+
endpoint: {
53+
// TODO: Use the string directly when deserialization is fixed.
54+
url: {
55+
scheme: url.protocol.slice(0, -1),
56+
authority: url.protocol === 'unix'
57+
? Buffer.from(url.pathname).toString('hex')
58+
: url.host,
59+
path_and_query: ''
60+
},
61+
timeout_ms: 3000
62+
},
63+
timeout_ms: 0,
64+
// TODO: Use `EnabledWithSymbolsInReceiver` instead for Linux when fixed.
65+
resolve_frames: 'EnabledWithInprocessSymbols'
66+
}
67+
}
68+
69+
_getMetadata (config) {
70+
const tags = Object.keys(config.tags).map(key => `${key}:${config.tags[key]}`)
71+
72+
return {
73+
library_name: pkg.name,
74+
library_version: pkg.version,
75+
family: 'nodejs',
76+
tags: [
77+
...tags,
78+
'is_crash:true',
79+
'language:javascript',
80+
`library_version:${pkg.version}`,
81+
'runtime:nodejs',
82+
'severity:crash'
83+
]
84+
}
85+
}
86+
87+
_getReceiverConfig () {
88+
return {
89+
args: [],
90+
env: [],
91+
path_to_receiver_binary: libdatadog.find('crashtracker-receiver', true),
92+
stderr_filename: null,
93+
stdout_filename: null
94+
}
95+
}
96+
}
97+
98+
module.exports = new Crashtracker()
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
'use strict'
2+
3+
const { isMainThread } = require('worker_threads')
4+
const log = require('../log')
5+
6+
if (isMainThread) {
7+
try {
8+
module.exports = require('./crashtracker')
9+
} catch (e) {
10+
log.warn(e.message)
11+
module.exports = require('./noop')
12+
}
13+
} else {
14+
module.exports = require('./noop')
15+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
'use strict'
2+
3+
class NoopCrashtracker {
4+
configure () {}
5+
start () {}
6+
}
7+
8+
module.exports = new NoopCrashtracker()

packages/dd-trace/src/proxy.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,11 @@ class Tracer extends NoopProxy {
5959

6060
try {
6161
const config = new Config(options) // TODO: support dynamic code config
62+
63+
if (config.crashtracking.enabled) {
64+
require('./crashtracking').start(config)
65+
}
66+
6267
telemetry.start(config, this._pluginManager)
6368

6469
if (config.dogstatsd) {

packages/dd-trace/test/config.spec.js

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -212,6 +212,7 @@ describe('Config', () => {
212212
expect(config).to.have.property('queryStringObfuscation').with.length(626)
213213
expect(config).to.have.property('clientIpEnabled', false)
214214
expect(config).to.have.property('clientIpHeader', null)
215+
expect(config).to.have.nested.property('crashtracking.enabled', false)
215216
expect(config).to.have.property('sampleRate', undefined)
216217
expect(config).to.have.property('runtimeMetrics', false)
217218
expect(config.tags).to.have.property('service', 'node')
@@ -440,6 +441,7 @@ describe('Config', () => {
440441
process.env.DD_TRACE_OBFUSCATION_QUERY_STRING_REGEXP = '.*'
441442
process.env.DD_TRACE_CLIENT_IP_ENABLED = 'true'
442443
process.env.DD_TRACE_CLIENT_IP_HEADER = 'x-true-client-ip'
444+
process.env.DD_CRASHTRACKING_ENABLED = 'true'
443445
process.env.DD_RUNTIME_METRICS_ENABLED = 'true'
444446
process.env.DD_TRACE_REPORT_HOSTNAME = 'true'
445447
process.env.DD_ENV = 'test'
@@ -529,6 +531,7 @@ describe('Config', () => {
529531
expect(config).to.have.property('queryStringObfuscation', '.*')
530532
expect(config).to.have.property('clientIpEnabled', true)
531533
expect(config).to.have.property('clientIpHeader', 'x-true-client-ip')
534+
expect(config).to.have.nested.property('crashtracking.enabled', true)
532535
expect(config.grpc.client.error.statuses).to.deep.equal([3, 13, 400, 401, 402, 403])
533536
expect(config.grpc.server.error.statuses).to.deep.equal([3, 13, 400, 401, 402, 403])
534537
expect(config).to.have.property('runtimeMetrics', true)
@@ -633,6 +636,7 @@ describe('Config', () => {
633636
{ name: 'appsec.wafTimeout', value: '42', origin: 'env_var' },
634637
{ name: 'clientIpEnabled', value: true, origin: 'env_var' },
635638
{ name: 'clientIpHeader', value: 'x-true-client-ip', origin: 'env_var' },
639+
{ name: 'crashtracking.enabled', value: true, origin: 'env_var' },
636640
{ name: 'codeOriginForSpans.enabled', value: true, origin: 'env_var' },
637641
{ name: 'dogstatsd.hostname', value: 'dsd-agent', origin: 'env_var' },
638642
{ name: 'dogstatsd.port', value: '5218', origin: 'env_var' },
@@ -738,6 +742,23 @@ describe('Config', () => {
738742
expect(config).to.have.nested.deep.property('tracePropagationStyle.extract', ['tracecontext'])
739743
})
740744

745+
it('should enable crash tracking for SSI by default', () => {
746+
process.env.DD_INJECTION_ENABLED = 'tracer'
747+
748+
const config = new Config()
749+
750+
expect(config).to.have.nested.deep.property('crashtracking.enabled', true)
751+
})
752+
753+
it('should disable crash tracking for SSI when configured', () => {
754+
process.env.DD_CRASHTRACKING_ENABLED = 'false'
755+
process.env.DD_INJECTION_ENABLED = 'tracer'
756+
757+
const config = new Config()
758+
759+
expect(config).to.have.nested.deep.property('crashtracking.enabled', false)
760+
})
761+
741762
it('should initialize from the options', () => {
742763
const logger = {}
743764
const tags = {
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
'use strict'
2+
3+
const { expect } = require('chai')
4+
const sinon = require('sinon')
5+
const proxyquire = require('proxyquire').noCallThru()
6+
7+
require('../setup/tap')
8+
9+
describe('crashtracking', () => {
10+
describe('crashtracker', () => {
11+
let crashtracker
12+
let binding
13+
let config
14+
let libdatadog
15+
let log
16+
17+
beforeEach(() => {
18+
libdatadog = require('@datadog/libdatadog')
19+
20+
binding = libdatadog.load('crashtracker')
21+
22+
config = {
23+
port: 7357,
24+
tags: {
25+
foo: 'bar'
26+
}
27+
}
28+
29+
log = {
30+
error: sinon.stub()
31+
}
32+
33+
sinon.spy(binding, 'init')
34+
sinon.spy(binding, 'updateConfig')
35+
sinon.spy(binding, 'updateMetadata')
36+
37+
crashtracker = proxyquire('../../src/crashtracking/crashtracker', {
38+
'../log': log
39+
})
40+
})
41+
42+
afterEach(() => {
43+
binding.init.restore()
44+
binding.updateConfig.restore()
45+
binding.updateMetadata.restore()
46+
})
47+
48+
describe('start', () => {
49+
it('should initialize the binding', () => {
50+
crashtracker.start(config)
51+
52+
expect(binding.init).to.have.been.called
53+
expect(log.error).to.not.have.been.called
54+
})
55+
56+
it('should initialize the binding only once', () => {
57+
crashtracker.start(config)
58+
crashtracker.start(config)
59+
60+
expect(binding.init).to.have.been.calledOnce
61+
})
62+
63+
it('should reconfigure when started multiple times', () => {
64+
crashtracker.start(config)
65+
crashtracker.start(config)
66+
67+
expect(binding.updateConfig).to.have.been.called
68+
expect(binding.updateMetadata).to.have.been.called
69+
})
70+
71+
it('should handle errors', () => {
72+
crashtracker.start(null)
73+
74+
expect(() => crashtracker.start(config)).to.not.throw()
75+
})
76+
})
77+
78+
describe('configure', () => {
79+
it('should reconfigure the binding when started', () => {
80+
crashtracker.start(config)
81+
crashtracker.configure(config)
82+
83+
expect(binding.updateConfig).to.have.been.called
84+
expect(binding.updateMetadata).to.have.been.called
85+
})
86+
87+
it('should reconfigure the binding only when started', () => {
88+
crashtracker.configure(config)
89+
90+
expect(binding.updateConfig).to.not.have.been.called
91+
expect(binding.updateMetadata).to.not.have.been.called
92+
})
93+
94+
it('should handle errors', () => {
95+
crashtracker.start(config)
96+
crashtracker.configure(null)
97+
98+
expect(() => crashtracker.configure(config)).to.not.throw()
99+
})
100+
})
101+
})
102+
})

0 commit comments

Comments
 (0)