From 787d97bf7cd357eff6d03b3139f2e9bc161f4ece Mon Sep 17 00:00:00 2001 From: Christof Marti Date: Tue, 2 Dec 2025 23:33:19 +0100 Subject: [PATCH] Log additional error details (#2336) --- .../extension/prompt/node/chatMLFetcher.ts | 7 +- .../src/platform/log/common/logService.ts | 69 ++++++++++++++++++- .../networking/node/fetcherFallback.ts | 2 +- 3 files changed, 73 insertions(+), 5 deletions(-) diff --git a/extensions/copilot/src/extension/prompt/node/chatMLFetcher.ts b/extensions/copilot/src/extension/prompt/node/chatMLFetcher.ts index 6db42a97221..0532a018431 100644 --- a/extensions/copilot/src/extension/prompt/node/chatMLFetcher.ts +++ b/extensions/copilot/src/extension/prompt/node/chatMLFetcher.ts @@ -393,7 +393,7 @@ export class ChatMLFetcherImpl extends AbstractChatMLFetcher { this._logService.info(`CAPI ping returned status ${res.status}, retrying ping...`); } } catch (err) { - connectivityTestError = collectSingleLineErrorMessage(err); + connectivityTestError = collectSingleLineErrorMessage(err, true); this._logService.info(`CAPI ping failed with error, retrying ping: ${connectivityTestError}`); } } @@ -1087,7 +1087,8 @@ export class ChatMLFetcherImpl extends AbstractChatMLFetcher { } this._logService.error(errorsUtil.fromUnknown(err), `Error on conversation request`); this._telemetryService.sendGHTelemetryException(err, 'Error on conversation request'); - const errorDetail = fetcher.getUserMessageForFetcherError(err); + const userMessage = fetcher.getUserMessageForFetcherError(err); + const errorDetail = collectSingleLineErrorMessage(err, true); const scrubbedErrorDetail = this.scrubErrorDetail(errorDetail, usernameToScrub); if (fetcher.isInternetDisconnectedError(err)) { return { @@ -1100,7 +1101,7 @@ export class ChatMLFetcherImpl extends AbstractChatMLFetcher { } else if (fetcher.isFetcherError(err)) { return { type: ChatFetchResponseType.NetworkError, - reason: errorDetail, + reason: userMessage, reasonDetail: scrubbedErrorDetail, requestId: requestId, serverRequestId: undefined, diff --git a/extensions/copilot/src/platform/log/common/logService.ts b/extensions/copilot/src/platform/log/common/logService.ts index 2f2aa22cb3d..0c8105fcb66 100644 --- a/extensions/copilot/src/platform/log/common/logService.ts +++ b/extensions/copilot/src/platform/log/common/logService.ts @@ -178,6 +178,7 @@ export function collectErrorMessages(e: any): string { const messageStr = message.toString?.() as (string | undefined) || ''; return [ messageStr ? `${messageStr.split('\n').map(line => `${indent}${line}`).join('\n')}\n` : '', + e.chromiumDetails ? `${indent}${JSON.stringify(extractChromiumDetails(e.chromiumDetails))}\n` : '', collect(e.cause, indent + ' '), ...(Array.isArray(e.errors) ? e.errors.map((e: any) => collect(e, indent + ' ')) : []), ].join(''); @@ -186,7 +187,7 @@ export function collectErrorMessages(e: any): string { .trim(); } -export function collectSingleLineErrorMessage(e: any): string { +export function collectSingleLineErrorMessage(e: any, includeDetails = false): string { // Collect error messages from nested errors as seen with Node's `fetch`. const seen = new Set(); function collect(e: any): string { @@ -198,6 +199,7 @@ export function collectSingleLineErrorMessage(e: any): string { const messageStr = message.toString?.() as (string | undefined) || ''; const messageLine = messageStr.trim().split('\n').join(' '); const details = [ + ...(includeDetails && e.chromiumDetails ? [JSON.stringify(extractChromiumDetails(e.chromiumDetails))] : []), ...(e.cause ? [collect(e.cause)] : []), ...(Array.isArray(e.errors) ? e.errors.map((e: any) => collect(e)) : []), ].join(', '); @@ -206,6 +208,71 @@ export function collectSingleLineErrorMessage(e: any): string { return collect(e); } +function extractChromiumDetails(details: any): any { + if (!details || typeof details !== 'object') { + return {}; + } + + const extracted: any = { + // source_id: details.source_id, + // host_port_pair: details.host_port_pair, + // network_anonymization_key: details.network_anonymization_key, + active_streams: details.active_streams, + created_streams: details.created_streams, + pending_create_stream_request_count: details.pending_create_stream_request_count, + negotiated_protocol: details.negotiated_protocol, + error: details.error, + error_on_unavailable: details.error_on_unavailable, + max_concurrent_streams: details.max_concurrent_streams, + streams_initiated_count: details.streams_initiated_count, + streams_abandoned_count: details.streams_abandoned_count, + stream_hi_water_mark: details.stream_hi_water_mark, + frames_received: details.frames_received, + send_window_size: details.send_window_size, + recv_window_size: details.recv_window_size, + unacked_recv_window_bytes: details.unacked_recv_window_bytes, + // support_websocket: details.support_websocket, + availability_state: details.availability_state, + last_good_stream_id: details.last_good_stream_id, + reused: details.reused, + drain_error: details.drain_error, + drain_description: details.drain_description, + go_away_error: details.go_away_error, + go_away_debug_data: details.go_away_debug_data, + rst_stream_error: details.rst_stream_error, + rst_stream_description: details.rst_stream_description, + aliases_length: Array.isArray(details.aliases) ? details.aliases.length : undefined, + }; + + // Extract proxy schemes + if (details.proxy) { + const proxyString = Array.isArray(details.proxy) ? details.proxy.join(' ') : String(details.proxy); + const proxySchemes = [...proxyString.matchAll(/([a-z][a-z0-9+.-]*):\/\//gi)].map(match => match[1]); + if (proxySchemes.length > 0) { + extracted.proxy_schemes = proxySchemes; + } + } + + if (details.spdy_session_key && typeof details.spdy_session_key === 'object') { + extracted.spdy_session_key = { + privacy_mode: details.spdy_session_key.privacy_mode, + secure_dns_policy: details.spdy_session_key.secure_dns_policy, + disable_cert_verification_network_fetches: details.spdy_session_key.disable_cert_verification_network_fetches, + }; + } + + if (Array.isArray(details.active_stream_details)) { + extracted.active_stream_details = details.active_stream_details.map((stream: any) => ({ + stream_id: stream.stream_id, + io_state: stream.io_state, + send_stalled_by_flow_control: stream.send_stalled_by_flow_control, + pending_send_status: stream.pending_send_status, + })); + } + + return extracted; +} + export class LogMemory { private static _logs: string[] = []; private static _requestIds: string[] = []; diff --git a/extensions/copilot/src/platform/networking/node/fetcherFallback.ts b/extensions/copilot/src/platform/networking/node/fetcherFallback.ts index 83c3e4395fe..7391f5782a4 100644 --- a/extensions/copilot/src/platform/networking/node/fetcherFallback.ts +++ b/extensions/copilot/src/platform/networking/node/fetcherFallback.ts @@ -32,7 +32,7 @@ export async function fetchWithFallbacks(availableFetchers: readonly IFetcher[], if ('response' in result) { lastError = `${fetcherId}: ${result.response.status} ${result.response.statusText}`; } else { - lastError = `${fetcherId}: ${collectSingleLineErrorMessage(result.err)}`; + lastError = `${fetcherId}: ${collectSingleLineErrorMessage(result.err, true)}`; } updatedKnownBadFetchers.add(fetcherId); continue;