TUI5: Added 4 new feature modules - Session Memory, Context Manager, Skills Library, Debug Logger
This commit is contained in:
219
lib/debug-logger.mjs
Normal file
219
lib/debug-logger.mjs
Normal file
@@ -0,0 +1,219 @@
|
||||
/**
|
||||
* 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;
|
||||
Reference in New Issue
Block a user