Add log rotation utility with file management and cleanup
- Implemented log rotation module with configurable size and age-based log management - Added utility functions to parse log file sizes and retention durations - Created methods for identifying, filtering, and cleaning up old log files - Integrated log rotation initialization in the main application entry point - Added periodic checks for log file size and age-based cleanup
This commit is contained in:
@@ -40,6 +40,7 @@
|
|||||||
"@types/ajv": "^1.0.0",
|
"@types/ajv": "^1.0.0",
|
||||||
"@types/express": "^4.17.21",
|
"@types/express": "^4.17.21",
|
||||||
"@types/express-rate-limit": "^6.0.0",
|
"@types/express-rate-limit": "^6.0.0",
|
||||||
|
"@types/glob": "^8.1.0",
|
||||||
"@types/helmet": "^4.0.0",
|
"@types/helmet": "^4.0.0",
|
||||||
"@types/jest": "^29.5.14",
|
"@types/jest": "^29.5.14",
|
||||||
"@types/node": "^20.17.16",
|
"@types/node": "^20.17.16",
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import { requestLogger, errorLogger } from './middleware/logging.middleware.js';
|
|||||||
import { get_hass } from './hass/index.js';
|
import { get_hass } from './hass/index.js';
|
||||||
import { LiteMCP } from 'litemcp';
|
import { LiteMCP } from 'litemcp';
|
||||||
import { logger } from './utils/logger.js';
|
import { logger } from './utils/logger.js';
|
||||||
|
import { initLogRotation } from './utils/log-rotation.js';
|
||||||
|
|
||||||
logger.info('Starting Home Assistant MCP...');
|
logger.info('Starting Home Assistant MCP...');
|
||||||
logger.info('Initializing Home Assistant connection...');
|
logger.info('Initializing Home Assistant connection...');
|
||||||
@@ -27,6 +28,9 @@ logger.info('Initializing Home Assistant connection...');
|
|||||||
*/
|
*/
|
||||||
const app = express();
|
const app = express();
|
||||||
|
|
||||||
|
// Initialize log rotation
|
||||||
|
initLogRotation();
|
||||||
|
|
||||||
// Apply logging middleware first to catch all requests
|
// Apply logging middleware first to catch all requests
|
||||||
app.use(requestLogger);
|
app.use(requestLogger);
|
||||||
|
|
||||||
|
|||||||
155
src/utils/log-rotation.ts
Normal file
155
src/utils/log-rotation.ts
Normal file
@@ -0,0 +1,155 @@
|
|||||||
|
/**
|
||||||
|
* Log Rotation Utility
|
||||||
|
*
|
||||||
|
* This module provides functionality for managing log file rotation and cleanup.
|
||||||
|
* It handles log file archiving, compression, and deletion based on configuration.
|
||||||
|
*
|
||||||
|
* @module log-rotation
|
||||||
|
*/
|
||||||
|
|
||||||
|
import fs from 'fs/promises';
|
||||||
|
import path from 'path';
|
||||||
|
import { glob } from 'glob';
|
||||||
|
import { logger } from './logger.js';
|
||||||
|
import { APP_CONFIG } from '../config/app.config.js';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interface for log file information
|
||||||
|
*/
|
||||||
|
interface LogFileInfo {
|
||||||
|
path: string;
|
||||||
|
filename: string;
|
||||||
|
date: Date;
|
||||||
|
size: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse size string to bytes
|
||||||
|
* @param size - Size string (e.g., '20m', '1g')
|
||||||
|
* @returns Size in bytes
|
||||||
|
*/
|
||||||
|
const parseSize = (size: string): number => {
|
||||||
|
const units = {
|
||||||
|
b: 1,
|
||||||
|
k: 1024,
|
||||||
|
m: 1024 * 1024,
|
||||||
|
g: 1024 * 1024 * 1024,
|
||||||
|
};
|
||||||
|
const match = size.toLowerCase().match(/^(\d+)([bkmg])$/);
|
||||||
|
if (!match) {
|
||||||
|
throw new Error(`Invalid size format: ${size}`);
|
||||||
|
}
|
||||||
|
const [, value, unit] = match;
|
||||||
|
return parseInt(value) * units[unit as keyof typeof units];
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse duration string to days
|
||||||
|
* @param duration - Duration string (e.g., '14d', '2w')
|
||||||
|
* @returns Duration in days
|
||||||
|
*/
|
||||||
|
const parseDuration = (duration: string): number => {
|
||||||
|
const units = {
|
||||||
|
d: 1,
|
||||||
|
w: 7,
|
||||||
|
m: 30,
|
||||||
|
};
|
||||||
|
const match = duration.toLowerCase().match(/^(\d+)([dwm])$/);
|
||||||
|
if (!match) {
|
||||||
|
throw new Error(`Invalid duration format: ${duration}`);
|
||||||
|
}
|
||||||
|
const [, value, unit] = match;
|
||||||
|
return parseInt(value) * units[unit as keyof typeof units];
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get information about log files
|
||||||
|
* @returns Array of log file information
|
||||||
|
*/
|
||||||
|
const getLogFiles = async (): Promise<LogFileInfo[]> => {
|
||||||
|
const logDir = APP_CONFIG.LOGGING.DIR;
|
||||||
|
const files = await glob('*.log*', { cwd: logDir });
|
||||||
|
|
||||||
|
const fileInfos: LogFileInfo[] = [];
|
||||||
|
for (const file of files) {
|
||||||
|
const filePath = path.join(logDir, file);
|
||||||
|
const stats = await fs.stat(filePath);
|
||||||
|
const dateMatch = file.match(/\d{4}-\d{2}-\d{2}/);
|
||||||
|
|
||||||
|
if (dateMatch) {
|
||||||
|
fileInfos.push({
|
||||||
|
path: filePath,
|
||||||
|
filename: file,
|
||||||
|
date: new Date(dateMatch[0]),
|
||||||
|
size: stats.size,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return fileInfos;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clean up old log files
|
||||||
|
*/
|
||||||
|
const cleanupOldLogs = async (): Promise<void> => {
|
||||||
|
try {
|
||||||
|
const maxDays = parseDuration(APP_CONFIG.LOGGING.MAX_DAYS);
|
||||||
|
const maxAge = Date.now() - (maxDays * 24 * 60 * 60 * 1000);
|
||||||
|
|
||||||
|
const files = await getLogFiles();
|
||||||
|
const oldFiles = files.filter(file => file.date.getTime() < maxAge);
|
||||||
|
|
||||||
|
for (const file of oldFiles) {
|
||||||
|
await fs.unlink(file.path);
|
||||||
|
logger.debug(`Deleted old log file: ${file.filename}`);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
logger.error('Error cleaning up old logs:', error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check and rotate log files based on size
|
||||||
|
*/
|
||||||
|
const checkLogSize = async (): Promise<void> => {
|
||||||
|
try {
|
||||||
|
const maxSize = parseSize(APP_CONFIG.LOGGING.MAX_SIZE);
|
||||||
|
const files = await getLogFiles();
|
||||||
|
|
||||||
|
for (const file of files) {
|
||||||
|
if (file.size > maxSize && !file.filename.endsWith('.gz')) {
|
||||||
|
// Current log file is handled by winston-daily-rotate-file
|
||||||
|
if (!file.filename.includes(new Date().toISOString().split('T')[0])) {
|
||||||
|
logger.debug(`Log file exceeds max size: ${file.filename}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
logger.error('Error checking log sizes:', error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize log rotation
|
||||||
|
* Sets up periodic checks for log rotation and cleanup
|
||||||
|
*/
|
||||||
|
export const initLogRotation = (): void => {
|
||||||
|
// Check log sizes every hour
|
||||||
|
setInterval(checkLogSize, 60 * 60 * 1000);
|
||||||
|
|
||||||
|
// Clean up old logs daily
|
||||||
|
setInterval(cleanupOldLogs, 24 * 60 * 60 * 1000);
|
||||||
|
|
||||||
|
// Initial check
|
||||||
|
checkLogSize().catch(error => {
|
||||||
|
logger.error('Error in initial log size check:', error);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Initial cleanup
|
||||||
|
cleanupOldLogs().catch(error => {
|
||||||
|
logger.error('Error in initial log cleanup:', error);
|
||||||
|
});
|
||||||
|
|
||||||
|
logger.info('Log rotation initialized');
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user