@link-assistant/agent 0.5.2 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +5 -3
- package/src/auth/claude-oauth.ts +50 -24
- package/src/auth/plugins.ts +28 -16
- package/src/bun/index.ts +33 -27
- package/src/bus/index.ts +3 -5
- package/src/config/config.ts +39 -22
- package/src/file/ripgrep.ts +1 -1
- package/src/file/time.ts +1 -1
- package/src/file/watcher.ts +10 -5
- package/src/format/index.ts +12 -10
- package/src/index.js +30 -35
- package/src/mcp/index.ts +32 -15
- package/src/patch/index.ts +8 -4
- package/src/project/project.ts +1 -1
- package/src/project/state.ts +15 -7
- package/src/provider/cache.ts +259 -0
- package/src/provider/echo.ts +174 -0
- package/src/provider/models.ts +4 -5
- package/src/provider/provider.ts +164 -29
- package/src/server/server.ts +4 -5
- package/src/session/agent.js +16 -2
- package/src/session/compaction.ts +4 -6
- package/src/session/index.ts +2 -2
- package/src/session/processor.ts +3 -7
- package/src/session/prompt.ts +95 -60
- package/src/session/revert.ts +1 -1
- package/src/session/summary.ts +2 -2
- package/src/snapshot/index.ts +27 -12
- package/src/storage/storage.ts +18 -18
- package/src/util/log-lazy.ts +291 -0
- package/src/util/log.ts +205 -28
|
@@ -0,0 +1,291 @@
|
|
|
1
|
+
import makeLog, { levels, LogLevel } from 'log-lazy';
|
|
2
|
+
import { Flag } from '../flag/flag.ts';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* JSON Lazy Logger
|
|
6
|
+
*
|
|
7
|
+
* Implements lazy logging pattern using log-lazy library.
|
|
8
|
+
* All log output is JSON formatted and wrapped in { log: { ... } } structure
|
|
9
|
+
* for easy parsing alongside regular JSON output.
|
|
10
|
+
*
|
|
11
|
+
* Key features:
|
|
12
|
+
* - Lazy evaluation: log arguments are only computed if logging is enabled
|
|
13
|
+
* - JSON output: all logs are parsable JSON in { log: { ... } } format
|
|
14
|
+
* - Level control: logs respect --verbose flag and LINK_ASSISTANT_AGENT_VERBOSE env
|
|
15
|
+
* - Type-safe: full TypeScript support
|
|
16
|
+
*
|
|
17
|
+
* Usage:
|
|
18
|
+
* import { lazyLog } from './util/log-lazy.ts';
|
|
19
|
+
*
|
|
20
|
+
* // Simple string message
|
|
21
|
+
* lazyLog.info(() => 'Starting process');
|
|
22
|
+
*
|
|
23
|
+
* // Object data (preferred - avoids expensive JSON.stringify when disabled)
|
|
24
|
+
* lazyLog.debug(() => ({ action: 'fetch', url: someUrl }));
|
|
25
|
+
*
|
|
26
|
+
* // Complex computed message
|
|
27
|
+
* lazyLog.verbose(() => `Processed ${items.length} items: ${JSON.stringify(items)}`);
|
|
28
|
+
*/
|
|
29
|
+
|
|
30
|
+
// Custom log levels using bit flags for fine-grained control
|
|
31
|
+
const LEVEL_DISABLED = 0;
|
|
32
|
+
const LEVEL_ERROR = levels.error;
|
|
33
|
+
const LEVEL_WARN = levels.warn | LEVEL_ERROR;
|
|
34
|
+
const LEVEL_INFO = levels.info | LEVEL_WARN;
|
|
35
|
+
const LEVEL_DEBUG = levels.debug | LEVEL_INFO;
|
|
36
|
+
const LEVEL_VERBOSE = levels.verbose | LEVEL_DEBUG;
|
|
37
|
+
const LEVEL_TRACE = levels.trace | LEVEL_VERBOSE;
|
|
38
|
+
|
|
39
|
+
// Map of preset level configurations
|
|
40
|
+
const LEVEL_PRESETS = {
|
|
41
|
+
disabled: LEVEL_DISABLED,
|
|
42
|
+
error: LEVEL_ERROR,
|
|
43
|
+
warn: LEVEL_WARN,
|
|
44
|
+
info: LEVEL_INFO,
|
|
45
|
+
debug: LEVEL_DEBUG,
|
|
46
|
+
verbose: LEVEL_VERBOSE,
|
|
47
|
+
trace: LEVEL_TRACE,
|
|
48
|
+
// Convenience aliases
|
|
49
|
+
production: LEVEL_WARN,
|
|
50
|
+
development: LEVEL_DEBUG,
|
|
51
|
+
} as const;
|
|
52
|
+
|
|
53
|
+
type LevelPreset = keyof typeof LEVEL_PRESETS;
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Format a log entry as JSON object wrapped in { log: { ... } }
|
|
57
|
+
*/
|
|
58
|
+
function formatLogEntry(
|
|
59
|
+
level: string,
|
|
60
|
+
data: unknown,
|
|
61
|
+
tags?: Record<string, unknown>
|
|
62
|
+
): string {
|
|
63
|
+
const timestamp = new Date().toISOString();
|
|
64
|
+
const logEntry: Record<string, unknown> = {
|
|
65
|
+
level,
|
|
66
|
+
timestamp,
|
|
67
|
+
...tags,
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
// Handle different data types
|
|
71
|
+
if (typeof data === 'string') {
|
|
72
|
+
logEntry.message = data;
|
|
73
|
+
} else if (data instanceof Error) {
|
|
74
|
+
logEntry.message = data.message;
|
|
75
|
+
logEntry.error = {
|
|
76
|
+
name: data.name,
|
|
77
|
+
message: data.message,
|
|
78
|
+
stack: data.stack,
|
|
79
|
+
};
|
|
80
|
+
} else if (typeof data === 'object' && data !== null) {
|
|
81
|
+
// Spread object properties into the log entry
|
|
82
|
+
Object.assign(logEntry, data);
|
|
83
|
+
} else {
|
|
84
|
+
logEntry.message = String(data);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return JSON.stringify({ log: logEntry });
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Create the output function that writes to stderr
|
|
92
|
+
*/
|
|
93
|
+
function createOutput(
|
|
94
|
+
level: string,
|
|
95
|
+
tags?: Record<string, unknown>
|
|
96
|
+
): (data: unknown) => void {
|
|
97
|
+
return (data: unknown) => {
|
|
98
|
+
const json = formatLogEntry(level, data, tags);
|
|
99
|
+
// Use stderr to avoid interfering with stdout JSON output
|
|
100
|
+
process.stderr.write(json + '\n');
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* LazyLogger interface extending log-lazy with JSON output
|
|
106
|
+
*/
|
|
107
|
+
export interface LazyLogger {
|
|
108
|
+
// Log at info level (default)
|
|
109
|
+
(fn: () => unknown): void;
|
|
110
|
+
|
|
111
|
+
// Log levels
|
|
112
|
+
error(fn: () => unknown): void;
|
|
113
|
+
warn(fn: () => unknown): void;
|
|
114
|
+
info(fn: () => unknown): void;
|
|
115
|
+
debug(fn: () => unknown): void;
|
|
116
|
+
verbose(fn: () => unknown): void;
|
|
117
|
+
trace(fn: () => unknown): void;
|
|
118
|
+
|
|
119
|
+
// Level management
|
|
120
|
+
enableLevel(level: LogLevel): void;
|
|
121
|
+
disableLevel(level: LogLevel): void;
|
|
122
|
+
setLevel(level: LevelPreset | number): void;
|
|
123
|
+
getEnabledLevels(): LogLevel[];
|
|
124
|
+
shouldLog(level: LogLevel): boolean;
|
|
125
|
+
|
|
126
|
+
// Tag support
|
|
127
|
+
tag(key: string, value: unknown): LazyLogger;
|
|
128
|
+
clone(): LazyLogger;
|
|
129
|
+
|
|
130
|
+
// Configuration
|
|
131
|
+
readonly enabled: boolean;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Create a lazy logger with JSON output format
|
|
136
|
+
*/
|
|
137
|
+
export function createLazyLogger(
|
|
138
|
+
initialTags?: Record<string, unknown>
|
|
139
|
+
): LazyLogger {
|
|
140
|
+
// Determine initial log level based on verbose flag
|
|
141
|
+
const initialLevel = Flag.OPENCODE_VERBOSE ? LEVEL_VERBOSE : LEVEL_DISABLED;
|
|
142
|
+
|
|
143
|
+
// Create base log-lazy instance
|
|
144
|
+
const baseLog = makeLog({ level: initialLevel });
|
|
145
|
+
const tags = { ...initialTags };
|
|
146
|
+
|
|
147
|
+
// Custom output functions that format as JSON
|
|
148
|
+
const outputError = createOutput('error', tags);
|
|
149
|
+
const outputWarn = createOutput('warn', tags);
|
|
150
|
+
const outputInfo = createOutput('info', tags);
|
|
151
|
+
const outputDebug = createOutput('debug', tags);
|
|
152
|
+
const outputVerbose = createOutput('verbose', tags);
|
|
153
|
+
const outputTrace = createOutput('trace', tags);
|
|
154
|
+
|
|
155
|
+
// Create wrapper that uses JSON output
|
|
156
|
+
const wrappedLog = function (fn: () => unknown): void {
|
|
157
|
+
baseLog.info(() => {
|
|
158
|
+
const result = fn();
|
|
159
|
+
outputInfo(result);
|
|
160
|
+
return ''; // Return empty string as the base logger just needs something
|
|
161
|
+
});
|
|
162
|
+
} as LazyLogger;
|
|
163
|
+
|
|
164
|
+
wrappedLog.error = (fn: () => unknown): void => {
|
|
165
|
+
baseLog.error(() => {
|
|
166
|
+
const result = fn();
|
|
167
|
+
outputError(result);
|
|
168
|
+
return '';
|
|
169
|
+
});
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
wrappedLog.warn = (fn: () => unknown): void => {
|
|
173
|
+
baseLog.warn(() => {
|
|
174
|
+
const result = fn();
|
|
175
|
+
outputWarn(result);
|
|
176
|
+
return '';
|
|
177
|
+
});
|
|
178
|
+
};
|
|
179
|
+
|
|
180
|
+
wrappedLog.info = (fn: () => unknown): void => {
|
|
181
|
+
baseLog.info(() => {
|
|
182
|
+
const result = fn();
|
|
183
|
+
outputInfo(result);
|
|
184
|
+
return '';
|
|
185
|
+
});
|
|
186
|
+
};
|
|
187
|
+
|
|
188
|
+
wrappedLog.debug = (fn: () => unknown): void => {
|
|
189
|
+
baseLog.debug(() => {
|
|
190
|
+
const result = fn();
|
|
191
|
+
outputDebug(result);
|
|
192
|
+
return '';
|
|
193
|
+
});
|
|
194
|
+
};
|
|
195
|
+
|
|
196
|
+
wrappedLog.verbose = (fn: () => unknown): void => {
|
|
197
|
+
baseLog.verbose(() => {
|
|
198
|
+
const result = fn();
|
|
199
|
+
outputVerbose(result);
|
|
200
|
+
return '';
|
|
201
|
+
});
|
|
202
|
+
};
|
|
203
|
+
|
|
204
|
+
wrappedLog.trace = (fn: () => unknown): void => {
|
|
205
|
+
baseLog.trace(() => {
|
|
206
|
+
const result = fn();
|
|
207
|
+
outputTrace(result);
|
|
208
|
+
return '';
|
|
209
|
+
});
|
|
210
|
+
};
|
|
211
|
+
|
|
212
|
+
// Level management
|
|
213
|
+
wrappedLog.enableLevel = (level: LogLevel): void => {
|
|
214
|
+
baseLog.enableLevel(level);
|
|
215
|
+
};
|
|
216
|
+
|
|
217
|
+
wrappedLog.disableLevel = (level: LogLevel): void => {
|
|
218
|
+
baseLog.disableLevel(level);
|
|
219
|
+
};
|
|
220
|
+
|
|
221
|
+
wrappedLog.setLevel = (level: LevelPreset | number): void => {
|
|
222
|
+
const numericLevel =
|
|
223
|
+
typeof level === 'string' ? LEVEL_PRESETS[level] : level;
|
|
224
|
+
|
|
225
|
+
// Reset all levels and enable the new one
|
|
226
|
+
Object.values([
|
|
227
|
+
'error',
|
|
228
|
+
'warn',
|
|
229
|
+
'info',
|
|
230
|
+
'debug',
|
|
231
|
+
'verbose',
|
|
232
|
+
'trace',
|
|
233
|
+
]).forEach((l) => baseLog.disableLevel(l as LogLevel));
|
|
234
|
+
|
|
235
|
+
// Enable appropriate levels based on the numeric level
|
|
236
|
+
if (numericLevel & levels.error) baseLog.enableLevel('error');
|
|
237
|
+
if (numericLevel & levels.warn) baseLog.enableLevel('warn');
|
|
238
|
+
if (numericLevel & levels.info) baseLog.enableLevel('info');
|
|
239
|
+
if (numericLevel & levels.debug) baseLog.enableLevel('debug');
|
|
240
|
+
if (numericLevel & levels.verbose) baseLog.enableLevel('verbose');
|
|
241
|
+
if (numericLevel & levels.trace) baseLog.enableLevel('trace');
|
|
242
|
+
};
|
|
243
|
+
|
|
244
|
+
wrappedLog.getEnabledLevels = (): LogLevel[] => {
|
|
245
|
+
return baseLog.getEnabledLevels();
|
|
246
|
+
};
|
|
247
|
+
|
|
248
|
+
wrappedLog.shouldLog = (level: LogLevel): boolean => {
|
|
249
|
+
return baseLog.shouldLog(level);
|
|
250
|
+
};
|
|
251
|
+
|
|
252
|
+
// Tag support
|
|
253
|
+
wrappedLog.tag = (key: string, value: unknown): LazyLogger => {
|
|
254
|
+
tags[key] = value;
|
|
255
|
+
return wrappedLog;
|
|
256
|
+
};
|
|
257
|
+
|
|
258
|
+
wrappedLog.clone = (): LazyLogger => {
|
|
259
|
+
return createLazyLogger({ ...tags });
|
|
260
|
+
};
|
|
261
|
+
|
|
262
|
+
// Configuration
|
|
263
|
+
Object.defineProperty(wrappedLog, 'enabled', {
|
|
264
|
+
get: () => Flag.OPENCODE_VERBOSE,
|
|
265
|
+
enumerable: true,
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
return wrappedLog;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
/**
|
|
272
|
+
* Default lazy logger instance
|
|
273
|
+
* Enabled only when --verbose flag or LINK_ASSISTANT_AGENT_VERBOSE env is set
|
|
274
|
+
*/
|
|
275
|
+
export const lazyLog = createLazyLogger({ service: 'agent' });
|
|
276
|
+
|
|
277
|
+
/**
|
|
278
|
+
* Utility to update the global logger level at runtime
|
|
279
|
+
* Call this after Flag.setVerbose() to sync the logger state
|
|
280
|
+
*/
|
|
281
|
+
export function syncLoggerWithVerboseFlag(): void {
|
|
282
|
+
if (Flag.OPENCODE_VERBOSE) {
|
|
283
|
+
lazyLog.setLevel('verbose');
|
|
284
|
+
} else {
|
|
285
|
+
lazyLog.setLevel('disabled');
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
// Export level constants for external use
|
|
290
|
+
export { levels, LEVEL_PRESETS };
|
|
291
|
+
export type { LevelPreset };
|
package/src/util/log.ts
CHANGED
|
@@ -2,7 +2,20 @@ import path from 'path';
|
|
|
2
2
|
import fs from 'fs/promises';
|
|
3
3
|
import { Global } from '../global';
|
|
4
4
|
import z from 'zod';
|
|
5
|
+
import makeLog, { levels } from 'log-lazy';
|
|
6
|
+
import { Flag } from '../flag/flag.ts';
|
|
5
7
|
|
|
8
|
+
/**
|
|
9
|
+
* Logging module with JSON output and lazy evaluation support.
|
|
10
|
+
*
|
|
11
|
+
* Features:
|
|
12
|
+
* - JSON formatted output: All logs are wrapped in { log: { ... } } structure
|
|
13
|
+
* - Lazy evaluation: Use lazy() methods to defer expensive computations
|
|
14
|
+
* - Level control: Respects --verbose flag and log level settings
|
|
15
|
+
* - File logging: Writes to file when not in verbose/print mode
|
|
16
|
+
*
|
|
17
|
+
* The JSON format ensures all output is parsable, separating logs from regular output.
|
|
18
|
+
*/
|
|
6
19
|
export namespace Log {
|
|
7
20
|
export const Level = z
|
|
8
21
|
.enum(['DEBUG', 'INFO', 'WARN', 'ERROR'])
|
|
@@ -17,16 +30,41 @@ export namespace Log {
|
|
|
17
30
|
};
|
|
18
31
|
|
|
19
32
|
let level: Level = 'INFO';
|
|
33
|
+
let jsonOutput = false; // Whether to output JSON format (enabled in verbose mode)
|
|
20
34
|
|
|
21
35
|
function shouldLog(input: Level): boolean {
|
|
22
36
|
return levelPriority[input] >= levelPriority[level];
|
|
23
37
|
}
|
|
24
38
|
|
|
39
|
+
/**
|
|
40
|
+
* Logger interface with support for both immediate and lazy logging.
|
|
41
|
+
*
|
|
42
|
+
* All logging methods accept either:
|
|
43
|
+
* - A message string/object/Error with optional extra data
|
|
44
|
+
* - A function that returns the data to log (lazy evaluation)
|
|
45
|
+
*
|
|
46
|
+
* Lazy logging: The function is only called if logging is enabled for that level,
|
|
47
|
+
* avoiding expensive computations when logs are disabled.
|
|
48
|
+
*/
|
|
25
49
|
export type Logger = {
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
50
|
+
// Unified logging - supports both immediate and lazy (callback) styles
|
|
51
|
+
debug(
|
|
52
|
+
message?: any | (() => { message?: string; [key: string]: any }),
|
|
53
|
+
extra?: Record<string, any>
|
|
54
|
+
): void;
|
|
55
|
+
info(
|
|
56
|
+
message?: any | (() => { message?: string; [key: string]: any }),
|
|
57
|
+
extra?: Record<string, any>
|
|
58
|
+
): void;
|
|
59
|
+
error(
|
|
60
|
+
message?: any | (() => { message?: string; [key: string]: any }),
|
|
61
|
+
extra?: Record<string, any>
|
|
62
|
+
): void;
|
|
63
|
+
warn(
|
|
64
|
+
message?: any | (() => { message?: string; [key: string]: any }),
|
|
65
|
+
extra?: Record<string, any>
|
|
66
|
+
): void;
|
|
67
|
+
|
|
30
68
|
tag(key: string, value: string): Logger;
|
|
31
69
|
clone(): Logger;
|
|
32
70
|
time(
|
|
@@ -54,24 +92,47 @@ export namespace Log {
|
|
|
54
92
|
}
|
|
55
93
|
let write = (msg: any) => Bun.stderr.write(msg);
|
|
56
94
|
|
|
95
|
+
// Initialize log-lazy for controlling lazy log execution
|
|
96
|
+
let lazyLogInstance = makeLog({ level: 0 }); // Start disabled
|
|
97
|
+
|
|
57
98
|
export async function init(options: Options) {
|
|
58
99
|
if (options.level) level = options.level;
|
|
59
100
|
cleanup(Global.Path.log);
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
101
|
+
|
|
102
|
+
// Always use JSON output format for logs
|
|
103
|
+
jsonOutput = true;
|
|
104
|
+
|
|
105
|
+
// Configure lazy logging level based on verbose flag
|
|
106
|
+
if (Flag.OPENCODE_VERBOSE || options.print) {
|
|
107
|
+
// Enable all levels for lazy logging when verbose
|
|
108
|
+
lazyLogInstance = makeLog({
|
|
109
|
+
level: levels.debug | levels.info | levels.warn | levels.error,
|
|
110
|
+
});
|
|
111
|
+
} else {
|
|
112
|
+
// Disable lazy logging when not verbose
|
|
113
|
+
lazyLogInstance = makeLog({ level: 0 });
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
if (options.print) {
|
|
117
|
+
// In print mode, output to stderr
|
|
118
|
+
// No file logging needed
|
|
119
|
+
} else {
|
|
120
|
+
// In normal mode, write to file
|
|
121
|
+
logpath = path.join(
|
|
122
|
+
Global.Path.log,
|
|
123
|
+
options.dev
|
|
124
|
+
? 'dev.log'
|
|
125
|
+
: new Date().toISOString().split('.')[0].replace(/:/g, '') + '.log'
|
|
126
|
+
);
|
|
127
|
+
const logfile = Bun.file(logpath);
|
|
128
|
+
await fs.truncate(logpath).catch(() => {});
|
|
129
|
+
const writer = logfile.writer();
|
|
130
|
+
write = async (msg: any) => {
|
|
131
|
+
const num = writer.write(msg);
|
|
132
|
+
writer.flush();
|
|
133
|
+
return num;
|
|
134
|
+
};
|
|
135
|
+
}
|
|
75
136
|
}
|
|
76
137
|
|
|
77
138
|
async function cleanup(dir: string) {
|
|
@@ -97,6 +158,43 @@ export namespace Log {
|
|
|
97
158
|
: result;
|
|
98
159
|
}
|
|
99
160
|
|
|
161
|
+
/**
|
|
162
|
+
* Format log entry as JSON object wrapped in { log: { ... } }
|
|
163
|
+
*/
|
|
164
|
+
function formatJson(
|
|
165
|
+
logLevel: Level,
|
|
166
|
+
message: any,
|
|
167
|
+
tags: Record<string, any>,
|
|
168
|
+
extra?: Record<string, any>
|
|
169
|
+
): string {
|
|
170
|
+
const timestamp = new Date().toISOString();
|
|
171
|
+
const logEntry: Record<string, any> = {
|
|
172
|
+
level: logLevel.toLowerCase(),
|
|
173
|
+
timestamp,
|
|
174
|
+
...tags,
|
|
175
|
+
...extra,
|
|
176
|
+
};
|
|
177
|
+
|
|
178
|
+
if (message !== undefined && message !== null) {
|
|
179
|
+
if (typeof message === 'string') {
|
|
180
|
+
logEntry.message = message;
|
|
181
|
+
} else if (message instanceof Error) {
|
|
182
|
+
logEntry.message = message.message;
|
|
183
|
+
logEntry.error = {
|
|
184
|
+
name: message.name,
|
|
185
|
+
message: message.message,
|
|
186
|
+
stack: message.stack,
|
|
187
|
+
};
|
|
188
|
+
} else if (typeof message === 'object') {
|
|
189
|
+
Object.assign(logEntry, message);
|
|
190
|
+
} else {
|
|
191
|
+
logEntry.message = String(message);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
return JSON.stringify({ log: logEntry });
|
|
196
|
+
}
|
|
197
|
+
|
|
100
198
|
let last = Date.now();
|
|
101
199
|
export function create(tags?: Record<string, any>) {
|
|
102
200
|
tags = tags || {};
|
|
@@ -109,7 +207,8 @@ export namespace Log {
|
|
|
109
207
|
}
|
|
110
208
|
}
|
|
111
209
|
|
|
112
|
-
|
|
210
|
+
// Legacy format for file logging (backward compatibility)
|
|
211
|
+
function buildLegacy(message: any, extra?: Record<string, any>) {
|
|
113
212
|
const prefix = Object.entries({
|
|
114
213
|
...tags,
|
|
115
214
|
...extra,
|
|
@@ -131,27 +230,83 @@ export namespace Log {
|
|
|
131
230
|
.join(' ') + '\n'
|
|
132
231
|
);
|
|
133
232
|
}
|
|
233
|
+
|
|
234
|
+
// Choose output format based on jsonOutput flag
|
|
235
|
+
function output(
|
|
236
|
+
logLevel: Level,
|
|
237
|
+
message: any,
|
|
238
|
+
extra?: Record<string, any>
|
|
239
|
+
) {
|
|
240
|
+
if (jsonOutput) {
|
|
241
|
+
// Use our custom JSON formatting for { log: { ... } } format
|
|
242
|
+
write(formatJson(logLevel, message, tags || {}, extra) + '\n');
|
|
243
|
+
} else {
|
|
244
|
+
write(logLevel.padEnd(5) + ' ' + buildLegacy(message, extra));
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
134
248
|
const result: Logger = {
|
|
135
249
|
debug(message?: any, extra?: Record<string, any>) {
|
|
136
|
-
if (shouldLog('DEBUG'))
|
|
137
|
-
|
|
250
|
+
if (!shouldLog('DEBUG')) return;
|
|
251
|
+
|
|
252
|
+
// Check if message is a function (lazy logging)
|
|
253
|
+
if (typeof message === 'function') {
|
|
254
|
+
lazyLogInstance.debug(() => {
|
|
255
|
+
const data = message();
|
|
256
|
+
const { message: msg, ...extraData } = data;
|
|
257
|
+
output('DEBUG', msg, extraData);
|
|
258
|
+
return '';
|
|
259
|
+
});
|
|
260
|
+
} else {
|
|
261
|
+
output('DEBUG', message, extra);
|
|
138
262
|
}
|
|
139
263
|
},
|
|
140
264
|
info(message?: any, extra?: Record<string, any>) {
|
|
141
|
-
if (shouldLog('INFO'))
|
|
142
|
-
|
|
265
|
+
if (!shouldLog('INFO')) return;
|
|
266
|
+
|
|
267
|
+
// Check if message is a function (lazy logging)
|
|
268
|
+
if (typeof message === 'function') {
|
|
269
|
+
lazyLogInstance.info(() => {
|
|
270
|
+
const data = message();
|
|
271
|
+
const { message: msg, ...extraData } = data;
|
|
272
|
+
output('INFO', msg, extraData);
|
|
273
|
+
return '';
|
|
274
|
+
});
|
|
275
|
+
} else {
|
|
276
|
+
output('INFO', message, extra);
|
|
143
277
|
}
|
|
144
278
|
},
|
|
145
279
|
error(message?: any, extra?: Record<string, any>) {
|
|
146
|
-
if (shouldLog('ERROR'))
|
|
147
|
-
|
|
280
|
+
if (!shouldLog('ERROR')) return;
|
|
281
|
+
|
|
282
|
+
// Check if message is a function (lazy logging)
|
|
283
|
+
if (typeof message === 'function') {
|
|
284
|
+
lazyLogInstance.error(() => {
|
|
285
|
+
const data = message();
|
|
286
|
+
const { message: msg, ...extraData } = data;
|
|
287
|
+
output('ERROR', msg, extraData);
|
|
288
|
+
return '';
|
|
289
|
+
});
|
|
290
|
+
} else {
|
|
291
|
+
output('ERROR', message, extra);
|
|
148
292
|
}
|
|
149
293
|
},
|
|
150
294
|
warn(message?: any, extra?: Record<string, any>) {
|
|
151
|
-
if (shouldLog('WARN'))
|
|
152
|
-
|
|
295
|
+
if (!shouldLog('WARN')) return;
|
|
296
|
+
|
|
297
|
+
// Check if message is a function (lazy logging)
|
|
298
|
+
if (typeof message === 'function') {
|
|
299
|
+
lazyLogInstance.warn(() => {
|
|
300
|
+
const data = message();
|
|
301
|
+
const { message: msg, ...extraData } = data;
|
|
302
|
+
output('WARN', msg, extraData);
|
|
303
|
+
return '';
|
|
304
|
+
});
|
|
305
|
+
} else {
|
|
306
|
+
output('WARN', message, extra);
|
|
153
307
|
}
|
|
154
308
|
},
|
|
309
|
+
|
|
155
310
|
tag(key: string, value: string) {
|
|
156
311
|
if (tags) tags[key] = value;
|
|
157
312
|
return result;
|
|
@@ -184,4 +339,26 @@ export namespace Log {
|
|
|
184
339
|
|
|
185
340
|
return result;
|
|
186
341
|
}
|
|
342
|
+
|
|
343
|
+
/**
|
|
344
|
+
* Check if JSON output mode is enabled
|
|
345
|
+
*/
|
|
346
|
+
export function isJsonOutput(): boolean {
|
|
347
|
+
return jsonOutput;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
/**
|
|
351
|
+
* Sync lazy logging with verbose flag at runtime
|
|
352
|
+
* Call after Flag.setVerbose() to update lazy logging state
|
|
353
|
+
*/
|
|
354
|
+
export function syncWithVerboseFlag(): void {
|
|
355
|
+
if (Flag.OPENCODE_VERBOSE) {
|
|
356
|
+
jsonOutput = true;
|
|
357
|
+
lazyLogInstance = makeLog({
|
|
358
|
+
level: levels.debug | levels.info | levels.warn | levels.error,
|
|
359
|
+
});
|
|
360
|
+
} else {
|
|
361
|
+
lazyLogInstance = makeLog({ level: 0 });
|
|
362
|
+
}
|
|
363
|
+
}
|
|
187
364
|
}
|