sfcc-dev-mcp
Version:
MCP server for Salesforce B2C Commerce Cloud development assistance including logs, debugging, and development tools
200 lines • 7.7 kB
JavaScript
/**
* Logger class for standardized logging across the SFCC MCP application.
* Provides consistent logging with timestamps and log levels.
* Always logs to files for consistent debugging and to avoid interfering with stdio.
*
* ## Log Directory Location
*
* The logger uses the operating system's temporary directory via Node.js `os.tmpdir()`:
* - **macOS**: `/var/folders/{user-specific-path}/T/sfcc-mcp-logs/`
* - **Linux**: `/tmp/sfcc-mcp-logs/` (typically)
* - **Windows**: `%TEMP%\sfcc-mcp-logs\` (typically `C:\Users\{user}\AppData\Local\Temp\`)
*
* This approach provides:
* - User-specific isolation (more secure than system-wide `/tmp`)
* - Automatic cleanup by the OS
* - Platform-appropriate temporary storage
* - Proper permissions handling
*
* To find your log directory, use `Logger.getInstance().getLogDirectory()` or check
* the debug logs which show the directory path during initialization.
*/
import { appendFileSync, existsSync, mkdirSync } from 'fs';
import { join } from 'path';
import { tmpdir } from 'os';
export class Logger {
context;
enableTimestamp;
debugEnabled;
logDir;
static instance = null;
/**
* Create a new Logger instance
* @param context The context/component name for this logger
* @param enableTimestamp Whether to include timestamps in log messages (default: true)
* @param debugEnabled Whether to enable debug logging (default: false)
* @param customLogDir Custom log directory for testing purposes
*/
constructor(context = 'SFCC-MCP', enableTimestamp = true, debugEnabled = false, customLogDir) {
this.context = context;
this.enableTimestamp = enableTimestamp;
this.debugEnabled = debugEnabled;
// Set up log directory - use custom directory for testing or default for production
this.logDir = customLogDir ?? join(tmpdir(), 'sfcc-mcp-logs');
if (!existsSync(this.logDir)) {
mkdirSync(this.logDir, { recursive: true });
}
}
/**
* Initialize the global logger instance with specific settings
* This should be called once at application startup
*/
static initialize(context = 'SFCC-MCP', enableTimestamp = true, debugEnabled = false, customLogDir) {
Logger.instance = new Logger(context, enableTimestamp, debugEnabled, customLogDir);
}
/**
* Get the global logger instance
* If not initialized, creates a default instance
*/
static getInstance() {
Logger.instance ??= new Logger();
return Logger.instance;
}
/**
* Create a child logger with a new context but inheriting other settings from the global instance
* @param subContext The sub-context to append to the current context
* @returns A new Logger instance with the combined context
*/
static getChildLogger(subContext) {
const globalLogger = Logger.getInstance();
return new Logger(`${globalLogger.context}:${subContext}`, globalLogger.enableTimestamp, globalLogger.debugEnabled, globalLogger.logDir);
}
/**
* Format a log message with optional timestamp and context
* @param message The message to format
* @returns Formatted message string
*/
formatMessage(message) {
const timestamp = this.enableTimestamp ? `[${new Date().toISOString()}] ` : '';
return `${timestamp}[${this.context}] ${message}`;
}
/**
* Write log message to appropriate log file
*/
writeLog(level, message, ...args) {
const formattedMessage = this.formatMessage(message);
const fullMessage = args.length > 0 ? `${formattedMessage} ${args.map(arg => typeof arg === 'object' ? JSON.stringify(arg, null, 2) : String(arg)).join(' ')}` : formattedMessage;
// Always write to log files
const logFile = join(this.logDir, `sfcc-mcp-${level}.log`);
const logEntry = `${fullMessage}\n`;
try {
appendFileSync(logFile, logEntry, 'utf8');
}
catch (error) {
// Fallback: if file logging fails, try stderr for critical errors only
if (level === 'error') {
process.stderr.write(`[LOGGER ERROR] Could not write to log file: ${error}\n`);
process.stderr.write(`${logEntry}`);
}
}
}
/**
* Log an informational message
* @param message The message to log
* @param args Optional arguments to include
*/
log(message, ...args) {
this.writeLog('info', message, ...args);
}
/**
* Log an informational message (alias for log)
* @param message The message to log
* @param args Optional arguments to include
*/
info(message, ...args) {
this.writeLog('info', message, ...args);
}
/**
* Log a warning message
* @param message The warning message to log
* @param args Optional arguments to include
*/
warn(message, ...args) {
this.writeLog('warn', message, ...args);
}
/**
* Log an error message
* @param message The error message to log
* @param args Optional arguments to include
*/
error(message, ...args) {
this.writeLog('error', message, ...args);
}
/**
* Log a debug message (only if debug is enabled)
* @param message The debug message to log
* @param args Optional arguments to include
*/
debug(message, ...args) {
if (this.debugEnabled) {
this.writeLog('debug', `[DEBUG] ${message}`, ...args);
}
}
/**
* Log method entry with parameters
* @param methodName The name of the method being entered
* @param params Optional parameters being passed to the method
*/
methodEntry(methodName, params) {
if (this.debugEnabled) {
const paramStr = params ? ` with params: ${JSON.stringify(params)}` : '';
this.debug(`Entering method: ${methodName}${paramStr}`);
}
}
/**
* Log method exit with optional result
* @param methodName The name of the method being exited
* @param result Optional result being returned from the method
*/
methodExit(methodName, result) {
if (this.debugEnabled) {
const resultStr = result !== undefined ? ` with result: ${typeof result === 'object' ? JSON.stringify(result) : result}` : '';
this.debug(`Exiting method: ${methodName}${resultStr}`);
}
}
/**
* Log performance timing information
* @param operation The operation being timed
* @param startTime The start time (from performance.now() or Date.now())
*/
timing(operation, startTime) {
if (this.debugEnabled) {
const duration = Date.now() - startTime;
this.debug(`Performance: ${operation} took ${duration}ms`);
}
}
/**
* Create a child logger with a new context but inheriting other settings
* @param subContext The sub-context to append to the current context
* @returns A new Logger instance with the combined context
*/
createChildLogger(subContext) {
return new Logger(`${this.context}:${subContext}`, this.enableTimestamp, this.debugEnabled, this.logDir);
}
/**
* Enable or disable debug logging
* @param enabled Whether debug logging should be enabled
*/
setDebugEnabled(enabled) {
this.debugEnabled = enabled;
}
/**
* Get the current log directory
*/
getLogDirectory() {
return this.logDir;
}
}
// Export the singleton instance getter for convenience
export const getLogger = Logger.getInstance;
//# sourceMappingURL=logger.js.map