diff --git a/README.md b/README.md index 9e554e0..27cd678 100644 --- a/README.md +++ b/README.md @@ -269,7 +269,7 @@ pnpm test --- -linked PR 5,4 +linked PR 5,4,6 ## 📄 License diff --git a/src/app.ts b/src/app.ts index 4b10ccf..294ad61 100644 --- a/src/app.ts +++ b/src/app.ts @@ -4,9 +4,8 @@ import helmet from 'helmet'; import compression from 'compression'; import rateLimit from 'express-rate-limit'; import { connectDatabase } from './config/database'; -import logger from './config/logger'; -import { corsOptionsDelegate, helmetOptions } from './config/security'; import errorHandler from './middleware/errorHandler'; +import requestLogger from './middleware/requestLogger'; import routes from './routes'; import env from './config/env'; @@ -40,11 +39,8 @@ app.use(express.urlencoded({ extended: true, limit: '10mb' })); // Compression app.use(compression()); -// Logging middleware -app.use((req, res, next) => { - logger.info(`${req.method} ${req.url}`); - next(); -}); +// Request logging middleware +app.use(requestLogger); // Health check endpoint app.get('/health', (req, res) => { diff --git a/src/config/env.ts b/src/config/env.ts index c9e13fe..f45e519 100644 --- a/src/config/env.ts +++ b/src/config/env.ts @@ -1,6 +1,5 @@ import { z } from 'zod'; import dotenv from 'dotenv'; -import logger from './logger'; dotenv.config(); @@ -23,12 +22,12 @@ try { env = envSchema.parse(process.env); } catch (error) { if (error instanceof z.ZodError) { - logger.error('❌ Invalid environment variables:'); - error.issues.forEach((err) => { - logger.error(` - ${err.path.join('.')}: ${err.message}`); + console.error('❌ Invalid environment variables:'); + error.issues.forEach((issue) => { + console.error(` - ${issue.path.join('.')}: ${issue.message}`); }); } else { - logger.error('❌ Failed to parse environment variables:', error); + console.error('❌ Failed to parse environment variables:', error); } process.exit(1); } diff --git a/src/config/logger.ts b/src/config/logger.ts index f4d3070..4f078f8 100644 --- a/src/config/logger.ts +++ b/src/config/logger.ts @@ -1,4 +1,5 @@ import winston from 'winston'; +import env from './env'; const levels = { error: 0, @@ -8,12 +9,6 @@ const levels = { debug: 4, }; -const level = (): string => { - const env = process.env.NODE_ENV || 'development'; - const isDevelopment = env === 'development'; - return process.env.LOG_LEVEL || (isDevelopment ? 'debug' : 'warn'); -}; - const colors = { error: 'red', warn: 'yellow', @@ -24,25 +19,33 @@ const colors = { winston.addColors(colors); -const format = winston.format.combine( +const devFormat = 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}`), ); +const prodFormat = winston.format.combine(winston.format.timestamp(), winston.format.json()); + const transports = [ - new winston.transports.Console(), + new winston.transports.Console({ + format: env.NODE_ENV === 'development' ? devFormat : prodFormat, + }), new winston.transports.File({ filename: 'logs/error.log', level: 'error', + format: prodFormat, + }), + new winston.transports.File({ + filename: 'logs/all.log', + format: prodFormat, }), - new winston.transports.File({ filename: 'logs/all.log' }), ]; const logger = winston.createLogger({ - level: level(), + level: env.LOG_LEVEL, levels, - format, + format: env.NODE_ENV === 'development' ? devFormat : prodFormat, transports, }); diff --git a/src/middleware/requestLogger.ts b/src/middleware/requestLogger.ts new file mode 100644 index 0000000..9eb0e8c --- /dev/null +++ b/src/middleware/requestLogger.ts @@ -0,0 +1,29 @@ +import { Request, Response, NextFunction } from 'express'; +import logger from '../config/logger'; + +const requestLogger = (req: Request, res: Response, next: NextFunction): void => { + const start = Date.now(); + + res.on('finish', () => { + const duration = Date.now() - start; + const logMessage = `${req.method} ${req.originalUrl} - ${res.statusCode} (${duration}ms)`; + const logData = { + method: req.method, + url: req.originalUrl, + statusCode: res.statusCode, + duration, + ip: req.ip, + userAgent: req.get('user-agent'), + }; + + if (res.statusCode >= 400) { + logger.warn(logMessage, logData); + } else { + logger.info(logMessage, logData); + } + }); + + next(); +}; + +export default requestLogger;