|
| 1 | +const morgan = require('morgan'); |
| 2 | +const fs = require('fs'); |
| 3 | +const path = require('path'); |
| 4 | + |
| 5 | +// Create logs directory if it doesn't exist |
| 6 | +const logsDir = path.join(__dirname, '..', 'logs'); |
| 7 | +if (!fs.existsSync(logsDir)) { |
| 8 | + fs.mkdirSync(logsDir, { recursive: true }); |
| 9 | + console.log('Created logs directory:', logsDir); |
| 10 | +} |
| 11 | + |
| 12 | +// Custom format for logging |
| 13 | +morgan.token('reqId', (req) => req.reqId || 'unknown'); |
| 14 | +morgan.token('userId', (req) => req.user?.id || 'anonymous'); |
| 15 | + |
| 16 | +// Create a simple log format |
| 17 | +const logFormat = ':remote-addr - :remote-user [:date[clf]] ":method :url HTTP/:http-version" :status :res[content-length] ":referrer" ":user-agent" reqId=:reqId userId=:userId'; |
| 18 | + |
| 19 | +// Create write stream for combined logs |
| 20 | +const accessLogStream = fs.createWriteStream( |
| 21 | + path.join(logsDir, 'access.log'), |
| 22 | + { flags: 'a' } |
| 23 | +); |
| 24 | + |
| 25 | +// Create write stream for error logs |
| 26 | +const errorLogStream = fs.createWriteStream( |
| 27 | + path.join(logsDir, 'error.log'), |
| 28 | + { flags: 'a' } |
| 29 | +); |
| 30 | + |
| 31 | +// Middleware to add request ID |
| 32 | +const addRequestId = (req, res, next) => { |
| 33 | + req.reqId = require('nanoid').nanoid(8); |
| 34 | + res.setHeader('X-Request-ID', req.reqId); |
| 35 | + next(); |
| 36 | +}; |
| 37 | + |
| 38 | +// Standard request logger |
| 39 | +const requestLogger = morgan(logFormat, { |
| 40 | + stream: accessLogStream, |
| 41 | + skip: (req, res) => req.url === '/health' // Skip health checks |
| 42 | +}); |
| 43 | + |
| 44 | +// Error logger (only logs 4xx and 5xx responses) |
| 45 | +const errorLogger = morgan(logFormat, { |
| 46 | + stream: errorLogStream, |
| 47 | + skip: (req, res) => res.statusCode < 400 |
| 48 | +}); |
| 49 | + |
| 50 | +// Security logger for suspicious activity |
| 51 | +const securityLogger = (req, res, next) => { |
| 52 | + // Log suspicious patterns |
| 53 | + const suspiciousPatterns = [ |
| 54 | + /script.*alert/i, |
| 55 | + /union.*select/i, |
| 56 | + /drop.*table/i, |
| 57 | + /<script/i, |
| 58 | + /javascript:/i |
| 59 | + ]; |
| 60 | + |
| 61 | + const url = req.url.toLowerCase(); |
| 62 | + const userAgent = (req.get('User-Agent') || '').toLowerCase(); |
| 63 | + |
| 64 | + for (const pattern of suspiciousPatterns) { |
| 65 | + if (pattern.test(url) || pattern.test(userAgent)) { |
| 66 | + const logEntry = { |
| 67 | + timestamp: new Date().toISOString(), |
| 68 | + type: 'SECURITY_ALERT', |
| 69 | + ip: req.ip || req.connection.remoteAddress, |
| 70 | + method: req.method, |
| 71 | + url: req.url, |
| 72 | + userAgent: req.get('User-Agent'), |
| 73 | + reqId: req.reqId, |
| 74 | + pattern: pattern.source |
| 75 | + }; |
| 76 | + |
| 77 | + fs.appendFileSync( |
| 78 | + path.join(logsDir, 'security.log'), |
| 79 | + JSON.stringify(logEntry) + '\n' |
| 80 | + ); |
| 81 | + } |
| 82 | + } |
| 83 | + |
| 84 | + next(); |
| 85 | +}; |
| 86 | + |
| 87 | +module.exports = { |
| 88 | + addRequestId, |
| 89 | + requestLogger, |
| 90 | + errorLogger, |
| 91 | + securityLogger |
| 92 | +}; |
0 commit comments