@jmlq/logger 0.1.0-alpha.2 → 0.1.0-alpha.21

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.
Files changed (102) hide show
  1. package/README.md +639 -0
  2. package/architecture.md +193 -0
  3. package/assets/mongo-log.png +0 -0
  4. package/assets/pg-log.png +0 -0
  5. package/dist/application/factory/index.d.ts +1 -0
  6. package/dist/{config → application/factory}/index.js +1 -2
  7. package/dist/application/factory/logger.factory.d.ts +11 -0
  8. package/dist/application/factory/logger.factory.js +71 -0
  9. package/dist/application/index.d.ts +2 -0
  10. package/dist/{presentation → application}/index.js +1 -0
  11. package/dist/application/types/index.d.ts +1 -0
  12. package/dist/application/types/index.js +17 -0
  13. package/dist/application/types/logger-factory-config.type.d.ts +28 -0
  14. package/dist/{config/interfaces/index.js → application/types/logger-factory-config.type.js} +0 -1
  15. package/dist/application/use-cases/flush-buffers.use-case.d.ts +6 -0
  16. package/dist/application/use-cases/flush-buffers.use-case.js +13 -0
  17. package/dist/application/use-cases/get-logs.use-case.d.ts +8 -0
  18. package/dist/application/use-cases/get-logs.use-case.js +24 -0
  19. package/dist/application/use-cases/index.d.ts +3 -0
  20. package/dist/application/use-cases/index.js +19 -0
  21. package/dist/application/use-cases/save-log/index.d.ts +1 -0
  22. package/dist/application/use-cases/save-log/index.js +17 -0
  23. package/dist/application/use-cases/save-log/save-log.props.d.ts +7 -0
  24. package/dist/application/use-cases/save-log/save-log.props.js +2 -0
  25. package/dist/application/use-cases/save-log.use-case.d.ts +8 -0
  26. package/dist/application/use-cases/save-log.use-case.js +27 -0
  27. package/dist/domain/index.d.ts +7 -0
  28. package/dist/domain/index.js +7 -0
  29. package/dist/domain/model/index.d.ts +3 -0
  30. package/dist/domain/model/index.js +19 -0
  31. package/dist/domain/model/log-entry.model.d.ts +8 -0
  32. package/dist/domain/model/log-entry.model.js +2 -0
  33. package/dist/domain/model/pii-options.model.d.ts +8 -0
  34. package/dist/domain/model/pii-options.model.js +2 -0
  35. package/dist/domain/model/pii-replacement-rule.d.ts +5 -0
  36. package/dist/domain/model/pii-replacement-rule.js +2 -0
  37. package/dist/domain/ports/create-logger-options.port.d.ts +7 -0
  38. package/dist/domain/ports/create-logger-options.port.js +2 -0
  39. package/dist/domain/ports/index.d.ts +4 -0
  40. package/dist/domain/ports/index.js +20 -0
  41. package/dist/domain/ports/log-datasource.port.d.ts +10 -0
  42. package/dist/domain/ports/log-datasource.port.js +2 -0
  43. package/dist/domain/ports/logger.port.d.ts +15 -0
  44. package/dist/domain/ports/logger.port.js +2 -0
  45. package/dist/domain/ports/pii-redactor.port.d.ts +5 -0
  46. package/dist/domain/ports/pii-redactor.port.js +2 -0
  47. package/dist/domain/request/index.d.ts +1 -0
  48. package/dist/domain/request/index.js +17 -0
  49. package/dist/domain/request/log-filter.request.d.ts +9 -0
  50. package/dist/domain/request/log-filter.request.js +2 -0
  51. package/dist/domain/response/index.d.ts +1 -0
  52. package/dist/domain/response/index.js +17 -0
  53. package/dist/domain/response/log.response.d.ts +8 -0
  54. package/dist/domain/response/log.response.js +2 -0
  55. package/dist/domain/services/index.d.ts +1 -1
  56. package/dist/domain/services/index.js +1 -1
  57. package/dist/domain/services/pii-redactor.service.d.ts +10 -0
  58. package/dist/domain/services/pii-redactor.service.js +68 -0
  59. package/dist/domain/types/index.d.ts +1 -0
  60. package/dist/domain/types/index.js +17 -0
  61. package/dist/domain/types/log-message.type.d.ts +1 -0
  62. package/dist/domain/types/log-message.type.js +2 -0
  63. package/dist/domain/utils/index.d.ts +3 -0
  64. package/dist/domain/utils/index.js +19 -0
  65. package/dist/domain/utils/normalize-message.util.d.ts +3 -0
  66. package/dist/domain/utils/normalize-message.util.js +8 -0
  67. package/dist/domain/utils/parse-log-level.util.d.ts +2 -0
  68. package/dist/domain/utils/parse-log-level.util.js +27 -0
  69. package/dist/domain/utils/pii-regex.util.d.ts +2 -0
  70. package/dist/domain/utils/pii-regex.util.js +13 -0
  71. package/dist/domain/value-objects/index.d.ts +1 -0
  72. package/dist/domain/value-objects/index.js +17 -0
  73. package/dist/domain/value-objects/log-level.vo.d.ts +8 -0
  74. package/dist/domain/value-objects/log-level.vo.js +13 -0
  75. package/dist/index.d.ts +9 -4
  76. package/dist/index.js +32 -7
  77. package/dist/infrastructure/index.d.ts +1 -0
  78. package/dist/infrastructure/index.js +17 -0
  79. package/dist/infrastructure/services/datasource.service.d.ts +18 -0
  80. package/dist/infrastructure/services/datasource.service.js +102 -0
  81. package/dist/infrastructure/services/index.d.ts +1 -0
  82. package/dist/infrastructure/services/index.js +17 -0
  83. package/dist/infrastructure/types/index.d.ts +1 -0
  84. package/dist/infrastructure/types/index.js +17 -0
  85. package/dist/infrastructure/types/on-data-source-error.type.d.ts +5 -0
  86. package/dist/infrastructure/types/on-data-source-error.type.js +2 -0
  87. package/package.json +37 -11
  88. package/dist/Composite/index.d.ts +0 -9
  89. package/dist/Composite/index.js +0 -54
  90. package/dist/Factory/index.d.ts +0 -5
  91. package/dist/Factory/index.js +0 -23
  92. package/dist/config/index.d.ts +0 -2
  93. package/dist/config/interfaces/index.d.ts +0 -67
  94. package/dist/config/types/index.d.ts +0 -10
  95. package/dist/config/types/index.js +0 -13
  96. package/dist/domain/services/pii-redactor.d.ts +0 -27
  97. package/dist/domain/services/pii-redactor.js +0 -139
  98. package/dist/interfaces/index.d.ts +0 -35
  99. package/dist/interfaces/index.js +0 -13
  100. package/dist/presentation/factory/index.d.ts +0 -2
  101. package/dist/presentation/factory/index.js +0 -74
  102. package/dist/presentation/index.d.ts +0 -1
