Skip to content

Commit b7f5fca

Browse files
authored
fix: prevent duplicate debug logs when multiple undici instances exist (#4630)
1 parent cb2b04e commit b7f5fca

File tree

3 files changed

+89
-0
lines changed

3 files changed

+89
-0
lines changed

lib/core/diagnostics.js

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,14 @@ function trackClientEvents (debugLog = undiciDebugLog) {
3636
return
3737
}
3838

39+
// Check if any of the channels already have subscribers to prevent duplicate subscriptions
40+
// This can happen when both Node.js built-in undici and undici as a dependency are present
41+
if (channels.beforeConnect.hasSubscribers || channels.connected.hasSubscribers ||
42+
channels.connectError.hasSubscribers || channels.sendHeaders.hasSubscribers) {
43+
isTrackingClientEvents = true
44+
return
45+
}
46+
3947
isTrackingClientEvents = true
4048

4149
diagnosticsChannel.subscribe('undici:client:beforeConnect',
@@ -98,6 +106,14 @@ function trackRequestEvents (debugLog = undiciDebugLog) {
98106
return
99107
}
100108

109+
// Check if any of the channels already have subscribers to prevent duplicate subscriptions
110+
// This can happen when both Node.js built-in undici and undici as a dependency are present
111+
if (channels.headers.hasSubscribers || channels.trailers.hasSubscribers ||
112+
channels.error.hasSubscribers) {
113+
isTrackingRequestEvents = true
114+
return
115+
}
116+
101117
isTrackingRequestEvents = true
102118

103119
diagnosticsChannel.subscribe('undici:request:headers',
@@ -146,6 +162,15 @@ function trackWebSocketEvents (debugLog = websocketDebuglog) {
146162
return
147163
}
148164

165+
// Check if any of the channels already have subscribers to prevent duplicate subscriptions
166+
// This can happen when both Node.js built-in undici and undici as a dependency are present
167+
if (channels.open.hasSubscribers || channels.close.hasSubscribers ||
168+
channels.socketError.hasSubscribers || channels.ping.hasSubscribers ||
169+
channels.pong.hasSubscribers) {
170+
isTrackingWebSocketEvents = true
171+
return
172+
}
173+
149174
isTrackingWebSocketEvents = true
150175

151176
diagnosticsChannel.subscribe('undici:websocket:open',

test/fixtures/duplicate-debug.js

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
'use strict'
2+
3+
const { createServer } = require('node:http')
4+
const { request } = require('../..')
5+
6+
// Simulate the scenario where diagnostics module is loaded multiple times
7+
// This mimics having both Node.js built-in undici and undici as dependency
8+
delete require.cache[require.resolve('../../lib/core/diagnostics.js')]
9+
require('../../lib/core/diagnostics.js')
10+
11+
const server = createServer({ joinDuplicateHeaders: true }, (_req, res) => {
12+
res.writeHead(200, { 'Content-Type': 'text/plain' })
13+
res.end('hello world')
14+
})
15+
16+
server.listen(0, () => {
17+
const { port, address, family } = server.address()
18+
const hostname = family === 'IPv6' ? `[${address}]` : address
19+
request(`http://${hostname}:${port}`)
20+
.then(res => res.body.dump())
21+
.then(() => {
22+
server.close()
23+
})
24+
})

test/node-test/debug.js

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,46 @@ test('debug#undici', { skip: isCITGM || isNode23Plus }, async t => {
126126
await assert.completed
127127
})
128128

129+
test('debug#undici no duplicates', { skip: isCITGM || isNode23Plus }, async t => {
130+
const assert = tspl(t, { plan: 7 })
131+
const child = spawn(
132+
process.execPath,
133+
[
134+
'--no-experimental-fetch',
135+
join(__dirname, '../fixtures/duplicate-debug.js')
136+
],
137+
{
138+
env: {
139+
NODE_DEBUG: 'undici'
140+
}
141+
}
142+
)
143+
const chunks = []
144+
const assertions = [
145+
/(UNDICI [0-9]+:) (connecting to)/,
146+
/(UNDICI [0-9]+:) (connected to)/,
147+
/(UNDICI [0-9]+:) (sending request)/,
148+
/(UNDICI [0-9]+:) (received response)/,
149+
/(UNDICI [0-9]+:) (trailers received)/,
150+
/^$/
151+
]
152+
153+
child.stderr.setEncoding('utf8')
154+
child.stderr.on('data', chunk => {
155+
chunks.push(chunk)
156+
})
157+
child.stderr.on('end', () => {
158+
const lines = extractLines(chunks)
159+
// Should have exactly the expected number of lines, no duplicates
160+
assert.strictEqual(lines.length, assertions.length, 'Should not have duplicate log lines')
161+
for (let i = 0; i < lines.length; i++) {
162+
assert.match(lines[i], assertions[i])
163+
}
164+
})
165+
166+
await assert.completed
167+
})
168+
129169
function extractLines (chunks) {
130170
return chunks
131171
.join('')

0 commit comments

Comments
 (0)