Add logging
This commit is contained in:
parent
2a6153002c
commit
8814658142
11 changed files with 826 additions and 55 deletions
|
|
@ -23,11 +23,6 @@ export const userBasicRoute = "/user";
|
|||
*/
|
||||
const router = Router();
|
||||
|
||||
router.use((req, res, next) => {
|
||||
console.log(`Incoming request: ${req.method} ${req.path}`);
|
||||
next();
|
||||
});
|
||||
|
||||
// Inject repo + mapper here
|
||||
const handler = new UserHandler(new UserRepository(), new UserDtoEntityMapper());
|
||||
|
||||
|
|
|
|||
120
src/index.ts
120
src/index.ts
|
|
@ -1,69 +1,93 @@
|
|||
import "reflect-metadata";
|
||||
import express, { NextFunction, Request, Response } from "express";
|
||||
import express from "express";
|
||||
import dotenv from "dotenv";
|
||||
import { AppDataSource } from "./data-source.js";
|
||||
import { authentication } from "./middleware/authenticationMiddleware.js";
|
||||
import { requestLoggerMiddleware } from "./middleware/requestLoggerMiddleware.js";
|
||||
import { errorLoggerMiddleware } from "./middleware/errorLoggerMiddleware.js";
|
||||
import { corsHeaders } from "./middleware/corsMiddleware.js";
|
||||
import { logger } from "./utils/logger.js";
|
||||
import { HttpStatusCode } from "./apiHelpers/HttpStatusCodes.js";
|
||||
import authRoutes, { authBasicRoute } from "./endpoints/AuthPoint.js";
|
||||
import userRoutes, {userBasicRoute} from "./endpoints/UserPoint.js";
|
||||
import userRoutes, { userBasicRoute } from "./endpoints/UserPoint.js";
|
||||
import compactRecipeRoutes from "./endpoints/CompactRecipePoint.js";
|
||||
import recipeRoutes from "./endpoints/RecipePoint.js";
|
||||
import { errorHandler } from "./middleware/errorHandler.js";
|
||||
import { authentication } from "./middleware/authenticationMiddleware.js";
|
||||
import { corsHeaders } from "./middleware/corsMiddleware.js";
|
||||
|
||||
dotenv.config();
|
||||
|
||||
const app = express();
|
||||
app.use(express.json());
|
||||
app.use(errorHandler);
|
||||
|
||||
async function startServer() {
|
||||
try {
|
||||
console.log("starting server")
|
||||
// Initialize database
|
||||
await AppDataSource.initialize();
|
||||
console.log("Data Source initialized");
|
||||
try {
|
||||
logger.info("Starting server...");
|
||||
|
||||
// Run pending migrations
|
||||
console.log(AppDataSource.migrations);
|
||||
await AppDataSource.runMigrations();
|
||||
console.log("Migrations executed");
|
||||
// Initialize database
|
||||
await AppDataSource.initialize();
|
||||
logger.info("Database connection established");
|
||||
|
||||
// Enable CORS before anything else
|
||||
app.use(corsHeaders);
|
||||
// Run pending migrations
|
||||
if (AppDataSource.migrations.length > 0) {
|
||||
await AppDataSource.runMigrations();
|
||||
logger.info("Migrations executed");
|
||||
}
|
||||
|
||||
// Activate Authentication
|
||||
app.use(authentication);
|
||||
|
||||
// Setup routes
|
||||
app.use(authBasicRoute, authRoutes);
|
||||
app.use(userBasicRoute, userRoutes);
|
||||
app.use("/recipe", recipeRoutes);
|
||||
app.use("/compact-recipe", compactRecipeRoutes);
|
||||
// Body parser middleware
|
||||
app.use(express.json());
|
||||
app.use(express.urlencoded({ extended: true }));
|
||||
|
||||
// Error handling for all rest-calls
|
||||
// must come last!
|
||||
app.use(errorHandler);
|
||||
// Enable CORS
|
||||
app.use(corsHeaders);
|
||||
|
||||
console.log("all routes added")
|
||||
// catch all other routes
|
||||
app.get(/(.*)/, (req: Request, res: Response, next: NextFunction) => {
|
||||
console.log("unknown route", req.url);
|
||||
res.status(400).json({ message: "Bad Request - unknown route" });
|
||||
});
|
||||
console.log("Routes set up")
|
||||
// Request logging - logs all incoming requests
|
||||
app.use(requestLoggerMiddleware);
|
||||
|
||||
// Start listening
|
||||
const PORT = Number(process.env.PORT) || 4000;
|
||||
const HOST = process.env.HOST || "localhost";
|
||||
// Authentication
|
||||
app.use(authentication);
|
||||
|
||||
app.listen(PORT, HOST, () => {
|
||||
console.log(`✅ Server running on http://${HOST}:${PORT}`);
|
||||
});
|
||||
} catch (err) {
|
||||
console.error("❌ Error during Data Source initialization:", err);
|
||||
process.exit(1);
|
||||
}
|
||||
// Setup routes
|
||||
app.use(authBasicRoute, authRoutes);
|
||||
app.use(userBasicRoute, userRoutes);
|
||||
app.use("/recipe", recipeRoutes);
|
||||
app.use("/compact-recipe", compactRecipeRoutes);
|
||||
|
||||
// Catch-all for unknown routes
|
||||
app.use((req, res) => {
|
||||
logger.warn(`Unknown route accessed: ${req.method} ${req.originalUrl}`);
|
||||
res.status(HttpStatusCode.NOT_FOUND).json({
|
||||
statusCode: HttpStatusCode.NOT_FOUND,
|
||||
error: "Route not found"
|
||||
});
|
||||
});
|
||||
|
||||
// Error logging and handling - MUST be last
|
||||
app.use(errorLoggerMiddleware);
|
||||
|
||||
// Start listening
|
||||
const PORT = Number(process.env.PORT) || 4000;
|
||||
const HOST = process.env.HOST || "localhost";
|
||||
|
||||
app.listen(PORT, HOST, () => {
|
||||
logger.info(`Server running on http://${HOST}:${PORT}`);
|
||||
logger.info(`Environment: ${process.env.NODE_ENV || "development"}`);
|
||||
});
|
||||
} catch (err) {
|
||||
logger.error("Error during server initialization:", err);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
// Call the async start function
|
||||
// Handle uncaught exceptions
|
||||
process.on("uncaughtException", (error) => {
|
||||
logger.error("Uncaught Exception:", { error });
|
||||
process.exit(1);
|
||||
});
|
||||
|
||||
// Handle unhandled promise rejections
|
||||
process.on("unhandledRejection", (reason, promise) => {
|
||||
logger.error("Unhandled Rejection:", { promise, reason });
|
||||
process.exit(1);
|
||||
});
|
||||
|
||||
// Start the server
|
||||
startServer();
|
||||
|
||||
export default app;
|
||||
50
src/middleware/errorLoggerMiddleware.ts
Normal file
50
src/middleware/errorLoggerMiddleware.ts
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
import { Request, Response, NextFunction } from "express";
|
||||
import { HttpError } from "../errors/httpErrors.js";
|
||||
import { logger } from "../utils/logger.js";
|
||||
import { HttpStatusCode } from "../apiHelpers/HttpStatusCodes.js";
|
||||
|
||||
/**
|
||||
* Error logging and handling middleware
|
||||
* Must be placed AFTER all routes
|
||||
* Logs all errors and sends appropriate responses
|
||||
*/
|
||||
export const errorLoggerMiddleware = (
|
||||
err: Error,
|
||||
req: Request,
|
||||
res: Response,
|
||||
next: NextFunction
|
||||
) => {
|
||||
// Determine status code
|
||||
const statusCode = err instanceof HttpError
|
||||
? err.statusCode
|
||||
: HttpStatusCode.INTERNAL_SERVER_ERROR;
|
||||
|
||||
// Log the error with full details
|
||||
const userId = req.currentUser?.id || "anonymous";
|
||||
const fullPath = req.baseUrl + req.path;
|
||||
const errorDetails = {
|
||||
method: req.method,
|
||||
path: fullPath,
|
||||
userId,
|
||||
statusCode,
|
||||
message: err.message,
|
||||
name: err.name,
|
||||
};
|
||||
|
||||
if (statusCode >= HttpStatusCode.INTERNAL_SERVER_ERROR) {
|
||||
// Server errors - log with stack trace
|
||||
logger.error(`Server Error: ${JSON.stringify(errorDetails)}`, { stack: err.stack });
|
||||
} else if (statusCode >= HttpStatusCode.BAD_REQUEST) {
|
||||
// Client errors - log as warning without stack trace
|
||||
logger.warn(`Client Error: ${JSON.stringify(errorDetails)}`);
|
||||
}
|
||||
|
||||
// Send error response to client
|
||||
res.status(statusCode).json({
|
||||
error: {
|
||||
message: err.message,
|
||||
status: statusCode,
|
||||
...(process.env.NODE_ENV === "development" && { stack: err.stack }),
|
||||
},
|
||||
});
|
||||
};
|
||||
50
src/middleware/requestLoggerMiddleware.ts
Normal file
50
src/middleware/requestLoggerMiddleware.ts
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
import { Request, Response, NextFunction } from "express";
|
||||
import { logger, logRequest } from "../utils/logger.js";
|
||||
|
||||
/**
|
||||
* Middleware to log all incoming requests and their responses
|
||||
* Logs: method, path, status code, response time, and user info
|
||||
*/
|
||||
export const requestLoggerMiddleware = (
|
||||
req: Request,
|
||||
res: Response,
|
||||
next: NextFunction
|
||||
) => {
|
||||
const startTime = Date.now();
|
||||
|
||||
// Get the full URL path (baseUrl + path)
|
||||
const fullPath = req.baseUrl + req.path;
|
||||
|
||||
// Log incoming request
|
||||
const userId = req.currentUser?.id || "anonymous";
|
||||
logger.info(`→ ${req.method} ${fullPath} (User: ${userId})`);
|
||||
|
||||
// Capture the original res.json and res.send to log response
|
||||
const originalJson = res.json.bind(res);
|
||||
const originalSend = res.send.bind(res);
|
||||
|
||||
// Override res.json
|
||||
res.json = function (body: any) {
|
||||
const duration = Date.now() - startTime;
|
||||
logRequest(req.method, fullPath, res.statusCode, duration);
|
||||
return originalJson(body);
|
||||
};
|
||||
|
||||
// Override res.send
|
||||
res.send = function (body: any) {
|
||||
const duration = Date.now() - startTime;
|
||||
logRequest(req.method, fullPath, res.statusCode, duration);
|
||||
return originalSend(body);
|
||||
};
|
||||
|
||||
// Handle responses that end without json/send
|
||||
res.on("finish", () => {
|
||||
// Only log if we haven't already logged (json/send weren't called)
|
||||
if (!res.headersSent) {
|
||||
const duration = Date.now() - startTime;
|
||||
logRequest(req.method, fullPath, res.statusCode, duration);
|
||||
}
|
||||
});
|
||||
|
||||
next();
|
||||
};
|
||||
62
src/utils/logger.ts
Normal file
62
src/utils/logger.ts
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
import winston from "winston";
|
||||
import path from "path";
|
||||
import { HttpStatusCode } from "../apiHelpers/HttpStatusCodes.js";
|
||||
|
||||
// Define log format
|
||||
const logFormat = winston.format.combine(
|
||||
winston.format.timestamp({ format: "YYYY-MM-DD HH:mm:ss" }),
|
||||
winston.format.errors({ stack: true }),
|
||||
winston.format.printf(({ timestamp, level, message, stack }) => {
|
||||
if (stack) {
|
||||
return `${timestamp} [${level.toUpperCase()}]: ${message}\n${stack}`;
|
||||
}
|
||||
return `${timestamp} [${level.toUpperCase()}]: ${message}`;
|
||||
})
|
||||
);
|
||||
|
||||
// Create logs directory if it doesn't exist
|
||||
const logsDir = path.join(process.cwd(), "logs");
|
||||
|
||||
// Create the logger
|
||||
export const logger = winston.createLogger({
|
||||
level: process.env.LOG_LEVEL || "info",
|
||||
format: logFormat,
|
||||
transports: [
|
||||
// Console transport with colors
|
||||
new winston.transports.Console({
|
||||
format: winston.format.combine(
|
||||
winston.format.colorize(),
|
||||
logFormat
|
||||
),
|
||||
}),
|
||||
|
||||
// File transport for all logs
|
||||
new winston.transports.File({
|
||||
filename: path.join(logsDir, "combined.log"),
|
||||
maxsize: 5242880, // 5MB
|
||||
maxFiles: 5,
|
||||
}),
|
||||
|
||||
// File transport for errors only
|
||||
new winston.transports.File({
|
||||
filename: path.join(logsDir, "error.log"),
|
||||
level: "error",
|
||||
maxsize: 5242880, // 5MB
|
||||
maxFiles: 5,
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
||||
// Add request-specific logging helper
|
||||
export const logRequest = (method: string, path: string, statusCode: number, duration: number) => {
|
||||
const message = `${method} ${path} ${statusCode} - ${duration}ms`;
|
||||
if (statusCode >= HttpStatusCode.INTERNAL_SERVER_ERROR) {
|
||||
logger.error(message);
|
||||
} else if (statusCode >= HttpStatusCode.BAD_REQUEST) {
|
||||
logger.warn(message);
|
||||
} else {
|
||||
logger.info(message);
|
||||
}
|
||||
};
|
||||
|
||||
export default logger;
|
||||
Loading…
Add table
Add a link
Reference in a new issue