codemesh
Version:
Execute TypeScript code against multiple MCP servers, weaving them together into powerful workflows
188 lines (187 loc) • 7.37 kB
JavaScript
import { appendFileSync, mkdirSync, existsSync, readFileSync } from 'node:fs';
import { join, dirname } from 'node:path';
export class FileLogger {
static instance = null;
config = null;
currentLogFile = null;
sessionStartTime = null;
constructor() { }
static getInstance() {
if (!FileLogger.instance) {
FileLogger.instance = new FileLogger();
}
return FileLogger.instance;
}
configure(config) {
this.config = config || null;
if (this.config?.enabled) {
this.initSession();
}
}
isEnabled() {
return this.config?.enabled === true;
}
getVersion() {
try {
// Read package.json from the codemesh-server directory
const packageJsonPath = join(dirname(new URL(import.meta.url).pathname), '..', 'package.json');
const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf-8'));
return packageJson.version || 'unknown';
}
catch (error) {
console.error('Failed to read package.json version:', error);
return 'unknown';
}
}
initSession() {
if (!this.config?.enabled)
return;
const now = new Date();
this.sessionStartTime = now;
const dateStr = now.toISOString().split('T')[0]; // YYYY-MM-DD
const logDir = this.config.logDir || '.codemesh/logs';
const version = this.getVersion();
// Create log directory if it doesn't exist
if (!existsSync(logDir)) {
mkdirSync(logDir, { recursive: true });
}
this.currentLogFile = join(logDir, `${dateStr}.md`);
// Write session header if this is a new file
if (!existsSync(this.currentLogFile)) {
this.writeToFile(`# CodeMesh Session Log\n`);
this.writeToFile(`**Date:** ${dateStr}\n`);
this.writeToFile(`**Version:** ${version}\n\n`);
this.writeToFile(`---\n\n`);
}
else {
// Add separator for new session in existing file
this.writeToFile(`\n---\n\n`);
this.writeToFile(`## New Session - ${now.toTimeString().split(' ')[0]}\n`);
this.writeToFile(`**Version:** ${version}\n\n`);
}
}
writeToFile(content) {
if (!this.currentLogFile)
return;
try {
appendFileSync(this.currentLogFile, content, 'utf-8');
}
catch (error) {
// Silently fail to avoid disrupting tool execution
console.error('Failed to write to log file:', error);
}
}
formatTimestamp() {
return new Date().toTimeString().split(' ')[0]; // HH:MM:SS
}
formatDuration(ms) {
if (ms < 1000)
return `${ms}ms`;
return `${(ms / 1000).toFixed(1)}s`;
}
getStatusEmoji(status) {
return status === 'success' ? '✅' : '❌';
}
logToolCall(log) {
if (!this.config?.enabled)
return;
const timestamp = this.formatTimestamp();
const duration = this.formatDuration(log.duration);
const statusEmoji = this.getStatusEmoji(log.status);
const exploringTag = log.isExploring ? ' (EXPLORING)' : '';
let content = `## ${timestamp} - ${log.tool}${exploringTag}\n`;
content += `**Duration:** ${duration} \n`;
content += `**Status:** ${statusEmoji} ${log.status === 'success' ? 'Success' : 'Error'}\n\n`;
// Log arguments if present
if (log.args && Object.keys(log.args).length > 0) {
content += `### Request\n`;
if (log.tool === 'execute-code' && log.code) {
content += `\`\`\`typescript\n${log.code}\n\`\`\`\n\n`;
}
else if (log.tool === 'add-augmentation' && log.args.toolName) {
content += `**Tool Name:** ${log.args.toolName}\n\n`;
if (log.args.markdown) {
content += `**Markdown:**\n\n---\n\n${log.args.markdown}\n\n---\n\n`;
}
if (log.args.codemeshDir) {
content += `**Directory:** ${log.args.codemeshDir}\n\n`;
}
}
else {
content += `\`\`\`json\n${JSON.stringify(log.args, null, 2)}\n\`\`\`\n\n`;
}
}
else {
content += `### Request\nNo arguments\n\n`;
}
// Log console output if present
if (log.consoleOutput) {
content += `### Console Output\n`;
content += `\`\`\`\n${log.consoleOutput}\n\`\`\`\n\n`;
}
// Log response
content += `### Response\n`;
if (log.status === 'error' && log.error) {
content += `\`\`\`\n${log.error}\n\`\`\`\n\n`;
}
else if (log.response) {
// Try to format response nicely
if (typeof log.response === 'string') {
// Check if it looks like code or JSON
if (log.response.trim().startsWith('{') || log.response.trim().startsWith('[')) {
try {
const parsed = JSON.parse(log.response);
content += `\`\`\`json\n${JSON.stringify(parsed, null, 2)}\n\`\`\`\n\n`;
}
catch {
content += `\`\`\`\n${log.response}\n\`\`\`\n\n`;
}
}
else {
content += `\n${log.response}\n\n`;
}
}
else {
content += `\`\`\`json\n${JSON.stringify(log.response, null, 2)}\n\`\`\`\n\n`;
}
}
content += `---\n\n`;
this.writeToFile(content);
}
logMcpResponse(tool, result, duration, isExploring) {
if (!this.config?.enabled)
return;
const timestamp = this.formatTimestamp();
const durationStr = this.formatDuration(duration);
const exploringTag = isExploring ? ' (EXPLORING)' : '';
const statusEmoji = result.isError ? '❌' : '✅';
const statusText = result.isError ? 'Error' : 'Success';
let content = `### 📤 MCP Response - ${timestamp} - ${tool}${exploringTag}\n`;
content += `**Duration:** ${durationStr} \n`;
content += `**Status:** ${statusEmoji} ${statusText} \n`;
content += `**isError:** ${result.isError || false}\n\n`;
content += `#### Complete Response Object\n`;
content += `\`\`\`json\n${JSON.stringify(result, null, 2)}\n\`\`\`\n\n`;
content += `---\n\n`;
this.writeToFile(content);
}
logMessage(level, message) {
if (!this.config?.enabled)
return;
const levels = ['debug', 'info', 'warn', 'error'];
const configLevel = this.config.level || 'info';
// Check if message should be logged based on level
if (levels.indexOf(level) < levels.indexOf(configLevel)) {
return;
}
const timestamp = this.formatTimestamp();
const emoji = {
debug: '🔍',
info: 'ℹ️',
warn: '⚠️',
error: '❌',
}[level];
const content = `${timestamp} ${emoji} **${level.toUpperCase()}**: ${message}\n\n`;
this.writeToFile(content);
}
}