All files / src/utils logger.ts

68.18% Statements 30/44
50% Branches 13/26
83.33% Functions 10/12
68.18% Lines 30/44

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 1284x 4x       4x       4x       4x 4x   4x     4x 4x     4x       4x 4x       6x       10x               10x         4x 4x 1x                           10x 10x   10x     2x             10x 10x                   8x 8x             2x 2x                                             6x         4x  
import * as fs from 'fs';
import * as path from 'path';
 
export type LoggingMode = 'file' | 'stderr' | 'auto';
 
export class Logger {
  private static instance: Logger | undefined;
  private logFile: string;
  private sessionId: string;
  private isStdioMode: boolean = false;
  private loggingMode: LoggingMode;
 
  private constructor() {
    const date = new Date().toISOString().split('T')[0];
    this.sessionId = Math.random().toString(36).substring(2, 8);
    // Use single log file for all sessions
    this.logFile = path.join('/tmp', `.log.nexus.${date}`);
 
    // Get logging mode from environment variable
    const envMode = process.env.NEXUS_LOG_MODE?.toLowerCase() as LoggingMode;
    this.loggingMode = ['file', 'stderr', 'auto'].includes(envMode) ? envMode : 'auto';
 
    // Create log file if it doesn't exist (for file mode)
    this.ensureLogFile();
  }
 
  static getInstance(): Logger {
    Logger.instance ??= new Logger();
    return Logger.instance;
  }
 
  setStdioMode(enabled: boolean): void {
    this.isStdioMode = enabled;
  }
 
  private shouldUseFileLogging(): boolean {
    switch (this.loggingMode) {
      case 'file':
        return true;
      case 'stderr':
        return false;
      case 'auto':
      default:
        // Auto mode: file for STDIO, stderr for HTTP/SSE
        return this.isStdioMode;
    }
  }
 
  private ensureLogFile(): void {
    try {
      if (!fs.existsSync(this.logFile)) {
        fs.writeFileSync(
          this.logFile,
          `[${new Date().toISOString()}] [INFO] [${this.sessionId}] Project Nexus MCP Server Log Started\n`,
        );
      }
    } catch {
      // Fallback to stderr if we can't create log file
      if (!this.shouldUseFileLogging()) {
        console.error('Failed to create log file, falling back to stderr');
      }
    }
  }
 
  private writeToFile(level: string, message: string, ...args: unknown[]): void {
    try {
      const timestamp = new Date().toISOString();
      const formattedMessage =
        args.length > 0
          ? `${message} ${args
              .map((arg) =>
                typeof arg === 'object' && arg !== null
                  ? JSON.stringify(arg, null, 2)
                  : String(arg),
              )
              .join(' ')}`
          : message;
 
      const logLine = `[${timestamp}] [${level}] [${this.sessionId}] ${formattedMessage}\n`;
      fs.appendFileSync(this.logFile, logLine);
    } catch {
      // Silent fail - don't pollute stderr when using file logging
      if (!this.shouldUseFileLogging()) {
        console.error('Failed to write to log file');
      }
    }
  }
 
  log(message: string, ...args: unknown[]): void {
    if (this.shouldUseFileLogging()) {
      this.writeToFile('INFO', message, ...args);
    } else E{
      console.log(message, ...args);
    }
  }
 
  error(message: string, ...args: unknown[]): void {
    if (this.shouldUseFileLogging()) {
      this.writeToFile('ERROR', message, ...args);
    } else E{
      console.error(message, ...args);
    }
  }
 
  warn(message: string, ...args: unknown[]): void {
    if (this.shouldUseFileLogging()) {
      this.writeToFile('WARN', message, ...args);
    } else {
      console.warn(message, ...args);
    }
  }
 
  debug(message: string, ...args: unknown[]): void {
    if (this.shouldUseFileLogging()) {
      this.writeToFile('DEBUG', message, ...args);
    } else {
      console.debug(message, ...args);
    }
  }
 
  getLogFile(): string {
    return this.logFile;
  }
}
 
// Global logger instance
export const logger = Logger.getInstance();