@growconnect/growtickets-nest 0.1.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +8 -0
- package/README.md +319 -6
- package/dist/cli/args.d.ts +9 -1
- package/dist/cli/args.js +13 -4
- package/dist/cli/check.d.ts +4 -1
- package/dist/cli/check.js +47 -2
- package/dist/cli/env-example.js +17 -0
- package/dist/cli/env.d.ts +34 -0
- package/dist/cli/env.js +125 -0
- package/dist/cli/index.js +35 -1
- package/dist/cli/init.js +5 -1
- package/dist/client/ingest-client.js +6 -1
- package/dist/env/applier.d.ts +83 -0
- package/dist/env/applier.js +162 -0
- package/dist/env/errors.d.ts +12 -0
- package/dist/env/errors.js +18 -0
- package/dist/env/load.d.ts +35 -0
- package/dist/env/load.js +43 -0
- package/dist/env/overlay.d.ts +23 -0
- package/dist/env/overlay.js +48 -0
- package/dist/env/policy.d.ts +30 -0
- package/dist/env/policy.js +101 -0
- package/dist/env/poller.d.ts +27 -0
- package/dist/env/poller.js +54 -0
- package/dist/env/runtime.d.ts +122 -0
- package/dist/env/runtime.js +238 -0
- package/dist/env/settings.d.ts +26 -0
- package/dist/env/settings.js +41 -0
- package/dist/env/snapshot.d.ts +32 -0
- package/dist/env/snapshot.js +85 -0
- package/dist/env/state.d.ts +27 -0
- package/dist/env/state.js +51 -0
- package/dist/env/storage.d.ts +33 -0
- package/dist/env/storage.js +133 -0
- package/dist/env/transport/client.d.ts +58 -0
- package/dist/env/transport/client.js +249 -0
- package/dist/env/transport/config.d.ts +20 -0
- package/dist/env/transport/config.js +38 -0
- package/dist/env/transport/credential.d.ts +33 -0
- package/dist/env/transport/credential.js +76 -0
- package/dist/env/transport/gt2-transport.d.ts +53 -0
- package/dist/env/transport/gt2-transport.js +162 -0
- package/dist/env/transport/index.d.ts +10 -0
- package/dist/env/transport/index.js +17 -0
- package/dist/env/transport/keygen.d.ts +32 -0
- package/dist/env/transport/keygen.js +77 -0
- package/dist/env/transport/nonce-cache.d.ts +18 -0
- package/dist/env/transport/nonce-cache.js +37 -0
- package/dist/env/transport/protocol.d.ts +100 -0
- package/dist/env/transport/protocol.js +292 -0
- package/dist/env/transport/types.d.ts +78 -0
- package/dist/env/transport/types.js +2 -0
- package/dist/index.d.ts +14 -1
- package/dist/index.js +18 -1
- package/dist/logs/buffer.d.ts +25 -0
- package/dist/logs/buffer.js +58 -0
- package/dist/logs/doctor.d.ts +17 -0
- package/dist/logs/doctor.js +66 -0
- package/dist/logs/entry-builder.d.ts +20 -0
- package/dist/logs/entry-builder.js +127 -0
- package/dist/logs/flusher.d.ts +72 -0
- package/dist/logs/flusher.js +189 -0
- package/dist/logs/format.d.ts +4 -0
- package/dist/logs/format.js +76 -0
- package/dist/logs/levels.d.ts +12 -0
- package/dist/logs/levels.js +55 -0
- package/dist/logs/monitor.d.ts +92 -0
- package/dist/logs/monitor.js +224 -0
- package/dist/logs/normalize.d.ts +14 -0
- package/dist/logs/normalize.js +114 -0
- package/dist/logs/pino-line.d.ts +13 -0
- package/dist/logs/pino-line.js +52 -0
- package/dist/logs/redactor.d.ts +38 -0
- package/dist/logs/redactor.js +146 -0
- package/dist/logs/remote-config.d.ts +37 -0
- package/dist/logs/remote-config.js +89 -0
- package/dist/logs/runtime.d.ts +24 -0
- package/dist/logs/runtime.js +59 -0
- package/dist/logs/sampler.d.ts +15 -0
- package/dist/logs/sampler.js +36 -0
- package/dist/logs/sender.d.ts +20 -0
- package/dist/logs/sender.js +64 -0
- package/dist/logs/settings.d.ts +22 -0
- package/dist/logs/settings.js +30 -0
- package/dist/logs/types.d.ts +74 -0
- package/dist/logs/types.js +9 -0
- package/dist/logs-nest/api.d.ts +5 -0
- package/dist/logs-nest/api.js +13 -0
- package/dist/logs-nest/host.d.ts +10 -0
- package/dist/logs-nest/host.js +45 -0
- package/dist/logs-nest/lifecycle.d.ts +20 -0
- package/dist/logs-nest/lifecycle.js +40 -0
- package/dist/logs-nest/nest-args.d.ts +7 -0
- package/dist/logs-nest/nest-args.js +66 -0
- package/dist/logs-nest/service.d.ts +25 -0
- package/dist/logs-nest/service.js +61 -0
- package/dist/logs-nest/stream.d.ts +21 -0
- package/dist/logs-nest/stream.js +32 -0
- package/dist/maintenance/feed.d.ts +68 -0
- package/dist/maintenance/feed.js +160 -0
- package/dist/maintenance/http.d.ts +67 -0
- package/dist/maintenance/http.js +177 -0
- package/dist/maintenance/page.d.ts +11 -0
- package/dist/maintenance/page.js +208 -0
- package/dist/maintenance/settings.d.ts +24 -0
- package/dist/maintenance/settings.js +42 -0
- package/dist/maintenance/windows.d.ts +78 -0
- package/dist/maintenance/windows.js +167 -0
- package/dist/nest/csp-response.d.ts +19 -0
- package/dist/nest/csp-response.js +84 -0
- package/dist/nest/env-push.d.ts +35 -0
- package/dist/nest/env-push.js +110 -0
- package/dist/nest/growtickets.module.js +11 -3
- package/dist/nest/maintenance-request.d.ts +12 -0
- package/dist/nest/maintenance-request.js +30 -0
- package/dist/nest/maintenance.guard.d.ts +28 -0
- package/dist/nest/maintenance.guard.js +90 -0
- package/dist/nest/options.d.ts +15 -0
- package/dist/nest/request.d.ts +1 -1
- package/dist/nest/request.js +2 -2
- package/dist/nest/route-tag.d.ts +6 -0
- package/dist/nest/route-tag.js +13 -0
- package/dist/nest/security.middleware.d.ts +10 -4
- package/dist/nest/security.middleware.js +77 -18
- package/dist/nest/services.js +1 -1
- package/dist/proxy/handle-portal-request.js +2 -1
- package/dist/proxy/types.d.ts +1 -0
- package/dist/proxy/unread.js +1 -1
- package/dist/security/client-ip.d.ts +15 -1
- package/dist/security/client-ip.js +20 -2
- package/dist/security/csp.d.ts +58 -0
- package/dist/security/csp.js +109 -0
- package/dist/security/flusher.js +4 -2
- package/dist/security/proxy-chain.d.ts +35 -0
- package/dist/security/proxy-chain.js +58 -0
- package/dist/security/remote-config.d.ts +4 -0
- package/dist/security/remote-config.js +7 -0
- package/dist/security/request-ip.d.ts +3 -2
- package/dist/security/request-ip.js +6 -3
- package/dist/security/runtime.d.ts +8 -0
- package/dist/security/runtime.js +17 -0
- package/dist/security/sdk.d.ts +7 -1
- package/dist/security/sdk.js +27 -1
- package/dist/security/settings.d.ts +3 -1
- package/dist/security/settings.js +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.formatArgs = formatArgs;
|
|
4
|
+
exports.stringify = stringify;
|
|
5
|
+
/**
|
|
6
|
+
* Cómo se escribe un valor o una lista de argumentos como lo haría `console.log`, sin `node:util`: así vale también donde no hay Node.
|
|
7
|
+
*/
|
|
8
|
+
const MAX_ARG = 2000;
|
|
9
|
+
/** Lo que `console.log` haría con los argumentos: la sustitución estilo printf del primero (`%s`, `%d`, `%j`…) y el resto unido con espacios. */
|
|
10
|
+
function formatArgs(args) {
|
|
11
|
+
if (args.length === 0)
|
|
12
|
+
return '';
|
|
13
|
+
const [primero, ...resto] = args;
|
|
14
|
+
if (typeof primero !== 'string' || !primero.includes('%'))
|
|
15
|
+
return args.map(stringify).join(' ');
|
|
16
|
+
let usados = 0;
|
|
17
|
+
const texto = primero.replace(/%([sdifjoOc%])/g, (coincidencia, tipo) => {
|
|
18
|
+
if (tipo === '%')
|
|
19
|
+
return '%';
|
|
20
|
+
if (usados >= resto.length)
|
|
21
|
+
return coincidencia;
|
|
22
|
+
const valor = resto[usados++];
|
|
23
|
+
switch (tipo) {
|
|
24
|
+
case 's':
|
|
25
|
+
return typeof valor === 'string' ? valor : stringify(valor);
|
|
26
|
+
case 'd':
|
|
27
|
+
case 'i':
|
|
28
|
+
return String(Number.parseInt(String(valor), 10));
|
|
29
|
+
case 'f':
|
|
30
|
+
return String(Number.parseFloat(String(valor)));
|
|
31
|
+
case 'c':
|
|
32
|
+
return '';
|
|
33
|
+
default:
|
|
34
|
+
return stringify(valor);
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
return [texto, ...resto.slice(usados).map(stringify)].join(' ');
|
|
38
|
+
}
|
|
39
|
+
/** Un valor como texto, sin ejecutar nada suyo: un `Error` como `Clase: mensaje`, un objeto como JSON (acotado y sin ciclos). */
|
|
40
|
+
function stringify(valor) {
|
|
41
|
+
switch (typeof valor) {
|
|
42
|
+
case 'string':
|
|
43
|
+
return valor;
|
|
44
|
+
case 'undefined':
|
|
45
|
+
return 'undefined';
|
|
46
|
+
case 'function':
|
|
47
|
+
return `[Function${valor.name ? `: ${valor.name}` : ''}]`;
|
|
48
|
+
case 'symbol':
|
|
49
|
+
return valor.toString();
|
|
50
|
+
case 'object':
|
|
51
|
+
break;
|
|
52
|
+
default:
|
|
53
|
+
return String(valor);
|
|
54
|
+
}
|
|
55
|
+
if (valor === null)
|
|
56
|
+
return 'null';
|
|
57
|
+
if (valor instanceof Error)
|
|
58
|
+
return `${valor.name}: ${valor.message}`;
|
|
59
|
+
const vistos = new WeakSet();
|
|
60
|
+
try {
|
|
61
|
+
const json = JSON.stringify(valor, (_clave, v) => {
|
|
62
|
+
if (typeof v === 'bigint')
|
|
63
|
+
return v.toString();
|
|
64
|
+
if (v !== null && typeof v === 'object') {
|
|
65
|
+
if (vistos.has(v))
|
|
66
|
+
return '[circular]';
|
|
67
|
+
vistos.add(v);
|
|
68
|
+
}
|
|
69
|
+
return v;
|
|
70
|
+
});
|
|
71
|
+
return json.length > MAX_ARG ? `${json.slice(0, MAX_ARG)}…` : json;
|
|
72
|
+
}
|
|
73
|
+
catch {
|
|
74
|
+
return '[object]';
|
|
75
|
+
}
|
|
76
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { LogLevel } from './types';
|
|
2
|
+
export declare const LEVELS: readonly LogLevel[];
|
|
3
|
+
/** Un número de nivel de pino (10 trace, 20 debug, 30 info, 40 warn, 50 error, 60 fatal) llevado al nivel de PSR-3 que lo contiene. */
|
|
4
|
+
export declare function levelFromNumber(valor: number): LogLevel;
|
|
5
|
+
/**
|
|
6
|
+
* Cualquier nombre corriente de nivel (`warn`, `fatal`, `verbose`, `CRITICAL`) o un número de pino, llevado a uno de los ocho. Lo que no
|
|
7
|
+
* se reconoce da `null`: inventar un nivel dejaría la línea donde nadie la busca.
|
|
8
|
+
*/
|
|
9
|
+
export declare function normalizeLevel(entrada: unknown): LogLevel | null;
|
|
10
|
+
export declare function levelRank(nivel: LogLevel): number;
|
|
11
|
+
/** `warning` o peor: lo que nunca se muestrea ni se descarta primero, y lo que la central agrupa en errores repetidos. */
|
|
12
|
+
export declare function isSevere(nivel: LogLevel): boolean;
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.LEVELS = void 0;
|
|
4
|
+
exports.levelFromNumber = levelFromNumber;
|
|
5
|
+
exports.normalizeLevel = normalizeLevel;
|
|
6
|
+
exports.levelRank = levelRank;
|
|
7
|
+
exports.isSevere = isSevere;
|
|
8
|
+
exports.LEVELS = ['debug', 'info', 'notice', 'warning', 'error', 'critical', 'alert', 'emergency'];
|
|
9
|
+
const ALIASES = {
|
|
10
|
+
trace: 'debug',
|
|
11
|
+
verbose: 'debug',
|
|
12
|
+
log: 'info',
|
|
13
|
+
information: 'info',
|
|
14
|
+
warn: 'warning',
|
|
15
|
+
err: 'error',
|
|
16
|
+
crit: 'critical',
|
|
17
|
+
fatal: 'critical',
|
|
18
|
+
emerg: 'emergency',
|
|
19
|
+
panic: 'emergency',
|
|
20
|
+
};
|
|
21
|
+
/** Un número de nivel de pino (10 trace, 20 debug, 30 info, 40 warn, 50 error, 60 fatal) llevado al nivel de PSR-3 que lo contiene. */
|
|
22
|
+
function levelFromNumber(valor) {
|
|
23
|
+
if (!Number.isFinite(valor))
|
|
24
|
+
return 'info';
|
|
25
|
+
if (valor <= 20)
|
|
26
|
+
return 'debug';
|
|
27
|
+
if (valor <= 30)
|
|
28
|
+
return 'info';
|
|
29
|
+
if (valor <= 40)
|
|
30
|
+
return 'warning';
|
|
31
|
+
if (valor <= 50)
|
|
32
|
+
return 'error';
|
|
33
|
+
return 'critical';
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Cualquier nombre corriente de nivel (`warn`, `fatal`, `verbose`, `CRITICAL`) o un número de pino, llevado a uno de los ocho. Lo que no
|
|
37
|
+
* se reconoce da `null`: inventar un nivel dejaría la línea donde nadie la busca.
|
|
38
|
+
*/
|
|
39
|
+
function normalizeLevel(entrada) {
|
|
40
|
+
if (typeof entrada === 'number')
|
|
41
|
+
return levelFromNumber(entrada);
|
|
42
|
+
if (typeof entrada !== 'string')
|
|
43
|
+
return null;
|
|
44
|
+
const nombre = entrada.trim().toLowerCase();
|
|
45
|
+
if (exports.LEVELS.includes(nombre))
|
|
46
|
+
return nombre;
|
|
47
|
+
return ALIASES[nombre] ?? null;
|
|
48
|
+
}
|
|
49
|
+
function levelRank(nivel) {
|
|
50
|
+
return exports.LEVELS.indexOf(nivel);
|
|
51
|
+
}
|
|
52
|
+
/** `warning` o peor: lo que nunca se muestrea ni se descarta primero, y lo que la central agrupa en errores repetidos. */
|
|
53
|
+
function isSevere(nivel) {
|
|
54
|
+
return levelRank(nivel) >= levelRank('warning');
|
|
55
|
+
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { type FlushResult } from './flusher';
|
|
2
|
+
import { type Transport } from './sender';
|
|
3
|
+
import { type LogSettings } from './settings';
|
|
4
|
+
import type { LogRecord, LogsHost } from './types';
|
|
5
|
+
/** Cada cuánto se envía lo acumulado. */
|
|
6
|
+
export declare const FLUSH_INTERVAL_MS = 60000;
|
|
7
|
+
/**
|
|
8
|
+
* Cuánto se espera, como mucho, para mandar una línea grave (`warning` o peor): un proceso que se cae o al que le llega un `SIGTERM` no deja
|
|
9
|
+
* tiempo de mandar el último minuto, y lo que más hace falta tener es justo el error de antes de morir. Una ráfaga de errores no manda un
|
|
10
|
+
* lote por línea: el aviso se agrupa en este margen.
|
|
11
|
+
*/
|
|
12
|
+
export declare const URGENT_FLUSH_MS = 5000;
|
|
13
|
+
export interface MonitorOptions {
|
|
14
|
+
now?: () => number;
|
|
15
|
+
flushIntervalMs?: number;
|
|
16
|
+
/** Cuánto se espera para mandar una línea grave. De serie, `URGENT_FLUSH_MS`. */
|
|
17
|
+
urgentFlushMs?: number;
|
|
18
|
+
transport?: Transport;
|
|
19
|
+
random?: () => number;
|
|
20
|
+
/** Dónde se escribe un aviso de la propia librería. De serie, el error estándar: NUNCA la consola, que otros adaptadores capturan. */
|
|
21
|
+
warn?: (mensaje: string) => void;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* La puerta de los logs de UN proceso: recibe cada línea que entrega un adaptador, la tapa, la acota, la muestrea y la deja en la
|
|
25
|
+
* cola para mandarla a la mesa de ayuda cada minuto.
|
|
26
|
+
*
|
|
27
|
+
* ## Capturar nunca tumba nada
|
|
28
|
+
*
|
|
29
|
+
* - **A prueba de reentrada.** La propia librería (o un `console` parcheado) puede escribir mientras captura; sin la guarda cada línea
|
|
30
|
+
* suya dispararía otra captura. Lo que llega mientras se captura se ignora.
|
|
31
|
+
* - **No lanza.** Una excepción aquí se traga: el peor fallo permitido es perder una línea, nunca romper la petición que casualmente
|
|
32
|
+
* escribió en el log.
|
|
33
|
+
* - **No espera.** Capturar es síncrono y barato; el envío es de un temporizador que no retiene el proceso (`unref`).
|
|
34
|
+
*
|
|
35
|
+
* ## Lo grave no espera al minuto
|
|
36
|
+
*
|
|
37
|
+
* Una línea `warning` o peor programa un envío a los 5 segundos (agrupando las que lleguen en ese margen): si el proceso muere —un fallo, un
|
|
38
|
+
* `SIGTERM` de un despliegue— se pierden a lo sumo esos segundos y no el último minuto, justo donde está el error que importa.
|
|
39
|
+
*
|
|
40
|
+
* ## El muestreo no toca lo importante
|
|
41
|
+
*
|
|
42
|
+
* Por encima de `sample_cap` líneas por minuto se guarda 1 de cada N con peso N —lo leve: `debug`, `info`, `notice`—; un `warning` o
|
|
43
|
+
* peor se guarda SIEMPRE, con peso 1: un bucle de `debug` no puede esconder el error que de verdad hay que ver.
|
|
44
|
+
*
|
|
45
|
+
* ## Sin clave no hay nada
|
|
46
|
+
*
|
|
47
|
+
* Si el entorno no trae la dirección y la clave (o el módulo está apagado con `GROWTICKETS_LOGS=false`) no se acumula ni se envía nada,
|
|
48
|
+
* y no se lanza ni un aviso por línea: se avisa UNA vez por el error estándar.
|
|
49
|
+
*/
|
|
50
|
+
export declare class LogMonitor {
|
|
51
|
+
private readonly host;
|
|
52
|
+
private readonly opciones;
|
|
53
|
+
private readonly buffer;
|
|
54
|
+
private readonly remote;
|
|
55
|
+
private readonly flusher;
|
|
56
|
+
private readonly sampler;
|
|
57
|
+
private readonly redactor;
|
|
58
|
+
private readonly transport;
|
|
59
|
+
private readonly ahora;
|
|
60
|
+
private readonly avisos;
|
|
61
|
+
private ajustes;
|
|
62
|
+
private temporizador;
|
|
63
|
+
private urgente;
|
|
64
|
+
private ocupado;
|
|
65
|
+
private apagando;
|
|
66
|
+
constructor(host: LogsHost, opciones?: MonitorOptions);
|
|
67
|
+
settings(): LogSettings;
|
|
68
|
+
/** Recibe una línea. No lanza, no espera y no hace nada si los logs están apagados o el entorno no está completo. */
|
|
69
|
+
capture(registro: LogRecord): void;
|
|
70
|
+
/** Arranca el envío periódico. Varias llamadas son una. El temporizador no retiene el proceso. */
|
|
71
|
+
start(): void;
|
|
72
|
+
/** Manda lo acumulado dentro de unos segundos si no hay ya un envío urgente en camino. El reloj no retiene el proceso. */
|
|
73
|
+
private scheduleUrgent;
|
|
74
|
+
/** Envía ya lo acumulado. `force` se salta el corte tras fallos. Nunca lanza. */
|
|
75
|
+
flush(force?: boolean): Promise<FlushResult>;
|
|
76
|
+
/** Para el temporizador y manda lo último, esperando como mucho tres segundos. Lo llama el adaptador al cerrar la app. */
|
|
77
|
+
shutdown(): Promise<void>;
|
|
78
|
+
/** Lo que hay sin enviar. Lo usan las pruebas y `doctor`. */
|
|
79
|
+
pending(): number;
|
|
80
|
+
private env;
|
|
81
|
+
private warn;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* El monitor de ESTE proceso. Vive en `globalThis` porque un empaquetador puede cargar esta librería una vez por ruta, y cada copia
|
|
85
|
+
* tendría su cola y su temporizador: con uno solo, una línea se manda una vez.
|
|
86
|
+
*
|
|
87
|
+
* Al crearlo se engancha, una sola vez por proceso, a `beforeExit` —cuando el bucle de eventos se vacía, por ejemplo tras `app.close()`—
|
|
88
|
+
* para mandar lo último. NO se engancha a ninguna señal (`SIGTERM`, `SIGINT`): quien lo hace cambia lo que el proceso hace al recibirla.
|
|
89
|
+
*/
|
|
90
|
+
export declare function logMonitor(host?: LogsHost, opciones?: MonitorOptions): LogMonitor | null;
|
|
91
|
+
/** Sólo las pruebas: olvida el monitor del proceso. */
|
|
92
|
+
export declare function resetLogMonitor(): void;
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.LogMonitor = exports.URGENT_FLUSH_MS = exports.FLUSH_INTERVAL_MS = void 0;
|
|
4
|
+
exports.logMonitor = logMonitor;
|
|
5
|
+
exports.resetLogMonitor = resetLogMonitor;
|
|
6
|
+
const buffer_1 = require("./buffer");
|
|
7
|
+
const entry_builder_1 = require("./entry-builder");
|
|
8
|
+
const flusher_1 = require("./flusher");
|
|
9
|
+
const levels_1 = require("./levels");
|
|
10
|
+
const redactor_1 = require("./redactor");
|
|
11
|
+
const remote_config_1 = require("./remote-config");
|
|
12
|
+
const runtime_1 = require("./runtime");
|
|
13
|
+
const sampler_1 = require("./sampler");
|
|
14
|
+
const sender_1 = require("./sender");
|
|
15
|
+
const settings_1 = require("./settings");
|
|
16
|
+
/** Cada cuánto se envía lo acumulado. */
|
|
17
|
+
exports.FLUSH_INTERVAL_MS = 60_000;
|
|
18
|
+
/**
|
|
19
|
+
* Cuánto se espera, como mucho, para mandar una línea grave (`warning` o peor): un proceso que se cae o al que le llega un `SIGTERM` no deja
|
|
20
|
+
* tiempo de mandar el último minuto, y lo que más hace falta tener es justo el error de antes de morir. Una ráfaga de errores no manda un
|
|
21
|
+
* lote por línea: el aviso se agrupa en este margen.
|
|
22
|
+
*/
|
|
23
|
+
exports.URGENT_FLUSH_MS = 5000;
|
|
24
|
+
/** Lo más que se espera al enviar lo último, al apagar: el proceso se está yendo y no puede quedarse esperando a una mesa de ayuda caída. */
|
|
25
|
+
const SHUTDOWN_MS = 3000;
|
|
26
|
+
/** Cuánto vive la lectura de los interruptores: capturar corre en cada línea y no relee el entorno cada vez. */
|
|
27
|
+
const SETTINGS_TTL_MS = 5000;
|
|
28
|
+
const WARN_EVERY_MS = 3_600_000;
|
|
29
|
+
/**
|
|
30
|
+
* La puerta de los logs de UN proceso: recibe cada línea que entrega un adaptador, la tapa, la acota, la muestrea y la deja en la
|
|
31
|
+
* cola para mandarla a la mesa de ayuda cada minuto.
|
|
32
|
+
*
|
|
33
|
+
* ## Capturar nunca tumba nada
|
|
34
|
+
*
|
|
35
|
+
* - **A prueba de reentrada.** La propia librería (o un `console` parcheado) puede escribir mientras captura; sin la guarda cada línea
|
|
36
|
+
* suya dispararía otra captura. Lo que llega mientras se captura se ignora.
|
|
37
|
+
* - **No lanza.** Una excepción aquí se traga: el peor fallo permitido es perder una línea, nunca romper la petición que casualmente
|
|
38
|
+
* escribió en el log.
|
|
39
|
+
* - **No espera.** Capturar es síncrono y barato; el envío es de un temporizador que no retiene el proceso (`unref`).
|
|
40
|
+
*
|
|
41
|
+
* ## Lo grave no espera al minuto
|
|
42
|
+
*
|
|
43
|
+
* Una línea `warning` o peor programa un envío a los 5 segundos (agrupando las que lleguen en ese margen): si el proceso muere —un fallo, un
|
|
44
|
+
* `SIGTERM` de un despliegue— se pierden a lo sumo esos segundos y no el último minuto, justo donde está el error que importa.
|
|
45
|
+
*
|
|
46
|
+
* ## El muestreo no toca lo importante
|
|
47
|
+
*
|
|
48
|
+
* Por encima de `sample_cap` líneas por minuto se guarda 1 de cada N con peso N —lo leve: `debug`, `info`, `notice`—; un `warning` o
|
|
49
|
+
* peor se guarda SIEMPRE, con peso 1: un bucle de `debug` no puede esconder el error que de verdad hay que ver.
|
|
50
|
+
*
|
|
51
|
+
* ## Sin clave no hay nada
|
|
52
|
+
*
|
|
53
|
+
* Si el entorno no trae la dirección y la clave (o el módulo está apagado con `GROWTICKETS_LOGS=false`) no se acumula ni se envía nada,
|
|
54
|
+
* y no se lanza ni un aviso por línea: se avisa UNA vez por el error estándar.
|
|
55
|
+
*/
|
|
56
|
+
class LogMonitor {
|
|
57
|
+
constructor(host, opciones = {}) {
|
|
58
|
+
this.host = host;
|
|
59
|
+
this.opciones = opciones;
|
|
60
|
+
this.sampler = new sampler_1.MinuteSampler();
|
|
61
|
+
this.avisos = new Map();
|
|
62
|
+
this.ajustes = null;
|
|
63
|
+
this.temporizador = null;
|
|
64
|
+
this.urgente = null;
|
|
65
|
+
this.ocupado = false;
|
|
66
|
+
this.apagando = false;
|
|
67
|
+
this.ahora = opciones.now ?? Date.now;
|
|
68
|
+
this.transport = opciones.transport ?? (0, sender_1.createTransport)(host);
|
|
69
|
+
this.buffer = new buffer_1.LogBuffer(10_000, this.ahora);
|
|
70
|
+
this.remote = new remote_config_1.RemoteConfig(this.transport, this.ahora);
|
|
71
|
+
this.redactor = new redactor_1.Redactor(() => this.env(), () => this.settings().redactKeys, this.ahora);
|
|
72
|
+
this.flusher = new flusher_1.Flusher({
|
|
73
|
+
host,
|
|
74
|
+
transport: this.transport,
|
|
75
|
+
remote: this.remote,
|
|
76
|
+
buffer: this.buffer,
|
|
77
|
+
settings: () => this.settings(),
|
|
78
|
+
warn: (clave, mensaje) => this.warn(clave, mensaje),
|
|
79
|
+
now: this.ahora,
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
settings() {
|
|
83
|
+
const ahora = this.ahora();
|
|
84
|
+
if (!this.ajustes || ahora - this.ajustes.leidosEn >= SETTINGS_TTL_MS) {
|
|
85
|
+
this.ajustes = { valor: (0, settings_1.readLogSettings)(this.env()), leidosEn: ahora };
|
|
86
|
+
}
|
|
87
|
+
return this.ajustes.valor;
|
|
88
|
+
}
|
|
89
|
+
/** Recibe una línea. No lanza, no espera y no hace nada si los logs están apagados o el entorno no está completo. */
|
|
90
|
+
capture(registro) {
|
|
91
|
+
if (this.ocupado)
|
|
92
|
+
return;
|
|
93
|
+
this.ocupado = true;
|
|
94
|
+
try {
|
|
95
|
+
const ajustes = this.settings();
|
|
96
|
+
if (!ajustes.enabled)
|
|
97
|
+
return;
|
|
98
|
+
if (!this.host.target()) {
|
|
99
|
+
this.warn('sin-entorno', 'GrowTickets logs: faltan GROWTICKETS_URL o GROWTICKETS_API_KEY (o no son válidas): no se manda ninguna línea.');
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
if ((0, levels_1.levelRank)(registro.level) < (0, levels_1.levelRank)(ajustes.minLevel))
|
|
103
|
+
return;
|
|
104
|
+
if (this.remote.enabled() === null)
|
|
105
|
+
void this.remote.refreshIfStale();
|
|
106
|
+
if (this.remote.enabled() === false)
|
|
107
|
+
return;
|
|
108
|
+
const grave = (0, levels_1.isSevere)(registro.level);
|
|
109
|
+
const peso = grave ? 1 : this.sampler.weight(this.ahora(), this.remote.sampleCap());
|
|
110
|
+
if (!(0, sampler_1.keep)(peso, this.opciones.random))
|
|
111
|
+
return;
|
|
112
|
+
const entrada = (0, entry_builder_1.buildEntry)(registro, { limits: this.remote.limits(), redactor: this.redactor, root: (0, runtime_1.currentDir)() });
|
|
113
|
+
if (peso > 1)
|
|
114
|
+
entrada.weight = peso;
|
|
115
|
+
this.buffer.add(entrada, grave);
|
|
116
|
+
this.start();
|
|
117
|
+
if (grave)
|
|
118
|
+
this.scheduleUrgent();
|
|
119
|
+
}
|
|
120
|
+
catch {
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
finally {
|
|
124
|
+
this.ocupado = false;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
/** Arranca el envío periódico. Varias llamadas son una. El temporizador no retiene el proceso. */
|
|
128
|
+
start() {
|
|
129
|
+
this.apagando = false;
|
|
130
|
+
if (this.temporizador)
|
|
131
|
+
return;
|
|
132
|
+
const reloj = setInterval(() => {
|
|
133
|
+
void this.flush();
|
|
134
|
+
}, this.opciones.flushIntervalMs ?? exports.FLUSH_INTERVAL_MS);
|
|
135
|
+
reloj.unref?.();
|
|
136
|
+
this.temporizador = reloj;
|
|
137
|
+
}
|
|
138
|
+
/** Manda lo acumulado dentro de unos segundos si no hay ya un envío urgente en camino. El reloj no retiene el proceso. */
|
|
139
|
+
scheduleUrgent() {
|
|
140
|
+
if (this.urgente)
|
|
141
|
+
return;
|
|
142
|
+
const reloj = setTimeout(() => {
|
|
143
|
+
this.urgente = null;
|
|
144
|
+
void this.flush();
|
|
145
|
+
}, this.opciones.urgentFlushMs ?? exports.URGENT_FLUSH_MS);
|
|
146
|
+
reloj.unref?.();
|
|
147
|
+
this.urgente = reloj;
|
|
148
|
+
}
|
|
149
|
+
/** Envía ya lo acumulado. `force` se salta el corte tras fallos. Nunca lanza. */
|
|
150
|
+
flush(force = false) {
|
|
151
|
+
return this.flusher.flush({ force });
|
|
152
|
+
}
|
|
153
|
+
/** Para el temporizador y manda lo último, esperando como mucho tres segundos. Lo llama el adaptador al cerrar la app. */
|
|
154
|
+
async shutdown() {
|
|
155
|
+
if (this.apagando)
|
|
156
|
+
return;
|
|
157
|
+
this.apagando = true;
|
|
158
|
+
if (this.temporizador) {
|
|
159
|
+
clearInterval(this.temporizador);
|
|
160
|
+
this.temporizador = null;
|
|
161
|
+
}
|
|
162
|
+
if (this.urgente) {
|
|
163
|
+
clearTimeout(this.urgente);
|
|
164
|
+
this.urgente = null;
|
|
165
|
+
}
|
|
166
|
+
let limite;
|
|
167
|
+
const agotado = new Promise((resolver) => {
|
|
168
|
+
limite = setTimeout(resolver, SHUTDOWN_MS);
|
|
169
|
+
limite.unref?.();
|
|
170
|
+
});
|
|
171
|
+
try {
|
|
172
|
+
await Promise.race([this.flusher.flush({ final: true }), agotado]);
|
|
173
|
+
}
|
|
174
|
+
finally {
|
|
175
|
+
clearTimeout(limite);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
/** Lo que hay sin enviar. Lo usan las pruebas y `doctor`. */
|
|
179
|
+
pending() {
|
|
180
|
+
return this.buffer.size() + this.flusher.state().pendientes;
|
|
181
|
+
}
|
|
182
|
+
env() {
|
|
183
|
+
return this.host.env ? this.host.env() : ((0, runtime_1.proc)()?.env ?? {});
|
|
184
|
+
}
|
|
185
|
+
warn(clave, mensaje) {
|
|
186
|
+
const ahora = this.ahora();
|
|
187
|
+
const antes = this.avisos.get(clave);
|
|
188
|
+
if (antes !== undefined && ahora - antes < WARN_EVERY_MS)
|
|
189
|
+
return;
|
|
190
|
+
this.avisos.set(clave, ahora);
|
|
191
|
+
if (this.opciones.warn)
|
|
192
|
+
this.opciones.warn(mensaje);
|
|
193
|
+
else
|
|
194
|
+
(0, runtime_1.proc)()?.stderr?.write(`[growtickets] ${mensaje}\n`);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
exports.LogMonitor = LogMonitor;
|
|
198
|
+
const KEY = Symbol.for('growtickets.logs.monitor');
|
|
199
|
+
const EXIT_KEY = Symbol.for('growtickets.logs.exit');
|
|
200
|
+
/**
|
|
201
|
+
* El monitor de ESTE proceso. Vive en `globalThis` porque un empaquetador puede cargar esta librería una vez por ruta, y cada copia
|
|
202
|
+
* tendría su cola y su temporizador: con uno solo, una línea se manda una vez.
|
|
203
|
+
*
|
|
204
|
+
* Al crearlo se engancha, una sola vez por proceso, a `beforeExit` —cuando el bucle de eventos se vacía, por ejemplo tras `app.close()`—
|
|
205
|
+
* para mandar lo último. NO se engancha a ninguna señal (`SIGTERM`, `SIGINT`): quien lo hace cambia lo que el proceso hace al recibirla.
|
|
206
|
+
*/
|
|
207
|
+
function logMonitor(host, opciones) {
|
|
208
|
+
const global = globalThis;
|
|
209
|
+
if (!global[KEY] && host) {
|
|
210
|
+
global[KEY] = new LogMonitor(host, opciones);
|
|
211
|
+
if (!global[EXIT_KEY]) {
|
|
212
|
+
global[EXIT_KEY] = true;
|
|
213
|
+
(0, runtime_1.proc)()?.on?.('beforeExit', () => {
|
|
214
|
+
void global[KEY]?.shutdown();
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
return global[KEY] ?? null;
|
|
219
|
+
}
|
|
220
|
+
/** Sólo las pruebas: olvida el monitor del proceso. */
|
|
221
|
+
function resetLogMonitor() {
|
|
222
|
+
const global = globalThis;
|
|
223
|
+
delete global[KEY];
|
|
224
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* El contexto de una línea convertido a algo que se pueda mandar por JSON SIN ejecutar nada del producto.
|
|
3
|
+
*
|
|
4
|
+
* - **Nunca se llama a un método ni a un getter del producto.** Un modelo, una conexión o un cliente HTTP en el contexto se convierte en
|
|
5
|
+
* `[object Clase]`: `toJSON()` o un getter de cualquiera podría lanzar, tardar o volcar sus datos. De un objeto corriente sólo se leen
|
|
6
|
+
* las propiedades de datos; un getter sale como `[getter]`.
|
|
7
|
+
* - Las fechas van en ISO, los `bigint` como texto, los `Buffer` y `Map`/`Set` como su tamaño, un `Error` como una línea
|
|
8
|
+
* (`[exception Clase: mensaje]`) y un ciclo como `[circular]`.
|
|
9
|
+
* - Todo con tope: cinco niveles, cien elementos por nivel y 4.096 caracteres por texto.
|
|
10
|
+
*
|
|
11
|
+
* Devuelve `null` si no hay nada que mandar.
|
|
12
|
+
*/
|
|
13
|
+
export declare function safeContext(contexto: unknown): Record<string, unknown> | null;
|
|
14
|
+
export declare function cutString(texto: string, max?: number): string;
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.safeContext = safeContext;
|
|
4
|
+
exports.cutString = cutString;
|
|
5
|
+
const MAX_DEPTH = 5;
|
|
6
|
+
const MAX_ITEMS = 100;
|
|
7
|
+
const MAX_STRING = 4096;
|
|
8
|
+
const MAX_ERROR_MESSAGE = 200;
|
|
9
|
+
/**
|
|
10
|
+
* El contexto de una línea convertido a algo que se pueda mandar por JSON SIN ejecutar nada del producto.
|
|
11
|
+
*
|
|
12
|
+
* - **Nunca se llama a un método ni a un getter del producto.** Un modelo, una conexión o un cliente HTTP en el contexto se convierte en
|
|
13
|
+
* `[object Clase]`: `toJSON()` o un getter de cualquiera podría lanzar, tardar o volcar sus datos. De un objeto corriente sólo se leen
|
|
14
|
+
* las propiedades de datos; un getter sale como `[getter]`.
|
|
15
|
+
* - Las fechas van en ISO, los `bigint` como texto, los `Buffer` y `Map`/`Set` como su tamaño, un `Error` como una línea
|
|
16
|
+
* (`[exception Clase: mensaje]`) y un ciclo como `[circular]`.
|
|
17
|
+
* - Todo con tope: cinco niveles, cien elementos por nivel y 4.096 caracteres por texto.
|
|
18
|
+
*
|
|
19
|
+
* Devuelve `null` si no hay nada que mandar.
|
|
20
|
+
*/
|
|
21
|
+
function safeContext(contexto) {
|
|
22
|
+
if (contexto === null || typeof contexto !== 'object' || Array.isArray(contexto))
|
|
23
|
+
return null;
|
|
24
|
+
const salida = walkObject(contexto, 0, new WeakSet());
|
|
25
|
+
return Object.keys(salida).length > 0 ? salida : null;
|
|
26
|
+
}
|
|
27
|
+
function walkObject(objeto, profundidad, vistos) {
|
|
28
|
+
const salida = {};
|
|
29
|
+
let n = 0;
|
|
30
|
+
vistos.add(objeto);
|
|
31
|
+
for (const clave of Object.keys(objeto)) {
|
|
32
|
+
if (++n > MAX_ITEMS) {
|
|
33
|
+
salida['…'] = 'recortado';
|
|
34
|
+
break;
|
|
35
|
+
}
|
|
36
|
+
const descriptor = Object.getOwnPropertyDescriptor(objeto, clave);
|
|
37
|
+
if (!descriptor)
|
|
38
|
+
continue;
|
|
39
|
+
if (descriptor.get || descriptor.set) {
|
|
40
|
+
salida[clave] = '[getter]';
|
|
41
|
+
continue;
|
|
42
|
+
}
|
|
43
|
+
const valor = convert(descriptor.value, profundidad + 1, vistos);
|
|
44
|
+
if (valor !== undefined)
|
|
45
|
+
salida[clave] = valor;
|
|
46
|
+
}
|
|
47
|
+
vistos.delete(objeto);
|
|
48
|
+
return salida;
|
|
49
|
+
}
|
|
50
|
+
function convert(valor, profundidad, vistos) {
|
|
51
|
+
switch (typeof valor) {
|
|
52
|
+
case 'string':
|
|
53
|
+
return cutString(valor);
|
|
54
|
+
case 'number':
|
|
55
|
+
return Number.isFinite(valor) ? valor : String(valor);
|
|
56
|
+
case 'boolean':
|
|
57
|
+
return valor;
|
|
58
|
+
case 'bigint':
|
|
59
|
+
return valor.toString();
|
|
60
|
+
case 'undefined':
|
|
61
|
+
return undefined;
|
|
62
|
+
case 'function':
|
|
63
|
+
return '[function]';
|
|
64
|
+
case 'symbol':
|
|
65
|
+
return valor.toString();
|
|
66
|
+
default:
|
|
67
|
+
break;
|
|
68
|
+
}
|
|
69
|
+
if (valor === null)
|
|
70
|
+
return null;
|
|
71
|
+
const objeto = valor;
|
|
72
|
+
if (vistos.has(objeto))
|
|
73
|
+
return '[circular]';
|
|
74
|
+
if (objeto instanceof Date)
|
|
75
|
+
return Number.isNaN(objeto.getTime()) ? '[invalid date]' : objeto.toISOString();
|
|
76
|
+
if (objeto instanceof Error)
|
|
77
|
+
return `[exception ${cutString(String(objeto.name), 80)}: ${cutString(String(objeto.message), MAX_ERROR_MESSAGE)}]`;
|
|
78
|
+
if (typeof URL !== 'undefined' && objeto instanceof URL)
|
|
79
|
+
return cutString(objeto.href);
|
|
80
|
+
if (ArrayBuffer.isView(objeto))
|
|
81
|
+
return `[Buffer ${objeto.byteLength} bytes]`;
|
|
82
|
+
if (objeto instanceof ArrayBuffer)
|
|
83
|
+
return `[Buffer ${objeto.byteLength} bytes]`;
|
|
84
|
+
if (objeto instanceof Map)
|
|
85
|
+
return `[Map ${objeto.size}]`;
|
|
86
|
+
if (objeto instanceof Set)
|
|
87
|
+
return `[Set ${objeto.size}]`;
|
|
88
|
+
if (Array.isArray(objeto)) {
|
|
89
|
+
if (profundidad >= MAX_DEPTH)
|
|
90
|
+
return '[array]';
|
|
91
|
+
vistos.add(objeto);
|
|
92
|
+
const salida = [];
|
|
93
|
+
for (let i = 0; i < objeto.length && i < MAX_ITEMS; i++)
|
|
94
|
+
salida.push(convert(objeto[i], profundidad + 1, vistos) ?? null);
|
|
95
|
+
if (objeto.length > MAX_ITEMS)
|
|
96
|
+
salida.push('…');
|
|
97
|
+
vistos.delete(objeto);
|
|
98
|
+
return salida;
|
|
99
|
+
}
|
|
100
|
+
if (isPlain(objeto))
|
|
101
|
+
return profundidad >= MAX_DEPTH ? '[object]' : walkObject(objeto, profundidad, vistos);
|
|
102
|
+
return `[object ${constructorName(objeto)}]`;
|
|
103
|
+
}
|
|
104
|
+
function isPlain(objeto) {
|
|
105
|
+
const proto = Object.getPrototypeOf(objeto);
|
|
106
|
+
return proto === null || proto === Object.prototype;
|
|
107
|
+
}
|
|
108
|
+
function constructorName(objeto) {
|
|
109
|
+
const nombre = Object.getPrototypeOf(objeto)?.constructor?.name;
|
|
110
|
+
return typeof nombre === 'string' && nombre !== '' ? cutString(nombre, 80) : 'Object';
|
|
111
|
+
}
|
|
112
|
+
function cutString(texto, max = MAX_STRING) {
|
|
113
|
+
return texto.length > max ? `${texto.slice(0, max)}…` : texto;
|
|
114
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { LogRecord } from './types';
|
|
2
|
+
export interface PinoOptions {
|
|
3
|
+
/** Mandar también las líneas por petición de Fastify. */
|
|
4
|
+
requests: boolean;
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Una línea de pino (un objeto JSON y un salto de línea) convertida en un registro. Devuelve `null` si no es una línea de pino o si es
|
|
8
|
+
* de las que no se mandan.
|
|
9
|
+
*
|
|
10
|
+
* El nivel numérico de pino (10 a 60) se lleva a uno de los ocho de PSR-3; `err` (el error ya serializado: `type`, `message`, `stack`)
|
|
11
|
+
* pasa a ser la excepción; `name` es el canal, y el resto de campos —`reqId`, `req`, `res`, lo que el producto añadió— es el contexto.
|
|
12
|
+
*/
|
|
13
|
+
export declare function recordFromPinoLine(linea: string, opciones: PinoOptions): LogRecord | null;
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.recordFromPinoLine = recordFromPinoLine;
|
|
4
|
+
const levels_1 = require("./levels");
|
|
5
|
+
/** Lo que pino escribe en cada línea y no es contexto del producto. */
|
|
6
|
+
const RESERVED = new Set(['level', 'time', 'pid', 'hostname', 'msg', 'message', 'err', 'error', 'v', 'name']);
|
|
7
|
+
/** Las dos líneas que Fastify escribe por cada petición: sin pedirlo, llenarían la central de lo que ya tiene el proxy. */
|
|
8
|
+
const REQUEST_LINES = new Set(['incoming request', 'request completed']);
|
|
9
|
+
/**
|
|
10
|
+
* Una línea de pino (un objeto JSON y un salto de línea) convertida en un registro. Devuelve `null` si no es una línea de pino o si es
|
|
11
|
+
* de las que no se mandan.
|
|
12
|
+
*
|
|
13
|
+
* El nivel numérico de pino (10 a 60) se lleva a uno de los ocho de PSR-3; `err` (el error ya serializado: `type`, `message`, `stack`)
|
|
14
|
+
* pasa a ser la excepción; `name` es el canal, y el resto de campos —`reqId`, `req`, `res`, lo que el producto añadió— es el contexto.
|
|
15
|
+
*/
|
|
16
|
+
function recordFromPinoLine(linea, opciones) {
|
|
17
|
+
if (linea.charCodeAt(0) !== 123)
|
|
18
|
+
return null;
|
|
19
|
+
let dato;
|
|
20
|
+
try {
|
|
21
|
+
const analizado = JSON.parse(linea);
|
|
22
|
+
if (analizado === null || typeof analizado !== 'object' || Array.isArray(analizado))
|
|
23
|
+
return null;
|
|
24
|
+
dato = analizado;
|
|
25
|
+
}
|
|
26
|
+
catch {
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
29
|
+
const error = errorOf(dato.err) ?? errorOf(dato.error);
|
|
30
|
+
const mensaje = typeof dato.msg === 'string' ? dato.msg : typeof dato.message === 'string' ? dato.message : '';
|
|
31
|
+
if (!opciones.requests && REQUEST_LINES.has(mensaje))
|
|
32
|
+
return null;
|
|
33
|
+
const contexto = {};
|
|
34
|
+
for (const [clave, valor] of Object.entries(dato))
|
|
35
|
+
if (!RESERVED.has(clave))
|
|
36
|
+
contexto[clave] = valor;
|
|
37
|
+
const tiempo = dato.time;
|
|
38
|
+
return {
|
|
39
|
+
time: typeof tiempo === 'number' || typeof tiempo === 'string' ? tiempo : undefined,
|
|
40
|
+
level: (0, levels_1.normalizeLevel)(dato.level) ?? 'info',
|
|
41
|
+
message: mensaje !== '' ? mensaje : typeof error?.message === 'string' ? error.message : '',
|
|
42
|
+
channel: typeof dato.name === 'string' ? dato.name : null,
|
|
43
|
+
context: contexto,
|
|
44
|
+
error,
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
function errorOf(valor) {
|
|
48
|
+
if (valor === null || typeof valor !== 'object')
|
|
49
|
+
return null;
|
|
50
|
+
const candidato = valor;
|
|
51
|
+
return typeof candidato.message === 'string' || typeof candidato.stack === 'string' ? candidato : null;
|
|
52
|
+
}
|