@jmlq/logger 0.1.0-alpha.12 → 0.1.0-alpha.14

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/install.md CHANGED
@@ -1,367 +1,632 @@
1
- # Guía de Instalación y Configuración para @jmlq/logger
1
+ # GUÍA DE CONFIGURACIÓN @jmlq/logger Y SUS PLUGINS
2
2
 
3
- ## Instalación
3
+ ## PASO 1 – BOOTSTRAP DEL LOGGER CORE
4
4
 
5
- ```bash
6
- npm install @jmlq/logger
5
+ ### 1.1. OPCIONES DE BOOTSTRAP
6
+
7
+ Path sugerido: `src/infrastructure/adapters/jmlq/logger/core/logger.bootstrap.options.ts`
8
+
9
+ `LoggerBootstrapOptions` es un **contrato de configuración** usado al momento de inicializar el logger. Define:
10
+
11
+ - El **nivel mínimo de log** permitido.
12
+ - Configuración de **redacción de PII** (datos sensibles).
13
+ - Los **datasources** del logger que se deben habilitar (FS, Mongo, PostgreSQL).
14
+
15
+ Describe cómo debe construirse el logger, qué debe censurar, y a dónde deben enviarse los logs.
16
+
17
+ ```ts
18
+ import { LogLevel } from "@jmlq/logger";
19
+ import { IFilesystemDatasourceOptions } from "@jmlq/logger-plugin-fs";
20
+ import { MongoPluginConfig } from "@jmlq/logger-plugin-mongo";
21
+ import { PostgresPluginConfig } from "@jmlq/logger-plugin-postgresql";
22
+
23
+ export interface LoggerBootstrapOptions {
24
+ minLevel: LogLevel;
25
+ pii?: {
26
+ enabled?: boolean;
27
+ whitelistKeys?: string[];
28
+ blacklistKeys?: string[];
29
+ patterns?: any[];
30
+ deep?: boolean;
31
+ includeDefaults?: boolean;
32
+ };
33
+ adapters?: {
34
+ // NOTA: Opcionales depende de las necesidades del cliente
35
+ fs?: IFilesystemDatasourceOptions;
36
+ mongo?: MongoPluginConfig;
37
+ pg?: PostgresPluginConfig;
38
+ };
39
+ }
7
40
  ```
8
41
 
9
- ## Configuración Básica
10
-
11
- ### NestJS
12
-
13
- 1. Crear el módulo de Logger
14
-
15
- ```typescript
16
- // src/logger/logger.module.ts
17
- import { Module, Global } from "@nestjs/common";
18
- import { ConfigModule, ConfigService } from "@nestjs/config";
19
- import { LoggerFactory } from "@jmlq/logger";
20
-
21
- @Global()
22
- @Module({
23
- imports: [ConfigModule],
24
- providers: [
25
- {
26
- provide: "LOGGER",
27
- useFactory: async (configService: ConfigService) => {
28
- // Configurar datasources según necesidades
29
- const datasources = [];
30
-
31
- // Ejemplo: Plugin de filesystem (requiere @jmlq/logger-plugin-fs)
32
- if (configService.get("LOG_FS_ENABLED") === "true") {
33
- const { FileSystemDatasource } = await import(
34
- "@jmlq/logger-plugin-fs"
35
- );
36
- datasources.push(
37
- new FileSystemDatasource({
38
- basePath: configService.get("LOG_FS_PATH", "./logs"),
39
- })
40
- );
41
- }
42
-
43
- return LoggerFactory.create({
44
- datasources,
45
- minLevel: configService.get("LOG_LEVEL", "info"),
46
- redactorOptions: {
47
- enabled: true,
48
- deep: true,
49
- includeDefaults: true,
50
- },
51
- });
42
+ - `minLevel`: nivel mínimo de log (ss de tipo `LogLevel` de `@jmlq/logger`).
43
+ - `pii`: configuración de redacción (redaction) de datos sensibles.
44
+ - `adapters`: configuración específica para cada plugin (**todos opcionales**).
45
+
46
+ ---
47
+
48
+ ### 1.2. IMPLEMENTACIÓN DEL BOOTSTRAP
49
+
50
+ Path sugerido: `src/infrastructure/adapters/jmlq/logger/core/logger.bootstrap.ts`
51
+
52
+ La clase `LoggerBootstrap` es un componente de infraestructura cuyo objetivo es:
53
+
54
+ - Crear e inicializar el **logger principal** de la aplicación.
55
+ - Construir la lista de **datasources** (FS, Mongo, PostgreSQL) según la configuración del usuario.
56
+ - Exponer funciones para **flush** y **dispose**, importantes para apagado limpio.
57
+ - Encapsular la instancia del logger para no exponer detalles internos.
58
+
59
+ La clase completa se encarga de arrancar correctamente el sistema de logging.
60
+
61
+ ```ts
62
+ import { type ILogDatasource, LoggerFactory } from "@jmlq/logger";
63
+ import { FsAdapter } from "../fs/fs.adapter";
64
+ import { MongoAdapter } from "../mongo/mongo.adapter";
65
+ import { PgAdapter } from "../pg/pg.adapter";
66
+ import { LoggerBootstrapOptions } from "./logger.bootstrap.options";
67
+
68
+ export class LoggerBootstrap {
69
+ private constructor(
70
+ private readonly _logger: ReturnType<typeof LoggerFactory.create>
71
+ ) {}
72
+
73
+ /**
74
+ * Este es el punto de entrada para construir el logger.
75
+ */
76
+ static async create(opts: LoggerBootstrapOptions): Promise<LoggerBootstrap> {
77
+ // Almacena los plugins inicializados.
78
+ const dsList: ILogDatasource[] = [];
79
+
80
+ // Inyectar datasources
81
+ // NOTA: Opcionales depende de las necesidades del cliente
82
+ // FS si está configurado
83
+ if (opts.adapters?.fs) {
84
+ const fs = FsAdapter.create(opts.adapters.fs);
85
+ if (fs) dsList.push(fs.datasource);
86
+ }
87
+ // MongoDB si está configurado
88
+ if (opts.adapters?.mongo) {
89
+ const mongo = await MongoAdapter.create(opts.adapters.mongo);
90
+ if (mongo) dsList.push(mongo.datasource);
91
+ }
92
+ // Postgresql si está configurado
93
+ if (opts.adapters?.pg) {
94
+ const pg = await PgAdapter.create(opts.adapters.pg);
95
+ if (pg) dsList.push(pg.datasource);
96
+ }
97
+
98
+ if (dsList.length === 0)
99
+ throw new Error("[logger] No hay datasources válidos.");
100
+
101
+ // Crear el logger central
102
+ const logger = LoggerFactory.create({
103
+ datasources: dsList,
104
+ minLevel: opts.minLevel,
105
+ redactorOptions: {
106
+ enabled: opts.pii?.enabled ?? false,
107
+ deep: opts.pii?.deep ?? true,
108
+ patterns: opts.pii?.patterns ?? [],
52
109
  },
53
- inject: [ConfigService],
54
- },
55
- ],
56
- exports: ["LOGGER"],
57
- })
58
- export class LoggerModule {}
110
+ });
111
+
112
+ console.log("✅ Logger creado correctamente.\n");
113
+
114
+ // Retornar la instancia final
115
+ return new LoggerBootstrap(logger);
116
+ }
117
+
118
+ // Permite acceder al logger real pero manteniendo encapsulación.
119
+ get logger() {
120
+ return this._logger;
121
+ }
122
+
123
+ // Fuerza a que todos los logs pendientes se escriban en los datasources.
124
+ // Lo usa el ciclo de vida del servidor (shutdown, errores críticos, etc.).
125
+ async flush() {
126
+ const logs = this._logger as any;
127
+ if (typeof logs.flush === "function") await logs.flush();
128
+ }
129
+
130
+ // Cierra conexiones y limpia recursos (como conexiones a BD).
131
+ // Asegura un apagado ordenado.
132
+ async dispose() {
133
+ const logs = this._logger as any;
134
+ if (typeof logs.dispose === "function") await logs.dispose();
135
+ }
136
+ }
59
137
  ```
60
138
 
61
- 2. Usar en servicios
139
+ ---
62
140
 
63
- ```typescript
64
- // src/users/users.service.ts
65
- import { Injectable, Inject } from "@nestjs/common";
66
- import { ILogger } from "@jmlq/logger";
141
+ ## PASO 2 – CONFIGURAR PLUGINS / DATASOURCES
142
+
143
+ ### 2.1. Plugin FileSystem (`@jmlq/logger-plugin-fs`)
144
+
145
+ Path sugerido: `src/infrastructure/adapters/jmlq/logger/fs/fs.adapter.ts`
67
146
 
68
- @Injectable()
69
- export class UsersService {
70
- constructor(@Inject("LOGGER") private readonly logger: ILogger) {}
147
+ `FsAdapter` es un **adaptador de infraestructura** cuyo propósito es `crear`, `inicializar` y `exponer` un `datasource` de logs basado en archivos (FileSystem) usando el plugin `@jmlq/logger-plugin-fs`.
71
148
 
72
- async createUser(userData: any) {
149
+ ```ts
150
+ import {
151
+ // Función del plugin que crea realmente el datasource de FileSystem.
152
+ createFsDatasource,
153
+ // Tipo de configuración que el plugin requiere
154
+ IFilesystemDatasourceOptions,
155
+ } from "@jmlq/logger-plugin-fs";
156
+ // Interfaz estándar que todos los datasources deben implementar para funcionar dentro de @jmlq/logger
157
+ import type { ILogDatasource } from "@jmlq/logger";
158
+
159
+ export class FsAdapter {
160
+ private constructor(private readonly ds: ILogDatasource) {}
161
+
162
+ // Este es el punto central del adaptador.
163
+ static create(opts: IFilesystemDatasourceOptions): FsAdapter | undefined {
164
+ try {
165
+ // Se inicializa el plugin.
166
+ // Se configuran rutas, políticas de rotación, serializer, etc.
167
+ // El datasource queda listo para recibir logs.
168
+ const ds = createFsDatasource(opts);
169
+ console.log("[logger] Conectado a FS para logs");
170
+ return new FsAdapter(ds);
171
+ } catch (e: any) {
172
+ console.warn("[logger] FS deshabilitado:", e?.message ?? e);
173
+ }
174
+ }
175
+ // Permite a otras capas (especialmente LoggerBootstrap) obtener el datasource real que necesita LoggerFactory.create
176
+ get datasource(): ILogDatasource {
177
+ return this.ds;
178
+ }
179
+ }
180
+ ```
181
+
182
+ ---
183
+
184
+ ### 2.2. Plugin MongoDB (`@jmlq/logger-plugin-mongo`)
185
+
186
+ Path sugerido: `src/infrastructure/adapters/jmlq/logger/mongo/mongo.adapter.ts`
187
+
188
+ `MongoAdapter` es un **adaptador de infraestructura** cuyo propósito es `crear`, `inicializar` y `exponer` un `datasource` de los logs almacenados en `MongoDB` usando el plugin `@jmlq/logger-plugin-mongo`.
189
+
190
+ ```ts
191
+ // Interfaz estándar que todos los datasources deben implementar para funcionar dentro de @jmlq/logger
192
+ import type { ILogDatasource } from "@jmlq/logger";
193
+ import {
194
+ // Función del plugin que crea realmente el datasource de MongoDb.
195
+ createMongoDatasource,
196
+ // Tipo de configuración que el plugin requiere
197
+ MongoPluginConfig,
198
+ } from "@jmlq/logger-plugin-mongo";
199
+
200
+ export class MongoAdapter {
201
+ private constructor(private readonly ds: ILogDatasource) {}
202
+
203
+ // Este es el punto central del adaptador.
204
+ static async create(
205
+ opts: MongoPluginConfig
206
+ ): Promise<MongoAdapter | undefined> {
73
207
  try {
74
- await this.logger.info("Creando usuario", { userData });
75
- // ... lógica del servicio
76
- await this.logger.info("Usuario creado exitosamente", {
77
- userId: result.id,
78
- });
79
- return result;
80
- } catch (error) {
81
- await this.logger.error("Error al crear usuario", {
82
- error: error.message,
83
- userData,
84
- });
85
- throw error;
208
+ const ds = await createMongoDatasource(opts);
209
+ console.log("[logger] Conectado a MongoDB para logs");
210
+ return new MongoAdapter(ds);
211
+ } catch (e: any) {
212
+ console.warn("[logger] MongoDB deshabilitado:", e?.message ?? e);
86
213
  }
87
214
  }
215
+ get datasource(): ILogDatasource {
216
+ return this.ds;
217
+ }
88
218
  }
89
219
  ```
90
220
 
91
- ### Express
221
+ ---
92
222
 
93
- 1. Configurar logger en app.js
223
+ ### 2.3. Plugin PostgreSQL (`@jmlq/logger-plugin-postgresql`)
224
+
225
+ Path sugerido: `src/infrastructure/adapters/jmlq/logger/pg/pg.adapter.ts`
226
+
227
+ `PgAdapter` es un **adaptador de infraestructura** cuyo propósito es `crear`, `inicializar` y `exponer` un `datasource` de los logs almacenados en `Postgresal` usando el plugin `@jmlq/logger-plugin-postgresql`.
94
228
 
95
229
  ```ts
96
- // src/logger.ts
97
- import { LoggerFactory, LogLevel } from "@jmlq/logger";
98
-
99
- export const createLogger = async () => {
100
- const datasources = [];
101
-
102
- // Configurar datasources según variables de entorno
103
- if (process.env.LOG_FS_ENABLED === "true") {
104
- const { FileSystemDatasource } = await import("@jmlq/logger-plugin-fs");
105
- datasources.push(
106
- new FileSystemDatasource({
107
- basePath: process.env.LOG_FS_PATH || "./logs",
108
- })
109
- );
230
+ // Interfaz estándar que todos los datasources deben implementar para funcionar dentro de @jmlq/logger
231
+ import type { ILogDatasource } from "@jmlq/logger";
232
+ import {
233
+ // Función del plugin que crea realmente el datasource de Postgresql.
234
+ createPostgresDatasource,
235
+ // Tipo de configuración que el plugin requiere
236
+ PostgresPluginConfig,
237
+ } from "@jmlq/logger-plugin-postgresql";
238
+
239
+ export class PgAdapter {
240
+ private constructor(private readonly ds: ILogDatasource) {}
241
+
242
+ // Este es el punto central del adaptador.
243
+ static async create(
244
+ opts: PostgresPluginConfig
245
+ ): Promise<PgAdapter | undefined> {
246
+ try {
247
+ const ds = await createPostgresDatasource(opts);
248
+ console.log("[logger] Conectado a PostgreSQL para logs");
249
+ return new PgAdapter(ds);
250
+ } catch (e: any) {
251
+ console.warn("[logger] PostgreSQL deshabilitado:", e?.message ?? e);
252
+ }
110
253
  }
111
254
 
112
- return LoggerFactory.create({
113
- datasources,
114
- minLevel: process.env.LOG_LEVEL || LogLevel.INFO,
115
- redactorOptions: {
116
- enabled: true,
255
+ get datasource(): ILogDatasource {
256
+ return this.ds;
257
+ }
258
+ }
259
+ ```
260
+
261
+ ### 2.4. Configuración Global @jmlq/Logger
262
+
263
+ Path sugerido: `src/infrastructure/adapters/jmlq/logger/index.ts`
264
+
265
+ Este archivo implementa el **bootstrap global** y singleton del sistema de logging de tu proyecto.
266
+ En términos simples: garantiza que **el logger, sus adapters y sus reglas de PII se inicialicen una sola vez**, sin importar cuántas veces se importe este archivo en distintos módulos.
267
+
268
+ ```ts
269
+ import { envs } from "../../../../config/plugins";
270
+ import { LoggerBootstrap } from "./core/logger.bootstrap";
271
+ import { parseLogLevel } from "./helper";
272
+
273
+ // Promesa Singleton
274
+ declare global {
275
+ var __LOGGER_BOOT__: Promise<LoggerBootstrap> | undefined;
276
+ }
277
+
278
+ // Construye toda la infraestructura del logger
279
+ async function init() {
280
+ // Envía la configuración del logger en envs (se puede usar .env)
281
+ return LoggerBootstrap.create({
282
+ // Nivel mínimo de log
283
+ minLevel: parseLogLevel(envs.logger.LOGGER_LEVEL),
284
+ // Configuración de PII (ocultamiento de datos sensibles)
285
+ pii: {
286
+ // Activación global
287
+ enabled: envs.logger.LOGGER_PII_ENABLED,
288
+ // Incluir reglas de validación declaradas en @jmlq/logger
289
+ includeDefaults: envs.logger.LOGGER_PII_INCLUDE_DEFAULTS,
290
+ // Incluye recursión profunda
117
291
  deep: true,
118
- includeDefaults: true,
119
292
  patterns: [
293
+ // Patrón para ocultar tarjetas
120
294
  {
121
295
  pattern: "\\b\\d{4}-\\d{4}-\\d{4}-\\d{4}\\b",
122
296
  replaceWith: "****-****-****-****",
123
297
  },
298
+ // Patrón para ocultar correos
299
+ {
300
+ pattern: "[\\w.-]+@[\\w.-]+",
301
+ replaceWith: "***@***",
302
+ },
303
+ // NOTA: Se pueden agregar mas reglas
124
304
  ],
125
305
  },
306
+ adapters: {
307
+ // NOTA: Opcionales depende de las necesidades del cliente
308
+ // Adapter FS
309
+ fs: envs.logger.LOGGER_FS_PATH
310
+ ? {
311
+ // Path donde se guarda el log
312
+ basePath: envs.logger.LOGGER_FS_PATH,
313
+ // Patrón de archivo por fecha
314
+ fileNamePattern: "app-{yyyy}{MM}{dd}.log",
315
+ // Tipo de Rotacion (Generación de un nuevo archivo puede ser por día, peso, etc)
316
+ rotation: { by: "day" },
317
+ // Crear directorio nuevo
318
+ mkdir: true,
319
+ // Darle formato a la trama
320
+ serializer: {
321
+ serialize(log: any) {
322
+ // Serializer personalizado
323
+ return JSON.stringify(log, null, 2);
324
+ },
325
+ },
326
+ // Se ejecuta acción cuando se genera un nuevo archivo (Se podría enviar un email por ejempo)
327
+ onRotate: (oldPath, newPath) => {
328
+ console.log(
329
+ ` [Rotate] Rotación completada: ${oldPath.absolutePath} → ${newPath.absolutePath}`
330
+ );
331
+ },
332
+ // Se ejecuta acción cuando se presenta un error
333
+ onError: (err) => {
334
+ console.error(" [Error Handler]", err.message);
335
+ },
336
+ }
337
+ : undefined,
338
+ // Adapter MongoDB
339
+ mongo: envs.logger.LOGGER_MONGO_DB_URL
340
+ ? {
341
+ url: envs.logger.LOGGER_MONGO_DB_URL,
342
+ dbName: envs.logger.LOGGER_MONGO_DB_NAME ?? "logs",
343
+ collectionName:
344
+ envs.logger.LOGGER_MONGO_COLLECTION_NAME ?? "app_logs",
345
+ ensureIndexes: true,
346
+ // Tiempo de retención del log
347
+ retentionDays: 7,
348
+ // Crear Index extra (opcional)
349
+ extraIndexes: [{ key: { level: 1 } }],
350
+ // writeOrdered: true,
351
+ createIfMissing: true,
352
+ }
353
+ : undefined,
354
+ // Adapter Postgresql
355
+ pg: envs.logger.LOGGER_PG_CONNECTION_STRING
356
+ ? {
357
+ connectionString: envs.logger.LOGGER_PG_CONNECTION_STRING,
358
+ table: envs.logger.LOGGER_PG_TABLE_NAME ?? "app_logs",
359
+ schema: envs.logger.LOGGER_PG_SCHEMA ?? "public",
360
+ createIfMissing: true,
361
+ // Tiempo de retención del log
362
+ retentionDays: 7,
363
+ }
364
+ : undefined,
365
+ //----
366
+ },
126
367
  });
127
- };
128
- ```
368
+ }
129
369
 
130
- 2. Middleware de logging
370
+ // Inicializar promesa Singleton
371
+ export const bootReady: Promise<LoggerBootstrap> =
372
+ globalThis.__LOGGER_BOOT__ ?? (globalThis.__LOGGER_BOOT__ = init());
131
373
 
132
- ```ts
133
- // src/middleware/logger.middleware.ts
134
- import { Request, Response, NextFunction } from "express";
135
- import { ILogger } from "@jmlq/logger";
374
+ // Acceso directo al logger
375
+ export const loggerReady = bootReady.then((b) => b.logger);
136
376
 
137
- export const createLoggerMiddleware = (logger: ILogger) => {
138
- return async (req: Request, res: Response, next: NextFunction) => {
139
- const startTime = Date.now();
377
+ // Vaciar buffers antes de terminar el proceso
378
+ export async function flushLogs() {
379
+ const boot = await bootReady;
380
+ await boot.flush();
381
+ }
140
382
 
141
- await logger.info("Request iniciado", {
142
- method: req.method,
143
- url: req.url,
144
- ip: req.ip,
145
- userAgent: req.get("User-Agent"),
146
- });
383
+ // Cerrar recursos
384
+ export async function disposeLogs() {
385
+ const boot = await bootReady;
386
+ await boot.dispose();
387
+ }
388
+ ```
147
389
 
148
- res.on("finish", async () => {
149
- const duration = Date.now() - startTime;
150
- const level = res.statusCode >= 400 ? "error" : "info";
390
+ **NOTA**: `parseLogLevel` es un helper para convertir un `string` en `LogLevel`:
151
391
 
152
- await logger[level]("Request completado", {
153
- method: req.method,
154
- url: req.url,
155
- statusCode: res.statusCode,
156
- duration,
157
- });
158
- });
392
+ ```ts
393
+ import { LogLevel } from "@jmlq/logger";
159
394
 
160
- next();
161
- };
162
- };
163
- ```
395
+ export function parseLogLevel(value?: string): LogLevel {
396
+ if (!value) return LogLevel.DEBUG; // fallback seguro
164
397
 
165
- 3. Usar en la aplicación
398
+ const normalized = value.toUpperCase();
166
399
 
167
- ```ts
168
- // src/app.ts
169
- import express from "express";
170
- import { createLogger } from "./logger";
171
- import { createLoggerMiddleware } from "./middleware/logger.middleware";
172
-
173
- const app = express();
174
- const logger = await createLogger();
175
-
176
- app.use(createLoggerMiddleware(logger));
177
-
178
- app.get("/users/:id", async (req, res) => {
179
- try {
180
- await logger.info("Obteniendo usuario", { userId: req.params.id });
181
- // ... lógica
182
- res.json(user);
183
- } catch (error) {
184
- await logger.error("Error al obtener usuario", {
185
- userId: req.params.id,
186
- error: error.message,
187
- });
188
- res.status(500).json({ error: "Internal server error" });
189
- }
190
- });
400
+ const map: Record<string, LogLevel> = {
401
+ TRACE: LogLevel.TRACE,
402
+ DEBUG: LogLevel.DEBUG,
403
+ INFO: LogLevel.INFO,
404
+ WARN: LogLevel.WARN,
405
+ ERROR: LogLevel.ERROR,
406
+ FATAL: LogLevel.FATAL,
407
+ };
408
+
409
+ return map[normalized] ?? LogLevel.DEBUG; // fallback en caso de valor inválido
410
+ }
191
411
  ```
192
412
 
193
- ## Configuración de PII (Datos Sensibles)
413
+ ---
194
414
 
195
- ### Patrones Predefinidos
415
+ ## PASO 3 – INTEGRACIÓN CON EXPRESS
196
416
 
197
- El logger incluye patrones comunes para enmascarar datos sensibles:
417
+ ### 3.1. Extender `Request` con logger y requestId
418
+
419
+ Path sugerido: `src/types/express.d.ts`
198
420
 
199
421
  ```ts
200
- const logger = LoggerFactory.create({
201
- datasources,
202
- redactorOptions: {
203
- enabled: true,
204
- deep: true, // Busca en objetos anidados
205
- includeDefaults: true, // Incluye patrones predefinidos (email, teléfono, etc.)
206
- },
207
- });
422
+ import "express";
423
+ import type { ILogger } from "@jmlq/logger";
424
+
425
+ // Cada req trae un logger y un requestId.
426
+ declare module "express-serve-static-core" {
427
+ interface Request {
428
+ logger?: ILogger;
429
+ requestId?: string;
430
+ }
431
+ }
208
432
  ```
209
433
 
210
- Patrones Personalizados
434
+ Esto permite escribir `req.logger` y `req.requestId` con type safety.
435
+
436
+ ### 3.2. Crear el servidor y adjuntar el logger
437
+
438
+ Path sugerido: `src/presentation/server.ts`
211
439
 
212
440
  ```ts
213
- const logger = LoggerFactory.create({
214
- datasources,
215
- redactorOptions: {
216
- enabled: true,
217
- deep: true,
218
- includeDefaults: true,
219
- patterns: [
220
- // Tarjetas de crédito
221
- {
222
- pattern: "\\b\\d{4}[-\\s]?\\d{4}[-\\s]?\\d{4}[-\\s]?\\d{4}\\b",
223
- replaceWith: "****-****-****-****",
224
- },
225
- // CURP (México)
226
- {
227
- pattern: "[A-Z]{4}\\d{6}[HM][A-Z]{5}[A-Z0-9]\\d",
228
- replaceWith: "****CURP****",
229
- },
230
- // Números de cuenta bancaria
231
- {
232
- pattern: "\\b\\d{10,20}\\b",
233
- replaceWith: "****ACCOUNT****",
234
- },
235
- // Tokens JWT
236
- {
237
- pattern: "eyJ[A-Za-z0-9-_=]+\\.[A-Za-z0-9-_=]+\\.?[A-Za-z0-9-_.+/=]*",
238
- replaceWith: "****JWT****",
239
- },
240
- ],
241
- },
242
- });
243
- ```
441
+ import { ILogger } from "@jmlq/logger";
442
+ import express, { Request, Response, NextFunction } from "express";
443
+ import router from "./routes";
444
+ import { randomUUID } from "crypto";
445
+
446
+ // Middleware para adjuntar logger por request
447
+ function attachLogger(base: ILogger) {
448
+ return async (req: Request, _res: Response, next: NextFunction) => {
449
+ req.logger = base;
450
+ req.requestId = (req.headers["x-request-id"] as string) ?? randomUUID();
451
+ next();
452
+ };
453
+ }
244
454
 
245
- ## Filtrado de Registros
455
+ export function createServer(base: ILogger) {
456
+ const app = express();
457
+ app.use(express.json());
458
+ app.use(attachLogger(base));
246
459
 
247
- ### Filtros Básicos
460
+ // Aquí se montan las rutas que llamarán a tus casos de uso
461
+ // <-- los handlers pueden usar req.logger
248
462
 
249
- ```ts
250
- // Obtener todos los logs de nivel ERROR o superior
251
- const errors = await logger.getLogs({
252
- levelMin: LogLevel.ERROR,
253
- });
463
+ app.get("/health", (_req, res) => res.json({ ok: true }));
254
464
 
255
- // Filtrar por rango de fechas
256
- const yesterday = Date.now() - 24 * 60 * 60 * 1000;
257
- const recentLogs = await logger.getLogs({
258
- since: yesterday,
259
- until: Date.now(),
260
- });
465
+ // IMPORTANTE: Siempre colocar un errorHandler al final
261
466
 
262
- // Búsqueda por texto
263
- const userLogs = await logger.getLogs({
264
- query: "usuario",
265
- limit: 50,
266
- });
467
+ return app;
468
+ }
267
469
  ```
268
470
 
269
- ### Filtros Avanzados
471
+ En tus controladores, la idea es:
270
472
 
271
473
  ```ts
272
- // Combinación de filtros
273
- const criticalRecentErrors = await logger.getLogs({
274
- levelMin: LogLevel.ERROR,
275
- since: Date.now() - 2 * 60 * 60 * 1000, // Últimas 2 horas
276
- query: "database connection",
277
- limit: 20,
278
- offset: 0,
474
+ // controlador de ejemplo (no incluido en este archivo)
475
+ req.logger?.info("user.create.request", {
476
+ requestId: req.requestId,
477
+ // aquí podrías pasar info de la request (sin PII sensible)
279
478
  });
280
479
 
281
- // Paginación
282
- const page1 = await logger.getLogs({ limit: 10, offset: 0 });
283
- const page2 = await logger.getLogs({ limit: 10, offset: 10 });
480
+ // aquí llamarías al caso de uso correspondiente (no se implementa en este doc)
284
481
  ```
285
482
 
286
- ## Variables de Entorno
483
+ ---
287
484
 
288
- ```bash
289
- # Nivel mínimo de logs
290
- LOG_LEVEL=info
485
+ ## PASO 4 – BOOTSTRAP DE LA APLICACIÓN (Express + logger)
291
486
 
292
- # Filesystem
293
- LOG_FS_ENABLED=true
294
- LOG_FS_PATH=./logs
487
+ Path sugerido: `src/app.ts`
295
488
 
296
- # MongoDB (si usas @jmlq/logger-plugin-mongo)
297
- LOG_MONGO_ENABLED=true
298
- LOG_MONGO_URL=mongodb://localhost:27017
299
- LOG_MONGO_DB=logs
489
+ Aquí se orquesta todo:
300
490
 
301
- # PostgreSQL (si usas @jmlq/logger-plugin-postgresql)
302
- LOG_PG_ENABLED=true
303
- LOG_PG_HOST=localhost
304
- LOG_PG_PORT=5432
305
- LOG_PG_DATABASE=logs
306
- LOG_PG_USERNAME=logger
307
- LOG_PG_PASSWORD=secret
308
- ```
491
+ ```ts
492
+ import { envs } from "./config/plugins";
493
+ import {
494
+ disposeLogs,
495
+ flushLogs,
496
+ loggerReady,
497
+ } from "./infrastructure/adapters/jmlq/logger";
498
+ import { createServer } from "./presentation/server";
499
+
500
+ async function bootstrap() {
501
+ const logger = await loggerReady;
502
+
503
+ const app = createServer(logger);
504
+ const server = app.listen(envs.PORT, envs.HOST, () => {
505
+ console.log(`🚀 Server running at http://${envs.HOST}:${envs.PORT}`);
506
+ logger.info("http.start", { host: envs.HOST, port: envs.PORT });
507
+ });
309
508
 
310
- ## Ejemplo Completo
509
+ server.on("error", async (err: any) => {
510
+ logger.error("http.listen.error", {
511
+ message: err?.message,
512
+ stack: err?.stack,
513
+ });
514
+ await flushLogs().catch(() => {});
515
+ await disposeLogs().catch(() => {});
516
+ process.exit(1);
517
+ });
311
518
 
312
- ```ts
313
- import { LoggerFactory, LogLevel } from "@jmlq/logger";
314
-
315
- // Configuración completa
316
- const logger = await LoggerFactory.create({
317
- datasources: [
318
- // Múltiples datasources
319
- new FileSystemDatasource({ basePath: "./logs" }),
320
- new MongoDatasource({ url: "mongodb://localhost:27017", dbName: "logs" }),
321
- ],
322
- minLevel: LogLevel.DEBUG,
323
- redactorOptions: {
324
- enabled: true,
325
- deep: true,
326
- includeDefaults: true,
327
- patterns: [
328
- {
329
- pattern: "\\b\\d{4}-\\d{4}-\\d{4}-\\d{4}\\b",
330
- replaceWith: "****-****-****-****",
331
- },
332
- ],
333
- },
334
- });
519
+ // Señales de apagado limpio
520
+ const shutdown = async (reason: string, code = 0) => {
521
+ logger.info("app.shutdown.begin", { reason });
522
+ server.close(async () => {
523
+ try {
524
+ await flushLogs();
525
+ } catch {}
526
+ try {
527
+ await disposeLogs();
528
+ } catch {}
529
+ logger.info("app.shutdown.end", { code });
530
+ process.exit(code);
531
+ });
532
+ };
335
533
 
336
- // Uso con diferentes niveles
337
- await logger.debug("Debug info", { userId: 123 });
338
- await logger.info("User login", { email: "user@example.com" });
339
- await logger.warn("High latency", { endpoint: "/api/users", duration: 950 });
340
- await logger.error("Database error", { error: "Connection timeout" });
341
- await logger.fatal("System crash", { stack: error.stack });
342
-
343
- // Filtrar y consultar logs
344
- const recentErrors = await logger.getLogs({
345
- levelMin: LogLevel.ERROR,
346
- since: Date.now() - 24 * 60 * 60 * 1000,
347
- limit: 100,
348
- });
534
+ process.on("SIGINT", () => shutdown("SIGINT"));
535
+ process.on("SIGTERM", () => shutdown("SIGTERM"));
349
536
 
350
- // Limpiar buffers (útil antes de cerrar la aplicación)
351
- await logger.flush();
537
+ // Fallos no controlados
538
+ process.on("unhandledRejection", async (err) => {
539
+ const e = err as any;
540
+ logger.error("unhandled.rejection", {
541
+ message: e?.message,
542
+ stack: e?.stack,
543
+ });
544
+ // Aquí se podría enviar una notificación (email, etc.)
545
+ await shutdown("unhandledRejection", 1);
546
+ });
547
+
548
+ process.on("uncaughtException", async (err) => {
549
+ logger.error("uncaught.exception", {
550
+ message: err.message,
551
+ stack: err.stack,
552
+ });
553
+ // Aquí se podría enviar una notificación (email, etc.)
554
+ await shutdown("uncaughtException", 1);
555
+ });
556
+ }
557
+
558
+ bootstrap().catch(async (err) => {
559
+ // Falla durante el bootstrap
560
+ const logger = await loggerReady.catch(() => null);
561
+ if (logger) {
562
+ logger.error("bootstrap.fatal", {
563
+ message: (err as Error)?.message,
564
+ stack: (err as Error)?.stack,
565
+ });
566
+ await flushLogs().catch(() => {});
567
+ await disposeLogs().catch(() => {});
568
+ } else {
569
+ // Último recurso si el logger no llegó a inicializar
570
+ console.error("Fatal error (no logger):", err);
571
+ }
572
+ process.exit(1);
573
+ });
352
574
  ```
353
575
 
354
- ## Plugins Disponibles
576
+ Puntos clave:
355
577
 
356
- > - `@jmlq/logger-plugin-fs`: Persistencia en archivos
357
- > - `@jmlq/logger-plugin-mongo`: Persistencia en MongoDB
358
- > - `@jmlq/logger-plugin-postgresql`: Persistencia en PostgreSQL
578
+ - `loggerReady` asegura que el logger está inicializado antes de levantar HTTP.
579
+ - `flushLogs` y `disposeLogs` se usan:
580
+ - En errores de `server.listen`.
581
+ - En apagado por señales.
582
+ - En fallos no controlados.
359
583
 
360
- Cada plugin se instala por separado según las necesidades de tu aplicación.
584
+ ---
361
585
 
362
- ## Consideraciones de Rendimiento
586
+ ### Filtros Avanzados
363
587
 
364
- Los logs se procesan de forma asíncrona
365
- Múltiples datasources escriben en paralelo
366
- Usa [flush()](./examples/logger-factory.example.ts) antes de cerrar la aplicación para asegurar que todos los logs se persistan
367
- Configura límites apropiados en las consultas para evitar sobrecarga de memoria
588
+ ```ts
589
+ // Paginación
590
+ const page1 = await logger.getLogs({ limit: 10, offset: 0 });
591
+ const page2 = await logger.getLogs({ limit: 10, offset: 10 });
592
+ ```
593
+
594
+ ## 🔧 Configuración con Variables de Entorno
595
+
596
+ ```env
597
+ ###############################################
598
+ # @jmlq/logger – Dummy Environment Variables
599
+ ###############################################
600
+
601
+ # ---------------------------------------------
602
+ # Nivel mínimo del logger
603
+ # Valores: TRACE, DEBUG, INFO, WARN, ERROR, FATAL
604
+ # ---------------------------------------------
605
+ LOG_LEVEL=INFO
606
+
607
+ # ---------------------------------------------
608
+ # Protección de PII (datos sensibles)
609
+ # ---------------------------------------------
610
+ LOGGER_PII_ENABLED=true
611
+ LOGGER_PII_INCLUDE_DEFAULTS=false
612
+
613
+ # ---------------------------------------------
614
+ # FileSystem Plugin
615
+ # ---------------------------------------------
616
+ LOGGER_FS_PATH=./logs
617
+
618
+ # ---------------------------------------------
619
+ # MongoDB Plugin
620
+ # ---------------------------------------------
621
+ LOGGER_MONGO_DB_URL=mongodb://demo_user:demo_pass@localhost:27018/?authSource=admin
622
+ LOGGER_MONGO_DB_NAME=logger_demo_db
623
+ LOGGER_MONGO_COLLECTION_NAME=logger_demo_logs
624
+
625
+ # ---------------------------------------------
626
+ # PostgreSQL Plugin
627
+ # ---------------------------------------------
628
+ LOGGER_PG_CONNECTION_STRING=postgres://demo_user:demo_pass@localhost:5436/logger_demo_db
629
+ LOGGER_PG_TABLE_NAME=logger_demo_table
630
+ LOGGER_PG_SCHEMA=public
631
+
632
+ ```