package/README.md ADDED
@@ -0,0 +1,639 @@
1
+ # @jmlq/logger
2
+
3
+ Sistema de logging modular y extensible con **Arquitectura Limpia**. Soporta múltiples destinos (archivos, MongoDB, PostgreSQL) y enmascarado automático de datos sensibles (PII).
4
+
5
+ Se encarga de:
6
+
7
+ - Persistencia de logs en múltiples datasources
8
+ - Enmascarado automático de datos sensibles (PII)
9
+ - Filtros y búsquedas avanzadas
10
+ - Arquitectura extensible y testeable
11
+
12
+ ## Componentes principales expuestos
13
+
14
+ ### Factory
15
+
16
+ - **[`createLogger`](src/application/factory/logger.factory.ts)**
17
+ Factory de alto nivel que construye un `ILogger` compatible con `@jmlq/logger`, resolviendo internamente:
18
+
19
+ - Configuración de datasources múltiples (fan-out)
20
+ - Inicialización del redactor PII
21
+ - Composición de casos de uso
22
+ - Nivel mínimo de logging
23
+
24
+ > Es el **único punto recomendado de entrada** para consumidores del paquete.
25
+
26
+ ---
27
+
28
+ ### Tipos de configuración
29
+
30
+ - **[`ILoggerFactoryConfig`](src/application/types/logger-factory-config.type.ts)**
31
+ Define la configuración necesaria:
32
+ - `datasources`: Uno o múltiples datasources donde persistir logs
33
+ - `minLevel`: Nivel mínimo de logging (por defecto INFO)
34
+ - `redactor`: Instancia personalizada de redactor PII
35
+ - `redactorOptions`: Configuración para redactor interno
36
+
37
+ ---
38
+
39
+ ## Contratos (Types / Ports)
40
+
41
+ Estos tipos existen para **desacoplar el dominio y la aplicación**.
42
+ Son útiles para testing, extensiones avanzadas o integraciones personalizadas.
43
+
44
+ - **[`ILogger`](src/domain/ports/logger.port.ts)**: Interfaz principal del logger con métodos por nivel
45
+ - **[`ILogDatasource`](src/domain/ports/log-datasource.port.ts)**: Contrato para implementar datasources personalizados
46
+ - **[`LogLevel`](src/domain/value-objects/log-level.vo.ts)**: Enumeración de niveles de logging
47
+ - **[`LogFilterRequest`](src/domain/request/log-filter.request.ts)**: Filtros para consultas de logs
48
+ - **[`ILogResponse`](src/domain/response/log.response.ts)**: Formato de respuesta de logs consultados
49
+ - **[`PiiOptions`](src/domain/model/pii-options.model.ts)**: Configuración para enmascarado PII
50
+ - **[`LogEntry`](src/domain/model/log-entry.model.ts)**: Modelo de entrada de log
51
+ - **[`PiiRedactor`](src/domain/services/pii-redactor.service.ts)**: Servicio de enmascarado PII
52
+
53
+ ---
54
+
55
+ ## 📦 Instalación
56
+
57
+ ```bash
58
+ # Core del logger
59
+ npm install @jmlq/logger
60
+
61
+ # Plugins opcionales (instala según necesites)
62
+ npm install @jmlq/logger-plugin-fs # Para archivos
63
+ npm install @jmlq/logger-plugin-mongo # Para MongoDB
64
+ npm install @jmlq/logger-plugin-postgresql # Para PostgreSQL
65
+ ```
66
+
67
+ ---
68
+
69
+ ## Configuración @jmlq/logger
70
+
71
+ ### Variables de entorno
72
+
73
+ Ver cómo se configuran los adapters de `@jmlq/logger-plugin-fs`, `@jmlq/logger-plugin-mongo` y `@jmlq/logger-plugin-postgresql`.
74
+
75
+ | Plugin | Descripción | NPM |
76
+ | -------------------------------- | -------------------------- | --------------------------------------------------------------- |
77
+ | `@jmlq/logger-plugin-fs` | Persistencia en archivos | [npm](https://npmjs.com/package/@jmlq/logger-plugin-fs) |
78
+ | `@jmlq/logger-plugin-mongo` | Persistencia en MongoDB | [npm](https://npmjs.com/package/@jmlq/logger-plugin-mongo) |
79
+ | `@jmlq/logger-plugin-postgresql` | Persistencia en PostgreSQL | [npm](https://npmjs.com/package/@jmlq/logger-plugin-postgresql) |
80
+
81
+ El paquete no gestiona variables de entorno internamente, pero puedes implementar tu propia capa de configuración. Estas son las variables de entorno que se usan para configurar todos los plugins.
82
+
83
+ ```ini
84
+ # Settings @jmlq/logger
85
+ # LOGGER Plugins Configuration
86
+ # Nivel mínimo de logs
87
+ # Valores: TRACE, DEBUG, INFO, WARN, ERROR, FATAL
88
+ LOG_LEVEL=INFO
89
+ # PII Protection
90
+ LOGGER_PII_ENABLED=true
91
+ LOGGER_PII_INCLUDE_DEFAULTS=false
92
+ # FS Plugin Settings
93
+ LOGGER_FS_PATH=./logs/
94
+ # MongoDB Plugin Settings
95
+ LOGGER_MONGO_DB_URL=mongodb://root:secret@localhost:27018/?authSource=admin
96
+ LOGGER_MONGO_DB_NAME=logger_example_db
97
+ LOGGER_MONGO_COLLECTION_NAME=logs_datasource_factory_example
98
+ # PostgreSQL Plugin Settings
99
+ LOGGER_PG_CONNECTION_STRING=postgres://admin:secret@localhost:5436/logger_tests
100
+ LOGGER_PG_TABLE_NAME=logs_example_factory
101
+ LOGGER_PG_SCHEMA=public
102
+ ```
103
+
104
+ ### Interfaz de configuración del paquete
105
+
106
+ ```ts
107
+ // Path Ejemplo: src/infrastructure/logger/core/logger.bootstrap.options.ts
108
+
109
+ import { LogLevel } from "@jmlq/logger";
110
+ import { IFilesystemDatasourceOptions } from "@jmlq/logger-plugin-fs";
111
+ import { IMongoDatasourceOptions } from "@jmlq/logger-plugin-mongo";
112
+ import { IPostgresDatasourceOptions } from "@jmlq/logger-plugin-postgresql";
113
+
114
+ export interface LoggerBootstrapOptions {
115
+ minLevel: LogLevel;
116
+ pii?: {
117
+ enabled?: boolean;
118
+ whitelistKeys?: string[];
119
+ blacklistKeys?: string[];
120
+ patterns?: any[];
121
+ deep?: boolean;
122
+ includeDefaults?: boolean;
123
+ };
124
+ adapters?: {
125
+ // NOTA: Opcional, depende de las necesidades del cliente
126
+ fs?: IFilesystemDatasourceOptions;
127
+ mongo?: IMongoDatasourceOptions;
128
+ pg?: IPostgresDatasourceOptions;
129
+ };
130
+ }
131
+ ```
132
+
133
+ ### Implementación del Logger
134
+
135
+ ```ts
136
+ // Path Ejemplo: src/infrastructure/logger/core/logger.bootstrap.ts
137
+
138
+ import { type ILogDatasource, createLogger } from "@jmlq/logger";
139
+ import { FsAdapter } from "../fs/fs.adapter";
140
+ import { MongoAdapter } from "../mongo/mongo.adapter";
141
+ import { PgAdapter } from "../pg/pg.adapter";
142
+ import { LoggerBootstrapOptions } from "./logger.bootstrap.options";
143
+
144
+ export class LoggerBootstrap {
145
+ private constructor(
146
+ private readonly _logger: ReturnType<typeof createLogger>
147
+ ) {}
148
+
149
+ static async create(opts: LoggerBootstrapOptions): Promise<LoggerBootstrap> {
150
+ const dsList: ILogDatasource[] = [];
151
+
152
+ // NOTA: Opcional, depende de las necesidades del cliente
153
+ if (opts.adapters?.fs) {
154
+ const fs = FsAdapter.create(opts.adapters.fs);
155
+ if (fs) dsList.push(fs.datasource);
156
+ }
157
+ if (opts.adapters?.mongo) {
158
+ const mongo = await MongoAdapter.create(opts.adapters.mongo);
159
+ if (mongo) dsList.push(mongo.datasource);
160
+ }
161
+ if (opts.adapters?.pg) {
162
+ const pg = await PgAdapter.create(opts.adapters.pg);
163
+ if (pg) dsList.push(pg.datasource);
164
+ }
165
+ //----
166
+
167
+ if (dsList.length === 0)
168
+ throw new Error("[logger] No hay datasources válidos.");
169
+
170
+ const logger = createLogger({
171
+ datasources: dsList,
172
+ minLevel: opts.minLevel,
173
+ redactorOptions: {
174
+ enabled: opts.pii?.enabled ?? false,
175
+ deep: opts.pii?.deep ?? true,
176
+ patterns: opts.pii?.patterns ?? [],
177
+ },
178
+ });
179
+
180
+ console.log("✅ Logger creado correctamente.\n");
181
+
182
+ return new LoggerBootstrap(logger);
183
+ }
184
+
185
+ get logger() {
186
+ return this._logger;
187
+ }
188
+
189
+ // Limpia buffers pendientes antes del cierre
190
+ async flush() {
191
+ const logs = this._logger as any;
192
+ if (typeof logs.flush === "function") await logs.flush();
193
+ }
194
+
195
+ // Cierra recursos (conexiones, handles, etc.)
196
+ async dispose() {
197
+ const logs = this._logger as any;
198
+ if (typeof logs.dispose === "function") await logs.dispose();
199
+ }
200
+ }
201
+ ```
202
+
203
+ ### Inicializador del Logger
204
+
205
+ ```ts
206
+ // Path Ejemplo: src/infrastructure/logger/index.ts
207
+
208
+ import { envs } from "../../../../config/plugins";
209
+ // NOTA: envs es un archivo donde se leen variables de entorno .env
210
+ import { LoggerBootstrap } from "./core/logger.bootstrap";
211
+ import { parseLogLevel } from "./helper";
212
+ // NOTA: parseLogLevel hace un match con los valores del objeto LogLevel de @jmlq/logger
213
+
214
+ declare global {
215
+ var __LOGGER_BOOT__: Promise<LoggerBootstrap> | undefined;
216
+ }
217
+
218
+ async function init() {
219
+ return LoggerBootstrap.create({
220
+ minLevel: parseLogLevel(envs.logger.LOGGER_LEVEL),
221
+ pii: {
222
+ enabled: envs.logger.LOGGER_PII_ENABLED,
223
+ includeDefaults: envs.logger.LOGGER_PII_INCLUDE_DEFAULTS,
224
+ deep: true,
225
+ patterns: [
226
+ {
227
+ pattern: "\\b\\d{4}-\\d{4}-\\d{4}-\\d{4}\\b",
228
+ replaceWith: "****-****-****-****",
229
+ },
230
+ {
231
+ pattern: "[\\w.-]+@[\\w.-]+",
232
+ replaceWith: "***@***",
233
+ },
234
+ ],
235
+ },
236
+ adapters: {
237
+ // NOTA: Opcional, depende de las necesidades del cliente
238
+ fs: envs.logger.LOGGER_FS_PATH
239
+ ? {
240
+ basePath: envs.logger.LOGGER_FS_PATH,
241
+ fileNamePattern: "app-{yyyy}{MM}{dd}.log",
242
+ rotation: { by: "day" },
243
+ mkdir: true,
244
+ onRotate: (oldPath, newPath) => {
245
+ console.log(
246
+ ` [Rotate] Rotación completada: ${oldPath.absolutePath} → ${newPath.absolutePath}`
247
+ );
248
+ },
249
+ onError: (err) => {
250
+ console.error(" [Error Handler]", err.message);
251
+ },
252
+ }
253
+ : undefined,
254
+ mongo: envs.logger.LOGGER_MONGO_DB_URL
255
+ ? ({
256
+ dbName: envs.logger.LOGGER_MONGO_DB_NAME ?? "logs",
257
+ collectionName:
258
+ envs.logger.LOGGER_MONGO_COLLECTION_NAME ?? "app_logs",
259
+ ensureIndexes: true,
260
+ retentionDays: 7,
261
+ extraIndexes: [{ key: { level: 1 } }],
262
+ createIfMissing: true,
263
+ } as any)
264
+ : undefined,
265
+ pg: envs.logger.LOGGER_PG_CONNECTION_STRING
266
+ ? ({
267
+ schema: envs.logger.LOGGER_PG_SCHEMA ?? "public",
268
+ table: envs.logger.LOGGER_PG_TABLE_NAME ?? "logs",
269
+ createIfMissing: true,
270
+ enablePrune: true,
271
+ } as any)
272
+ : undefined,
273
+ },
274
+ });
275
+ }
276
+
277
+ // 1. Es una promesa singleton de LoggerBootstrap
278
+ // 2. Usa el operador nullish-coalescing (??) para: Reutilizar globalThis.__LOGGER_BOOT__ si ya existe.
279
+ // En caso contrario crea y memoriza (= init()) la promesa si no existe aún.
280
+ // 3. Garantiza una sola inicialización global del sistema de logging (adapters, datasources, PII, etc.)
281
+ // aunque el módulo se importe múltiples veces
282
+ export const bootReady: Promise<LoggerBootstrap> =
283
+ globalThis.__LOGGER_BOOT__ ?? (globalThis.__LOGGER_BOOT__ = init());
284
+
285
+ // 1. Es una promesa que resuelve directamente al logger
286
+ // 2. Hace un map de la promesa anterior: bootReady.then(b => b.logger).
287
+ export const loggerReady = bootReady.then((b) => b.logger);
288
+
289
+ // 1. Espera a bootReady y llama boot.flush(), que a su vez pide al logger/datasources
290
+ // que vacíen buffers pendientes (útil antes de apagar el proceso o en tests).
291
+ export async function flushLogs() {
292
+ const boot = await bootReady;
293
+ await boot.flush();
294
+ }
295
+
296
+ // 1. Espera a bootReady y llama boot.dispose(), que cierra recursos
297
+ // (conexiones a MongoDB/Postgres, file handles, etc.)
298
+ export async function disposeLogs() {
299
+ const boot = await bootReady;
300
+ await boot.dispose();
301
+ }
302
+ ```
303
+
304
+ ---
305
+
306
+ ## Configuración en Frameworks
307
+
308
+ **NOTA**: Se sugiere realizarlo en la capa `presentation` de la API que implementa `@jmlq/logger` y/o sus plugins.
309
+
310
+ ### Express
311
+
312
+ #### Crear middleware (opcional):
313
+
314
+ Se puede realizar de la forma que el usuario considere conveniente
315
+
316
+ ```ts
317
+ // Path Ejemplo: src/presentation/middlewares/attach-logger.middleware.ts
318
+
319
+ import { ILogger } from "@jmlq/logger";
320
+ import { randomUUID } from "crypto";
321
+ import { Request, Response, NextFunction } from "express";
322
+
323
+ /**
324
+ * Middleware factory que adjunta contexto de logging al request.
325
+ *
326
+ * Responsabilidades:
327
+ * 1. Inyectar un logger base accesible desde cualquier controller/middleware.
328
+ * 2. Garantizar un requestId único para trazabilidad y correlación de logs.
329
+ *
330
+ * No configura el logger ni emite logs:
331
+ * solo prepara el contexto por request.
332
+ */
333
+ export function attachLogger(base: ILogger) {
334
+ /**
335
+ * Middleware de Express ejecutado en cada request HTTP.
336
+ */
337
+ return async (req: Request, _res: Response, next: NextFunction) => {
338
+ /**
339
+ * Se adjunta el logger al objeto Request para evitar:
340
+ * - imports globales
341
+ * - singletons
342
+ * - acoplamiento directo en controllers
343
+ */
344
+ req.logger = base;
345
+
346
+ /**
347
+ * Se asigna un identificador único por request.
348
+ *
349
+ * - Si el cliente ya envía "x-request-id" (ej. API Gateway, Load Balancer),
350
+ * se reutiliza para mantener trazabilidad entre servicios.
351
+ * - Si no existe, se genera un UUID local.
352
+ */
353
+ req.requestId = (req.headers["x-request-id"] as string) ?? randomUUID();
354
+
355
+ /**
356
+ * Continúa el pipeline normal de Express.
357
+ */
358
+ next();
359
+ };
360
+ }
361
+ ```
362
+
363
+ #### Adjuntar middleware en el servidor
364
+
365
+ ```ts
366
+ // Path Ejemplo: src/presentation/server.ts
367
+
368
+ import { ILogger } from "@jmlq/logger";
369
+ import express from "express";
370
+ import { attachLogger } from "./middlewares";
371
+
372
+ export function createServer(base: ILogger) {
373
+ const app = express();
374
+ app.use(express.json()); // <-- necesario para POST JSON
375
+ app.use(attachLogger(base));
376
+
377
+ app.get("/health", (_req, res) => res.json({ ok: true }));
378
+
379
+ return app;
380
+ }
381
+ ```
382
+
383
+ #### Iniciar servidor
384
+
385
+ **NOTA**: Esto se realiza en la raíz de la API
386
+
387
+ ```ts
388
+ // Path Ejemplo: src/app.ts
389
+
390
+ import { envs } from "./config/plugins";
391
+ import { disposeLogs, flushLogs, loggerReady } from "./infrastructure/logger";
392
+ import { createServer } from "./presentation/server";
393
+
394
+ async function bootstrap() {
395
+ const logger = await loggerReady;
396
+
397
+ const app = createServer(logger);
398
+ const server = app.listen(envs.PORT, envs.HOST, () => {
399
+ console.log(`🚀 Server running at http://${envs.HOST}:${envs.PORT}`);
400
+ logger.info("http.start", { host: envs.HOST, port: envs.PORT });
401
+ });
402
+
403
+ server.on("error", async (err: any) => {
404
+ logger.error("http.listen.error", {
405
+ message: err?.message,
406
+ stack: err?.stack,
407
+ });
408
+ await flushLogs().catch(() => {});
409
+ await disposeLogs().catch(() => {});
410
+ process.exit(1);
411
+ });
412
+
413
+ // 3) Señales de apagado limpio
414
+ const shutdown = async (reason: string, code = 0) => {
415
+ logger.info("app.shutdown.begin", { reason });
416
+ server.close(async () => {
417
+ try {
418
+ await flushLogs();
419
+ } catch {}
420
+ try {
421
+ await disposeLogs();
422
+ } catch {}
423
+ logger.info("app.shutdown.end", { code });
424
+ process.exit(code);
425
+ });
426
+ };
427
+
428
+ process.on("SIGINT", () => shutdown("SIGINT"));
429
+ process.on("SIGTERM", () => shutdown("SIGTERM"));
430
+
431
+ // Fallos no controlados
432
+ process.on("unhandledRejection", async (err) => {
433
+ const e = err as any;
434
+ logger.error("unhandled.rejection", {
435
+ message: e?.message,
436
+ stack: e?.stack,
437
+ });
438
+ await shutdown("unhandledRejection", 1);
439
+ });
440
+
441
+ process.on("uncaughtException", async (err) => {
442
+ logger.error("uncaught.exception", {
443
+ message: err.message,
444
+ stack: err.stack,
445
+ });
446
+ await shutdown("uncaughtException", 1);
447
+ });
448
+ }
449
+
450
+ bootstrap().catch(async (err) => {
451
+ // Falla durante el bootstrap
452
+ const logger = await loggerReady.catch(() => null);
453
+ if (logger) {
454
+ logger.error("bootstrap.fatal", {
455
+ message: (err as Error)?.message,
456
+ stack: (err as Error)?.stack,
457
+ });
458
+ await flushLogs().catch(() => {});
459
+ await disposeLogs().catch(() => {});
460
+ } else {
461
+ // último recurso si el logger no llegó a inicializar
462
+ console.error("Fatal error (no logger):", err);
463
+ }
464
+ process.exit(1);
465
+ });
466
+ ```
467
+
468
+ ---
469
+
470
+ ## 🎯 Características Principales
471
+
472
+ ### Niveles de Log
473
+
474
+ - `TRACE` (10) - Información muy detallada
475
+ - `DEBUG` (20) - Información de depuración
476
+ - `INFO` (30) - Información general
477
+ - `WARN` (40) - Advertencias
478
+ - `ERROR` (50) - Errores
479
+ - `FATAL` (60) - Errores críticos
480
+
481
+ ### Enmascarado PII Automático
482
+
483
+ El logger detecta y enmascara automáticamente datos sensibles utilizando patrones personalizados:
484
+
485
+ ```js
486
+ patterns: [
487
+ // Tarjetas de crédito
488
+ {
489
+ pattern: "\\b\\d{4}[-\\s]?\\d{4}[-\\s]?\\d{4}[-\\s]?\\d{4}\\b",
490
+ replaceWith: "****-****-****-****",
491
+ },
492
+ // Números de cuenta bancaria
493
+ {
494
+ pattern: "\\b\\d{10,20}\\b",
495
+ replaceWith: "****ACCOUNT****",
496
+ },
497
+ // Tokens JWT
498
+ {
499
+ pattern: "eyJ[A-Za-z0-9-_=]+\\.[A-Za-z0-9-_=]+\\.?[A-Za-z0-9-_.+/=]*",
500
+ replaceWith: "****JWT****",
501
+ },
502
+ ],
503
+ ```
504
+
505
+ Por ejemplo, si se tiene la siguiente información:
506
+
507
+ ```js
508
+ [
509
+ {
510
+ id: "1",
511
+ name: "John Doe",
512
+ email: "john.doe@example.com",
513
+ card: "1234-5678-9012-3456",
514
+ },
515
+ {
516
+ id: "2",
517
+ name: "Jane Doe",
518
+ email: "jane.doe@example.com",
519
+ card: "6258-3842-9524-3251",
520
+ },
521
+ ];
522
+ ```
523
+
524
+ Los logs registrarán lo siguiente:
525
+
526
+ > - **Archivo .log** (`@jmlq/logger-plugin-fs`)
527
+
528
+ ```js
529
+ {
530
+ "level": 30,
531
+ "message": "Users listed successfully",
532
+ "meta": {
533
+ "count": 2,
534
+ "result": [
535
+ {
536
+ "id": "1",
537
+ "name": "John Doe",
538
+ "email": "***@***",
539
+ "card": "****-****-****-****"
540
+ },
541
+ {
542
+ "id": "2",
543
+ "name": "Jane Doe",
544
+ "email": "***@***",
545
+ "card": "****-****-****-****"
546
+ }
547
+ ]
548
+ },
549
+ "timestamp": 1765400661565,
550
+ "_id": "6939e0556925736129e1008d"
551
+ }
552
+ ```
553
+
554
+ > - **MongoDB** (`@jmlq/logger-plugin-mongo`)
555
+
556
+ ![MongoDB log](assets/mongo-log.png)
557
+
558
+ > - **PostgreSQL** (`@jmlq/logger-plugin-postgresql`)
559
+
560
+ ![PostgreSQL log](assets/pg-log.png)
561
+
562
+ ### Filtros y Consultas
563
+
564
+ #### 1. Obtener todos los logs de nivel WARN o superior (retorna WARN, ERROR y FATAL)
565
+
566
+ ```ts
567
+ const errors = await _req.logger?.getLogs({
568
+ levelMin: parseLogLevel("WARN"),
569
+ });
570
+ ```
571
+
572
+ Resultado:
573
+
574
+ ```ini
575
+ [2025-12-10T21:19:58.804Z] [50] app.error {"name":"AppError","code":"INTERNAL","message":"Unhandled exception","retryable":false,"meta":null,"cause":{"cause":{}},"stack":"AppError: Unhandled exception\n at AppError.internal (D:\\Fuentes\\Core\\ml-dev-rest-api\\dist\\shared\\errors\\app-error.js:61:16)\n at D:\\Fuentes\\Core\\ml-dev-rest-api\\dist\\presentation\\middlewares\\http\\error.middleware.js:36:40\n at Layer.handleError (D:\\Fuentes\\Core\\ml-dev-rest-api\\node_modules\\router\\lib\\layer.js:116:17)\n
576
+ at trimPrefix (D:\\Fuentes\\Core\\ml-dev-rest-api\\node_modules\\router\\index.js:340:13)\n at D:\\Fuentes\\Core\\ml-dev-rest-api\\node_modules\\router\\index.js:297:9\n at processParams (D:\\Fuentes\\Core\\ml-dev-rest-api\\node_modules\\router\\index.js:582:12)\n at Immediate.next (D:\\Fuentes\\Core\\ml-dev-rest-api\\node_modules\\router\\index.js:291:5)\n at Immediate._onImmediate (D:\\Fuentes\\Core\\ml-dev-rest-api\\node_modules\\router\\index.js:688:15)\n at process.processImmediate (node:internal/timers:485:21)"}
577
+ ```
578
+
579
+ **NOTA**: `parseLogLevel` es un helper para convertir un `string` a `LogLevel`.
580
+
581
+ #### 2. Filtrar por rango de fechas
582
+
583
+ ```ts
584
+ const yesterday = Date.now() - 24 * 60 * 60 * 1000;
585
+ const recentLogs = await _req.logger?.getLogs({
586
+ since: yesterday,
587
+ until: Date.now(),
588
+ });
589
+ ```
590
+
591
+ Resultado:
592
+
593
+ ```ini
594
+ [2025-12-10T22:21:58.500Z] [30] app.shutdown.begin {"reason":"SIGINT"}
595
+ [2025-12-10T21:12:59.216Z] [50] app.error {"name":"AppError","code":"INTERNAL","message":"Unhandled exception","retryable":false,"meta":null,"cause":{"cause":{}},"stack":"AppError: Unhandled exception\n at AppError.internal (D:\\Fuentes\\Core\\ml-dev-rest-api\\dist\\shared\\errors\\app-error.js:61:16)"}
596
+ [2025-12-10T21:04:21.565Z] [30] Users listed successfully {"count":2,"result":[{"id":"1","name":"John Doe","email":"***@***","card":"****-****-****-****"},{"id":"2","name":"Jane Doe","email":"***@***","card":"****-****-****-****"}]}
597
+ [2025-12-10T21:03:32.910Z] [30] http.start {"host":"127.0.0.1","port":3000}
598
+ ```
599
+
600
+ #### 3. Búsqueda por texto (en el campo mensaje del log)
601
+
602
+ ```ts
603
+ const userLogs = await _req.logger?.getLogs({
604
+ query: "Users",
605
+ });
606
+ ```
607
+
608
+ Resultado:
609
+
610
+ ```ini
611
+ [2025-12-10T22:58:09.874Z] [30] Users listed successfully {"count":2,"result":[{"id":"1","card":"****-****-****-****","name":"John Doe","email":"***@***"},{"id":"2","card":"****-****-****-****","name":"Jane Doe","email":"***@***"}]}
612
+ [2025-12-10T22:57:28.483Z] [30] Users listed successfully {"count":2,"result":[{"id":"1","name":"John Doe","email":"***@***","card":"****-****-****-****"},{"id":"2","name":"Jane Doe","email":"***@***","card":"****-****-****-****"}]}
613
+ ```
614
+
615
+ #### 4. Combinación de filtros
616
+
617
+ ```ts
618
+ const logs = await _req.logger?.getLogs({
619
+ levelMin: parseLogLevel("INFO"),
620
+ since: Date.now() - 2 * 60 * 60 * 1000, // Últimas 2 horas
621
+ query: "successfully",
622
+ });
623
+ ```
624
+
625
+ Resultado:
626
+
627
+ ```ini
628
+ [2025-12-10T23:05:31.791Z] [30] Users listed successfully {"count":2,"result":[{"id":"1","name":"John Doe","email":"***@***","card":"****-****-****-****"},{"id":"2","name":"Jane Doe","email":"***@***","card":"****-****-****-****"}]}
629
+ [2025-12-10T23:05:31.791Z] [30] Users listed successfully {"count":2,"result":[{"id":"1","card":"****-****-****-****","name":"John Doe","email":"***@***"},{"id":"2","card":"****-****-****-****","name":"Jane Doe","email":"***@***"}]}
630
+ [2025-12-10T22:58:09.874Z] [30] Users listed successfully {"count":2,"result":[{"id":"1","name":"John Doe","email":"***@***","card":"****-****-****-****"},{"id":"2","name":"Jane Doe","email":"***@***","card":"****-****-****-****"}]}
631
+ ```
632
+
633
+ ## 📄 Más Información
634
+
635
+ - **[Arquitectura Detallada](./architecture.md)** - Documentación técnica completa
636
+
637
+ ## 📝 Licencia
638
+
639
+ MIT © Mauricio Lahuasi