Skip to content

Commit 53714a1

Browse files
authored
chore: always redact exception messages for logs in telemetry (#6740)
1 parent 3debabc commit 53714a1

File tree

2 files changed

+72
-13
lines changed

2 files changed

+72
-13
lines changed

packages/dd-trace/src/telemetry/logs/log-collector.js

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -41,12 +41,14 @@ function sanitize (logEntry) {
4141

4242
const firstIndex = stackLines.findIndex(l => l.match(STACK_FRAME_LINE_REGEX))
4343

44-
const isDDCode = firstIndex !== -1 && stackLines[firstIndex].includes(ddBasePath)
44+
// Filter to keep only DD frames
4545
stackLines = stackLines
46-
.filter((line, index) => (isDDCode && index < firstIndex) || line.includes(ddBasePath))
46+
.filter((line, index) => index >= firstIndex && line.includes(ddBasePath))
4747
.map(line => line.replace(ddBasePath, ''))
4848

49-
if (!isDDCode && logEntry.errorType && stackLines.length) {
49+
// ALWAYS redact error messages (RFC requirement: exception type only, no message)
50+
// This handles single-line and multi-line error messages
51+
if (logEntry.errorType && stackLines.length) {
5052
stackLines = [`${logEntry.errorType}: redacted`, ...stackLines]
5153
}
5254

packages/dd-trace/test/telemetry/logs/log-collector.spec.js

Lines changed: 67 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -30,17 +30,49 @@ describe('telemetry log collector', () => {
3030
})
3131

3232
it('should store logs with same message but different stack', () => {
33-
const ddFrame = `at T (${ddBasePath}path/to/dd/file.js:1:2)`
34-
expect(logCollector.add({ message: 'Error 1', level: 'ERROR', stack_trace: `stack 1\n${ddFrame}` })).to.be.true
35-
expect(logCollector.add({ message: 'Error 1', level: 'ERROR', stack_trace: `stack 2\n${ddFrame}` })).to.be.true
36-
expect(logCollector.add({ message: 'Error 1', level: 'ERROR', stack_trace: `stack 3\n${ddFrame}` })).to.be.true
33+
const ddFrame1 = `at T (${ddBasePath}path/to/dd/file1.js:1:2)`
34+
const ddFrame2 = `at T (${ddBasePath}path/to/dd/file2.js:3:4)`
35+
const ddFrame3 = `at T (${ddBasePath}path/to/dd/file3.js:5:6)`
36+
expect(logCollector.add({
37+
message: 'Error 1',
38+
level: 'ERROR',
39+
stack_trace: `Error: msg\n${ddFrame1}`,
40+
errorType: 'Error'
41+
})).to.be.true
42+
expect(logCollector.add({
43+
message: 'Error 1',
44+
level: 'ERROR',
45+
stack_trace: `Error: msg\n${ddFrame2}`,
46+
errorType: 'Error'
47+
})).to.be.true
48+
expect(logCollector.add({
49+
message: 'Error 1',
50+
level: 'ERROR',
51+
stack_trace: `Error: msg\n${ddFrame3}`,
52+
errorType: 'Error'
53+
})).to.be.true
3754
})
3855

3956
it('should store logs with same message, same stack but different level', () => {
4057
const ddFrame = `at T (${ddBasePath}path/to/dd/file.js:1:2)`
41-
expect(logCollector.add({ message: 'Error 1', level: 'ERROR', stack_trace: `stack 1\n${ddFrame}` })).to.be.true
42-
expect(logCollector.add({ message: 'Error 1', level: 'WARN', stack_trace: `stack 1\n${ddFrame}` })).to.be.true
43-
expect(logCollector.add({ message: 'Error 1', level: 'DEBUG', stack_trace: `stack 1\n${ddFrame}` })).to.be.true
58+
expect(logCollector.add({
59+
message: 'Error 1',
60+
level: 'ERROR',
61+
stack_trace: `Error: msg\n${ddFrame}`,
62+
errorType: 'Error'
63+
})).to.be.true
64+
expect(logCollector.add({
65+
message: 'Error 1',
66+
level: 'WARN',
67+
stack_trace: `Error: msg\n${ddFrame}`,
68+
errorType: 'Error'
69+
})).to.be.true
70+
expect(logCollector.add({
71+
message: 'Error 1',
72+
level: 'DEBUG',
73+
stack_trace: `Error: msg\n${ddFrame}`,
74+
errorType: 'Error'
75+
})).to.be.true
4476
})
4577

4678
it('should not store logs with empty stack and \'Generic Error\' message', () => {
@@ -52,7 +84,7 @@ describe('telemetry log collector', () => {
5284
).to.be.false
5385
})
5486

55-
it('should include original message and dd frames', () => {
87+
it('should redact error message and include only dd frames', () => {
5688
const ddFrame = `at T (${ddBasePath}path/to/dd/file.js:1:2)`
5789
const stack = new TypeError('Error 1')
5890
.stack.replace(`Error 1${EOL}`, `Error 1${EOL}${ddFrame}${EOL}`)
@@ -73,11 +105,11 @@ describe('telemetry log collector', () => {
73105
expect(logCollector.hasEntry({
74106
message: 'Error 1',
75107
level: 'ERROR',
76-
stack_trace: `TypeError: Error 1${EOL}${ddFrames}`
108+
stack_trace: `TypeError: redacted${EOL}${ddFrames}`
77109
})).to.be.true
78110
})
79111

80-
it('should redact stack message if first frame is not a dd frame', () => {
112+
it('should redact error message regardless of whether first frame is DD code', () => {
81113
const thirdPartyFrame = `at callFn (/this/is/not/a/dd/frame/runnable.js:366:21)
82114
at T (${ddBasePath}path/to/dd/file.js:1:2)`
83115
const stack = new TypeError('Error 1')
@@ -104,6 +136,31 @@ describe('telemetry log collector', () => {
104136
stack_trace: ddFrames
105137
})).to.be.true
106138
})
139+
140+
it('should redact multi-line error messages', () => {
141+
const ddFrame = `at cachedExec (${ddBasePath}plugins/util/git-cache.js:96:17)`
142+
const multiLineError = 'Error: Command failed: git rev-parse --abbrev-ref ' +
143+
`--symbolic-full-name @{upstream}${EOL}fatal: HEAD does not point to a branch${EOL}${EOL}${ddFrame}`
144+
145+
const ddFrames = multiLineError
146+
.split(EOL)
147+
.filter(line => line.includes(ddBasePath))
148+
.map(line => line.replace(ddBasePath, ''))
149+
.join(EOL)
150+
151+
expect(logCollector.add({
152+
message: 'Git plugin error',
153+
level: 'ERROR',
154+
stack_trace: multiLineError,
155+
errorType: 'Error'
156+
})).to.be.true
157+
158+
expect(logCollector.hasEntry({
159+
message: 'Git plugin error',
160+
level: 'ERROR',
161+
stack_trace: `Error: redacted${EOL}${ddFrames}`
162+
})).to.be.true
163+
})
107164
})
108165

109166
describe('drain', () => {

0 commit comments

Comments
 (0)