forked from op7418/CodePilot
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathruntime-log.ts
More file actions
114 lines (98 loc) · 3.07 KB
/
runtime-log.ts
File metadata and controls
114 lines (98 loc) · 3.07 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
/**
* Runtime Log — ring buffer that intercepts console.error and console.warn.
*
* Uses globalThis pattern to survive HMR reloads in development.
*/
export interface LogEntry {
level: 'error' | 'warn';
message: string;
timestamp: string;
}
const BUFFER_SIZE = 200;
const GLOBAL_KEY = '__codepilot_runtime_log__' as const;
interface RuntimeLogState {
buffer: LogEntry[];
installed: boolean;
originalError: typeof console.error;
originalWarn: typeof console.warn;
}
function getState(): RuntimeLogState {
const g = globalThis as Record<string, unknown>;
if (!g[GLOBAL_KEY]) {
g[GLOBAL_KEY] = {
buffer: [] as LogEntry[],
installed: false,
originalError: console.error,
originalWarn: console.warn,
};
}
return g[GLOBAL_KEY] as RuntimeLogState;
}
/**
* Scrub embedded sensitive data from log message text.
* Catches tokens/keys/URLs/paths that appear inline in free-form text.
*/
function scrubMessage(msg: string): string {
return msg
// API keys / tokens (sk-xxx, anthropic-xxx, key-xxx, Bearer xxx)
.replace(/\b(sk-[a-zA-Z0-9_-]{8})[a-zA-Z0-9_-]+/g, '$1***')
.replace(/\b(anthropic-[a-zA-Z0-9_-]{4})[a-zA-Z0-9_-]+/g, '$1***')
.replace(/\b(key-[a-zA-Z0-9_-]{4})[a-zA-Z0-9_-]+/g, '$1***')
.replace(/(Bearer\s+)[a-zA-Z0-9_.-]{12,}/gi, '$1***')
// Generic long hex/base64 tokens (32+ chars)
.replace(/\b[a-f0-9]{32,}\b/gi, (m) => m.slice(0, 8) + '***')
// Home directory paths
.replace(new RegExp(homeDirPattern, 'g'), '~');
}
// Pre-compute home dir regex pattern (escaped for use in regex)
const homeDirPattern = (typeof process !== 'undefined' && process.env?.HOME)
? process.env.HOME.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
: '/Users/[^/\\s]+';
function pushEntry(level: 'error' | 'warn', args: unknown[]): void {
const state = getState();
const raw = args
.map(a => {
if (typeof a === 'string') return a;
try { return JSON.stringify(a); } catch { return String(a); }
})
.join(' ');
const message = scrubMessage(raw);
state.buffer.push({
level,
message: message.slice(0, 2000), // cap individual entry length
timestamp: new Date().toISOString(),
});
// Ring buffer: trim from the front when over capacity
if (state.buffer.length > BUFFER_SIZE) {
state.buffer.splice(0, state.buffer.length - BUFFER_SIZE);
}
}
/**
* Install console.error and console.warn intercepts.
* Safe to call multiple times — only installs once per globalThis lifetime.
*/
export function initRuntimeLog(): void {
const state = getState();
if (state.installed) return;
console.error = (...args: unknown[]) => {
pushEntry('error', args);
state.originalError.apply(console, args);
};
console.warn = (...args: unknown[]) => {
pushEntry('warn', args);
state.originalWarn.apply(console, args);
};
state.installed = true;
}
/**
* Return buffered log entries (oldest first).
*/
export function getRecentLogs(): LogEntry[] {
return [...getState().buffer];
}
/**
* Clear all buffered entries.
*/
export function clearLogs(): void {
getState().buffer.length = 0;
}