Add logging

This commit is contained in:
araemer 2025-11-20 19:58:14 +01:00
parent 2a6153002c
commit 8814658142
11 changed files with 826 additions and 55 deletions

View file

@ -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());

View file

@ -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;

View 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 }),
},
});
};

View 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
View 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;