Chapter 8: Error Handling and Logging System
Haiyue
35min
Chapter 8: Error Handling and Logging System
Learning Objectives
- Establish a comprehensive error handling mechanism
- Implement a structured logging system
- Master exception capture and recovery strategies
- Learn error code and error message standards
- Implement debugging and diagnostic functions
1. Error Handling Infrastructure
1.1 Error Classification System
enum ErrorCategory {
VALIDATION = "validation", // Parameter validation error
AUTHENTICATION = "auth", // Authentication error
AUTHORIZATION = "authz", // Authorization error
NOT_FOUND = "not_found", // Resource not found
CONFLICT = "conflict", // Conflict error
RATE_LIMIT = "rate_limit", // Rate limit
INTERNAL = "internal", // Internal error
NETWORK = "network", // Network error
TIMEOUT = "timeout", // Timeout error
DEPENDENCY = "dependency" // External dependency error
}
enum ErrorSeverity {
LOW = "low",
MEDIUM = "medium",
HIGH = "high",
CRITICAL = "critical"
}
interface MCPError {
code: string;
message: string;
category: ErrorCategory;
severity: ErrorSeverity;
details?: Record<string, any>;
timestamp: Date;
requestId?: string;
stackTrace?: string;
}
1.2 Base Error Class
class BaseMCPError extends Error {
public readonly code: string;
public readonly category: ErrorCategory;
public readonly severity: ErrorSeverity;
public readonly details: Record<string, any>;
public readonly timestamp: Date;
public readonly requestId?: string;
constructor(
code: string,
message: string,
category: ErrorCategory,
severity: ErrorSeverity = ErrorSeverity.MEDIUM,
details: Record<string, any> = {},
requestId?: string
) {
super(message);
this.name = this.constructor.name;
this.code = code;
this.category = category;
this.severity = severity;
this.details = details;
this.timestamp = new Date();
this.requestId = requestId;
// Maintain stack trace
Error.captureStackTrace(this, this.constructor);
}
toJSON(): MCPError {
return {
code: this.code,
message: this.message,
category: this.category,
severity: this.severity,
details: this.details,
timestamp: this.timestamp,
requestId: this.requestId,
stackTrace: this.stack
};
}
}
// Specific error types
class ValidationError extends BaseMCPError {
constructor(message: string, details: Record<string, any> = {}, requestId?: string) {
super("VALIDATION_ERROR", message, ErrorCategory.VALIDATION, ErrorSeverity.LOW, details, requestId);
}
}
class AuthenticationError extends BaseMCPError {
constructor(message: string, details: Record<string, any> = {}, requestId?: string) {
super("AUTH_ERROR", message, ErrorCategory.AUTHENTICATION, ErrorSeverity.HIGH, details, requestId);
}
}
class ResourceNotFoundError extends BaseMCPError {
constructor(resource: string, resourceId: string, requestId?: string) {
super(
"RESOURCE_NOT_FOUND",
`${resource} not found: ${resourceId}`,
ErrorCategory.NOT_FOUND,
ErrorSeverity.MEDIUM,
{ resource, resourceId },
requestId
);
}
}
class InternalServerError extends BaseMCPError {
constructor(message: string, originalError?: Error, requestId?: string) {
super(
"INTERNAL_ERROR",
message,
ErrorCategory.INTERNAL,
ErrorSeverity.CRITICAL,
{ originalError: originalError?.message },
requestId
);
}
}
2. Error Handling Middleware
2.1 Request-Level Error Handling
interface RequestContext {
requestId: string;
method: string;
startTime: Date;
params?: any;
}
class ErrorHandlingMiddleware {
private errorReporter: ErrorReporter;
private logger: Logger;
constructor(errorReporter: ErrorReporter, logger: Logger) {
this.errorReporter = errorReporter;
this.logger = logger;
}
wrapHandler<T>(handler: (request: any, context: RequestContext) => Promise<T>) {
return async (request: any): Promise<T> => {
const context: RequestContext = {
requestId: this.generateRequestId(),
method: request.method || "unknown",
startTime: new Date(),
params: request.params
};
try {
this.logger.debug("Request started", {
requestId: context.requestId,
method: context.method,
params: context.params
});
const result = await handler(request, context);
this.logger.info("Request completed", {
requestId: context.requestId,
method: context.method,
duration: Date.now() - context.startTime.getTime()
});
return result;
} catch (error) {
return this.handleError(error, context);
}
};
}
private async handleError(error: any, context: RequestContext): Promise<never> {
let mcpError: BaseMCPError;
if (error instanceof BaseMCPError) {
mcpError = error;
} else if (error instanceof Error) {
mcpError = new InternalServerError(
`Unhandled error in ${context.method}`,
error,
context.requestId
);
} else {
mcpError = new InternalServerError(
"Unknown error occurred",
undefined,
context.requestId
);
}
// Log error
await this.logError(mcpError, context);
// Report error (send to monitoring system, etc.)
await this.errorReporter.report(mcpError, context);
// Decide response based on severity
throw this.createClientError(mcpError);
}
private async logError(error: BaseMCPError, context: RequestContext) {
const logData = {
requestId: context.requestId,
method: context.method,
error: error.toJSON(),
duration: Date.now() - context.startTime.getTime()
};
switch (error.severity) {
case ErrorSeverity.LOW:
this.logger.warn("Request failed", logData);
break;
case ErrorSeverity.MEDIUM:
this.logger.error("Request error", logData);
break;
case ErrorSeverity.HIGH:
case ErrorSeverity.CRITICAL:
this.logger.fatal("Critical request error", logData);
break;
}
}
private createClientError(error: BaseMCPError): any {
// Create error response according to MCP protocol
const shouldExposeDetails = error.severity !== ErrorSeverity.CRITICAL;
return {
error: {
code: error.code,
message: error.message,
data: shouldExposeDetails ? error.details : undefined
}
};
}
private generateRequestId(): string {
return `req_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
}
}
2.2 Error Recovery Strategy
interface RetryConfig {
maxAttempts: number;
baseDelay: number;
maxDelay: number;
backoffMultiplier: number;
retryableErrorCategories: ErrorCategory[];
}
class ErrorRecoveryManager {
private defaultRetryConfig: RetryConfig = {
maxAttempts: 3,
baseDelay: 1000,
maxDelay: 10000,
backoffMultiplier: 2,
retryableErrorCategories: [
ErrorCategory.NETWORK,
ErrorCategory.TIMEOUT,
ErrorCategory.DEPENDENCY
]
};
async executeWithRetry<T>(
operation: () => Promise<T>,
config: Partial<RetryConfig> = {}
): Promise<T> {
const finalConfig = { ...this.defaultRetryConfig, ...config };
let lastError: Error;
for (let attempt = 1; attempt <= finalConfig.maxAttempts; attempt++) {
try {
return await operation();
} catch (error) {
lastError = error as Error;
// Check if should retry
if (!this.shouldRetry(error, attempt, finalConfig)) {
throw error;
}
// Calculate delay time
const delay = this.calculateDelay(attempt, finalConfig);
console.error(`Operation failed (attempt ${attempt}/${finalConfig.maxAttempts}), retrying in ${delay}ms:`, error.message);
await this.sleep(delay);
}
}
throw lastError!;
}
private shouldRetry(error: any, attempt: number, config: RetryConfig): boolean {
// Maximum retries reached
if (attempt >= config.maxAttempts) {
return false;
}
// Check if error type is retryable
if (error instanceof BaseMCPError) {
return config.retryableErrorCategories.includes(error.category);
}
// Retry for network-related native errors
if (error.code === 'ECONNREFUSED' ||
error.code === 'ETIMEDOUT' ||
error.code === 'ENOTFOUND') {
return true;
}
return false;
}
private calculateDelay(attempt: number, config: RetryConfig): number {
const exponentialDelay = config.baseDelay * Math.pow(config.backoffMultiplier, attempt - 1);
const jitteredDelay = exponentialDelay * (0.5 + Math.random() * 0.5); // Add jitter
return Math.min(jitteredDelay, config.maxDelay);
}
private sleep(ms: number): Promise<void> {
return new Promise(resolve => setTimeout(resolve, ms));
}
}
3. Structured Logging System
3.1 Log Levels and Format
enum LogLevel {
TRACE = 0,
DEBUG = 1,
INFO = 2,
WARN = 3,
ERROR = 4,
FATAL = 5
}
interface LogEntry {
timestamp: string;
level: LogLevel;
message: string;
data?: Record<string, any>;
requestId?: string;
component?: string;
userId?: string;
sessionId?: string;
}
interface LoggerConfig {
level: LogLevel;
format: 'json' | 'text';
outputs: LogOutput[];
enableStackTrace: boolean;
maxDataSize: number;
}
abstract class LogOutput {
abstract write(entry: LogEntry): Promise<void>;
}
class ConsoleLogOutput extends LogOutput {
async write(entry: LogEntry): Promise<void> {
const levelName = LogLevel[entry.level];
const timestamp = entry.timestamp;
const message = entry.message;
const data = entry.data ? JSON.stringify(entry.data) : '';
console.log(`${timestamp} [${levelName}] ${message} ${data}`);
}
}
class FileLogOutput extends LogOutput {
private filePath: string;
private writeStream: any;
constructor(filePath: string) {
this.filePath = filePath;
// Initialize file write stream
}
async write(entry: LogEntry): Promise<void> {
const logLine = JSON.stringify(entry) + '\n';
// Write to file
}
}
3.2 Advanced Logger
import { createHash } from 'crypto';
class AdvancedLogger {
private config: LoggerConfig;
private outputs: LogOutput[];
private contextData: Map<string, any> = new Map();
constructor(config: LoggerConfig) {
this.config = config;
this.outputs = config.outputs;
}
// Set context data
setContext(key: string, value: any): void {
this.contextData.set(key, value);
}
clearContext(): void {
this.contextData.clear();
}
// Main logging methods
trace(message: string, data?: Record<string, any>): void {
this.log(LogLevel.TRACE, message, data);
}
debug(message: string, data?: Record<string, any>): void {
this.log(LogLevel.DEBUG, message, data);
}
info(message: string, data?: Record<string, any>): void {
this.log(LogLevel.INFO, message, data);
}
warn(message: string, data?: Record<string, any>): void {
this.log(LogLevel.WARN, message, data);
}
error(message: string, data?: Record<string, any>): void {
this.log(LogLevel.ERROR, message, data);
}
fatal(message: string, data?: Record<string, any>): void {
this.log(LogLevel.FATAL, message, data);
}
// Performance logging
time(label: string): void {
this.setContext(`timer_${label}`, Date.now());
}
timeEnd(label: string, message?: string): void {
const startTime = this.contextData.get(`timer_${label}`);
if (startTime) {
const duration = Date.now() - startTime;
this.info(message || `Timer ${label}`, { duration, label });
this.contextData.delete(`timer_${label}`);
}
}
// Structured error logging
logError(error: Error | BaseMCPError, additionalData?: Record<string, any>): void {
let errorData: Record<string, any> = {
errorName: error.name,
errorMessage: error.message,
stackTrace: this.config.enableStackTrace ? error.stack : undefined,
...additionalData
};
if (error instanceof BaseMCPError) {
errorData = {
...errorData,
errorCode: error.code,
errorCategory: error.category,
errorSeverity: error.severity,
errorDetails: error.details,
requestId: error.requestId
};
}
this.error("Error occurred", errorData);
}
// Audit logging
audit(action: string, data?: Record<string, any>): void {
this.info(`AUDIT: ${action}`, {
...data,
auditTimestamp: new Date().toISOString(),
auditType: "action"
});
}
// Private core logging method
private async log(level: LogLevel, message: string, data?: Record<string, any>): Promise<void> {
// Check log level
if (level < this.config.level) {
return;
}
// Create log entry
const entry: LogEntry = {
timestamp: new Date().toISOString(),
level,
message,
data: this.sanitizeData({
...Object.fromEntries(this.contextData),
...data
}),
requestId: this.contextData.get('requestId'),
component: this.contextData.get('component'),
userId: this.contextData.get('userId'),
sessionId: this.contextData.get('sessionId')
};
// Output to all configured outputs
for (const output of this.outputs) {
try {
await output.write(entry);
} catch (error) {
// Handle log output failure
console.error('Failed to write log entry:', error);
}
}
}
private sanitizeData(data?: Record<string, any>): Record<string, any> | undefined {
if (!data) return undefined;
const sanitized: Record<string, any> = {};
for (const [key, value] of Object.entries(data)) {
// Filter sensitive information
if (this.isSensitiveKey(key)) {
sanitized[key] = this.maskSensitiveValue(value);
continue;
}
// Limit data size
if (typeof value === 'string' && value.length > this.config.maxDataSize) {
sanitized[key] = value.substring(0, this.config.maxDataSize) + '...';
continue;
}
// Handle objects
if (typeof value === 'object' && value !== null) {
sanitized[key] = this.sanitizeObject(value);
continue;
}
sanitized[key] = value;
}
return sanitized;
}
private isSensitiveKey(key: string): boolean {
const sensitiveKeys = [
'password', 'token', 'apikey', 'secret', 'auth',
'credentials', 'private', 'key', 'cert'
];
return sensitiveKeys.some(sensitiveKey =>
key.toLowerCase().includes(sensitiveKey)
);
}
private maskSensitiveValue(value: any): string {
if (typeof value === 'string') {
if (value.length <= 4) return '****';
return value.substring(0, 2) + '****' + value.substring(value.length - 2);
}
return '[MASKED]';
}
private sanitizeObject(obj: any): any {
if (Array.isArray(obj)) {
return obj.map(item =>
typeof item === 'object' ? this.sanitizeObject(item) : item
);
}
const sanitized: Record<string, any> = {};
for (const [key, value] of Object.entries(obj)) {
if (this.isSensitiveKey(key)) {
sanitized[key] = this.maskSensitiveValue(value);
} else if (typeof value === 'object' && value !== null) {
sanitized[key] = this.sanitizeObject(value);
} else {
sanitized[key] = value;
}
}
return sanitized;
}
}
4. Error Reporting and Monitoring
4.1 Error Reporting System
interface ErrorReport {
error: MCPError;
context: RequestContext;
environment: {
nodeVersion: string;
platform: string;
memory: NodeJS.MemoryUsage;
uptime: number;
};
metadata: Record<string, any>;
}
interface ErrorReportingConfig {
enableReporting: boolean;
reportingEndpoint?: string;
apiKey?: string;
samplingRate: number; // 0-1
maxReportsPerMinute: number;
}
class ErrorReporter {
private config: ErrorReportingConfig;
private reportQueue: ErrorReport[] = [];
private reportCounts: Map<string, number> = new Map();
private lastResetTime: number = Date.now();
private logger: AdvancedLogger;
constructor(config: ErrorReportingConfig, logger: AdvancedLogger) {
this.config = config;
this.logger = logger;
// Periodically send reports
setInterval(() => this.flushReports(), 10000);
// Periodically reset rate limit counters
setInterval(() => this.resetRateLimit(), 60000);
}
async report(error: BaseMCPError, context: RequestContext): Promise<void> {
if (!this.config.enableReporting) {
return;
}
// Sampling check
if (Math.random() > this.config.samplingRate) {
return;
}
// Rate limit check
const errorKey = `${error.code}_${error.category}`;
const currentCount = this.reportCounts.get(errorKey) || 0;
if (currentCount >= this.config.maxReportsPerMinute) {
this.logger.warn("Error reporting rate limit reached", { errorKey });
return;
}
this.reportCounts.set(errorKey, currentCount + 1);
// Create error report
const report: ErrorReport = {
error: error.toJSON(),
context,
environment: {
nodeVersion: process.version,
platform: process.platform,
memory: process.memoryUsage(),
uptime: process.uptime()
},
metadata: {
timestamp: new Date().toISOString(),
serverVersion: "1.0.0" // Get from config
}
};
// Add to queue
this.reportQueue.push(report);
this.logger.debug("Error report queued", {
errorCode: error.code,
queueSize: this.reportQueue.length
});
}
private async flushReports(): Promise<void> {
if (this.reportQueue.length === 0) {
return;
}
const reportsToSend = this.reportQueue.splice(0);
try {
await this.sendReports(reportsToSend);
this.logger.debug(`Sent ${reportsToSend.length} error reports`);
} catch (error) {
this.logger.error("Failed to send error reports", {
error: error.message,
reportCount: reportsToSend.length
});
// Re-add to queue (keep max 1000 reports)
this.reportQueue.unshift(...reportsToSend.slice(-1000));
}
}
private async sendReports(reports: ErrorReport[]): Promise<void> {
if (!this.config.reportingEndpoint) {
return;
}
// Implement logic to send to external monitoring service
// For example, send to Sentry, DataDog, or custom monitoring system
const response = await fetch(this.config.reportingEndpoint, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${this.config.apiKey}`
},
body: JSON.stringify({
reports,
timestamp: new Date().toISOString()
})
});
if (!response.ok) {
throw new Error(`Reporting failed: ${response.status} ${response.statusText}`);
}
}
private resetRateLimit(): void {
this.reportCounts.clear();
this.lastResetTime = Date.now();
}
}
4.2 Performance Monitoring Integration
interface PerformanceMetric {
name: string;
value: number;
unit: string;
timestamp: Date;
tags?: Record<string, string>;
}
class PerformanceLogger {
private logger: AdvancedLogger;
private metrics: PerformanceMetric[] = [];
constructor(logger: AdvancedLogger) {
this.logger = logger;
// Periodically output performance metrics
setInterval(() => this.flushMetrics(), 30000);
}
recordMetric(name: string, value: number, unit: string, tags?: Record<string, string>): void {
const metric: PerformanceMetric = {
name,
value,
unit,
timestamp: new Date(),
tags
};
this.metrics.push(metric);
}
recordExecutionTime(name: string, startTime: number, tags?: Record<string, string>): void {
const duration = Date.now() - startTime;
this.recordMetric(name, duration, 'ms', tags);
}
recordMemoryUsage(): void {
const usage = process.memoryUsage();
this.recordMetric('memory.heap.used', usage.heapUsed / 1024 / 1024, 'MB');
this.recordMetric('memory.heap.total', usage.heapTotal / 1024 / 1024, 'MB');
this.recordMetric('memory.external', usage.external / 1024 / 1024, 'MB');
}
recordResponseSize(size: number, endpoint?: string): void {
this.recordMetric('response.size', size, 'bytes', { endpoint });
}
private flushMetrics(): void {
if (this.metrics.length === 0) {
return;
}
// Calculate aggregated metrics
const aggregated = this.aggregateMetrics(this.metrics);
this.logger.info("Performance metrics", { metrics: aggregated });
// Clear metrics
this.metrics = [];
}
private aggregateMetrics(metrics: PerformanceMetric[]): Record<string, any> {
const grouped = new Map<string, number[]>();
for (const metric of metrics) {
const key = metric.name;
if (!grouped.has(key)) {
grouped.set(key, []);
}
grouped.get(key)!.push(metric.value);
}
const aggregated: Record<string, any> = {};
for (const [name, values] of grouped) {
aggregated[name] = {
count: values.length,
sum: values.reduce((a, b) => a + b, 0),
avg: values.reduce((a, b) => a + b, 0) / values.length,
min: Math.min(...values),
max: Math.max(...values)
};
}
return aggregated;
}
}
5. Debugging and Diagnostic Tools
5.1 Debug Information Collector
interface DebugInfo {
server: {
version: string;
uptime: number;
startTime: Date;
nodeVersion: string;
platform: string;
};
memory: NodeJS.MemoryUsage;
connections: {
active: number;
total: number;
};
requests: {
total: number;
successful: number;
failed: number;
averageResponseTime: number;
};
errors: {
recent: MCPError[];
byCategory: Record<string, number>;
bySeverity: Record<string, number>;
};
performance: {
cpuUsage: NodeJS.CpuUsage;
memoryTrend: number[];
};
}
class DiagnosticsCollector {
private logger: AdvancedLogger;
private startTime: Date;
private requestStats: {
total: number;
successful: number;
failed: number;
totalResponseTime: number;
};
private recentErrors: MCPError[] = [];
private memoryHistory: number[] = [];
constructor(logger: AdvancedLogger) {
this.logger = logger;
this.startTime = new Date();
this.requestStats = {
total: 0,
successful: 0,
failed: 0,
totalResponseTime: 0
};
// Periodically collect memory usage
setInterval(() => {
const usage = process.memoryUsage();
this.memoryHistory.push(usage.heapUsed);
// Keep only the last 50 data points
if (this.memoryHistory.length > 50) {
this.memoryHistory.shift();
}
}, 30000);
}
recordRequest(success: boolean, responseTime: number): void {
this.requestStats.total++;
this.requestStats.totalResponseTime += responseTime;
if (success) {
this.requestStats.successful++;
} else {
this.requestStats.failed++;
}
}
recordError(error: MCPError): void {
this.recentErrors.push(error);
// Keep only the last 100 errors
if (this.recentErrors.length > 100) {
this.recentErrors.shift();
}
}
collectDebugInfo(): DebugInfo {
const memoryUsage = process.memoryUsage();
const cpuUsage = process.cpuUsage();
// Count errors
const errorsByCategory: Record<string, number> = {};
const errorsBySeverity: Record<string, number> = {};
for (const error of this.recentErrors) {
errorsByCategory[error.category] = (errorsByCategory[error.category] || 0) + 1;
errorsBySeverity[error.severity] = (errorsBySeverity[error.severity] || 0) + 1;
}
return {
server: {
version: "1.0.0", // Get from config
uptime: process.uptime(),
startTime: this.startTime,
nodeVersion: process.version,
platform: process.platform
},
memory: memoryUsage,
connections: {
active: 0, // Get from connection manager
total: 0
},
requests: {
total: this.requestStats.total,
successful: this.requestStats.successful,
failed: this.requestStats.failed,
averageResponseTime: this.requestStats.total > 0
? this.requestStats.totalResponseTime / this.requestStats.total
: 0
},
errors: {
recent: this.recentErrors.slice(-20), // Last 20 errors
byCategory: errorsByCategory,
bySeverity: errorsBySeverity
},
performance: {
cpuUsage,
memoryTrend: [...this.memoryHistory]
}
};
}
generateHealthReport(): string {
const debugInfo = this.collectDebugInfo();
let report = "=== MCP Server Health Report ===\n\n";
report += `Server Version: ${debugInfo.server.version}\n`;
report += `Uptime: ${Math.floor(debugInfo.server.uptime)}s\n`;
report += `Node Version: ${debugInfo.server.nodeVersion}\n`;
report += `Platform: ${debugInfo.server.platform}\n\n`;
report += "Memory Usage:\n";
report += ` Heap Used: ${Math.round(debugInfo.memory.heapUsed / 1024 / 1024)}MB\n`;
report += ` Heap Total: ${Math.round(debugInfo.memory.heapTotal / 1024 / 1024)}MB\n`;
report += ` External: ${Math.round(debugInfo.memory.external / 1024 / 1024)}MB\n\n`;
report += "Request Statistics:\n";
report += ` Total Requests: ${debugInfo.requests.total}\n`;
report += ` Successful: ${debugInfo.requests.successful}\n`;
report += ` Failed: ${debugInfo.requests.failed}\n`;
report += ` Success Rate: ${debugInfo.requests.total > 0 ?
Math.round(debugInfo.requests.successful / debugInfo.requests.total * 100) : 0}%\n`;
report += ` Avg Response Time: ${Math.round(debugInfo.requests.averageResponseTime)}ms\n\n`;
if (Object.keys(debugInfo.errors.byCategory).length > 0) {
report += "Recent Errors by Category:\n";
for (const [category, count] of Object.entries(debugInfo.errors.byCategory)) {
report += ` ${category}: ${count}\n`;
}
report += "\n";
}
return report;
}
}
5.2 Runtime Debugging Tools
class DebugTools {
private logger: AdvancedLogger;
private diagnostics: DiagnosticsCollector;
private isDebugMode: boolean = false;
constructor(logger: AdvancedLogger, diagnostics: DiagnosticsCollector) {
this.logger = logger;
this.diagnostics = diagnostics;
// Check debug mode environment variable
this.isDebugMode = process.env.DEBUG === 'true';
}
enableDebugMode(): void {
this.isDebugMode = true;
this.logger.info("Debug mode enabled");
}
disableDebugMode(): void {
this.isDebugMode = false;
this.logger.info("Debug mode disabled");
}
debugRequest(method: string, params: any, result?: any, error?: Error): void {
if (!this.isDebugMode) return;
this.logger.debug("Request Debug Info", {
method,
params,
result: result ? JSON.stringify(result).substring(0, 500) : undefined,
error: error?.message,
timestamp: new Date().toISOString()
});
}
debugMemoryUsage(label: string): void {
if (!this.isDebugMode) return;
const usage = process.memoryUsage();
this.logger.debug(`Memory Usage - ${label}`, {
heapUsed: `${Math.round(usage.heapUsed / 1024 / 1024)}MB`,
heapTotal: `${Math.round(usage.heapTotal / 1024 / 1024)}MB`,
external: `${Math.round(usage.external / 1024 / 1024)}MB`,
rss: `${Math.round(usage.rss / 1024 / 1024)}MB`
});
}
dumpStackTrace(label: string): void {
if (!this.isDebugMode) return;
const stack = new Error().stack;
this.logger.debug(`Stack Trace - ${label}`, { stack });
}
profileFunction<T>(fn: () => T, name: string): T {
if (!this.isDebugMode) {
return fn();
}
const start = process.hrtime();
const result = fn();
const [seconds, nanoseconds] = process.hrtime(start);
const duration = seconds * 1000 + nanoseconds / 1000000;
this.logger.debug(`Function Profile - ${name}`, {
duration: `${duration.toFixed(2)}ms`
});
return result;
}
async profileAsyncFunction<T>(fn: () => Promise<T>, name: string): Promise<T> {
if (!this.isDebugMode) {
return fn();
}
const start = process.hrtime();
try {
const result = await fn();
const [seconds, nanoseconds] = process.hrtime(start);
const duration = seconds * 1000 + nanoseconds / 1000000;
this.logger.debug(`Async Function Profile - ${name}`, {
duration: `${duration.toFixed(2)}ms`,
status: 'success'
});
return result;
} catch (error) {
const [seconds, nanoseconds] = process.hrtime(start);
const duration = seconds * 1000 + nanoseconds / 1000000;
this.logger.debug(`Async Function Profile - ${name}`, {
duration: `${duration.toFixed(2)}ms`,
status: 'error',
error: error.message
});
throw error;
}
}
}
6. Integration Example
6.1 Complete Error Handling and Logging System
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
class LoggedMCPServer {
private server: Server;
private logger: AdvancedLogger;
private errorHandler: ErrorHandlingMiddleware;
private errorReporter: ErrorReporter;
private diagnostics: DiagnosticsCollector;
private debugTools: DebugTools;
private performanceLogger: PerformanceLogger;
constructor() {
// Initialize logging system
this.logger = new AdvancedLogger({
level: LogLevel.INFO,
format: 'json',
outputs: [
new ConsoleLogOutput(),
// new FileLogOutput('./logs/server.log')
],
enableStackTrace: true,
maxDataSize: 1000
});
// Initialize monitoring and diagnostics
this.diagnostics = new DiagnosticsCollector(this.logger);
this.debugTools = new DebugTools(this.logger, this.diagnostics);
this.performanceLogger = new PerformanceLogger(this.logger);
// Initialize error reporting
this.errorReporter = new ErrorReporter({
enableReporting: process.env.ENABLE_ERROR_REPORTING === 'true',
reportingEndpoint: process.env.ERROR_REPORTING_ENDPOINT,
apiKey: process.env.ERROR_REPORTING_API_KEY,
samplingRate: 1.0,
maxReportsPerMinute: 100
}, this.logger);
// Initialize error handling middleware
this.errorHandler = new ErrorHandlingMiddleware(this.errorReporter, this.logger);
// Create server
this.server = new Server(
{
name: "logged-mcp-server",
version: "1.0.0"
},
{
capabilities: {
tools: {},
resources: {},
prompts: {}
}
}
);
this.setupHandlers();
this.setupHealthEndpoints();
}
private setupHandlers(): void {
// Wrap all handlers with error handling middleware
this.server.setRequestHandler(
"tools/list",
this.errorHandler.wrapHandler(async (request, context) => {
this.debugTools.debugRequest("tools/list", request.params);
// Simulate processing logic
const startTime = Date.now();
const result = { tools: [] };
this.performanceLogger.recordExecutionTime(
"tools/list",
startTime,
{ method: "tools/list" }
);
this.diagnostics.recordRequest(true, Date.now() - startTime);
return result;
})
);
// Other handlers...
}
private setupHealthEndpoints(): void {
// Health check endpoint (if HTTP interface is supported)
this.server.setRequestHandler(
"debug/health",
this.errorHandler.wrapHandler(async (request, context) => {
const debugInfo = this.diagnostics.collectDebugInfo();
return { health: debugInfo };
})
);
this.server.setRequestHandler(
"debug/report",
this.errorHandler.wrapHandler(async (request, context) => {
const report = this.diagnostics.generateHealthReport();
return { report };
})
);
}
async start(): Promise<void> {
try {
this.logger.info("Starting MCP Server with logging and error handling");
// Start server
const transport = new StdioServerTransport();
await this.server.connect(transport);
this.logger.info("MCP Server started successfully");
} catch (error) {
this.logger.fatal("Failed to start MCP Server", { error: error.message });
throw error;
}
}
}
// Start server
const server = new LoggedMCPServer();
server.start().catch(error => {
console.error("Server startup failed:", error);
process.exit(1);
});
7. Best Practices
7.1 Error Handling Principles
- Fail Fast - Detect and handle errors as early as possible
- Error Classification - Classify errors by type and severity
- Context Preservation - Retain sufficient error context information
- Graceful Degradation - Provide degraded service when possible
- User-Friendly - Provide meaningful error messages to users
7.2 Logging Guidelines
- Structured Logging - Use structured format for easy analysis
- Sensitive Information Filtering - Automatically filter sensitive information
- Appropriate Levels - Choose appropriate log levels
- Performance Consideration - Avoid excessive logging affecting performance
- Log Rotation - Implement log file rotation and cleanup
7.3 Monitoring and Alerting
- Key Metrics - Monitor key performance and health metrics
- Real-time Alerts - Alert promptly when issues occur
- Trend Analysis - Analyze error and performance trends
- Capacity Planning - Perform capacity planning based on monitoring data
- Failure Prevention - Proactively discover and prevent potential issues
Summary
Through this chapter, we have mastered:
- Complete error classification and handling mechanisms
- Implementation of structured logging systems
- Error reporting and monitoring integration solutions
- Construction of debugging and diagnostic tools
- Complete implementation of enterprise-level error handling and logging systems
A good error handling and logging system is key infrastructure for building reliable MCP Servers, greatly improving system maintainability and observability.