Files
The-transit-Project/error_log_api/index.js
T
Sven laptop 0da6d07283
Build and Push Docker Images / detect-changes (push) Successful in 9s
Build and Push Docker Images / build-auth-api (push) Successful in 1m3s
Build and Push Docker Images / build-employee-api (push) Successful in 59s
Build and Push Docker Images / build-transit-api (push) Successful in 56s
Build and Push Docker Images / build-error-log-api (push) Successful in 1m4s
Build and Push Docker Images / build-admin-ui (push) Successful in 1m20s
Build and Push Docker Images / build-monitoring-ui (push) Successful in 1m12s
Build and Push Docker Images / deploy-test-node (push) Successful in 3s
Add local logging to all microservices; auto-create error_log table on startup
2026-09-23 18:29:46 +02:00

218 lines
6.0 KiB
JavaScript

const express = require("express");
const cors = require("cors");
const jwt = require("jsonwebtoken");
require("dotenv").config();
const db = require("./db");
function log(level, message, meta = {}) {
const timestamp = new Date().toISOString();
console.log(JSON.stringify({ timestamp, level, service: "error_log_api", message, ...meta }));
}
async function ensureErrorLogTable() {
try {
await db.query(`CREATE SCHEMA IF NOT EXISTS data`);
await db.query(`
CREATE TABLE IF NOT EXISTS data.errors (
id BIGSERIAL PRIMARY KEY,
"date" TIMESTAMPTZ NOT NULL DEFAULT NOW(),
"service" TEXT NOT NULL,
partofservice TEXT,
error TEXT NOT NULL
)
`);
console.log("Error log table ready");
} catch (err) {
console.error("Failed to create error log table:", err);
}
}
const app = express();
app.use(express.json({ limit: "64kb" }));
app.use(cors());
const ERROR_LOG_API_URL =
process.env.ERROR_LOG_API_URL ||
"https://error.xtransit.testinstance.nl/error_log";
function toFriendlyError(statusCode) {
if (statusCode === 400) return "Controleer je invoer en probeer opnieuw.";
if (statusCode === 401) return "Je sessie is verlopen. Log opnieuw in.";
if (statusCode === 403) return "Je hebt hier geen toegang toe.";
if (statusCode === 404) return "Niet gevonden.";
return "Er ging iets mis. Probeer het later opnieuw.";
}
async function reportError({ partofservice, error }) {
try {
await fetch(ERROR_LOG_API_URL, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
service: "error_log_api",
partofservice,
error,
}),
});
} catch (_) {}
}
app.use((req, res, next) => {
const originalJson = res.json.bind(res);
res.json = (body) => {
if (res.statusCode >= 400 && body && typeof body === "object") {
const rawError =
body.error || body.message || body.details || `HTTP ${res.statusCode}`;
void reportError({
partofservice: `${req.method} ${req.originalUrl}`,
error: rawError,
});
return originalJson({ error: toFriendlyError(res.statusCode) });
}
return originalJson(body);
};
next();
});
const PORT = Number(process.env.PORT || 5003);
function normalizeErrorText(value) {
if (!value) return "Unknown error";
if (typeof value === "string") return value;
try {
return JSON.stringify(value);
} catch (_) {
return String(value);
}
}
async function insertErrorLog({
service,
partofservice,
partOfService,
error,
}) {
const serviceName = String(service || "unknown_service").slice(0, 120);
const part =
partofservice || partOfService
? String(partofservice || partOfService).slice(0, 240)
: null;
const errorText = String(normalizeErrorText(error)).slice(0, 8000);
await db.query(
"INSERT INTO data.errors (service, partofservice, error) VALUES ($1, $2, $3)",
[serviceName, part, errorText],
);
}
function verifyAdmin(req, res, next) {
const auth = req.headers.authorization;
if (!auth || !auth.startsWith("Bearer ")) {
return res.status(401).json({ error: toFriendlyError(401) });
}
try {
const payload = jwt.verify(auth.slice(7), JWT_SECRET);
if (!payload?.isAdmin) {
return res.status(403).json({ error: toFriendlyError(403) });
}
req.user = payload;
return next();
} catch (err) {
return res.status(401).json({ error: toFriendlyError(401) });
}
}
app.get("/health", async (_req, res) => {
try {
await db.query("SELECT 1");
res.json({ status: "ok", service: "error_log_api", database: "ok" });
} catch (err) {
console.error("GET /health error:", err);
res.status(503).json({ status: "unavailable", service: "error_log_api" });
}
});
app.post("/error_log", async (req, res) => {
try {
const service = req.body?.service;
const partofservice =
req.body?.partofservice || req.body?.partOfService || null;
const error = req.body?.error;
if (!service || !error) {
return res.status(400).json({ error: toFriendlyError(400) });
}
await insertErrorLog({ service, partofservice, error });
return res.status(201).json({ ok: true });
} catch (err) {
console.error("POST /error_log failed:", err);
return res.status(500).json({ error: toFriendlyError(500) });
}
});
app.get("/error_log", verifyAdmin, async (req, res) => {
try {
const limit = Math.min(Number(req.query.limit || 100), 500);
const service = req.query.service ? String(req.query.service) : null;
const partofservice =
req.query.partofservice || req.query.partOfService
? String(req.query.partofservice || req.query.partOfService)
: null;
const where = [];
const params = [];
if (service) {
params.push(service);
where.push(`"service" = $${params.length}`);
}
if (partofservice) {
params.push(partofservice);
where.push(`partofservice ILIKE $${params.length}`);
params[params.length - 1] = `%${partofservice}%`;
}
params.push(limit);
const rows = await db.query(
`SELECT id, date, service, partofservice, error
FROM data.errors
${where.length ? `WHERE ${where.join(" AND ")}` : ""}
ORDER BY "date" DESC, id DESC
LIMIT $${params.length}`,
params,
);
return res.json({ errors: rows.rows });
} catch (err) {
console.error("GET /error_log failed:", err);
return res.status(500).json({ error: toFriendlyError(500) });
}
});
app.use(async (err, req, res, _next) => {
try {
await insertErrorLog({
service: "error_log_api",
partofservice: `${req.method} ${req.originalUrl}`,
error: err?.stack || err?.message || String(err),
});
} catch (logErr) {
console.error("Failed to log internal error:", logErr);
}
if (res.headersSent) return;
return res.status(500).json({ error: toFriendlyError(500) });
});
app.listen(PORT, async () => {
await ensureErrorLogTable();
console.log(`Error Log API listening on ${PORT}`);
});
module.exports = app;