feat: Add unified logger utility with file and console output

- Create logger factory with explicit module names (no stack traces)
- Support log/warn/error/debug levels with clean method names
- Write to ~/.vibetunnel/log.txt with automatic cleanup on startup
- Add colored console output with timestamps and module names
- Support debug mode via flag or environment variable

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Mario Zechner 2025-06-22 22:04:11 +02:00
parent 1c007a2181
commit 3a3b5e44e9

View file

@ -0,0 +1,180 @@
import * as fs from 'fs';
import * as path from 'path';
import * as os from 'os';
import chalk from 'chalk';
// Log file path
const LOG_DIR = path.join(os.homedir(), '.vibetunnel');
const LOG_FILE = path.join(LOG_DIR, 'log.txt');
// Debug mode flag
let debugMode = false;
// File handle for log file
let logFileHandle: fs.WriteStream | null = null;
// ANSI color codes for stripping from file output
// eslint-disable-next-line no-control-regex
const ANSI_PATTERN = /\x1b\[[0-9;]*m/g;
/**
* Initialize the logger - creates log directory and file
*/
export function initLogger(debug: boolean = false): void {
debugMode = debug;
try {
// Ensure log directory exists
if (!fs.existsSync(LOG_DIR)) {
fs.mkdirSync(LOG_DIR, { recursive: true });
}
// Delete old log file if it exists
if (fs.existsSync(LOG_FILE)) {
fs.unlinkSync(LOG_FILE);
}
// Create new log file write stream
logFileHandle = fs.createWriteStream(LOG_FILE, { flags: 'a' });
} catch (error) {
// Don't throw, just log to console
console.error('Failed to initialize log file:', error);
}
}
/**
* Close the logger
*/
export function closeLogger(): void {
if (logFileHandle) {
logFileHandle.end();
logFileHandle = null;
}
}
/**
* Format log message with timestamp
*/
function formatMessage(
level: string,
module: string,
args: any[]
): { console: string; file: string } {
const timestamp = new Date().toISOString();
// Format arguments
const message = args
.map((arg) => {
if (typeof arg === 'object') {
try {
// Use JSON.stringify with 2-space indent for objects
return JSON.stringify(arg, null, 2);
} catch {
return String(arg);
}
}
return String(arg);
})
.join(' ');
// Console format with colors
let consoleFormat: string;
const moduleColor = chalk.cyan(`[${module}]`);
const timestampColor = chalk.gray(timestamp);
switch (level) {
case 'ERROR':
consoleFormat = `${timestampColor} ${chalk.red(level)} ${moduleColor} ${chalk.red(message)}`;
break;
case 'WARN':
consoleFormat = `${timestampColor} ${chalk.yellow(level)} ${moduleColor} ${chalk.yellow(message)}`;
break;
case 'DEBUG':
consoleFormat = `${timestampColor} ${chalk.magenta(level)} ${moduleColor} ${chalk.gray(message)}`;
break;
default: // LOG
consoleFormat = `${timestampColor} ${chalk.green(level)} ${moduleColor} ${message}`;
}
// File format (no colors)
const fileFormat = `${timestamp} ${level.padEnd(5)} [${module}] ${message}`;
return { console: consoleFormat, file: fileFormat };
}
/**
* Write to log file
*/
function writeToFile(message: string): void {
if (logFileHandle) {
try {
// Strip ANSI color codes from message
const cleanMessage = message.replace(ANSI_PATTERN, '');
logFileHandle.write(cleanMessage + '\n');
} catch {
// Silently ignore file write errors
}
}
}
/**
* Enable or disable debug mode
*/
export function setDebugMode(enabled: boolean): void {
debugMode = enabled;
}
/**
* Log from a specific module (used by client-side API)
*/
export function logFromModule(level: string, module: string, args: any[]): void {
if (level === 'DEBUG' && !debugMode) return;
const { console: consoleMsg, file: fileMsg } = formatMessage(level, module, args);
// Log to console
switch (level) {
case 'ERROR':
console.error(consoleMsg);
break;
case 'WARN':
console.warn(consoleMsg);
break;
default:
console.log(consoleMsg);
}
// Log to file
writeToFile(fileMsg);
}
/**
* Create a logger for a specific module
* This is the main factory function that should be used
*/
export function createLogger(moduleName: string) {
return {
log: (...args: any[]) => {
const { console: consoleMsg, file: fileMsg } = formatMessage('LOG', moduleName, args);
console.log(consoleMsg);
writeToFile(fileMsg);
},
warn: (...args: any[]) => {
const { console: consoleMsg, file: fileMsg } = formatMessage('WARN', moduleName, args);
console.warn(consoleMsg);
writeToFile(fileMsg);
},
error: (...args: any[]) => {
const { console: consoleMsg, file: fileMsg } = formatMessage('ERROR', moduleName, args);
console.error(consoleMsg);
writeToFile(fileMsg);
},
debug: (...args: any[]) => {
if (debugMode) {
const { console: consoleMsg, file: fileMsg } = formatMessage('DEBUG', moduleName, args);
console.log(consoleMsg);
writeToFile(fileMsg);
}
},
};
}