@ideal-photography/shared
Version:
Shared MongoDB and utility logic for Ideal Photography PWAs: users, products, services, bookings, orders/cart, galleries, reviews, notifications, campaigns, settings, audit logs, minimart items/orders, and push notification subscriptions.
236 lines (203 loc) • 7.2 kB
JavaScript
import fs from 'fs';
import path from 'path';
/**
* Enhanced logging system for notification operations
* Provides structured logging with different levels and file rotation
*/
class NotificationLogger {
constructor() {
this.logDir = path.join(process.cwd(), 'logs', 'notifications');
this.ensureLogDirectory();
}
ensureLogDirectory() {
if (!fs.existsSync(this.logDir)) {
fs.mkdirSync(this.logDir, { recursive: true });
}
}
getLogFileName(level) {
const date = new Date().toISOString().split('T')[0];
return path.join(this.logDir, `notification-${level}-${date}.log`);
}
formatLogEntry(level, message, meta = {}) {
const timestamp = new Date().toISOString();
const logEntry = {
timestamp,
level,
message,
...meta
};
return JSON.stringify(logEntry) + '\n';
}
writeLog(level, message, meta = {}) {
const logFile = this.getLogFileName(level);
const logEntry = this.formatLogEntry(level, message, meta);
fs.appendFileSync(logFile, logEntry);
// Also log to console in development
if (process.env.NODE_ENV === 'development') {
console.log(`[${level.toUpperCase()}] ${message}`, meta);
}
}
info(message, meta = {}) {
this.writeLog('info', message, meta);
}
warn(message, meta = {}) {
this.writeLog('warn', message, meta);
}
error(message, meta = {}) {
this.writeLog('error', message, meta);
}
debug(message, meta = {}) {
if (process.env.NODE_ENV === 'development') {
this.writeLog('debug', message, meta);
}
}
// Specific notification logging methods
logNotificationCreated(notification, adminId) {
this.info('Notification created', {
notificationId: notification._id,
title: notification.title,
type: notification.type,
priority: notification.priority,
recipients: notification.recipients,
adminId,
timestamp: new Date().toISOString()
});
}
logNotificationSent(notification, result) {
this.info('Notification sent', {
notificationId: notification._id,
title: notification.title,
type: notification.type,
priority: notification.priority,
recipients: notification.recipients,
pushEnabled: notification.channels.push,
result,
timestamp: new Date().toISOString()
});
}
logNotificationFailed(notification, error) {
this.error('Notification failed to send', {
notificationId: notification._id,
title: notification.title,
type: notification.type,
priority: notification.priority,
recipients: notification.recipients,
error: error.message,
stack: error.stack,
timestamp: new Date().toISOString()
});
}
logPushNotificationSent(userId, notification, result) {
this.info('Push notification sent', {
userId,
notificationId: notification._id,
title: notification.title,
type: notification.type,
priority: notification.priority,
result,
timestamp: new Date().toISOString()
});
}
logPushNotificationFailed(userId, notification, error) {
this.error('Push notification failed', {
userId,
notificationId: notification._id,
title: notification.title,
type: notification.type,
priority: notification.priority,
error: error.message,
stack: error.stack,
timestamp: new Date().toISOString()
});
}
logUserAction(userId, action, notificationId, meta = {}) {
this.info('User notification action', {
userId,
action,
notificationId,
...meta,
timestamp: new Date().toISOString()
});
}
logAdminAction(adminId, action, notificationId, meta = {}) {
this.info('Admin notification action', {
adminId,
action,
notificationId,
...meta,
timestamp: new Date().toISOString()
});
}
logSystemEvent(event, meta = {}) {
this.info('System event', {
event,
...meta,
timestamp: new Date().toISOString()
});
}
logPerformance(operation, duration, meta = {}) {
this.info('Performance metric', {
operation,
duration,
...meta,
timestamp: new Date().toISOString()
});
}
// Error tracking and reporting
logError(error, context = {}) {
this.error('Notification system error', {
message: error.message,
stack: error.stack,
name: error.name,
context,
timestamp: new Date().toISOString()
});
}
// Clean up old log files (run this periodically)
cleanupOldLogs(daysToKeep = 30) {
const cutoffDate = new Date();
cutoffDate.setDate(cutoffDate.getDate() - daysToKeep);
try {
const files = fs.readdirSync(this.logDir);
files.forEach(file => {
const filePath = path.join(this.logDir, file);
const stats = fs.statSync(filePath);
if (stats.mtime < cutoffDate) {
fs.unlinkSync(filePath);
this.info('Old log file deleted', { file });
}
});
} catch (error) {
this.error('Failed to cleanup old logs', { error: error.message });
}
}
// Get log statistics
getLogStats() {
try {
const files = fs.readdirSync(this.logDir);
const stats = {
totalFiles: files.length,
totalSize: 0,
filesByLevel: {}
};
files.forEach(file => {
const filePath = path.join(this.logDir, file);
const fileStats = fs.statSync(filePath);
stats.totalSize += fileStats.size;
// Extract level from filename
const levelMatch = file.match(/notification-(\w+)-/);
if (levelMatch) {
const level = levelMatch[1];
stats.filesByLevel[level] = (stats.filesByLevel[level] || 0) + 1;
}
});
return stats;
} catch (error) {
this.error('Failed to get log stats', { error: error.message });
return null;
}
}
}
// Create singleton instance
const notificationLogger = new NotificationLogger();
export default notificationLogger;