|
| 1 | +import * as diagnostics_channel from 'node:diagnostics_channel'; |
| 2 | +import type { DiagnosticsChannel } from 'undici'; |
| 3 | + |
| 4 | +/** |
| 5 | + * Enable Undici diagnostics channel instrumentation for detailed connection and request logging. |
| 6 | + * |
| 7 | + * This includes fetch requests and websocket connections. |
| 8 | + * |
| 9 | + * Usage: enableUncidiDiagnostics(); |
| 10 | + */ |
| 11 | +export function enableUncidiDiagnostics() { |
| 12 | + new UndiciDiagnostics().enable(); |
| 13 | +} |
| 14 | + |
| 15 | +class UndiciDiagnostics { |
| 16 | + private requestCounter: number = 0; |
| 17 | + private activeRequests: WeakMap<any, number> = new WeakMap(); |
| 18 | + |
| 19 | + enable() { |
| 20 | + // Available events are documented here: |
| 21 | + // https://github.com/nodejs/undici/blob/main/docs/docs/api/DiagnosticsChannel.md |
| 22 | + |
| 23 | + diagnostics_channel.subscribe('undici:request:create', (message: DiagnosticsChannel.RequestCreateMessage) => { |
| 24 | + const requestId = ++this.requestCounter; |
| 25 | + const request = message.request; |
| 26 | + this.activeRequests.set(message.request, requestId); |
| 27 | + |
| 28 | + console.log(`🔄 [DIAG-${requestId}] REQUEST CREATE:`, { |
| 29 | + host: request.origin, |
| 30 | + path: request.path, |
| 31 | + method: request.method, |
| 32 | + headers: formatHeaders(request.headers), |
| 33 | + contentType: (request as any).contentType, |
| 34 | + contentLength: (request as any).contentLength |
| 35 | + }); |
| 36 | + }); |
| 37 | + |
| 38 | + diagnostics_channel.subscribe('undici:request:bodySent', (message: DiagnosticsChannel.RequestBodySentMessage) => { |
| 39 | + const requestId = this.activeRequests.get(message.request); |
| 40 | + console.log(`📤 [DIAG-${requestId}] REQUEST BODY SENT`); |
| 41 | + }); |
| 42 | + |
| 43 | + diagnostics_channel.subscribe('undici:request:headers', (message: DiagnosticsChannel.RequestHeadersMessage) => { |
| 44 | + const requestId = this.activeRequests.get(message.request); |
| 45 | + console.log(`📥 [DIAG-${requestId}] RESPONSE HEADERS:`, { |
| 46 | + statusCode: message.response.statusCode, |
| 47 | + statusText: message.response.statusText, |
| 48 | + headers: formatHeaders(message.response.headers) |
| 49 | + }); |
| 50 | + }); |
| 51 | + |
| 52 | + diagnostics_channel.subscribe('undici:request:trailers', (message: DiagnosticsChannel.RequestTrailersMessage) => { |
| 53 | + const requestId = this.activeRequests.get(message.request); |
| 54 | + console.log(`🏁 [DIAG-${requestId}] REQUEST TRAILERS:`, { |
| 55 | + trailers: message.trailers |
| 56 | + }); |
| 57 | + }); |
| 58 | + |
| 59 | + diagnostics_channel.subscribe('undici:request:error', (message: DiagnosticsChannel.RequestErrorMessage) => { |
| 60 | + const requestId = this.activeRequests.get(message.request); |
| 61 | + console.log(`❌ [DIAG-${requestId}] REQUEST ERROR:`, { |
| 62 | + error: message.error |
| 63 | + }); |
| 64 | + |
| 65 | + // Clean up tracking |
| 66 | + this.activeRequests.delete(message.request); |
| 67 | + }); |
| 68 | + |
| 69 | + // Client connection events |
| 70 | + diagnostics_channel.subscribe( |
| 71 | + 'undici:client:sendHeaders', |
| 72 | + (message: DiagnosticsChannel.ClientSendHeadersMessage) => { |
| 73 | + console.log(`📡 [DIAG] CLIENT SEND HEADERS:`, { |
| 74 | + headers: formatHeaders(message.headers) |
| 75 | + }); |
| 76 | + } |
| 77 | + ); |
| 78 | + |
| 79 | + diagnostics_channel.subscribe( |
| 80 | + 'undici:client:beforeConnect', |
| 81 | + (message: DiagnosticsChannel.ClientBeforeConnectMessage) => { |
| 82 | + console.log(`🔌 [DIAG] CLIENT BEFORE CONNECT:`, { |
| 83 | + connectParams: message.connectParams |
| 84 | + }); |
| 85 | + } |
| 86 | + ); |
| 87 | + |
| 88 | + diagnostics_channel.subscribe('undici:client:connected', (message: DiagnosticsChannel.ClientConnectedMessage) => { |
| 89 | + console.log(`✅ [DIAG] CLIENT CONNECTED:`, { |
| 90 | + connectParams: message.connectParams, |
| 91 | + connector: message.connector?.name, |
| 92 | + socket: { |
| 93 | + localAddress: message.socket?.localAddress, |
| 94 | + localPort: message.socket?.localPort, |
| 95 | + remoteAddress: message.socket?.remoteAddress, |
| 96 | + remotePort: message.socket?.remotePort |
| 97 | + } |
| 98 | + }); |
| 99 | + }); |
| 100 | + |
| 101 | + diagnostics_channel.subscribe( |
| 102 | + 'undici:client:connectError', |
| 103 | + (message: DiagnosticsChannel.ClientConnectErrorMessage) => { |
| 104 | + console.log(`❌ [DIAG] CLIENT CONNECT ERROR:`, { |
| 105 | + connectParams: message.connectParams, |
| 106 | + error: message.error |
| 107 | + }); |
| 108 | + } |
| 109 | + ); |
| 110 | + |
| 111 | + // WebSocket events |
| 112 | + diagnostics_channel.subscribe('undici:websocket:open', (message: any) => { |
| 113 | + console.log(`🌐 [DIAG] WEBSOCKET OPEN:`, { |
| 114 | + address: message.address, |
| 115 | + protocol: message.protocol, |
| 116 | + extensions: message.extensions |
| 117 | + }); |
| 118 | + }); |
| 119 | + |
| 120 | + diagnostics_channel.subscribe('undici:websocket:close', (message: any) => { |
| 121 | + console.log(`🌐 [DIAG] WEBSOCKET CLOSE:`, { |
| 122 | + websocket: message.websocket?.url, |
| 123 | + code: message.code, |
| 124 | + reason: message.reason |
| 125 | + }); |
| 126 | + }); |
| 127 | + |
| 128 | + diagnostics_channel.subscribe('undici:websocket:socket_error', (message: any) => { |
| 129 | + console.log(`❌ [DIAG] WEBSOCKET SOCKET ERROR:`, { |
| 130 | + websocket: message.websocket?.url, |
| 131 | + error: message.error |
| 132 | + }); |
| 133 | + }); |
| 134 | + } |
| 135 | +} |
| 136 | + |
| 137 | +function formatHeaders(headers: any[] | string | undefined) { |
| 138 | + if (typeof headers === 'string') { |
| 139 | + return headers; |
| 140 | + } |
| 141 | + |
| 142 | + return headers?.map((header) => { |
| 143 | + if (typeof header == 'string') { |
| 144 | + return header; |
| 145 | + } else if (Buffer.isBuffer(header)) { |
| 146 | + return header.toString('utf-8'); |
| 147 | + } else { |
| 148 | + return header; |
| 149 | + } |
| 150 | + }); |
| 151 | +} |
0 commit comments