backend logging

This commit is contained in:
jackiettran
2025-09-22 18:38:51 -04:00
parent 6199609a4d
commit 3e76769a3e
17 changed files with 1225 additions and 110 deletions

137
backend/utils/logger.js Normal file
View File

@@ -0,0 +1,137 @@
const winston = require('winston');
const DailyRotateFile = require('winston-daily-rotate-file');
// Define log levels and colors
const logLevels = {
error: 0,
warn: 1,
info: 2,
http: 3,
debug: 4,
};
winston.addColors({
error: 'red',
warn: 'yellow',
info: 'green',
http: 'magenta',
debug: 'white',
});
// Determine log level based on environment
const level = () => {
const env = process.env.NODE_ENV || 'dev';
const isDevelopment = env === 'dev' || env === 'development';
return isDevelopment ? 'debug' : process.env.LOG_LEVEL || 'info';
};
// Define log format
const logFormat = winston.format.combine(
winston.format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss:ms' }),
winston.format.colorize({ all: true }),
winston.format.printf((info) => {
if (info.stack) {
return `${info.timestamp} ${info.level}: ${info.message}\n${info.stack}`;
}
return `${info.timestamp} ${info.level}: ${info.message}`;
}),
);
// Define JSON format for file logging
const jsonFormat = winston.format.combine(
winston.format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss:ms' }),
winston.format.errors({ stack: true }),
winston.format.json()
);
// Create transports array
const transports = [
// Console transport for development
new winston.transports.Console({
level: level(),
format: logFormat,
}),
// Daily rotate file for all logs
new DailyRotateFile({
filename: 'logs/application-%DATE%.log',
datePattern: 'YYYY-MM-DD',
maxSize: '20m',
maxFiles: '14d',
level: 'info',
format: jsonFormat,
}),
// Daily rotate file for error logs only
new DailyRotateFile({
filename: 'logs/error-%DATE%.log',
datePattern: 'YYYY-MM-DD',
maxSize: '20m',
maxFiles: '14d',
level: 'error',
format: jsonFormat,
}),
];
// Create the logger
const logger = winston.createLogger({
level: level(),
levels: logLevels,
format: jsonFormat,
transports,
exceptionHandlers: [
new winston.transports.File({ filename: 'logs/exceptions.log' })
],
rejectionHandlers: [
new winston.transports.File({ filename: 'logs/rejections.log' })
],
});
// Create a stream object for Morgan
logger.stream = {
write: (message) => {
// Remove trailing newline from Morgan and log as http level
logger.http(message.trim());
},
};
// Add request ID correlation function
logger.withRequestId = (requestId) => {
return {
error: (message, meta = {}) => logger.error(message, { ...meta, requestId }),
warn: (message, meta = {}) => logger.warn(message, { ...meta, requestId }),
info: (message, meta = {}) => logger.info(message, { ...meta, requestId }),
http: (message, meta = {}) => logger.http(message, { ...meta, requestId }),
debug: (message, meta = {}) => logger.debug(message, { ...meta, requestId }),
};
};
// Add sanitization helper for sensitive data
logger.sanitize = (data) => {
if (typeof data !== 'object' || data === null) {
return data;
}
const sensitiveFields = [
'password', 'token', 'secret', 'key', 'authorization', 'cookie',
'ssn', 'credit_card', 'cvv', 'pin', 'account_number'
];
const sanitized = JSON.parse(JSON.stringify(data));
const sanitizeObject = (obj) => {
for (const [key, value] of Object.entries(obj)) {
const lowerKey = key.toLowerCase();
if (sensitiveFields.some(field => lowerKey.includes(field))) {
obj[key] = '[REDACTED]';
} else if (typeof value === 'object' && value !== null) {
sanitizeObject(value);
}
}
};
sanitizeObject(sanitized);
return sanitized;
};
module.exports = logger;