Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/extensions/copilot/src/platform/log/common/logService.ts
13401 views
1
/*---------------------------------------------------------------------------------------------
2
* Copyright (c) Microsoft Corporation. All rights reserved.
3
* Licensed under the MIT License. See License.txt in the project root for license information.
4
*--------------------------------------------------------------------------------------------*/
5
6
import { createServiceIdentifier } from '../../../util/common/services';
7
import { Disposable } from '../../../util/vs/base/common/lifecycle';
8
9
export const ILogService = createServiceIdentifier<ILogService>('ILogService');
10
11
/**
12
* Log levels (taken from vscode.d.ts)
13
*/
14
export enum LogLevel {
15
16
/**
17
* No messages are logged with this level.
18
*/
19
Off = 0,
20
21
/**
22
* All messages are logged with this level.
23
*/
24
Trace = 1,
25
26
/**
27
* Messages with debug and higher log level are logged with this level.
28
*/
29
Debug = 2,
30
31
/**
32
* Messages with info and higher log level are logged with this level.
33
*/
34
Info = 3,
35
36
/**
37
* Messages with warning and higher log level are logged with this level.
38
*/
39
Warning = 4,
40
41
/**
42
* Only error messages are logged with this level.
43
*/
44
Error = 5
45
}
46
47
export interface ILogTarget {
48
logIt(level: LogLevel, metadataStr: string, ...extra: any[]): void;
49
show?(preserveFocus?: boolean): void;
50
}
51
52
/**
53
* Utility functions for creating ILogTarget instances.
54
*/
55
export namespace LogTarget {
56
/**
57
* Creates an ILogTarget from a simple callback function.
58
*
59
* @example
60
* logger.withExtraTarget(LogTarget.fromCallback((level, msg) => {
61
* console.log(`[${LogLevel[level]}] ${msg}`);
62
* }));
63
*/
64
export function fromCallback(fn: (level: LogLevel, message: string) => void): ILogTarget {
65
return { logIt: fn };
66
}
67
}
68
69
// Simple implementation of a log targe used for logging to the console.
70
export class ConsoleLog implements ILogTarget {
71
constructor(private readonly prefix?: string, private readonly minLogLevel: LogLevel = LogLevel.Warning) { }
72
73
logIt(level: LogLevel, metadataStr: string, ...extra: any[]) {
74
if (this.prefix) {
75
metadataStr = `${this.prefix}${metadataStr}`;
76
}
77
78
// Note we don't log INFO or DEBUG messages into console.
79
// They are still logged in the output channel.
80
if (level === LogLevel.Error) {
81
console.error(metadataStr, ...extra);
82
} else if (level === LogLevel.Warning) {
83
console.warn(metadataStr, ...extra);
84
} else if (level >= this.minLogLevel) {
85
console.log(metadataStr, ...extra);
86
}
87
}
88
}
89
90
export interface ILogService extends ILogger {
91
readonly _serviceBrand: undefined;
92
}
93
94
/**
95
* Mirrors vscode's {@link LogOutputChannel} in terms of available logging functions
96
* Args has been ommitted for now in favor of simplifying the interface
97
*/
98
export interface ILogger {
99
trace(message: string): void;
100
debug(message: string): void;
101
info(message: string): void;
102
warn(message: string): void;
103
/**
104
* Logs an error message. Prefer this method over `error()` when logging exception details.
105
*
106
* @param error The Error object that was thrown
107
* @param message An optional message for context (e.g. "Request error"). Must not contain customer data. **Do not include stack trace or messages from the error object.**
108
*/
109
error(error: string | Error, message?: string): void;
110
show(preserveFocus?: boolean): void;
111
112
/**
113
* Creates a sub-logger with a topic prefix. All messages logged through
114
* the sub-logger will be prefixed with the topic, e.g., `[Topic] message`.
115
*
116
* Sub-loggers can be nested, and the prefixes will accumulate,
117
* e.g., `[Parent][Child] message`.
118
*
119
* Sub-loggers inherit extra targets from their parent.
120
*
121
* @param topic The topic name or array of topic names to prefix messages with
122
*/
123
createSubLogger(topic: string | readonly string[]): ILogger;
124
125
/**
126
* Returns a new logger that also logs to the specified extra target.
127
* The original logger is unchanged (immutable).
128
*
129
* Can be chained to add multiple targets. Sub-loggers created from this
130
* logger will inherit all extra targets.
131
*
132
* Errors thrown by extra targets are silently caught.
133
*
134
* @param target An ILogTarget instance
135
* @returns A new ILogger with the extra target attached
136
*
137
* @example
138
* const logger = logService
139
* .createSubLogger('MyFeature')
140
* .withExtraTarget(LogTarget.fromCallback((level, msg) => {
141
* logContext.trace(msg);
142
* }));
143
*/
144
withExtraTarget(target: ILogTarget): ILogger;
145
}
146
147
export class LogServiceImpl extends Disposable implements ILogService {
148
declare _serviceBrand: undefined;
149
150
readonly logger: LoggerImpl;
151
152
constructor(
153
logTargets: ILogTarget[],
154
) {
155
super();
156
this.logger = new LoggerImpl(logTargets);
157
}
158
159
// Delegate logging methods directly to the internal logger
160
trace(message: string): void {
161
this.logger.trace(message);
162
}
163
164
debug(message: string): void {
165
this.logger.debug(message);
166
}
167
168
info(message: string): void {
169
this.logger.info(message);
170
}
171
172
warn(message: string): void {
173
this.logger.warn(message);
174
}
175
176
error(error: string | Error, message?: string): void {
177
this.logger.error(error, message);
178
}
179
180
show(preserveFocus?: boolean): void {
181
this.logger.show(preserveFocus);
182
}
183
184
createSubLogger(topic: string | readonly string[]): ILogger {
185
return this.logger.createSubLogger(topic);
186
}
187
188
withExtraTarget(target: ILogTarget): ILogger {
189
return this.logger.withExtraTarget(target);
190
}
191
}
192
193
class LoggerImpl implements ILogger {
194
constructor(
195
private readonly _logTargets: ILogTarget[],
196
) { }
197
198
private _logIt(level: LogLevel, message: string): void {
199
LogMemory.addLog(LogLevel[level], message);
200
this._logTargets.forEach(t => t.logIt(level, message));
201
}
202
203
trace(message: string): void {
204
this._logIt(LogLevel.Trace, message);
205
}
206
207
debug(message: string): void {
208
this._logIt(LogLevel.Debug, message);
209
}
210
211
info(message: string): void {
212
this._logIt(LogLevel.Info, message);
213
}
214
215
warn(message: string): void {
216
this._logIt(LogLevel.Warning, message);
217
}
218
219
error(error: string | Error, message?: string): void {
220
this._logIt(LogLevel.Error, collectErrorMessages(error) + (message ? `: ${message}` : ''));
221
}
222
223
show(preserveFocus?: boolean): void {
224
this._logTargets.forEach(t => t.show?.(preserveFocus));
225
}
226
227
createSubLogger(topic: string | readonly string[]): ILogger {
228
return new SubLogger(this, topic);
229
}
230
231
withExtraTarget(target: ILogTarget): ILogger {
232
return new LoggerWithExtraTargets(this, [target]);
233
}
234
}
235
236
class SubLogger implements ILogger {
237
private readonly _prefix: string;
238
239
constructor(
240
private readonly _parent: ILogger,
241
topic: string | readonly string[],
242
existingPrefix?: string,
243
) {
244
const topics = Array.isArray(topic) ? topic : [topic];
245
const newPrefix = topics.map(t => `[${t}]`).join('');
246
this._prefix = existingPrefix ? existingPrefix + newPrefix : newPrefix;
247
}
248
249
private _prefixMessage(message: string): string {
250
return `${this._prefix} ${message}`;
251
}
252
253
trace(message: string): void {
254
this._parent.trace(this._prefixMessage(message));
255
}
256
257
debug(message: string): void {
258
this._parent.debug(this._prefixMessage(message));
259
}
260
261
info(message: string): void {
262
this._parent.info(this._prefixMessage(message));
263
}
264
265
warn(message: string): void {
266
this._parent.warn(this._prefixMessage(message));
267
}
268
269
error(error: string | Error, message?: string): void {
270
const prefixedMessage = message ? this._prefixMessage(message) : this._prefix;
271
this._parent.error(error, prefixedMessage);
272
}
273
274
show(preserveFocus?: boolean): void {
275
this._parent.show(preserveFocus);
276
}
277
278
createSubLogger(topic: string | readonly string[]): ILogger {
279
return new SubLogger(this._parent, topic, this._prefix);
280
}
281
282
withExtraTarget(target: ILogTarget): ILogger {
283
return new LoggerWithExtraTargets(this, [target], this._prefix);
284
}
285
}
286
287
class LoggerWithExtraTargets implements ILogger {
288
constructor(
289
private readonly _parent: ILogger,
290
private readonly _extraTargets: readonly ILogTarget[],
291
private readonly _prefix: string = '',
292
) { }
293
294
private _notifyExtraTargets(level: LogLevel, message: string): void {
295
const prefixedMessage = this._prefix ? `${this._prefix} ${message}` : message;
296
for (const target of this._extraTargets) {
297
try {
298
target.logIt(level, prefixedMessage);
299
} catch {
300
// Silent catch - extra targets must not affect primary logging
301
}
302
}
303
}
304
305
trace(message: string): void {
306
this._notifyExtraTargets(LogLevel.Trace, message);
307
this._parent.trace(message);
308
}
309
310
debug(message: string): void {
311
this._notifyExtraTargets(LogLevel.Debug, message);
312
this._parent.debug(message);
313
}
314
315
info(message: string): void {
316
this._notifyExtraTargets(LogLevel.Info, message);
317
this._parent.info(message);
318
}
319
320
warn(message: string): void {
321
this._notifyExtraTargets(LogLevel.Warning, message);
322
this._parent.warn(message);
323
}
324
325
error(error: string | Error, message?: string): void {
326
// For extra targets, format a simple message
327
const errorStr = typeof error === 'string' ? error : (error.message || 'Error');
328
const fullMessage = message ? `${errorStr}: ${message}` : errorStr;
329
this._notifyExtraTargets(LogLevel.Error, fullMessage);
330
this._parent.error(error, message);
331
}
332
333
show(preserveFocus?: boolean): void {
334
this._parent.show(preserveFocus);
335
for (const target of this._extraTargets) {
336
try {
337
target.show?.(preserveFocus);
338
} catch {
339
// Silent catch
340
}
341
}
342
}
343
344
createSubLogger(topic: string | readonly string[]): ILogger {
345
// Sub-logger inherits extra targets with updated prefix
346
const topics = Array.isArray(topic) ? topic : [topic];
347
const newPrefix = this._prefix + topics.map(t => `[${t}]`).join('');
348
return new LoggerWithExtraTargets(
349
this._parent.createSubLogger(topic),
350
this._extraTargets,
351
newPrefix
352
);
353
}
354
355
withExtraTarget(target: ILogTarget): ILogger {
356
return new LoggerWithExtraTargets(
357
this._parent,
358
[...this._extraTargets, target],
359
this._prefix
360
);
361
}
362
}
363
364
export function collectErrorMessages(e: any): string {
365
// Collect error messages from nested errors as seen with Node's `fetch`.
366
const seen = new Set<any>();
367
function collect(e: any, indent: string): string {
368
if (!e || !['object', 'string'].includes(typeof e) || seen.has(e)) {
369
return '';
370
}
371
seen.add(e);
372
const message = typeof e === 'string' ? e : (e.stack || e.message || e.code || '');
373
const messageStr = message.toString?.() as (string | undefined) || '';
374
return [
375
messageStr ? `${messageStr.split('\n').map(line => `${indent}${line}`).join('\n')}\n` : '',
376
e.chromiumDetails ? `${indent}${JSON.stringify(extractChromiumDetails(e.chromiumDetails))}\n` : '',
377
collect(e.cause, indent + ' '),
378
...(Array.isArray(e.errors) ? e.errors.map((e: any) => collect(e, indent + ' ')) : []),
379
].join('');
380
}
381
return collect(e, '')
382
.trim();
383
}
384
385
export function collectSingleLineErrorMessage(e: any, includeDetails = false): string {
386
// Collect error messages from nested errors as seen with Node's `fetch`.
387
const seen = new Set<any>();
388
function collect(e: any): string {
389
if (!e || !['object', 'string'].includes(typeof e) || seen.has(e)) {
390
return '';
391
}
392
seen.add(e);
393
const message = typeof e === 'string' ? e : (e.message || e.code || '');
394
const messageStr = message.toString?.() as (string | undefined) || '';
395
const messageLine = messageStr.trim().split('\n').join(' ');
396
const details = [
397
...(includeDetails && e.chromiumDetails ? [JSON.stringify(extractChromiumDetails(e.chromiumDetails))] : []),
398
...(e.cause ? [collect(e.cause)] : []),
399
...(Array.isArray(e.errors) ? e.errors.map((e: any) => collect(e)) : []),
400
].join(', ');
401
return details ? `${messageLine}: ${details}` : messageLine;
402
}
403
return collect(e);
404
}
405
406
/**
407
* Sanitizes a network error message for telemetry by replacing hostnames,
408
* IP addresses, and credentials with placeholders.
409
*/
410
export function sanitizeNetworkErrorForTelemetry(message: string): string {
411
// Strip credentials and host from proxy result strings (e.g., "PROXY user:pass@host" → "PROXY <credentials>@<host>")
412
message = message.replace(/(\b(?:PROXY|HTTPS?|SOCKS[45]?)\s+)[^\s]+@([^\s:\/]+)/gi, '$1<credentials>@<host>');
413
// Strip host from proxy result strings without credentials (e.g., "PROXY host:8080" → "PROXY <host>:8080")
414
message = message.replace(/(\b(?:PROXY|HTTPS?|SOCKS[45]?)\s+)([a-zA-Z0-9][-a-zA-Z0-9.]*)/gi, '$1<host>');
415
// Strip credentials and host from URLs (e.g., "://user:pass@host" → "://<credentials>@<host>")
416
message = message.replace(/(\/\/)[^\s/]+@([^\s:\/]+)/g, '$1<credentials>@<host>');
417
// Replace IPv4 addresses, preserving the port if present
418
message = message.replace(/\b\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\b/g, '<ip>');
419
// Replace IPv6 addresses (full form, e.g., "2001:db8:85a3:0:0:8a2e:370:7334")
420
message = message.replace(/(?<![a-zA-Z_:])(?:[0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}(?![a-zA-Z_])/g, '<ip>');
421
// Replace IPv6 addresses (compressed form with ::, e.g., "2001:db8::1" or "::1")
422
message = message.replace(/(?<![a-zA-Z_:])(?:(?:[0-9a-fA-F]{1,4}:){1,7}|:):[0-9a-fA-F:]*[0-9a-fA-F](?![a-zA-Z_])/g, '<ip>');
423
// Replace FQDNs (at least one dot, TLD of 2+ alpha chars), preserving the port if present
424
message = message.replace(/\b([a-zA-Z0-9][-a-zA-Z0-9]*\.)+[a-zA-Z]{2,}\b/g, '<host>');
425
return message;
426
}
427
428
/**
429
* Chromium error details attached by Electron to fetch errors.
430
* Electron's network service process runs separately; when it crashes,
431
* `network_process_crashed` is set to `true` on the error's `chromiumDetails`.
432
*/
433
export interface ElectronFetchErrorChromiumDetails {
434
readonly is_request_error?: boolean;
435
readonly network_process_crashed?: boolean;
436
readonly session_state?: any;
437
readonly drain_error?: any;
438
readonly drain_description?: any;
439
readonly go_away_error?: any;
440
readonly go_away_error_details?: any;
441
readonly go_away_debug_data?: any;
442
readonly rst_stream_error?: any;
443
readonly rst_stream_error_details?: any;
444
readonly rst_stream_description?: any;
445
readonly last_framer_error?: any;
446
readonly last_framer_error_details?: any;
447
readonly error_source?: any;
448
readonly aliases?: any;
449
readonly proxy?: any;
450
readonly in_flight_write?: any;
451
readonly buffered_spdy_framer?: any;
452
readonly tls_info?: any;
453
readonly socket_info?: any;
454
readonly url_loader_error?: any;
455
readonly active_stream_details?: any;
456
readonly closed_stream_details?: any;
457
}
458
459
function extractChromiumDetails(details: ElectronFetchErrorChromiumDetails): any {
460
if (!details || typeof details !== 'object') {
461
return {};
462
}
463
464
if (details.is_request_error !== undefined && details.session_state === undefined) {
465
return {
466
is_request_error: details.is_request_error,
467
network_process_crashed: details.network_process_crashed,
468
};
469
}
470
471
const extracted: any = {
472
drain_error: details.drain_error,
473
drain_description: details.drain_description,
474
go_away_error: details.go_away_error,
475
go_away_error_details: details.go_away_error_details,
476
go_away_debug_data: details.go_away_debug_data,
477
rst_stream_error: details.rst_stream_error,
478
rst_stream_error_details: details.rst_stream_error_details,
479
rst_stream_description: details.rst_stream_description,
480
last_framer_error: details.last_framer_error,
481
last_framer_error_details: details.last_framer_error_details,
482
error_source: details.error_source,
483
aliases_length: Array.isArray(details.aliases) ? details.aliases.length : undefined,
484
};
485
486
if (details.proxy) {
487
const proxyString = String(details.proxy);
488
const proxySchemes = [...proxyString.matchAll(/([a-z][a-z0-9+.-]*):\/\//gi)].map(match => match[1]);
489
if (proxySchemes.length > 0) {
490
extracted.proxy_schemes = proxySchemes;
491
}
492
}
493
494
if (details.in_flight_write && typeof details.in_flight_write === 'object') {
495
extracted.in_flight_write = {
496
frame_type: details.in_flight_write.frame_type,
497
frame_size: details.in_flight_write.frame_size,
498
remaining_size: details.in_flight_write.remaining_size,
499
};
500
}
501
502
if (details.buffered_spdy_framer && typeof details.buffered_spdy_framer === 'object') {
503
extracted.buffered_spdy_framer = {
504
frames_received: details.buffered_spdy_framer.frames_received,
505
has_error: details.buffered_spdy_framer.has_error,
506
message_fully_read: details.buffered_spdy_framer.message_fully_read,
507
};
508
}
509
510
if (details.session_state && typeof details.session_state === 'object') {
511
const state = details.session_state;
512
extracted.session_state = {
513
availability_state: state.availability_state,
514
session_send_window: state.session_send_window,
515
session_recv_window: state.session_recv_window,
516
stream_initial_send_window: state.stream_initial_send_window,
517
stream_initial_recv_window: state.stream_initial_recv_window,
518
send_stalled_by_session_window: state.send_stalled_by_session_window,
519
active_stream_count: state.active_stream_count,
520
created_stream_count: state.created_stream_count,
521
max_concurrent_streams: state.max_concurrent_streams,
522
highest_stream_id_sent: state.highest_stream_id_sent,
523
frames_sent: state.frames_sent,
524
frames_received: state.frames_received,
525
ping_in_flight: state.ping_in_flight,
526
last_ping_sent_ms: state.last_ping_sent_ms,
527
next_ping_id: state.next_ping_id,
528
failed_ping_count: state.failed_ping_count,
529
support_websocket: state.support_websocket,
530
deprecate_http2_priorities: state.deprecate_http2_priorities,
531
streams_initiated_count: state.streams_initiated_count,
532
streams_abandoned_count: state.streams_abandoned_count,
533
read_state: state.read_state,
534
write_state: state.write_state,
535
pending_create_stream_request_count: state.pending_create_stream_request_count,
536
error: state.error,
537
error_on_unavailable: state.error_on_unavailable,
538
unacked_recv_window_bytes: state.unacked_recv_window_bytes,
539
last_good_stream_id: state.last_good_stream_id,
540
debug_stream_id: state.debug_stream_id,
541
has_ping_based_connection_checking: state.has_ping_based_connection_checking,
542
num_broken_connection_detection_requests: state.num_broken_connection_detection_requests,
543
session_max_queued_capped_frames: state.session_max_queued_capped_frames,
544
num_queued_capped_frames: state.num_queued_capped_frames,
545
check_ping_status_pending: state.check_ping_status_pending,
546
in_confirm_handshake: state.in_confirm_handshake,
547
http2_end_stream_with_data_frame: state.http2_end_stream_with_data_frame,
548
reused: state.reused,
549
session_max_recv_window_size: state.session_max_recv_window_size,
550
max_header_table_size: state.max_header_table_size,
551
time_since_last_read_ms: state.time_since_last_read_ms,
552
time_since_last_write_ms: state.time_since_last_write_ms,
553
time_since_last_recv_window_update_ms: state.time_since_last_recv_window_update_ms,
554
};
555
}
556
557
if (details.tls_info && typeof details.tls_info === 'object') {
558
const tls = details.tls_info;
559
extracted.tls_info = {
560
is_secure_connection: tls.is_secure_connection,
561
ssl_version: tls.ssl_version,
562
cipher_suite: tls.cipher_suite,
563
negotiated_alpn: tls.negotiated_alpn,
564
cert_status: tls.cert_status,
565
is_issued_by_known_root: tls.is_issued_by_known_root,
566
handshake_type: tls.handshake_type,
567
client_cert_sent: tls.client_cert_sent,
568
exchange_group: tls.key_exchange_group,
569
ct_compliance: tls.ct_compliance,
570
alps_negotiated: tls.alps_negotiated,
571
};
572
}
573
574
if (details.socket_info && typeof details.socket_info === 'object') {
575
const socket = details.socket_info;
576
extracted.socket_info = {
577
is_connected: socket.is_connected,
578
was_ever_used: socket.was_ever_used,
579
dns_lookup_duration_ms: socket.dns_lookup_duration_ms,
580
tcp_connect_duration_ms: socket.tcp_connect_duration_ms,
581
ssl_handshake_duration_ms: socket.ssl_handshake_duration_ms,
582
owned_socket: socket.owned_socket,
583
socket_reuse_type: socket.socket_reuse_type,
584
};
585
}
586
587
if (details.url_loader_error && typeof details.url_loader_error === 'object') {
588
extracted.url_loader_error = {
589
is_request_error: details.url_loader_error.is_request_error,
590
network_process_crashed: details.url_loader_error.network_process_crashed,
591
};
592
}
593
594
if (Array.isArray(details.active_stream_details)) {
595
extracted.active_stream_details = details.active_stream_details.map((stream: any) => ({
596
stream_id: stream.stream_id,
597
io_state: stream.io_state,
598
type: stream.type,
599
priority: stream.priority,
600
send_window_size: stream.send_window_size,
601
recv_window_size: stream.recv_window_size,
602
max_recv_window_size: stream.max_recv_window_size,
603
unacked_recv_window_bytes: stream.unacked_recv_window_bytes,
604
send_stalled_by_flow_control: stream.send_stalled_by_flow_control,
605
raw_sent_bytes: stream.raw_sent_bytes,
606
raw_received_bytes: stream.raw_received_bytes,
607
recv_bytes: stream.recv_bytes,
608
pending_send_status: stream.pending_send_status,
609
response_state: stream.response_state,
610
pending_send_data_remaining: stream.pending_send_data_remaining,
611
request_time_ms: stream.request_time_ms,
612
response_time_ms: stream.response_time_ms,
613
}));
614
}
615
616
if (Array.isArray(details.closed_stream_details)) {
617
extracted.closed_stream_details = details.closed_stream_details.map((stream: any) => ({
618
stream_id: stream.stream_id,
619
io_state: stream.io_state,
620
type: stream.type,
621
priority: stream.priority,
622
send_window_size: stream.send_window_size,
623
recv_window_size: stream.recv_window_size,
624
max_recv_window_size: stream.max_recv_window_size,
625
unacked_recv_window_bytes: stream.unacked_recv_window_bytes,
626
send_stalled_by_flow_control: stream.send_stalled_by_flow_control,
627
raw_sent_bytes: stream.raw_sent_bytes,
628
raw_received_bytes: stream.raw_received_bytes,
629
recv_bytes: stream.recv_bytes,
630
pending_send_status: stream.pending_send_status,
631
response_state: stream.response_state,
632
pending_send_data_remaining: stream.pending_send_data_remaining,
633
request_time_ms: stream.request_time_ms,
634
response_time_ms: stream.response_time_ms,
635
}));
636
}
637
638
return extracted;
639
}
640
641
export class LogMemory {
642
private static _logs: string[] = [];
643
private static _requestIds: string[] = [];
644
private static readonly MAX_LOGS = 50;
645
646
/**
647
* Extracts the requestId from a log message if it matches the expected pattern.
648
* Returns a string in the format 'requestId: {string}' or undefined if not found.
649
*/
650
private static extractRequestIdFromMessage(message: string): string | undefined {
651
const match = message.match(/request done: requestId: \[([0-9a-fA-F-]+)\] model deployment ID: \[/);
652
if (match) {
653
const requestId = match[1];
654
if (!this._requestIds.includes(requestId)) {
655
return requestId;
656
}
657
}
658
return undefined;
659
}
660
661
static addLog(level: string, message: string): void {
662
if (this._logs.length >= this.MAX_LOGS) {
663
this._logs.shift();
664
}
665
this._logs.push(`${level}: ${message}`);
666
667
// Extract and store requestId if present
668
if (this._requestIds.length >= this.MAX_LOGS) {
669
this._requestIds.shift();
670
}
671
const requestId = this.extractRequestIdFromMessage(message);
672
if (requestId) {
673
this._requestIds.push(requestId);
674
}
675
}
676
677
static getLogs(): string[] {
678
return this._logs;
679
}
680
681
static getRequestIds(): string[] {
682
return this._requestIds;
683
}
684
}
685
686