Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions migrations/20260630_create_request_logs_table.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
-- Migration: 20260630_create_request_logs_table
-- Description: Create request_logs table to track all API requests for analytics, debugging, and compliance

CREATE TABLE IF NOT EXISTS request_logs (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
request_id UUID NOT NULL,
method VARCHAR(10) NOT NULL,
path TEXT NOT NULL,
status_code INTEGER NOT NULL,
duration_ms INTEGER NOT NULL,
api_key_id UUID,
user_id UUID,
ip_address INET,
user_agent TEXT,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
);

CREATE INDEX IF NOT EXISTS idx_request_logs_request_id ON request_logs(request_id);
CREATE INDEX IF NOT EXISTS idx_request_logs_created_at ON request_logs(created_at);
CREATE INDEX IF NOT EXISTS idx_request_logs_user_id ON request_logs(user_id);
CREATE INDEX IF NOT EXISTS idx_request_logs_api_key_id ON request_logs(api_key_id);
CREATE INDEX IF NOT EXISTS idx_request_logs_status_code ON request_logs(status_code);
2 changes: 2 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ import {
import { requireAuth } from "./middleware/auth";
import { responseTime } from "./middleware/responseTime";
import { requestId } from "./middleware/requestId";
import { requestLogger } from "./middleware/requestLogger";
import { readReplicaRoutingMiddleware } from "./middleware/readReplicaRouting";
import { dbConnectionLeakDetector } from "./middleware/dbConnectionLeakDetector";
import { i18nMiddleware } from "./utils/i18n";
Expand Down Expand Up @@ -173,6 +174,7 @@ app.use(
// app.use(rateLimitMiddleware);
app.use(responseTime);
app.use(requestId);
app.use(requestLogger);
app.use(readReplicaRoutingMiddleware);
app.use(i18nMiddleware);
app.use(dbConnectionLeakDetector);
Expand Down
57 changes: 57 additions & 0 deletions src/middleware/requestLogger.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { Request, Response, NextFunction } from "express";
import { queryWrite } from "../config/database";

// List of sensitive paths to exclude from logging
const EXCLUDED_PATHS = ["/health", "/health/lb", "/ready", "/metrics"];

export const requestLogger = (
req: Request,
res: Response,
next: NextFunction
) => {
// Skip logging for excluded paths
if (EXCLUDED_PATHS.some((path) => req.path.startsWith(path))) {
return next();
}

const startTime = Date.now();
const requestId = (req as any).id;
const method = req.method;
const path = req.originalUrl;
const ipAddress = req.ip || (req as any).connection?.remoteAddress;
const userAgent = req.get("user-agent");

// Capture response finish event
res.on("finish", async () => {
try {
const durationMs = Date.now() - startTime;
const statusCode = res.statusCode;
const userId = (req as any).user?.id;
const apiKeyId = (req as any).apiKey?.id;

// Log asynchronously, don't block
await queryWrite(
`INSERT INTO request_logs (
request_id, method, path, status_code, duration_ms,
api_key_id, user_id, ip_address, user_agent
) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)`,
[
requestId,
method,
path,
statusCode,
durationMs,
apiKeyId,
userId,
ipAddress,
userAgent,
]
);
} catch (error) {
// If logging fails, just log to console but don't throw
console.error("Failed to log request to database:", error);
}
});

next();
};
Loading