defect-inspection-tools-mcp-server
Version:
Defect inspection tools server handling defect detection, analysis, and reporting with AI/ML capabilities for quality control
279 lines • 11.1 kB
JavaScript
import { logger } from '../utils/logger.js';
import { ErrorHandler } from '../middleware/error-handler.js';
export var CircuitBreakerState;
(function (CircuitBreakerState) {
CircuitBreakerState["CLOSED"] = "CLOSED";
CircuitBreakerState["OPEN"] = "OPEN";
CircuitBreakerState["HALF_OPEN"] = "HALF_OPEN";
})(CircuitBreakerState || (CircuitBreakerState = {}));
export var ServiceHealthStatus;
(function (ServiceHealthStatus) {
ServiceHealthStatus["HEALTHY"] = "HEALTHY";
ServiceHealthStatus["DEGRADED"] = "DEGRADED";
ServiceHealthStatus["UNHEALTHY"] = "UNHEALTHY";
})(ServiceHealthStatus || (ServiceHealthStatus = {}));
export class BaseService {
constructor(serviceName, config = {}) {
this.responseTimes = [];
this.serviceName = serviceName;
this.config = {
timeout: config.timeout || 30000,
retries: config.retries || 3,
retryDelay: config.retryDelay || 1000,
circuitBreakerThreshold: config.circuitBreakerThreshold || 5,
circuitBreakerTimeout: config.circuitBreakerTimeout || 60000
};
this.metrics = {
totalRequests: 0,
successCount: 0,
errorCount: 0,
averageResponseTime: 0,
circuitBreakerState: CircuitBreakerState.CLOSED
};
this.circuitBreakerState = CircuitBreakerState.CLOSED;
logger.info(`${this.serviceName} initialized`, {
config: this.config,
serviceName: this.serviceName
});
}
// Main execution method with resilience patterns
async executeWithResilience(operation, context) {
const startTime = Date.now();
let lastError;
let retryCount = 0;
// Check circuit breaker state
if (!this.canExecute()) {
this.metrics.totalRequests++;
this.metrics.errorCount++;
return {
success: false,
error: 'Service circuit breaker is open',
metadata: {
responseTime: Date.now() - startTime,
retryCount: 0,
timestamp: new Date()
}
};
}
for (let attempt = 0; attempt <= this.config.retries; attempt++) {
try {
this.metrics.totalRequests++;
retryCount = attempt;
// Execute with timeout
const result = await ErrorHandler.executeWithTimeout(operation, this.config.timeout, context);
// Success - update metrics and circuit breaker
const responseTime = Date.now() - startTime;
this.updateMetrics(true, responseTime);
this.resetCircuitBreaker();
logger.debug(`${this.serviceName} operation succeeded`, {
serviceName: this.serviceName,
responseTime,
retryCount,
requestId: context.requestId
});
return {
success: true,
data: result,
metadata: {
responseTime,
retryCount,
timestamp: new Date()
}
};
}
catch (error) {
lastError = error;
const responseTime = Date.now() - startTime;
logger.warn(`${this.serviceName} operation failed (attempt ${attempt + 1}/${this.config.retries + 1})`, {
serviceName: this.serviceName,
error: error.message,
responseTime,
retryCount: attempt,
requestId: context.requestId
});
// Don't retry for certain error types
if (this.shouldNotRetry(error)) {
logger.info(`${this.serviceName} error is not retryable`, {
serviceName: this.serviceName,
error: error.message,
requestId: context.requestId
});
break;
}
// If not the last attempt, wait before retry
if (attempt < this.config.retries) {
const delay = this.calculateRetryDelay(attempt);
await new Promise(resolve => setTimeout(resolve, delay));
}
}
}
// All retries failed
const responseTime = Date.now() - startTime;
this.updateMetrics(false, responseTime);
this.checkCircuitBreaker();
const standardError = ErrorHandler.handleError(lastError, context);
return {
success: false,
error: standardError.message,
metadata: {
responseTime,
retryCount,
timestamp: new Date()
}
};
}
// Circuit breaker logic
canExecute() {
switch (this.circuitBreakerState) {
case CircuitBreakerState.CLOSED:
return true;
case CircuitBreakerState.OPEN:
if (this.circuitBreakerOpenTime) {
const timeElapsed = Date.now() - this.circuitBreakerOpenTime.getTime();
if (timeElapsed >= this.config.circuitBreakerTimeout) {
this.circuitBreakerState = CircuitBreakerState.HALF_OPEN;
logger.info(`${this.serviceName} circuit breaker transitioning to HALF_OPEN`, {
serviceName: this.serviceName
});
return true;
}
}
return false;
case CircuitBreakerState.HALF_OPEN:
return true;
default:
return false;
}
}
checkCircuitBreaker() {
if (this.circuitBreakerState === CircuitBreakerState.HALF_OPEN) {
// If we failed in half-open state, go back to open
this.circuitBreakerState = CircuitBreakerState.OPEN;
this.circuitBreakerOpenTime = new Date();
logger.warn(`${this.serviceName} circuit breaker back to OPEN after failure in HALF_OPEN`, {
serviceName: this.serviceName
});
}
else if (this.circuitBreakerState === CircuitBreakerState.CLOSED) {
// Check if we should open the circuit breaker
if (this.metrics.errorCount >= this.config.circuitBreakerThreshold) {
this.circuitBreakerState = CircuitBreakerState.OPEN;
this.circuitBreakerOpenTime = new Date();
logger.warn(`${this.serviceName} circuit breaker opened due to error threshold`, {
serviceName: this.serviceName,
errorCount: this.metrics.errorCount,
threshold: this.config.circuitBreakerThreshold
});
}
}
this.metrics.circuitBreakerState = this.circuitBreakerState;
}
resetCircuitBreaker() {
if (this.circuitBreakerState !== CircuitBreakerState.CLOSED) {
this.circuitBreakerState = CircuitBreakerState.CLOSED;
this.circuitBreakerOpenTime = undefined;
this.metrics.circuitBreakerState = CircuitBreakerState.CLOSED;
logger.info(`${this.serviceName} circuit breaker reset to CLOSED`, {
serviceName: this.serviceName
});
}
}
updateMetrics(success, responseTime) {
if (success) {
this.metrics.successCount++;
}
else {
this.metrics.errorCount++;
this.metrics.lastError = new Date();
}
// Update rolling average response time
this.responseTimes.push(responseTime);
if (this.responseTimes.length > 100) {
this.responseTimes.shift();
}
this.metrics.averageResponseTime = this.responseTimes.reduce((sum, time) => sum + time, 0) / this.responseTimes.length;
}
calculateRetryDelay(attempt) {
return this.config.retryDelay * Math.pow(2, attempt);
}
shouldNotRetry(error) {
// Don't retry for certain HTTP status codes
const nonRetryableStatuses = [400, 401, 403, 404, 422];
if (error.response && nonRetryableStatuses.includes(error.response.status)) {
return true;
}
// Don't retry for validation errors
if (error.message && error.message.includes('validation')) {
return true;
}
return false;
}
// Public methods for monitoring and management
getMetrics() {
return { ...this.metrics };
}
async getHealthStatus() {
try {
// Get service-specific health check
const healthStatus = await this.healthCheck();
// Override with circuit breaker state if necessary
if (this.circuitBreakerState === CircuitBreakerState.OPEN) {
return ServiceHealthStatus.UNHEALTHY;
}
// Check error rate
const errorRate = this.metrics.totalRequests > 0 ?
this.metrics.errorCount / this.metrics.totalRequests : 0;
if (errorRate > 0.1) { // More than 10% error rate
return ServiceHealthStatus.DEGRADED;
}
// Check response time
const timeoutThreshold = this.config.timeout * 0.8; // 80% of timeout
if (this.metrics.averageResponseTime > timeoutThreshold) {
return ServiceHealthStatus.DEGRADED;
}
return healthStatus;
}
catch (error) {
logger.error(`Health check failed for ${this.serviceName}`, {
serviceName: this.serviceName,
error: error.message
});
return ServiceHealthStatus.UNHEALTHY;
}
}
resetMetrics() {
this.metrics = {
totalRequests: 0,
successCount: 0,
errorCount: 0,
averageResponseTime: 0,
circuitBreakerState: this.circuitBreakerState
};
this.responseTimes = [];
logger.info(`${this.serviceName} metrics reset`, {
serviceName: this.serviceName
});
}
getServiceName() {
return this.serviceName;
}
getConfig() {
return { ...this.config };
}
// Force circuit breaker state (for testing)
setCircuitBreakerState(state) {
this.circuitBreakerState = state;
this.metrics.circuitBreakerState = state;
if (state === CircuitBreakerState.OPEN) {
this.circuitBreakerOpenTime = new Date();
}
else {
this.circuitBreakerOpenTime = undefined;
}
logger.info(`${this.serviceName} circuit breaker state manually set to ${state}`, {
serviceName: this.serviceName,
state
});
}
}
//# sourceMappingURL=base-service.js.map