Skip to main content
Glama

firewalla-mcp-server

logger.ts8.31 kB
import { productionConfig } from '../production/config.js'; import { getCurrentTimestamp } from '../utils/timestamp.js'; // DEBUG environment variable support const DEBUG_ENABLED = process.env.DEBUG === 'firewalla:*' || process.env.DEBUG === '1' || process.env.DEBUG === 'true'; const DEBUG_FILTERS = (process.env.DEBUG || '').split(',').map(f => f.trim()); /** * Determines if debug logging is enabled for a given namespace based on environment variable filters. * * Returns true if global debug is enabled or if the namespace matches any filter (with wildcard support) specified in the `DEBUG` environment variable. * * @param namespace - The debug namespace to check * @returns True if debug logging is enabled for the specified namespace */ function shouldDebug(namespace: string): boolean { if (!DEBUG_ENABLED && DEBUG_FILTERS.length === 0) { return false; } if (DEBUG_ENABLED) { return true; } return DEBUG_FILTERS.some(filter => { if (filter === '*') { return true; } if (filter.endsWith('*')) { return namespace.startsWith(filter.slice(0, -1)); } return namespace === filter; }); } export interface LogEntry { timestamp: string; level: 'error' | 'warn' | 'info' | 'debug'; message: string; service: string; version: string; metadata?: Record<string, unknown>; error?: { name: string; message: string; stack?: string; }; traceId?: string; requestId?: string; } export class StructuredLogger { private service = 'firewalla-mcp-server'; private version = '1.2.1'; private logLevel: LogEntry['level']; constructor(logLevel?: LogEntry['level']) { // Override log level if DEBUG is enabled if (DEBUG_ENABLED) { this.logLevel = 'debug'; } else { this.logLevel = logLevel || productionConfig.logLevel || 'info'; } } private shouldLog(level: LogEntry['level']): boolean { const levels = ['error', 'warn', 'info', 'debug']; const currentLevelIndex = levels.indexOf(this.logLevel); const messageLevelIndex = levels.indexOf(level); return messageLevelIndex <= currentLevelIndex; } private createLogEntry( level: LogEntry['level'], message: string, metadata?: Record<string, unknown>, error?: Error, traceId?: string, requestId?: string ): LogEntry { const entry: LogEntry = { timestamp: getCurrentTimestamp(), level, message, service: this.service, version: this.version, }; if (metadata) { entry.metadata = this.sanitizeMetadata(metadata); } if (error) { entry.error = { name: error.name, message: error.message, ...(error.stack && { stack: error.stack }), }; } if (traceId) { entry.traceId = traceId; } if (requestId) { entry.requestId = requestId; } return entry; } private sanitizeMetadata( metadata: Record<string, unknown> ): Record<string, unknown> { const sanitized: Record<string, unknown> = {}; for (const [key, value] of Object.entries(metadata)) { // Mask sensitive fields if ( key.toLowerCase().includes('token') || key.toLowerCase().includes('password') || key.toLowerCase().includes('secret') || key.toLowerCase().includes('key') ) { sanitized[key] = this.maskSensitiveValue(String(value)); } else { sanitized[key] = value; } } return sanitized; } private maskSensitiveValue(value: string): string { if (value.length <= 8) { return '*'.repeat(value.length); } return `${value.substring(0, 4)}****${value.substring(value.length - 4)}`; } private output(entry: LogEntry): void { const logString = JSON.stringify(entry); // Always write to stderr in MCP server to avoid polluting stdout JSON-RPC channel process.stderr.write(`${logString}\\n`); } error( message: string, error?: Error, metadata?: Record<string, unknown>, traceId?: string, requestId?: string ): void { if (!this.shouldLog('error')) { return; } const entry = this.createLogEntry( 'error', message, metadata, error, traceId, requestId ); this.output(entry); } warn( message: string, metadata?: Record<string, unknown>, traceId?: string, requestId?: string ): void { if (!this.shouldLog('warn')) { return; } const entry = this.createLogEntry( 'warn', message, metadata, undefined, traceId, requestId ); this.output(entry); } info( message: string, metadata?: Record<string, unknown>, traceId?: string, requestId?: string ): void { if (!this.shouldLog('info')) { return; } const entry = this.createLogEntry( 'info', message, metadata, undefined, traceId, requestId ); this.output(entry); } debug( message: string, metadata?: Record<string, unknown>, traceId?: string, requestId?: string ): void { if (!this.shouldLog('debug')) { return; } const entry = this.createLogEntry( 'debug', message, metadata, undefined, traceId, requestId ); this.output(entry); } // Namespace-specific debug logging debugNamespace( namespace: string, message: string, metadata?: Record<string, unknown>, traceId?: string, requestId?: string ): void { if (!shouldDebug(namespace)) { return; } const namespacedMessage = `[${namespace}] ${message}`; const entry = this.createLogEntry( 'debug', namespacedMessage, metadata, undefined, traceId, requestId ); this.output(entry); } // Convenience methods for common scenarios apiRequest( method: string, endpoint: string, duration: number, statusCode: number, requestId?: string ): void { this.info( 'API request completed', { http: { method, endpoint, duration_ms: duration, status_code: statusCode, }, }, undefined, requestId ); } apiError( method: string, endpoint: string, error: Error, requestId?: string ): void { this.error( 'API request failed', error, { http: { method, endpoint, }, }, undefined, requestId ); } securityEvent(event: string, metadata: Record<string, unknown>): void { this.warn('Security event detected', { security: { event, ...metadata, }, }); } cacheOperation( operation: string, key: string, hit: boolean, metadata?: Record<string, unknown> ): void { this.debugNamespace( 'cache', `Cache ${operation}: ${hit ? 'HIT' : 'MISS'}`, { cache: { operation, key, hit, ...metadata, }, } ); } // Performance monitoring logs performanceLog( operation: string, duration: number, metadata?: Record<string, unknown> ): void { this.debugNamespace( 'performance', `${operation} completed in ${duration}ms`, { performance: { operation, duration_ms: duration, ...metadata, }, } ); } // Data pipeline troubleshooting logs pipelineLog( stage: string, message: string, metadata?: Record<string, unknown> ): void { this.debugNamespace('pipeline', `[${stage}] ${message}`, { pipeline: { stage, ...metadata, }, }); } // Query performance logs queryLog( queryType: string, query: string, duration: number, resultCount: number, metadata?: Record<string, unknown> ): void { this.debugNamespace('query', `${queryType} query executed`, { query: { type: queryType, query: query.length > 100 ? `${query.substring(0, 100)}...` : query, duration_ms: duration, result_count: resultCount, ...metadata, }, }); } } // Global logger instance export const logger = new StructuredLogger();

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/amittell/firewalla-mcp-server'

If you have feedback or need assistance with the MCP directory API, please join our Discord server