admin e5c32ad25a Add health check utility, logger, metrics, backup, and SMTP toggle scripts
- Implemented a comprehensive health check utility to monitor system dependencies including NocoDB, SMTP, Represent API, disk space, and memory usage.
- Created a logger utility using Winston for structured logging with daily rotation and various log levels.
- Developed a metrics utility using Prometheus client to track application performance metrics such as email sends, HTTP requests, and user activity.
- Added a backup script for automated backups of NocoDB data, uploaded files, and environment configurations with optional S3 support.
- Introduced a toggle script to switch between development (MailHog) and production (ProtonMail) SMTP configurations.
2025-10-23 11:33:00 -06:00

191 lines
4.2 KiB
JavaScript

const winston = require('winston');
const DailyRotateFile = require('winston-daily-rotate-file');
const path = require('path');
// Define log levels
const levels = {
error: 0,
warn: 1,
info: 2,
http: 3,
debug: 4,
};
// Define colors for each level
const colors = {
error: 'red',
warn: 'yellow',
info: 'green',
http: 'magenta',
debug: 'white',
};
// Tell winston about our colors
winston.addColors(colors);
// Define which level to log based on environment
const level = () => {
const env = process.env.NODE_ENV || 'development';
const isDevelopment = env === 'development';
return isDevelopment ? 'debug' : 'info';
};
// Define log format
const logFormat = winston.format.combine(
winston.format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss:ms' }),
winston.format.errors({ stack: true }),
winston.format.splat(),
winston.format.json()
);
// Console format for development (pretty print)
const consoleFormat = winston.format.combine(
winston.format.colorize({ all: true }),
winston.format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }),
winston.format.printf(
(info) => `${info.timestamp} ${info.level}: ${info.message}${info.stack ? '\n' + info.stack : ''}`
)
);
// Define transports
const transports = [
// Console output
new winston.transports.Console({
format: consoleFormat,
handleExceptions: true,
}),
// Error logs - rotate daily
new DailyRotateFile({
filename: path.join(__dirname, '../../logs/error-%DATE%.log'),
datePattern: 'YYYY-MM-DD',
level: 'error',
format: logFormat,
maxSize: '20m',
maxFiles: '14d',
handleExceptions: true,
}),
// Combined logs - rotate daily
new DailyRotateFile({
filename: path.join(__dirname, '../../logs/combined-%DATE%.log'),
datePattern: 'YYYY-MM-DD',
format: logFormat,
maxSize: '20m',
maxFiles: '14d',
}),
// HTTP logs - rotate daily
new DailyRotateFile({
filename: path.join(__dirname, '../../logs/http-%DATE%.log'),
datePattern: 'YYYY-MM-DD',
level: 'http',
format: logFormat,
maxSize: '20m',
maxFiles: '7d',
}),
];
// Create the logger
const logger = winston.createLogger({
level: level(),
levels,
format: logFormat,
transports,
exitOnError: false,
});
// Create a stream object for Morgan HTTP logger
logger.stream = {
write: (message) => {
logger.http(message.trim());
},
};
// Helper methods for common logging patterns
logger.logRequest = (req, res, duration) => {
const logData = {
method: req.method,
url: req.url,
status: res.statusCode,
duration: `${duration}ms`,
ip: req.ip,
userAgent: req.get('user-agent'),
};
if (res.statusCode >= 500) {
logger.error('Request failed', logData);
} else if (res.statusCode >= 400) {
logger.warn('Request error', logData);
} else {
logger.http('Request completed', logData);
}
};
logger.logEmailSent = (recipient, campaign, status) => {
logger.info('Email sent', {
event: 'email_sent',
recipient,
campaign,
status,
timestamp: new Date().toISOString(),
});
};
logger.logEmailFailed = (recipient, campaign, error) => {
logger.error('Email failed', {
event: 'email_failed',
recipient,
campaign,
error: error.message,
stack: error.stack,
timestamp: new Date().toISOString(),
});
};
logger.logAuth = (action, username, success, ip) => {
const level = success ? 'info' : 'warn';
logger[level]('Auth action', {
event: 'auth',
action,
username,
success,
ip,
timestamp: new Date().toISOString(),
});
};
logger.logCampaignAction = (action, campaignId, userId, ip) => {
logger.info('Campaign action', {
event: 'campaign_action',
action,
campaignId,
userId,
ip,
timestamp: new Date().toISOString(),
});
};
logger.logRateLimitHit = (endpoint, ip, limit) => {
logger.warn('Rate limit hit', {
event: 'rate_limit',
endpoint,
ip,
limit,
timestamp: new Date().toISOString(),
});
};
logger.logHealthCheck = (service, status, details = {}) => {
const level = status === 'healthy' ? 'info' : 'error';
logger[level]('Health check', {
event: 'health_check',
service,
status,
...details,
timestamp: new Date().toISOString(),
});
};
module.exports = logger;