Diagnostics Channel Support
History
Undici is instrumented with Node.js' built-in diagnostics_channel module.
It is the preferred way to observe undici's internal behaviour without patching
the library, and it backs the human-readable debug logs.
Each integration point is exposed as a named channel. To observe an event, obtain the channel by name and subscribe to it:
import diagnosticsChannel from 'node:diagnostics_channel'
diagnosticsChannel.channel('undici:request:create').subscribe(({ request }) => {
console.log(request.method, request.origin, request.path)
})The message published to a channel is always a single object whose shape depends on the channel. The sections below describe every channel and the object it publishes. Subscriber callbacks run synchronously inside undici, so they should be fast and must not throw.
The same request object is shared across all undici:request:* channels for a
given request, so a subscriber may correlate the lifecycle of one request by
identity.
Published when a new outgoing request is created, before it is dispatched.
<Object><Object>The request object also exposes an addHeader(name, value) method that
appends a header before the request is sent.
import diagnosticsChannel from 'node:diagnostics_channel'
diagnosticsChannel.channel('undici:request:create').subscribe(({ request }) => {
console.log('origin', request.origin)
console.log('completed', request.completed)
console.log('method', request.method)
console.log('path', request.path)
console.log('headers', request.headers)
request.addHeader('hello', 'world')
})A request is only loosely bound to a given socket at this stage.
Published each time a chunk of the request body is written to the socket.
<Object><Object>'undici:request:create'
.<Uint8Array>
|
<string>import diagnosticsChannel from 'node:diagnostics_channel'
diagnosticsChannel.channel('undici:request:bodyChunkSent').subscribe(({ request, chunk }) => {
console.log('sent', chunk.length, 'bytes')
})Published after the request body has been fully sent.
<Object><Object>'undici:request:create'
.import diagnosticsChannel from 'node:diagnostics_channel'
diagnosticsChannel.channel('undici:request:bodySent').subscribe(({ request }) => {
// request is the same object as in 'undici:request:create'
})Published after the response headers have been received.
<Object><Object>'undici:request:create'
.import diagnosticsChannel from 'node:diagnostics_channel'
diagnosticsChannel.channel('undici:request:headers').subscribe(({ request, response }) => {
console.log('statusCode', response.statusCode)
console.log(response.statusText)
console.log(response.headers.map((x) => x.toString()))
})Published each time a chunk of the response body is received.
<Object><Object>'undici:request:create'
.<Buffer>import diagnosticsChannel from 'node:diagnostics_channel'
diagnosticsChannel.channel('undici:request:bodyChunkReceived').subscribe(({ request, chunk }) => {
console.log('received', chunk.length, 'bytes')
})Published after the response body and trailers have been received, that is, once the response has fully completed.
import diagnosticsChannel from 'node:diagnostics_channel'
diagnosticsChannel.channel('undici:request:trailers').subscribe(({ request, trailers }) => {
console.log('completed', request.completed)
console.log(trailers.map((x) => x.toString()))
})Published when the request is about to error, before the error is surfaced to the caller.
<Object><Object>'undici:request:create'
.<Error>import diagnosticsChannel from 'node:diagnostics_channel'
diagnosticsChannel.channel('undici:request:error').subscribe(({ request, error }) => {
console.error(error)
})undici:client:sendHeaders
History
Refactor the diagnostics channel internals.
Published immediately before the first byte of the request is written to the socket. The headers are published exactly as they will be sent to the server, in raw form.
<Object><Object>'undici:request:create'
.<string>\r\n
.<net.Socket>import diagnosticsChannel from 'node:diagnostics_channel'
diagnosticsChannel.channel('undici:client:sendHeaders').subscribe(({ request, headers, socket }) => {
console.log(`Full headers list: ${headers.split('\r\n')}`)
})undici:client:beforeConnect
History
Refactor the diagnostics channel internals.
Published before a new connection is opened for any request. This event cannot be attributed to a specific request, because connections are pooled and shared.
<Object><Object><Function>import diagnosticsChannel from 'node:diagnostics_channel'
diagnosticsChannel.channel('undici:client:beforeConnect').subscribe(({ connectParams, connector }) => {
const { host, hostname, protocol, port, servername } = connectParams
// connector is the function that creates the socket
})undici:client:connected
History
Refactor the diagnostics channel internals.
Published after a connection has been successfully established.
<Object><net.Socket><Object>'undici:client:beforeConnect'
for the field descriptions.<Function>import diagnosticsChannel from 'node:diagnostics_channel'
diagnosticsChannel.channel('undici:client:connected').subscribe(({ socket, connectParams, connector }) => {
const { host, hostname, protocol, port, servername } = connectParams
})undici:client:connectError
History
Refactor the diagnostics channel internals.
Published when a connection could not be established.
<Object><Error><net.Socket><Object>'undici:client:beforeConnect'
for the field descriptions.<Function>import diagnosticsChannel from 'node:diagnostics_channel'
diagnosticsChannel.channel('undici:client:connectError').subscribe(({ error, socket, connectParams, connector }) => {
console.error(`Connect failed with ${error.message}`)
})Published after a WebSocket has successfully connected to a server.
<Object>socket.address()
, or
null
if it cannot be determined. When present,
it contains
address
,
family
, and
port
.<string><string><WebSocket>WebSocket
instance.<Object><number>101
for an HTTP/1.1 upgrade, or
200
for an HTTP/2 extended
CONNECT
.<string>'Switching Protocols'
for
HTTP/1.1, and commonly
'OK'
for HTTP/2.<Object>upgrade
and
connection
headers are only present for HTTP/1.1 handshakes.import diagnosticsChannel from 'node:diagnostics_channel'
diagnosticsChannel.channel('undici:websocket:open').subscribe(({
address,
protocol,
extensions,
websocket,
handshakeResponse
}) => {
console.log(address)
console.log(protocol)
console.log(extensions)
console.log(handshakeResponse.status)
console.log(handshakeResponse.headers)
})Published after the WebSocket connection has closed.
<Object><WebSocket>WebSocket
instance.<number><string>import diagnosticsChannel from 'node:diagnostics_channel'
diagnosticsChannel.channel('undici:websocket:close').subscribe(({ websocket, code, reason }) => {
console.log(code, reason)
})Published when the underlying WebSocket socket experiences an error. Unlike the other WebSocket channels, the published message is the error itself.
<Error>import diagnosticsChannel from 'node:diagnostics_channel'
diagnosticsChannel.channel('undici:websocket:socket_error').subscribe((error) => {
console.error(error)
})Published after the client receives a ping frame.
<Object><Buffer>
|
<undefined><WebSocket>WebSocket
instance.import diagnosticsChannel from 'node:diagnostics_channel'
diagnosticsChannel.channel('undici:websocket:ping').subscribe(({ payload, websocket }) => {
console.log(payload)
})Published after the client receives a pong frame.
<Object><Buffer>
|
<undefined><WebSocket>WebSocket
instance.import diagnosticsChannel from 'node:diagnostics_channel'
diagnosticsChannel.channel('undici:websocket:pong').subscribe(({ payload, websocket }) => {
console.log(payload)
})Published after a ProxyAgent has established a tunnel to the proxy server.
<Object><net.Socket><Object><string><string>'example.com:443'
.<AbortSignal>
|
<undefined><Object>CONNECT
request.<string>
|
<undefined>import diagnosticsChannel from 'node:diagnostics_channel'
diagnosticsChannel.channel('undici:proxy:connected').subscribe(({ socket, connectParams }) => {
const { origin, port, path, signal, headers, servername } = connectParams
})Published when the dedupe interceptor's set of pending requests changes.
The interceptor coalesces concurrent identical requests so that only one reaches
the origin; this channel exposes that bookkeeping for monitoring and debugging.
<Object><string>'added'
when a new pending request is registered, or
'removed'
when a pending request completes, whether successfully or with an
error.<number><string>import diagnosticsChannel from 'node:diagnostics_channel'
diagnosticsChannel.channel('undici:request:pending-requests').subscribe(({ type, size, key }) => {
if (type === 'added') {
console.log(`New pending request: ${key} (${size} total pending)`)
} else {
console.log(`Request completed: ${key} (${size} remaining)`)
}
})This is useful for verifying that deduplication is working, monitoring the number of concurrent in-flight requests, and debugging deduplication behaviour in production.