fortify2-js
Version:
MOST POWERFUL JavaScript Security Library! Military-grade cryptography + 19 enhanced object methods + quantum-resistant algorithms + perfect TypeScript support. More powerful than Lodash with built-in security.
511 lines (505 loc) • 17.9 kB
JavaScript
'use strict';
var events = require('events');
var fs = require('fs');
var path = require('path');
function _interopNamespaceDefault(e) {
var n = Object.create(null);
if (e) {
Object.keys(e).forEach(function (k) {
if (k !== 'default') {
var d = Object.getOwnPropertyDescriptor(e, k);
Object.defineProperty(n, k, d.get ? d : {
enumerable: true,
get: function () { return e[k]; }
});
}
});
}
n.default = e;
return Object.freeze(n);
}
var fs__namespace = /*#__PURE__*/_interopNamespaceDefault(fs);
var path__namespace = /*#__PURE__*/_interopNamespaceDefault(path);
/**
* Server Maintenance Plugin
*
* Automatically detects issues and performs server maintenance in the background
* Features:
* - Error detection and analysis
* - Memory leak detection
* - Log cleanup and rotation
* - Performance degradation detection
* - Automatic health checks
* - Resource optimization
*/
class ServerMaintenancePlugin extends events.EventEmitter {
constructor(config = {}) {
super();
this.issues = [];
this.healthHistory = [];
this.startTime = Date.now();
this.errorCount = 0;
this.requestCount = 0;
this.responseTimes = [];
this.config = {
enabled: true,
checkInterval: 30000, // 30 seconds
errorThreshold: 5, // 5% error rate
memoryThreshold: 80, // 80% memory usage
responseTimeThreshold: 1000, // 1 second
logRetentionDays: 7,
maxLogFileSize: 10 * 1024 * 1024, // 10MB
autoCleanup: true,
autoRestart: false,
onIssueDetected: () => { },
onMaintenanceComplete: () => { },
...config,
};
}
/**
* Initialize the plugin
*/
initialize(app, logger) {
if (!this.config.enabled)
return;
this.app = app;
this.logger = logger;
// Install monitoring middleware
this.installMonitoringMiddleware();
// Start background maintenance
this.startBackgroundMaintenance();
// Setup error handlers
this.setupErrorHandlers();
this.logger.info("plugins", "Server Maintenance Plugin initialized");
}
/**
* Install middleware to monitor requests
*/
installMonitoringMiddleware() {
this.app.use((req, res, next) => {
const startTime = Date.now();
this.requestCount++;
// Override res.end to capture metrics
const originalEnd = res.end.bind(res);
res.end = (...args) => {
const responseTime = Date.now() - startTime;
this.responseTimes.push(responseTime);
// Keep only last 1000 response times
if (this.responseTimes.length > 1000) {
this.responseTimes = this.responseTimes.slice(-1000);
}
if (res.statusCode >= 400) {
this.errorCount++;
}
return originalEnd(...args);
};
next();
});
}
/**
* Setup error handlers
*/
setupErrorHandlers() {
// Uncaught exception handler
process.on("uncaughtException", (error) => {
this.reportIssue({
type: "error",
category: "errors",
message: `Uncaught exception: ${error.message}`,
severity: 9,
timestamp: new Date(),
details: { stack: error.stack },
resolved: false,
});
});
// Unhandled rejection handler
process.on("unhandledRejection", (reason, promise) => {
this.reportIssue({
type: "error",
category: "errors",
message: `Unhandled rejection: ${reason}`,
severity: 8,
timestamp: new Date(),
details: { promise },
resolved: false,
});
});
}
/**
* Start background maintenance checks
*/
startBackgroundMaintenance() {
this.maintenanceTimer = setInterval(() => {
this.performMaintenanceCheck();
}, this.config.checkInterval);
}
/**
* Perform comprehensive maintenance check
*/
performMaintenanceCheck() {
const metrics = this.collectHealthMetrics();
this.healthHistory.push(metrics);
// Keep only last 100 health records
if (this.healthHistory.length > 100) {
this.healthHistory = this.healthHistory.slice(-100);
}
// Check for issues
this.checkMemoryUsage(metrics);
this.checkErrorRate(metrics);
this.checkResponseTime(metrics);
this.checkLogFiles();
// Perform automatic cleanup if enabled
if (this.config.autoCleanup) {
this.performAutomaticCleanup();
}
this.emit("health_check", metrics);
}
/**
* Collect current health metrics
*/
collectHealthMetrics() {
const memUsage = process.memoryUsage();
const totalMemory = memUsage.heapTotal + memUsage.external;
const usedMemory = memUsage.heapUsed;
const avgResponseTime = this.responseTimes.length > 0
? this.responseTimes.reduce((a, b) => a + b, 0) /
this.responseTimes.length
: 0;
const p95ResponseTime = this.responseTimes.length > 0
? this.responseTimes.sort((a, b) => a - b)[Math.floor(this.responseTimes.length * 0.95)]
: 0;
const errorRate = this.requestCount > 0
? (this.errorCount / this.requestCount) * 100
: 0;
return {
memoryUsage: {
used: usedMemory,
total: totalMemory,
percentage: (usedMemory / totalMemory) * 100,
trend: this.calculateMemoryTrend(),
},
cpuUsage: process.cpuUsage().user / 1000000, // Convert to seconds
errorRate,
responseTime: {
average: avgResponseTime,
p95: p95ResponseTime,
trend: this.calculateResponseTimeTrend(),
},
activeConnections: 0, // Would need server reference to get actual count
uptime: Date.now() - this.startTime,
};
}
/**
* Calculate memory usage trend
*/
calculateMemoryTrend() {
if (this.healthHistory.length < 3)
return "stable";
const recent = this.healthHistory.slice(-3);
const trend = recent[2].memoryUsage.percentage - recent[0].memoryUsage.percentage;
if (trend > 5)
return "increasing";
if (trend < -5)
return "decreasing";
return "stable";
}
/**
* Calculate response time trend
*/
calculateResponseTimeTrend() {
if (this.healthHistory.length < 3)
return "stable";
const recent = this.healthHistory.slice(-3);
const trend = recent[2].responseTime.average - recent[0].responseTime.average;
if (trend > 100)
return "degrading";
if (trend < -100)
return "improving";
return "stable";
}
/**
* Check memory usage
*/
checkMemoryUsage(metrics) {
if (metrics.memoryUsage.percentage > this.config.memoryThreshold) {
this.reportIssue({
type: "warning",
category: "memory",
message: `High memory usage: ${metrics.memoryUsage.percentage.toFixed(1)}%`,
severity: 7,
timestamp: new Date(),
details: metrics.memoryUsage,
resolved: false,
});
}
if (metrics.memoryUsage.trend === "increasing" &&
metrics.memoryUsage.percentage > 60) {
this.reportIssue({
type: "warning",
category: "memory",
message: "Memory usage is consistently increasing - possible memory leak",
severity: 8,
timestamp: new Date(),
details: metrics.memoryUsage,
resolved: false,
});
// Trigger automatic memory optimization
this.performMemoryOptimization();
}
}
/**
* Check error rate
*/
checkErrorRate(metrics) {
if (metrics.errorRate > this.config.errorThreshold) {
this.reportIssue({
type: "error",
category: "errors",
message: `High error rate: ${metrics.errorRate.toFixed(1)}%`,
severity: 8,
timestamp: new Date(),
details: {
errorRate: metrics.errorRate,
errorCount: this.errorCount,
requestCount: this.requestCount,
},
resolved: false,
});
}
}
/**
* Check response time
*/
checkResponseTime(metrics) {
if (metrics.responseTime.average > this.config.responseTimeThreshold) {
this.reportIssue({
type: "warning",
category: "performance",
message: `Slow response time: ${metrics.responseTime.average.toFixed(0)}ms average`,
severity: 6,
timestamp: new Date(),
details: metrics.responseTime,
resolved: false,
});
}
if (metrics.responseTime.trend === "degrading") {
this.reportIssue({
type: "warning",
category: "performance",
message: "Response times are degrading",
severity: 7,
timestamp: new Date(),
details: metrics.responseTime,
resolved: false,
});
}
}
/**
* Check log files
*/
checkLogFiles() {
try {
const logsDir = path__namespace.join(process.cwd(), "logs");
if (!fs__namespace.existsSync(logsDir))
return;
const files = fs__namespace.readdirSync(logsDir);
for (const file of files) {
const filePath = path__namespace.join(logsDir, file);
const stats = fs__namespace.statSync(filePath);
// Check file size
if (stats.size > this.config.maxLogFileSize) {
this.reportIssue({
type: "warning",
category: "logs",
message: `Large log file: ${file} (${(stats.size /
1024 /
1024).toFixed(1)}MB)`,
severity: 4,
timestamp: new Date(),
details: { file, size: stats.size },
resolved: false,
});
}
// Check file age
const ageInDays = (Date.now() - stats.mtime.getTime()) /
(1000 * 60 * 60 * 24);
if (ageInDays > this.config.logRetentionDays) {
this.reportIssue({
type: "info",
category: "logs",
message: `Old log file: ${file} (${ageInDays.toFixed(1)} days old)`,
severity: 2,
timestamp: new Date(),
details: { file, ageInDays },
resolved: false,
});
}
}
}
catch (error) {
// Ignore log check errors
}
}
/**
* Perform memory optimization
*/
performMemoryOptimization() {
const actions = [];
// Force garbage collection if available
if (global.gc) {
const beforeMemory = process.memoryUsage().heapUsed;
global.gc();
const afterMemory = process.memoryUsage().heapUsed;
const freed = beforeMemory - afterMemory;
if (freed > 0) {
actions.push(`Freed ${(freed / 1024 / 1024).toFixed(2)}MB via garbage collection`);
}
}
// Clear internal caches if memory usage is critical
const currentMemory = process.memoryUsage();
const memoryPercentage = (currentMemory.heapUsed / currentMemory.heapTotal) * 100;
if (memoryPercentage > 85) {
// Clear response time history to free memory
if (this.responseTimes.length > 100) {
this.responseTimes = this.responseTimes.slice(-100);
actions.push("Cleared old response time history");
}
// Clear old health history
if (this.healthHistory.length > 50) {
this.healthHistory = this.healthHistory.slice(-50);
actions.push("Cleared old health history");
}
// Clear resolved issues older than 1 hour
const oneHourAgo = Date.now() - 60 * 60 * 1000;
const oldIssuesCount = this.issues.length;
this.issues = this.issues.filter((issue) => !issue.resolved || issue.timestamp.getTime() > oneHourAgo);
if (this.issues.length < oldIssuesCount) {
actions.push(`Cleared ${oldIssuesCount - this.issues.length} old resolved issues`);
}
}
if (actions.length > 0) {
this.logger.info("plugins", `Memory optimization completed: ${actions.join(", ")}`);
this.emit("memory_optimization", actions);
}
}
/**
* Perform automatic cleanup
*/
performAutomaticCleanup() {
const actions = [];
// Clean up old issues
const oldIssues = this.issues.filter((issue) => Date.now() - issue.timestamp.getTime() > 24 * 60 * 60 * 1000 &&
issue.resolved);
if (oldIssues.length > 0) {
this.issues = this.issues.filter((issue) => !oldIssues.includes(issue));
actions.push(`Cleaned up ${oldIssues.length} old issues`);
}
// Force garbage collection if memory usage is high
const latestMetrics = this.healthHistory[this.healthHistory.length - 1];
if (latestMetrics && latestMetrics.memoryUsage.percentage > 70) {
if (global.gc) {
global.gc();
actions.push("Forced garbage collection");
}
}
// Clean up old log files
this.cleanupOldLogs(actions);
if (actions.length > 0) {
this.logger.info("plugins", `Automatic cleanup completed: ${actions.join(", ")}`);
this.config.onMaintenanceComplete(actions);
this.emit("maintenance_complete", actions);
}
}
/**
* Clean up old log files
*/
cleanupOldLogs(actions) {
try {
const logsDir = path__namespace.join(process.cwd(), "logs");
if (!fs__namespace.existsSync(logsDir))
return;
const files = fs__namespace.readdirSync(logsDir);
let cleanedFiles = 0;
for (const file of files) {
const filePath = path__namespace.join(logsDir, file);
const stats = fs__namespace.statSync(filePath);
const ageInDays = (Date.now() - stats.mtime.getTime()) /
(1000 * 60 * 60 * 24);
if (ageInDays > this.config.logRetentionDays) {
fs__namespace.unlinkSync(filePath);
cleanedFiles++;
}
}
if (cleanedFiles > 0) {
actions.push(`Cleaned up ${cleanedFiles} old log files`);
}
}
catch (error) {
// Ignore cleanup errors
}
}
/**
* Report a maintenance issue
*/
reportIssue(issue) {
this.issues.push(issue);
// Keep only last 1000 issues
if (this.issues.length > 1000) {
this.issues = this.issues.slice(-1000);
}
this.logger.warn("plugins", `Maintenance issue detected: ${issue.message}`);
this.config.onIssueDetected(issue);
this.emit("issue_detected", issue);
// Check if critical restart is needed
if (this.config.autoRestart && issue.severity >= 9) {
this.logger.error("plugins", "Critical issue detected - restart may be required");
this.emit("critical_issue", issue);
}
}
/**
* Get current health metrics
*/
getHealthMetrics() {
return this.healthHistory[this.healthHistory.length - 1] || null;
}
/**
* Get all issues
*/
getIssues() {
return [...this.issues];
}
/**
* Get unresolved issues
*/
getUnresolvedIssues() {
return this.issues.filter((issue) => !issue.resolved);
}
/**
* Resolve an issue
*/
resolveIssue(issueIndex) {
if (this.issues[issueIndex]) {
this.issues[issueIndex].resolved = true;
this.emit("issue_resolved", this.issues[issueIndex]);
}
}
/**
* Force maintenance check
*/
forceMaintenanceCheck() {
this.performMaintenanceCheck();
}
/**
* Destroy the plugin
*/
destroy() {
if (this.maintenanceTimer) {
clearInterval(this.maintenanceTimer);
}
this.issues = [];
this.healthHistory = [];
this.removeAllListeners();
}
}
exports.ServerMaintenancePlugin = ServerMaintenancePlugin;
//# sourceMappingURL=server-maintenance-plugin.js.map