Add comprehensive logging infrastructure with configuration and middleware
- Implemented centralized logging utility using Winston with daily log rotation - Added logging middleware for request and error tracking - Extended .env.example with logging configuration options - Updated app configuration to support flexible logging settings - Replaced console.log with structured logging in main application entry point - Created logging middleware to capture request details and response times
This commit is contained in:
24
.env.example
24
.env.example
@@ -48,4 +48,26 @@ CORS_ORIGINS=http://localhost:3000,http://localhost:8123
|
||||
TEST_HASS_HOST=http://localhost:8123
|
||||
TEST_HASS_TOKEN=test_token
|
||||
TEST_HASS_SOCKET_URL=ws://localhost:8123/api/websocket
|
||||
TEST_PORT=3001
|
||||
TEST_PORT=3001
|
||||
|
||||
# Security Configuration
|
||||
JWT_SECRET=your-secret-key
|
||||
|
||||
# Rate Limiting
|
||||
RATE_LIMIT_WINDOW_MS=900000 # 15 minutes
|
||||
RATE_LIMIT_MAX=100
|
||||
|
||||
# SSE Configuration
|
||||
SSE_MAX_CLIENTS=1000
|
||||
SSE_PING_INTERVAL=30000
|
||||
|
||||
# Logging Configuration
|
||||
LOG_LEVEL=info
|
||||
LOG_DIR=logs
|
||||
LOG_MAX_SIZE=20m
|
||||
LOG_MAX_DAYS=14d
|
||||
LOG_COMPRESS=true
|
||||
LOG_REQUESTS=true
|
||||
|
||||
# Version
|
||||
VERSION=0.1.0
|
||||
@@ -46,6 +46,24 @@ export const APP_CONFIG = {
|
||||
PING_INTERVAL: 30000 // 30 seconds
|
||||
},
|
||||
|
||||
/** Logging Configuration */
|
||||
LOGGING: {
|
||||
/** Log level (error, warn, info, http, debug) */
|
||||
LEVEL: process.env.LOG_LEVEL || 'info',
|
||||
/** Directory for log files */
|
||||
DIR: process.env.LOG_DIR || 'logs',
|
||||
/** Maximum log file size before rotation */
|
||||
MAX_SIZE: process.env.LOG_MAX_SIZE || '20m',
|
||||
/** Maximum number of days to keep log files */
|
||||
MAX_DAYS: process.env.LOG_MAX_DAYS || '14d',
|
||||
/** Whether to compress rotated logs */
|
||||
COMPRESS: process.env.LOG_COMPRESS === 'true',
|
||||
/** Format for timestamps in logs */
|
||||
TIMESTAMP_FORMAT: 'YYYY-MM-DD HH:mm:ss:ms',
|
||||
/** Whether to include request logging */
|
||||
LOG_REQUESTS: process.env.LOG_REQUESTS === 'true',
|
||||
},
|
||||
|
||||
/** Application Version */
|
||||
VERSION: '0.1.0'
|
||||
} as const;
|
||||
|
||||
11
src/index.ts
11
src/index.ts
@@ -13,10 +13,13 @@ import express from 'express';
|
||||
import { APP_CONFIG } from './config/app.config.js';
|
||||
import { apiRoutes } from './routes/index.js';
|
||||
import { securityHeaders, rateLimiter, validateRequest, sanitizeInput, errorHandler } from './security/index.js';
|
||||
import { requestLogger, errorLogger } from './middleware/logging.middleware.js';
|
||||
import { get_hass } from './hass/index.js';
|
||||
import { LiteMCP } from 'litemcp';
|
||||
import { logger } from './utils/logger.js';
|
||||
|
||||
console.log('Initializing Home Assistant connection...');
|
||||
logger.info('Starting Home Assistant MCP...');
|
||||
logger.info('Initializing Home Assistant connection...');
|
||||
|
||||
/**
|
||||
* Initialize Express application with security middleware
|
||||
@@ -24,6 +27,9 @@ console.log('Initializing Home Assistant connection...');
|
||||
*/
|
||||
const app = express();
|
||||
|
||||
// Apply logging middleware first to catch all requests
|
||||
app.use(requestLogger);
|
||||
|
||||
// Apply security middleware
|
||||
app.use(securityHeaders);
|
||||
app.use(rateLimiter);
|
||||
@@ -47,6 +53,7 @@ app.use('/api', apiRoutes);
|
||||
* Apply error handling middleware
|
||||
* This should be the last middleware in the chain
|
||||
*/
|
||||
app.use(errorLogger);
|
||||
app.use(errorHandler);
|
||||
|
||||
/**
|
||||
@@ -54,5 +61,5 @@ app.use(errorHandler);
|
||||
* The port is configured in the environment variables
|
||||
*/
|
||||
app.listen(APP_CONFIG.PORT, () => {
|
||||
console.log(`Server is running on port ${APP_CONFIG.PORT}`);
|
||||
logger.info(`Server is running on port ${APP_CONFIG.PORT}`);
|
||||
});
|
||||
106
src/middleware/logging.middleware.ts
Normal file
106
src/middleware/logging.middleware.ts
Normal file
@@ -0,0 +1,106 @@
|
||||
/**
|
||||
* Logging Middleware
|
||||
*
|
||||
* This middleware provides request logging functionality.
|
||||
* It logs incoming requests and their responses.
|
||||
*
|
||||
* @module logging-middleware
|
||||
*/
|
||||
|
||||
import { Request, Response, NextFunction } from 'express';
|
||||
import { logger } from '../utils/logger.js';
|
||||
import { APP_CONFIG } from '../config/app.config.js';
|
||||
|
||||
/**
|
||||
* Interface for extended request object with timing information
|
||||
*/
|
||||
interface TimedRequest extends Request {
|
||||
startTime?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate the response time in milliseconds
|
||||
* @param startTime - Start time in milliseconds
|
||||
* @returns Response time in milliseconds
|
||||
*/
|
||||
const getResponseTime = (startTime: number): number => {
|
||||
const NS_PER_SEC = 1e9; // nanoseconds per second
|
||||
const NS_TO_MS = 1e6; // nanoseconds to milliseconds
|
||||
const diff = process.hrtime();
|
||||
return (diff[0] * NS_PER_SEC + diff[1]) / NS_TO_MS - startTime;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get client IP address from request
|
||||
* @param req - Express request object
|
||||
* @returns Client IP address
|
||||
*/
|
||||
const getClientIp = (req: Request): string => {
|
||||
return (
|
||||
(req.headers['x-forwarded-for'] as string)?.split(',')[0] ||
|
||||
req.socket.remoteAddress ||
|
||||
'unknown'
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Format log message for request
|
||||
* @param req - Express request object
|
||||
* @returns Formatted log message
|
||||
*/
|
||||
const formatRequestLog = (req: TimedRequest): string => {
|
||||
return `${req.method} ${req.originalUrl} - IP: ${getClientIp(req)}`;
|
||||
};
|
||||
|
||||
/**
|
||||
* Format log message for response
|
||||
* @param req - Express request object
|
||||
* @param res - Express response object
|
||||
* @param time - Response time in milliseconds
|
||||
* @returns Formatted log message
|
||||
*/
|
||||
const formatResponseLog = (req: TimedRequest, res: Response, time: number): string => {
|
||||
return `${req.method} ${req.originalUrl} - ${res.statusCode} - ${time.toFixed(2)}ms`;
|
||||
};
|
||||
|
||||
/**
|
||||
* Request logging middleware
|
||||
* Logs information about incoming requests and their responses
|
||||
*/
|
||||
export const requestLogger = (req: TimedRequest, res: Response, next: NextFunction): void => {
|
||||
if (!APP_CONFIG.LOGGING.LOG_REQUESTS) {
|
||||
next();
|
||||
return;
|
||||
}
|
||||
|
||||
// Record start time
|
||||
req.startTime = Date.now();
|
||||
|
||||
// Log request
|
||||
logger.http(formatRequestLog(req));
|
||||
|
||||
// Log response
|
||||
res.on('finish', () => {
|
||||
const responseTime = Date.now() - (req.startTime || 0);
|
||||
const logLevel = res.statusCode >= 400 ? 'warn' : 'http';
|
||||
logger[logLevel](formatResponseLog(req, res, responseTime));
|
||||
});
|
||||
|
||||
next();
|
||||
};
|
||||
|
||||
/**
|
||||
* Error logging middleware
|
||||
* Logs errors that occur during request processing
|
||||
*/
|
||||
export const errorLogger = (err: Error, req: Request, res: Response, next: NextFunction): void => {
|
||||
logger.error(`Error processing ${req.method} ${req.originalUrl}: ${err.message}`, {
|
||||
error: err.stack,
|
||||
method: req.method,
|
||||
url: req.originalUrl,
|
||||
body: req.body,
|
||||
query: req.query,
|
||||
ip: getClientIp(req)
|
||||
});
|
||||
next(err);
|
||||
};
|
||||
@@ -1,19 +1,49 @@
|
||||
/**
|
||||
* MCP Routes Module
|
||||
*
|
||||
* This module provides routes for accessing and executing MCP functionality.
|
||||
* It includes endpoints for retrieving the MCP schema and executing MCP tools.
|
||||
*
|
||||
* @module mcp-routes
|
||||
*/
|
||||
|
||||
import { Router } from 'express';
|
||||
import { MCP_SCHEMA } from '../mcp/schema.js';
|
||||
import { APP_CONFIG } from '../config/app.config.js';
|
||||
import { Tool } from '../types/index.js';
|
||||
|
||||
/**
|
||||
* Create router instance for MCP routes
|
||||
*/
|
||||
const router = Router();
|
||||
|
||||
// Array to track tools
|
||||
/**
|
||||
* Array to track registered tools
|
||||
* Tools are added to this array when they are registered with the MCP
|
||||
*/
|
||||
const tools: Tool[] = [];
|
||||
|
||||
// MCP schema endpoint - no auth required as it's just the schema
|
||||
/**
|
||||
* GET /mcp
|
||||
* Returns the MCP schema without requiring authentication
|
||||
* This endpoint allows clients to discover available tools and their parameters
|
||||
*/
|
||||
router.get('/', (_req, res) => {
|
||||
res.json(MCP_SCHEMA);
|
||||
});
|
||||
|
||||
// MCP execute endpoint - requires authentication
|
||||
/**
|
||||
* POST /mcp/execute
|
||||
* Execute a tool with the provided parameters
|
||||
* Requires authentication via Bearer token
|
||||
*
|
||||
* @param {Object} req.body.tool - Name of the tool to execute
|
||||
* @param {Object} req.body.parameters - Parameters for the tool
|
||||
* @returns {Object} Tool execution result
|
||||
* @throws {401} If authentication fails
|
||||
* @throws {404} If tool is not found
|
||||
* @throws {500} If execution fails
|
||||
*/
|
||||
router.post('/execute', async (req, res) => {
|
||||
try {
|
||||
// Get token from Authorization header
|
||||
@@ -48,4 +78,8 @@ router.post('/execute', async (req, res) => {
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Export the configured router
|
||||
* This will be mounted under /api/mcp in the main application
|
||||
*/
|
||||
export { router as mcpRoutes };
|
||||
112
src/utils/logger.ts
Normal file
112
src/utils/logger.ts
Normal file
@@ -0,0 +1,112 @@
|
||||
/**
|
||||
* Logging Module
|
||||
*
|
||||
* This module provides logging functionality with rotation support.
|
||||
* It uses winston for logging and winston-daily-rotate-file for rotation.
|
||||
*
|
||||
* @module logger
|
||||
*/
|
||||
|
||||
import winston from 'winston';
|
||||
import 'winston-daily-rotate-file';
|
||||
import { APP_CONFIG } from '../config/app.config.js';
|
||||
|
||||
/**
|
||||
* Log levels configuration
|
||||
* Defines the severity levels for logging
|
||||
*/
|
||||
const levels = {
|
||||
error: 0,
|
||||
warn: 1,
|
||||
info: 2,
|
||||
http: 3,
|
||||
debug: 4,
|
||||
};
|
||||
|
||||
/**
|
||||
* Log level colors configuration
|
||||
* Defines colors for different log levels
|
||||
*/
|
||||
const colors = {
|
||||
error: 'red',
|
||||
warn: 'yellow',
|
||||
info: 'green',
|
||||
http: 'magenta',
|
||||
debug: 'white',
|
||||
};
|
||||
|
||||
/**
|
||||
* Add colors to winston
|
||||
*/
|
||||
winston.addColors(colors);
|
||||
|
||||
/**
|
||||
* Log format configuration
|
||||
* Defines how log messages are formatted
|
||||
*/
|
||||
const format = winston.format.combine(
|
||||
winston.format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss:ms' }),
|
||||
winston.format.colorize({ all: true }),
|
||||
winston.format.printf(
|
||||
(info) => `${info.timestamp} ${info.level}: ${info.message}`,
|
||||
),
|
||||
);
|
||||
|
||||
/**
|
||||
* Transport for daily rotating file
|
||||
* Configures how logs are rotated and stored
|
||||
*/
|
||||
const dailyRotateFileTransport = new winston.transports.DailyRotateFile({
|
||||
filename: 'logs/%DATE%.log',
|
||||
datePattern: 'YYYY-MM-DD',
|
||||
zippedArchive: true,
|
||||
maxSize: '20m',
|
||||
maxFiles: '14d',
|
||||
format: winston.format.combine(
|
||||
winston.format.uncolorize(),
|
||||
winston.format.timestamp(),
|
||||
winston.format.json()
|
||||
)
|
||||
});
|
||||
|
||||
/**
|
||||
* Transport for error logs
|
||||
* Stores error logs in a separate file
|
||||
*/
|
||||
const errorFileTransport = new winston.transports.DailyRotateFile({
|
||||
filename: 'logs/error-%DATE%.log',
|
||||
datePattern: 'YYYY-MM-DD',
|
||||
level: 'error',
|
||||
zippedArchive: true,
|
||||
maxSize: '20m',
|
||||
maxFiles: '14d',
|
||||
format: winston.format.combine(
|
||||
winston.format.uncolorize(),
|
||||
winston.format.timestamp(),
|
||||
winston.format.json()
|
||||
)
|
||||
});
|
||||
|
||||
/**
|
||||
* Create the logger instance
|
||||
*/
|
||||
const logger = winston.createLogger({
|
||||
level: APP_CONFIG.NODE_ENV === 'development' ? 'debug' : 'info',
|
||||
levels,
|
||||
format,
|
||||
transports: [
|
||||
new winston.transports.Console({
|
||||
format: winston.format.combine(
|
||||
winston.format.colorize(),
|
||||
winston.format.simple()
|
||||
)
|
||||
}),
|
||||
dailyRotateFileTransport,
|
||||
errorFileTransport
|
||||
],
|
||||
});
|
||||
|
||||
/**
|
||||
* Export the logger instance
|
||||
*/
|
||||
export { logger };
|
||||
Reference in New Issue
Block a user