feat: filtro de severidad (Alta/Media/Baja) en el log de errores

Se asigna sola, sin etiquetar nada a mano: backend y errores de render de React (rompen toda la
pantalla) = alta; errores JS sueltos = media; promesas rechazadas sin manejar = baja (suelen ser
fallos de red puntuales, no criticos). Nuevo filtro en el panel ademas del de origen ya existente.

Migracion 014_error_logs_severity.sql (ALTER TABLE ADD COLUMN IF NOT EXISTS). Probado en dev: las
tres severidades se asignan correctamente segun el origen, un valor invalido del frontend cae en
media, y el filtro combinado con origen funciona.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Milton Gonzalez
2026-09-17 14:50:36 -05:00
parent a06df86881
commit feadd2e715
14 changed files with 81 additions and 23 deletions
@@ -0,0 +1,4 @@
-- Severidad automatica (nunca etiquetada a mano): backend y errores de render de React (rompen
-- toda la pantalla) = alta; errores JS sueltos = media; promesas rechazadas sin catch = baja.
-- Filas ya existentes (si las hay) quedan en 'media' por defecto, criterio neutral razonable.
ALTER TABLE error_logs ADD COLUMN IF NOT EXISTS severity ENUM('alta', 'media', 'baja') NOT NULL DEFAULT 'media' AFTER source;
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "project-linkedcorp-backend",
"version": "1.21.0",
"version": "1.21.1",
"private": true,
"type": "module",
"main": "src/server.js",
+1 -1
View File
@@ -2,7 +2,7 @@ import { ok, asyncHandler } from '../../utils/http.js';
import * as service from './service.js';
export const getErrorLogs = asyncHandler(async (req, res) => {
ok(res, await service.listErrors({ source: req.query.source, limit: req.query.limit }));
ok(res, await service.listErrors({ source: req.query.source, severity: req.query.severity, limit: req.query.limit }));
});
export const postErrorLog = asyncHandler(async (req, res) => {
+7 -6
View File
@@ -4,11 +4,11 @@ import { pool } from '../../config/db.js';
// errores): al insertar, se recorta a los MAX_ROWS mas recientes.
const MAX_ROWS = 1000;
export async function create({ source, message, stack, url, method, userId, userAgent }) {
export async function create({ source, severity, message, stack, url, method, userId, userAgent }) {
await pool.query(
`INSERT INTO error_logs (source, message, stack, url, method, user_id, user_agent)
VALUES (?, ?, ?, ?, ?, ?, ?)`,
[source, message, stack || null, url || null, method || null, userId || null, userAgent || null]
`INSERT INTO error_logs (source, severity, message, stack, url, method, user_id, user_agent)
VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,
[source, severity, message, stack || null, url || null, method || null, userId || null, userAgent || null]
);
await pool.query(
`DELETE FROM error_logs WHERE id NOT IN (SELECT id FROM (SELECT id FROM error_logs ORDER BY created_at DESC LIMIT ?) x)`,
@@ -16,13 +16,14 @@ export async function create({ source, message, stack, url, method, userId, user
);
}
export async function findAll({ source, limit = 200 } = {}) {
export async function findAll({ source, severity, limit = 200 } = {}) {
const clauses = [];
const params = [];
if (source) { clauses.push('el.source = ?'); params.push(source); }
if (severity) { clauses.push('el.severity = ?'); params.push(severity); }
const where = clauses.length ? `WHERE ${clauses.join(' AND ')}` : '';
const [rows] = await pool.query(
`SELECT el.id, el.source, el.message, el.stack, el.url, el.method,
`SELECT el.id, el.source, el.severity, el.message, el.stack, el.url, el.method,
el.user_id, u.name AS user_name, el.user_agent, el.created_at
FROM error_logs el LEFT JOIN users u ON u.id = el.user_id
${where} ORDER BY el.created_at DESC LIMIT ?`,
+12
View File
@@ -3,12 +3,23 @@ import * as repo from './repository.js';
import { env } from '../../config/env.js';
import { HttpError } from '../../utils/http.js';
// Severidad SIEMPRE automatica, nunca etiquetada a mano (mismo criterio que la criticidad del
// panel de Actualizaciones): backend y errores de render de React "alta" (rompen toda la
// pantalla), errores JS sueltos "media", promesas rechazadas sin catch "baja" (suelen ser fallos
// de red no criticos). El frontend indica cual de los tres le paso; si manda algo invalido u
// omite el dato, cae en "media" como default neutral.
const VALID_SEVERITIES = new Set(['alta', 'media', 'baja']);
function normalizeSeverity(value) {
return VALID_SEVERITIES.has(value) ? value : 'media';
}
// Nunca debe tirar: si falla el logueo de un error, no puede convertirse en OTRO error sin
// manejar que rompa la respuesta original. Se traga cualquier fallo propio (solo avisa por consola).
export async function logBackendError(err, req) {
try {
await repo.create({
source: 'backend',
severity: 'alta',
message: (err?.message || String(err)).slice(0, 2000),
stack: (err?.stack || '').slice(0, 5000) || null,
url: req?.originalUrl?.slice(0, 500) || null,
@@ -39,6 +50,7 @@ export async function logFrontendError(payload, req) {
if (!message) throw new HttpError(400, 'message es obligatorio');
await repo.create({
source: 'frontend',
severity: normalizeSeverity(payload.severity),
message,
stack: (payload.stack || '').slice(0, 5000) || null,
url: (payload.url || '').slice(0, 500) || null,