spb/vquant Public MIT
VibeQuant — AI-powered institutional-grade financial intelligence platform.
TypeScript 84.3%
Python 11.7%
JavaScript 1.6%
CSS 1.5%
HTML 0.7%
1/*2 * =============================================================================3 * VibeQuant (vquant) — AI-Powered Financial Intelligence Platform4 * -----------------------------------------------------------------------------5 * File: server/utils/logger.ts6 *7 * Author: Simon-Pierre Boucher8 * Contact: contact@spboucher.ai9 * Website: https://www.spboucher.ai10 * Demo: https://www.vquant.ai11 * License: MIT (see LICENSE)12 *13 * Copyright © 2026 Simon-Pierre Boucher. All rights reserved.14 * =============================================================================15 */1617/**18 * VibeSurf Logger - Colorful terminal logging utility19 *20 * Provides colored console output with VibeSurf branding21 * Also saves all logs to a text file22 */2324import { appendFileSync, existsSync, mkdirSync } from 'fs';25import { join } from 'path';2627// ANSI color codes28const colors = {29 reset: '\x1b[0m',30 bright: '\x1b[1m',31 dim: '\x1b[2m',3233 // Text colors34 black: '\x1b[30m',35 red: '\x1b[31m',36 green: '\x1b[32m',37 yellow: '\x1b[33m',38 blue: '\x1b[34m',39 magenta: '\x1b[35m',40 cyan: '\x1b[36m',41 white: '\x1b[37m',4243 // Background colors44 bgBlack: '\x1b[40m',45 bgRed: '\x1b[41m',46 bgGreen: '\x1b[42m',47 bgYellow: '\x1b[43m',48 bgBlue: '\x1b[44m',49 bgMagenta: '\x1b[45m',50 bgCyan: '\x1b[46m',51 bgWhite: '\x1b[47m',5253 // Bright text colors54 brightRed: '\x1b[91m',55 brightGreen: '\x1b[92m',56 brightYellow: '\x1b[93m',57 brightBlue: '\x1b[94m',58 brightMagenta: '\x1b[95m',59 brightCyan: '\x1b[96m',60 brightWhite: '\x1b[97m',61};6263/**64 * Format timestamp65 */66function getTimestamp(): string {67 const now = new Date();68 return now.toLocaleTimeString('en-US', {69 hour: '2-digit',70 minute: '2-digit',71 second: '2-digit',72 hour12: false73 });74}7576/**77 * Format full timestamp with date for file logging78 */79function getFullTimestamp(): string {80 const now = new Date();81 return now.toISOString().replace('T', ' ').substring(0, 19);82}8384/**85 * Get log file path for current date86 */87function getLogFilePath(): string {88 const logsDir = join(process.cwd(), 'logs');8990 // Create logs directory if it doesn't exist91 if (!existsSync(logsDir)) {92 mkdirSync(logsDir, { recursive: true });93 }9495 const date = new Date().toISOString().split('T')[0]; // YYYY-MM-DD96 return join(logsDir, `vibesurf-${date}.log`);97}9899/**100 * Strip ANSI color codes from text101 */102function stripColors(text: string): string {103 return text.replace(/\x1b\[[0-9;]*m/g, '');104}105106/**107 * Write log to file108 */109function writeToFile(message: string) {110 try {111 const logFile = getLogFilePath();112 const timestamp = getFullTimestamp();113 const cleanMessage = stripColors(message);114 const logEntry = `${timestamp} ${cleanMessage}\n`;115 appendFileSync(logFile, logEntry, 'utf8');116 } catch (error) {117 // Fail silently to avoid breaking the application118 console.error('Failed to write to log file:', error);119 }120}121122/**123 * Create VibeSurf branded prefix124 */125function vibeSurfPrefix(color: string = colors.brightCyan): string {126 return `${color}${colors.bright}[VibeSurf]${colors.reset}`;127}128129/**130 * Logger class with colorful output131 */132class VibeSurfLogger {133 private enabled: boolean = true;134135 /**136 * Enable/disable logging137 */138 setEnabled(enabled: boolean) {139 this.enabled = enabled;140 }141142 /**143 * General info log - Cyan144 */145 info(message: string, ...args: any[]) {146 if (!this.enabled) return;147 const timestamp = `${colors.dim}${getTimestamp()}${colors.reset}`;148 const prefix = vibeSurfPrefix(colors.brightCyan);149 const logMessage = `${timestamp} ${prefix} ${colors.cyan}${message}${colors.reset}`;150 console.log(logMessage, ...args);151 writeToFile(`[VibeSurf] INFO: ${message} ${args.length > 0 ? JSON.stringify(args) : ''}`);152 }153154 /**155 * Success log - Green156 */157 success(message: string, ...args: any[]) {158 if (!this.enabled) return;159 const timestamp = `${colors.dim}${getTimestamp()}${colors.reset}`;160 const prefix = vibeSurfPrefix(colors.brightGreen);161 const logMessage = `${timestamp} ${prefix} ${colors.green}✓${colors.reset} ${colors.brightGreen}${message}${colors.reset}`;162 console.log(logMessage, ...args);163 writeToFile(`[VibeSurf] SUCCESS: ${message} ${args.length > 0 ? JSON.stringify(args) : ''}`);164 }165166 /**167 * Error log - Red168 */169 error(message: string, ...args: any[]) {170 if (!this.enabled) return;171 const timestamp = `${colors.dim}${getTimestamp()}${colors.reset}`;172 const prefix = vibeSurfPrefix(colors.brightRed);173 const logMessage = `${timestamp} ${prefix} ${colors.red}✗${colors.reset} ${colors.brightRed}${message}${colors.reset}`;174 console.error(logMessage, ...args);175 writeToFile(`[VibeSurf] ERROR: ${message} ${args.length > 0 ? JSON.stringify(args) : ''}`);176 }177178 /**179 * Warning log - Yellow180 */181 warn(message: string, ...args: any[]) {182 if (!this.enabled) return;183 const timestamp = `${colors.dim}${getTimestamp()}${colors.reset}`;184 const prefix = vibeSurfPrefix(colors.brightYellow);185 const logMessage = `${timestamp} ${prefix} ${colors.yellow}⚠${colors.reset} ${colors.brightYellow}${message}${colors.reset}`;186 console.warn(logMessage, ...args);187 writeToFile(`[VibeSurf] WARN: ${message} ${args.length > 0 ? JSON.stringify(args) : ''}`);188 }189190 /**191 * Debug log - Magenta192 */193 debug(message: string, ...args: any[]) {194 if (!this.enabled) return;195 const timestamp = `${colors.dim}${getTimestamp()}${colors.reset}`;196 const prefix = vibeSurfPrefix(colors.brightMagenta);197 const logMessage = `${timestamp} ${prefix} ${colors.magenta}◆${colors.reset} ${colors.brightMagenta}${message}${colors.reset}`;198 console.log(logMessage, ...args);199 writeToFile(`[VibeSurf] DEBUG: ${message} ${args.length > 0 ? JSON.stringify(args) : ''}`);200 }201202 /**203 * Server/System log - Blue204 */205 server(message: string, ...args: any[]) {206 if (!this.enabled) return;207 const timestamp = `${colors.dim}${getTimestamp()}${colors.reset}`;208 const prefix = vibeSurfPrefix(colors.brightBlue);209 const logMessage = `${timestamp} ${prefix} ${colors.blue}▸${colors.reset} ${colors.brightBlue}${message}${colors.reset}`;210 console.log(logMessage, ...args);211 writeToFile(`[VibeSurf] SERVER: ${message} ${args.length > 0 ? JSON.stringify(args) : ''}`);212 }213214 /**215 * API/Request log - Cyan with special formatting216 */217 request(method: string, path: string, status?: number) {218 if (!this.enabled) return;219 const timestamp = `${colors.dim}${getTimestamp()}${colors.reset}`;220 const prefix = vibeSurfPrefix(colors.brightCyan);221 const methodColor = method === 'GET' ? colors.green :222 method === 'POST' ? colors.blue :223 method === 'PUT' ? colors.yellow :224 method === 'DELETE' ? colors.red : colors.white;225226 let statusStr = '';227 let statusText = '';228 if (status) {229 const statusColor = status < 300 ? colors.green :230 status < 400 ? colors.cyan :231 status < 500 ? colors.yellow : colors.red;232 statusStr = ` ${statusColor}[${status}]${colors.reset}`;233 statusText = ` [${status}]`;234 }235236 console.log(`${timestamp} ${prefix} ${methodColor}${method}${colors.reset} ${colors.white}${path}${colors.reset}${statusStr}`);237 writeToFile(`[VibeSurf] REQUEST: ${method} ${path}${statusText}`);238 }239240 /**241 * Tool execution log - Magenta with tool name242 */243 tool(toolName: string, message: string, ...args: any[]) {244 if (!this.enabled) return;245 const timestamp = `${colors.dim}${getTimestamp()}${colors.reset}`;246 const prefix = vibeSurfPrefix(colors.brightMagenta);247 console.log(`${timestamp} ${prefix} ${colors.magenta}🔧${colors.reset} ${colors.bright}${toolName}${colors.reset} ${colors.white}${message}${colors.reset}`, ...args);248 writeToFile(`[VibeSurf] TOOL: ${toolName} - ${message} ${args.length > 0 ? JSON.stringify(args) : ''}`);249 }250251 /**252 * Database log - Green253 */254 database(message: string, ...args: any[]) {255 if (!this.enabled) return;256 const timestamp = `${colors.dim}${getTimestamp()}${colors.reset}`;257 const prefix = vibeSurfPrefix(colors.brightGreen);258 console.log(`${timestamp} ${prefix} ${colors.green}💾${colors.reset} ${colors.brightGreen}${message}${colors.reset}`, ...args);259 writeToFile(`[VibeSurf] DATABASE: ${message} ${args.length > 0 ? JSON.stringify(args) : ''}`);260 }261262 /**263 * Banner - Large colorful header264 */265 banner(title: string, subtitle?: string) {266 if (!this.enabled) return;267 const line = '═'.repeat(60);268 console.log(`\n${colors.brightCyan}${line}${colors.reset}`);269 console.log(`${colors.brightCyan}${colors.bright} 🌊 VibeSurf - ${title}${colors.reset}`);270 if (subtitle) {271 console.log(`${colors.cyan} ${subtitle}${colors.reset}`);272 }273 console.log(`${colors.brightCyan}${line}${colors.reset}\n`);274 writeToFile(`\n${'='.repeat(60)}`);275 writeToFile(`[VibeSurf] BANNER: ${title}${subtitle ? ' - ' + subtitle : ''}`);276 writeToFile(`${'='.repeat(60)}\n`);277 }278279 /**280 * Section header - Medium colored header281 */282 section(title: string) {283 if (!this.enabled) return;284 console.log(`\n${colors.brightCyan}${colors.bright}▶ ${title}${colors.reset}`);285 writeToFile(`\n[VibeSurf] SECTION: ${title}`);286 }287288 /**289 * Key-value pair log290 */291 kv(key: string, value: any) {292 if (!this.enabled) return;293 const timestamp = `${colors.dim}${getTimestamp()}${colors.reset}`;294 const prefix = vibeSurfPrefix(colors.brightCyan);295 console.log(`${timestamp} ${prefix} ${colors.bright}${key}:${colors.reset} ${colors.white}${value}${colors.reset}`);296 writeToFile(`[VibeSurf] ${key}: ${value}`);297 }298299 /**300 * Timer start301 */302 private timers: Map<string, number> = new Map();303304 startTimer(label: string) {305 this.timers.set(label, Date.now());306 }307308 /**309 * Timer end - shows elapsed time310 */311 endTimer(label: string, message?: string) {312 if (!this.enabled) return;313 const start = this.timers.get(label);314 if (!start) {315 this.warn(`Timer '${label}' was not started`);316 return;317 }318319 const elapsed = Date.now() - start;320 const timestamp = `${colors.dim}${getTimestamp()}${colors.reset}`;321 const prefix = vibeSurfPrefix(colors.brightYellow);322 const msg = message || label;323 console.log(`${timestamp} ${prefix} ${colors.yellow}⏱${colors.reset} ${colors.white}${msg}${colors.reset} ${colors.dim}(${elapsed}ms)${colors.reset}`);324 writeToFile(`[VibeSurf] TIMER: ${msg} (${elapsed}ms)`);325326 this.timers.delete(label);327 }328}329330// Export singleton instance331export const logger = new VibeSurfLogger();332333// Export colors for custom formatting334export { colors };335