firewalla-mcp-server
Version:
Model Context Protocol (MCP) server for Firewalla MSP API - Provides real-time network monitoring, security analysis, and firewall management through 28 specialized tools compatible with any MCP client
212 lines • 7.22 kB
JavaScript
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) {
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 class StructuredLogger {
constructor(logLevel) {
this.service = 'firewalla-mcp-server';
this.version = '1.0.0';
// Override log level if DEBUG is enabled
if (DEBUG_ENABLED) {
this.logLevel = 'debug';
}
else {
this.logLevel = logLevel || productionConfig.logLevel || 'info';
}
}
shouldLog(level) {
const levels = ['error', 'warn', 'info', 'debug'];
const currentLevelIndex = levels.indexOf(this.logLevel);
const messageLevelIndex = levels.indexOf(level);
return messageLevelIndex <= currentLevelIndex;
}
createLogEntry(level, message, metadata, error, traceId, requestId) {
const entry = {
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;
}
sanitizeMetadata(metadata) {
const sanitized = {};
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;
}
maskSensitiveValue(value) {
if (value.length <= 8) {
return '*'.repeat(value.length);
}
return `${value.substring(0, 4)}****${value.substring(value.length - 4)}`;
}
output(entry) {
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, error, metadata, traceId, requestId) {
if (!this.shouldLog('error')) {
return;
}
const entry = this.createLogEntry('error', message, metadata, error, traceId, requestId);
this.output(entry);
}
warn(message, metadata, traceId, requestId) {
if (!this.shouldLog('warn')) {
return;
}
const entry = this.createLogEntry('warn', message, metadata, undefined, traceId, requestId);
this.output(entry);
}
info(message, metadata, traceId, requestId) {
if (!this.shouldLog('info')) {
return;
}
const entry = this.createLogEntry('info', message, metadata, undefined, traceId, requestId);
this.output(entry);
}
debug(message, metadata, traceId, requestId) {
if (!this.shouldLog('debug')) {
return;
}
const entry = this.createLogEntry('debug', message, metadata, undefined, traceId, requestId);
this.output(entry);
}
// Namespace-specific debug logging
debugNamespace(namespace, message, metadata, traceId, requestId) {
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, endpoint, duration, statusCode, requestId) {
this.info('API request completed', {
http: {
method,
endpoint,
duration_ms: duration,
status_code: statusCode,
},
}, undefined, requestId);
}
apiError(method, endpoint, error, requestId) {
this.error('API request failed', error, {
http: {
method,
endpoint,
},
}, undefined, requestId);
}
securityEvent(event, metadata) {
this.warn('Security event detected', {
security: {
event,
...metadata,
},
});
}
cacheOperation(operation, key, hit, metadata) {
this.debugNamespace('cache', `Cache ${operation}: ${hit ? 'HIT' : 'MISS'}`, {
cache: {
operation,
key,
hit,
...metadata,
},
});
}
// Performance monitoring logs
performanceLog(operation, duration, metadata) {
this.debugNamespace('performance', `${operation} completed in ${duration}ms`, {
performance: {
operation,
duration_ms: duration,
...metadata,
},
});
}
// Data pipeline troubleshooting logs
pipelineLog(stage, message, metadata) {
this.debugNamespace('pipeline', `[${stage}] ${message}`, {
pipeline: {
stage,
...metadata,
},
});
}
// Query performance logs
queryLog(queryType, query, duration, resultCount, metadata) {
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();
//# sourceMappingURL=logger.js.map