Logs en hora local, errores de red legibles y guía NSSM en el README
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
ec312d9461
commit
9e85c4cee8
3 files changed
+127
-12
No files matched your search
+64
-5
@@ -1,5 +1,6 @@
|
||||
import { appendFileSync, mkdirSync } from 'node:fs';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import { inspect } from 'node:util';
|
||||
|
||||
// Log a archivo, uno por día (logs/dispatcher-YYYY-MM-DD.log), además de
|
||||
// consola — para que un servicio Windows (NSSM/PM2/Task Scheduler, ver
|
||||
@@ -9,14 +10,65 @@ import { fileURLToPath } from 'node:url';
|
||||
const LOGS_DIR = fileURLToPath(new URL('../../logs/', import.meta.url));
|
||||
mkdirSync(LOGS_DIR, { recursive: true });
|
||||
|
||||
// Hora local del server (no UTC) con offset explícito, para que los logs
|
||||
// sean auditables directamente contra la hora de pared sin tener que
|
||||
// restar el huso horario a mano.
|
||||
function pad(value: number, length = 2): string {
|
||||
return String(value).padStart(length, '0');
|
||||
}
|
||||
|
||||
function localTimestamp(date: Date): string {
|
||||
const offsetMinutes = -date.getTimezoneOffset();
|
||||
const sign = offsetMinutes >= 0 ? '+' : '-';
|
||||
const absMinutes = Math.abs(offsetMinutes);
|
||||
const offset = `${sign}${pad(Math.floor(absMinutes / 60))}:${pad(absMinutes % 60)}`;
|
||||
return (
|
||||
`${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())}` +
|
||||
`T${pad(date.getHours())}:${pad(date.getMinutes())}:${pad(date.getSeconds())}.${pad(date.getMilliseconds(), 3)}` +
|
||||
offset
|
||||
);
|
||||
}
|
||||
|
||||
function logFilePath(date: Date): string {
|
||||
const day = date.toISOString().slice(0, 10);
|
||||
const day = localTimestamp(date).slice(0, 10);
|
||||
return `${LOGS_DIR}dispatcher-${day}.log`;
|
||||
}
|
||||
|
||||
function write(level: string, message: string): void {
|
||||
const now = new Date();
|
||||
appendFileSync(logFilePath(now), `${now.toISOString()} [${level}] ${message}\n`);
|
||||
appendFileSync(logFilePath(now), `${localTimestamp(now)} [${level}] ${message}\n`);
|
||||
}
|
||||
|
||||
// Errores de red de axios (ECONNREFUSED, timeout, DNS...): primero se
|
||||
// muestra una frase legible para el operador y después el error completo.
|
||||
// El error de axios trae adjunto el body del Login, así que antes de
|
||||
// imprimirlo se enmascara la contraseña.
|
||||
const NETWORK_ERRORS: Record<string, string> = {
|
||||
ECONNREFUSED: 'conexión rechazada (¿Service Layer detenido o puerto bloqueado?)',
|
||||
ETIMEDOUT: 'tiempo de espera agotado',
|
||||
ECONNABORTED: 'tiempo de espera agotado',
|
||||
ENOTFOUND: 'no se pudo resolver el nombre del servidor',
|
||||
ECONNRESET: 'el servidor cerró la conexión',
|
||||
EHOSTUNREACH: 'servidor inalcanzable',
|
||||
};
|
||||
|
||||
function describeError(err: unknown): string {
|
||||
if (!(err instanceof Error)) return err !== undefined ? String(err) : '';
|
||||
const axiosErr = err as Error & { isAxiosError?: boolean; code?: string; config?: { baseURL?: string; url?: string } };
|
||||
if (axiosErr.isAxiosError) {
|
||||
const target = `${axiosErr.config?.baseURL ?? ''}${axiosErr.config?.url ?? ''}`;
|
||||
const reason = (axiosErr.code && NETWORK_ERRORS[axiosErr.code]) ?? axiosErr.message;
|
||||
return `No se pudo conectar a ${target}: ${reason}`;
|
||||
}
|
||||
return err.message;
|
||||
}
|
||||
|
||||
function redactPassword(err: unknown): unknown {
|
||||
const config = (err as { config?: { data?: unknown } } | undefined)?.config;
|
||||
if (config && typeof config.data === 'string') {
|
||||
config.data = config.data.replace(/("Password"\s*:\s*")(?:[^"\\]|\\.)*"/g, '$1***"');
|
||||
}
|
||||
return err;
|
||||
}
|
||||
|
||||
export const logger = {
|
||||
@@ -29,8 +81,15 @@ export const logger = {
|
||||
write('WARN', message);
|
||||
},
|
||||
error(message: string, err?: unknown): void {
|
||||
const detail = err instanceof Error ? (err.stack ?? err.message) : err !== undefined ? String(err) : '';
|
||||
console.error(message, err ?? '');
|
||||
write('ERROR', detail ? `${message} ${detail}` : message);
|
||||
const summary = describeError(err);
|
||||
const headline = summary ? `${message} ${summary}` : message;
|
||||
if (err === undefined) {
|
||||
console.error(headline);
|
||||
write('ERROR', headline);
|
||||
return;
|
||||
}
|
||||
const full = inspect(redactPassword(err));
|
||||
console.error(`${headline}\n--- Detalle completo ---\n${full}`);
|
||||
write('ERROR', `${headline}\n${full}`);
|
||||
},
|
||||
};
|
||||
Reference in new issue
Block a user