SPB Git

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%
10.5 KB · 335 lines typescript
Raw Blame History
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