220 lines
5.2 KiB
JavaScript
220 lines
5.2 KiB
JavaScript
/**
|
|
* Debug Logger - Comprehensive request/response logging for TUI 5
|
|
* Enabled via --debug flag or /debug command
|
|
*
|
|
* Original implementation for OpenQode TUI
|
|
*/
|
|
|
|
import { appendFile, writeFile, readFile, access } from 'fs/promises';
|
|
import { join } from 'path';
|
|
|
|
const DEBUG_FILE = '.openqode-debug.log';
|
|
|
|
/**
|
|
* DebugLogger class - Logs all API requests, responses, and tool executions
|
|
*/
|
|
export class DebugLogger {
|
|
constructor(options = {}) {
|
|
this.enabled = options.enabled || false;
|
|
this.logPath = options.logPath || join(process.cwd(), DEBUG_FILE);
|
|
this.maxLogSize = options.maxLogSize || 5 * 1024 * 1024; // 5MB max
|
|
this.sessionId = Date.now().toString(36);
|
|
}
|
|
|
|
/**
|
|
* Enable debug logging
|
|
*/
|
|
enable() {
|
|
this.enabled = true;
|
|
this.log('DEBUG', 'Debug logging enabled');
|
|
}
|
|
|
|
/**
|
|
* Disable debug logging
|
|
*/
|
|
disable() {
|
|
this.log('DEBUG', 'Debug logging disabled');
|
|
this.enabled = false;
|
|
}
|
|
|
|
/**
|
|
* Toggle debug mode
|
|
*/
|
|
toggle() {
|
|
if (this.enabled) {
|
|
this.disable();
|
|
} else {
|
|
this.enable();
|
|
}
|
|
return this.enabled;
|
|
}
|
|
|
|
/**
|
|
* Format timestamp
|
|
*/
|
|
timestamp() {
|
|
return new Date().toISOString();
|
|
}
|
|
|
|
/**
|
|
* Log a message
|
|
* @param {string} level - Log level (INFO, DEBUG, WARN, ERROR, API, TOOL)
|
|
* @param {string} message - Log message
|
|
* @param {Object} data - Optional data to log
|
|
*/
|
|
async log(level, message, data = null) {
|
|
if (!this.enabled && level !== 'DEBUG') return;
|
|
|
|
const entry = {
|
|
timestamp: this.timestamp(),
|
|
session: this.sessionId,
|
|
level,
|
|
message,
|
|
...(data && { data: this.truncate(data) })
|
|
};
|
|
|
|
const logLine = JSON.stringify(entry) + '\n';
|
|
|
|
try {
|
|
await appendFile(this.logPath, logLine);
|
|
} catch (error) {
|
|
// Silent fail - debug logging shouldn't break the app
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Log API request
|
|
*/
|
|
async logRequest(provider, model, prompt, options = {}) {
|
|
await this.log('API_REQUEST', `${provider}/${model}`, {
|
|
promptLength: prompt?.length || 0,
|
|
promptPreview: prompt?.substring(0, 200) + '...',
|
|
options
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Log API response
|
|
*/
|
|
async logResponse(provider, model, response, duration) {
|
|
await this.log('API_RESPONSE', `${provider}/${model}`, {
|
|
success: response?.success,
|
|
responseLength: response?.response?.length || 0,
|
|
responsePreview: response?.response?.substring(0, 200) + '...',
|
|
durationMs: duration,
|
|
usage: response?.usage
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Log tool execution
|
|
*/
|
|
async logTool(toolName, input, output, duration) {
|
|
await this.log('TOOL', toolName, {
|
|
input: this.truncate(input),
|
|
output: this.truncate(output),
|
|
durationMs: duration
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Log error
|
|
*/
|
|
async logError(context, error) {
|
|
await this.log('ERROR', context, {
|
|
message: error?.message,
|
|
stack: error?.stack?.substring(0, 500)
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Log user command
|
|
*/
|
|
async logCommand(command, args) {
|
|
await this.log('COMMAND', command, { args });
|
|
}
|
|
|
|
/**
|
|
* Truncate large objects for logging
|
|
*/
|
|
truncate(obj, maxLength = 1000) {
|
|
if (!obj) return obj;
|
|
|
|
if (typeof obj === 'string') {
|
|
return obj.length > maxLength
|
|
? obj.substring(0, maxLength) + '...[truncated]'
|
|
: obj;
|
|
}
|
|
|
|
try {
|
|
const str = JSON.stringify(obj);
|
|
if (str.length > maxLength) {
|
|
return JSON.parse(str.substring(0, maxLength) + '..."}}');
|
|
}
|
|
return obj;
|
|
} catch {
|
|
return '[Object]';
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Clear log file
|
|
*/
|
|
async clear() {
|
|
try {
|
|
await writeFile(this.logPath, '');
|
|
await this.log('DEBUG', 'Log file cleared');
|
|
} catch (error) {
|
|
// Ignore
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get recent log entries
|
|
* @param {number} count - Number of entries to return
|
|
*/
|
|
async getRecent(count = 50) {
|
|
try {
|
|
const content = await readFile(this.logPath, 'utf8');
|
|
const lines = content.trim().split('\n').filter(l => l);
|
|
return lines.slice(-count).map(l => {
|
|
try {
|
|
return JSON.parse(l);
|
|
} catch {
|
|
return { raw: l };
|
|
}
|
|
});
|
|
} catch {
|
|
return [];
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get log file path
|
|
*/
|
|
getPath() {
|
|
return this.logPath;
|
|
}
|
|
}
|
|
|
|
// Singleton instance
|
|
let _logger = null;
|
|
|
|
export function getDebugLogger(options = {}) {
|
|
if (!_logger) {
|
|
_logger = new DebugLogger(options);
|
|
}
|
|
return _logger;
|
|
}
|
|
|
|
// Check CLI args for --debug flag
|
|
export function initFromArgs() {
|
|
const logger = getDebugLogger();
|
|
if (process.argv.includes('--debug')) {
|
|
logger.enable();
|
|
}
|
|
return logger;
|
|
}
|
|
|
|
export default DebugLogger;
|