@jmlq/logger-plugin-fs 0.1.0-alpha.1 → 0.1.0-alpha.11

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 (119) hide show
  1. package/README.md +551 -0
  2. package/architecture.md +299 -0
  3. package/dist/application/dto/index.d.ts +1 -0
  4. package/dist/application/dto/index.js +17 -0
  5. package/dist/application/dto/rotate-if-needed.request.d.ts +10 -0
  6. package/dist/application/dto/rotate-if-needed.request.js +2 -0
  7. package/dist/application/factory/create-fs-datasource.factory.d.ts +16 -0
  8. package/dist/application/factory/create-fs-datasource.factory.js +51 -0
  9. package/dist/application/factory/index.d.ts +1 -0
  10. package/dist/application/factory/index.js +17 -0
  11. package/dist/application/types/filesystem-datasource-options.type.d.ts +45 -0
  12. package/dist/application/types/filesystem-datasource-options.type.js +2 -0
  13. package/dist/application/types/index.d.ts +1 -0
  14. package/dist/application/types/index.js +17 -0
  15. package/dist/application/use-cases/append-log.use-case.d.ts +30 -0
  16. package/dist/application/use-cases/append-log.use-case.js +39 -0
  17. package/dist/application/use-cases/ensure-directory.use-case.d.ts +58 -0
  18. package/dist/application/use-cases/ensure-directory.use-case.js +64 -0
  19. package/dist/application/use-cases/find-logs-use-case.d.ts +17 -0
  20. package/dist/application/use-cases/find-logs-use-case.js +64 -0
  21. package/dist/application/use-cases/index.d.ts +5 -0
  22. package/dist/application/use-cases/index.js +21 -0
  23. package/dist/application/use-cases/persist-log.use-case.d.ts +96 -0
  24. package/dist/application/use-cases/persist-log.use-case.js +105 -0
  25. package/dist/application/use-cases/rotate-if-needed.use-case.d.ts +55 -0
  26. package/dist/application/use-cases/rotate-if-needed.use-case.js +61 -0
  27. package/dist/domain/model/index.d.ts +1 -0
  28. package/dist/domain/model/index.js +17 -0
  29. package/dist/domain/model/log-entry.model.d.ts +8 -0
  30. package/dist/domain/model/log-entry.model.js +2 -0
  31. package/dist/domain/ports/file/file-path.port.d.ts +38 -0
  32. package/dist/domain/ports/file/file-path.port.js +2 -0
  33. package/dist/domain/ports/file/file-rotator.port.d.ts +42 -0
  34. package/dist/domain/ports/file/file-rotator.port.js +2 -0
  35. package/dist/domain/ports/file/index.d.ts +2 -0
  36. package/dist/domain/ports/file/index.js +18 -0
  37. package/dist/domain/ports/file/log-stream-writer.port.d.ts +70 -0
  38. package/dist/domain/ports/file/log-stream-writer.port.js +2 -0
  39. package/dist/domain/ports/filesystem-provider.port.d.ts +61 -0
  40. package/dist/domain/ports/filesystem-provider.port.js +2 -0
  41. package/dist/domain/ports/index.d.ts +5 -0
  42. package/dist/domain/ports/index.js +21 -0
  43. package/dist/domain/ports/logs/find/index.d.ts +2 -0
  44. package/dist/domain/ports/logs/find/index.js +18 -0
  45. package/dist/domain/ports/logs/find/log-file-line-reader.port.d.ts +3 -0
  46. package/dist/domain/ports/logs/find/log-file-line-reader.port.js +2 -0
  47. package/dist/domain/ports/logs/find/log-file-numerator.port.d.ts +3 -0
  48. package/dist/domain/ports/logs/find/log-file-numerator.port.js +2 -0
  49. package/dist/domain/ports/logs/index.d.ts +2 -0
  50. package/dist/domain/ports/logs/index.js +18 -0
  51. package/dist/domain/ports/logs/log-datasource.port.d.ts +10 -0
  52. package/dist/domain/ports/logs/log-datasource.port.js +2 -0
  53. package/dist/domain/ports/system-clock.port.d.ts +8 -0
  54. package/dist/domain/ports/system-clock.port.js +2 -0
  55. package/dist/domain/request/index.d.ts +2 -0
  56. package/dist/domain/request/index.js +18 -0
  57. package/dist/domain/request/log-filter.request.d.ts +9 -0
  58. package/dist/domain/request/log-filter.request.js +2 -0
  59. package/dist/domain/request/save-log.request.d.ts +7 -0
  60. package/dist/domain/request/save-log.request.js +2 -0
  61. package/dist/domain/response/index.d.ts +1 -0
  62. package/dist/domain/response/index.js +17 -0
  63. package/dist/domain/response/log.response.d.ts +8 -0
  64. package/dist/domain/response/log.response.js +2 -0
  65. package/dist/domain/types/fs-rotation-by.type.d.ts +8 -0
  66. package/dist/domain/types/fs-rotation-by.type.js +2 -0
  67. package/dist/domain/types/index.d.ts +1 -0
  68. package/dist/domain/types/index.js +17 -0
  69. package/dist/domain/value-objects/file-name-pattern.vo.d.ts +36 -0
  70. package/dist/domain/value-objects/file-name-pattern.vo.js +53 -0
  71. package/dist/domain/value-objects/file-path.vo.d.ts +91 -0
  72. package/dist/domain/value-objects/file-path.vo.js +100 -0
  73. package/dist/domain/value-objects/file-rotation-policy.vo.d.ts +51 -0
  74. package/dist/domain/value-objects/file-rotation-policy.vo.js +76 -0
  75. package/dist/domain/value-objects/file-size.vo.d.ts +75 -0
  76. package/dist/domain/value-objects/file-size.vo.js +114 -0
  77. package/dist/domain/value-objects/index.d.ts +5 -0
  78. package/dist/domain/value-objects/index.js +21 -0
  79. package/dist/domain/value-objects/log-level.vo.d.ts +8 -0
  80. package/dist/domain/value-objects/log-level.vo.js +13 -0
  81. package/dist/index.d.ts +4 -13
  82. package/dist/index.js +10 -38
  83. package/dist/infrastructure/adapters/file-rotator.adapter.d.ts +79 -0
  84. package/dist/infrastructure/adapters/file-rotator.adapter.js +171 -0
  85. package/dist/infrastructure/adapters/fileSystem-datasource.adapter.d.ts +26 -0
  86. package/dist/infrastructure/adapters/fileSystem-datasource.adapter.js +45 -0
  87. package/dist/infrastructure/adapters/filesystem-log-file-enumerator.adapter.d.ts +6 -0
  88. package/dist/infrastructure/adapters/filesystem-log-file-enumerator.adapter.js +54 -0
  89. package/dist/infrastructure/adapters/filesystem-log-file-line-reader.adapter.d.ts +4 -0
  90. package/dist/infrastructure/adapters/filesystem-log-file-line-reader.adapter.js +53 -0
  91. package/dist/infrastructure/adapters/filesystem-provider.adapter.d.ts +122 -0
  92. package/dist/infrastructure/adapters/filesystem-provider.adapter.js +182 -0
  93. package/dist/infrastructure/adapters/index.d.ts +8 -0
  94. package/dist/infrastructure/adapters/index.js +24 -0
  95. package/dist/infrastructure/adapters/log-stream-writer.adapter.d.ts +80 -0
  96. package/dist/infrastructure/adapters/log-stream-writer.adapter.js +163 -0
  97. package/dist/infrastructure/adapters/system-clock.adapter.d.ts +25 -0
  98. package/dist/infrastructure/adapters/system-clock.adapter.js +30 -0
  99. package/dist/infrastructure/adapters/system-file-path.adapter.d.ts +47 -0
  100. package/dist/infrastructure/adapters/system-file-path.adapter.js +141 -0
  101. package/dist/infrastructure/errors/file-operation.error.d.ts +28 -0
  102. package/dist/infrastructure/errors/file-operation.error.js +54 -0
  103. package/dist/infrastructure/errors/index.d.ts +1 -0
  104. package/dist/infrastructure/errors/index.js +17 -0
  105. package/dist/infrastructure/errors/types/file-operation-error-options.type.d.ts +8 -0
  106. package/dist/infrastructure/errors/types/file-operation-error-options.type.js +2 -0
  107. package/dist/infrastructure/errors/types/file-operation.type.d.ts +1 -0
  108. package/dist/infrastructure/errors/types/file-operation.type.js +2 -0
  109. package/dist/infrastructure/errors/types/fs-error-scope.type.d.ts +1 -0
  110. package/dist/infrastructure/errors/types/fs-error-scope.type.js +2 -0
  111. package/dist/infrastructure/errors/types/index.d.ts +3 -0
  112. package/dist/infrastructure/errors/types/index.js +19 -0
  113. package/dist/infrastructure/filesystem/index.d.ts +1 -0
  114. package/dist/infrastructure/filesystem/index.js +17 -0
  115. package/dist/infrastructure/filesystem/types/filesystem-rotation.type.d.ts +12 -0
  116. package/dist/infrastructure/filesystem/types/filesystem-rotation.type.js +2 -0
  117. package/dist/infrastructure/filesystem/types/index.d.ts +1 -0
  118. package/dist/infrastructure/filesystem/types/index.js +17 -0
  119. package/package.json +40 -13
package/README.md ADDED
@@ -0,0 +1,551 @@
1
+ # @jmlq/logger-plugin-fs
2
+
3
+ ## Introducción técnica
4
+
5
+ Plugin de **sistema de archivos** para [`@jmlq/logger`](https://www.npmjs.com/package/@jmlq/logger). Permite persistir logs en archivos con soporte para rotación automática, manejo de backpressure y configuración flexible de formato y estructura de archivos.
6
+
7
+ El plugin se encarga de:
8
+
9
+ - Persistencia de logs en File System
10
+ - Rotación automática de archivos por fecha o tamaño
11
+ - Búsqueda y filtrado de logs históricos
12
+ - Gestión eficiente de streams de escritura
13
+
14
+ ---
15
+
16
+ ## Componentes principales expuestos
17
+
18
+ ### Factory
19
+
20
+ - **[`createFsDatasource`](src/application/factory/create-fs-datasource.factory.ts)**
21
+ Factory de alto nivel que construye un `ILogDatasource` compatible con `@jmlq/logger`, resolviendo internamente:
22
+
23
+ - Cliente File System con adaptadores específicos de Node.js
24
+ - Casos de uso para persistencia, rotación y búsqueda
25
+ - Adaptadores de infraestructura para operaciones de archivos
26
+
27
+ > Es el **único punto recomendado de entrada** para consumidores del paquete.
28
+
29
+ ---
30
+
31
+ ### Tipos de configuración
32
+
33
+ - **[`IFilesystemDatasourceOptions`](src/application/types/filesystem-datasource-options.type.ts)**
34
+ Define la configuración necesaria para crear el datasource de File System:
35
+ - `basePath`: Directorio base donde se almacenarán los logs
36
+ - `fileNamePattern`: Patrón de nombres con tokens de fecha
37
+ - `rotation`: Configuración de rotación automática
38
+ - `mkdir`: Creación automática de directorios
39
+ - `onRotate`: Callback ejecutado al rotar archivos
40
+ - `onError`: Manejador de errores personalizado
41
+
42
+ ---
43
+
44
+ ## Contratos (Types / Ports)
45
+
46
+ Estos tipos existen para **desacoplar el dominio y la aplicación del File System**.
47
+ Son útiles para testing, extensiones avanzadas o integraciones personalizadas.
48
+
49
+ - **[`IFileSystemRotationConfig`](src/infrastructure/filesystem/types/filesystem-rotation.type.ts)**: Define estrategias de rotación (día, tamaño, ninguna)
50
+ - **[`FsRotationBy`](src/domain/types/fs-rotation-by.type.ts)**: Enum con tipos de rotación disponibles
51
+ - **[`FilePath`](src/domain/value-objects/file-path.vo.ts)**: Value Object para rutas de archivos con validaciones
52
+ - **[`FileSize`](src/domain/value-objects/file-size.vo.ts)**: Value Object para tamaños de archivos con conversiones
53
+ - **[`FileRotationPolicy`](src/domain/value-objects/file-rotation-policy.vo.ts)**: Encapsula la política de rotación configurada
54
+ - **[`FileNamePattern`](src/domain/value-objects/file-name-pattern.vo.ts)**: Maneja patrones de nombres con tokens de fecha
55
+
56
+ ---
57
+
58
+ ## 📦 Instalación
59
+
60
+ ```bash
61
+ npm install @jmlq/logger @jmlq/logger-plugin-fs
62
+ ```
63
+
64
+ - `@jmlq/logger` >= 0.1.0-alpha.20
65
+
66
+ ---
67
+
68
+ ## Configuración @jmlq/logger-plugin-fs
69
+
70
+ Se recomienda implementar en la capa `infrastructure` de la API REST que usa [`@jmlq/logger`](https://www.npmjs.com/package/@jmlq/logger) y [`@jmlq/logger-plugin-fs`](https://www.npmjs.com/package/@jmlq/logger-plugin-fs):
71
+
72
+ > - **Adapter de infraestructura**
73
+
74
+ ```ts
75
+ // Path Ejemplo: src/infrastructure/logger/fs/fs.adapter.ts
76
+
77
+ import {
78
+ createFsDatasource,
79
+ IFilesystemDatasourceOptions,
80
+ } from "@jmlq/logger-plugin-fs";
81
+ import type { ILogDatasource } from "@jmlq/logger";
82
+
83
+ export class FsAdapter {
84
+ private constructor(private readonly ds: ILogDatasource) {}
85
+ static create(opts: IFilesystemDatasourceOptions): FsAdapter | undefined {
86
+ try {
87
+ const ds = createFsDatasource(opts);
88
+ console.log("[logger] Conectado a FS para logs");
89
+ return new FsAdapter(ds);
90
+ } catch (e: any) {
91
+ console.warn("[logger] FS deshabilitado:", e?.message ?? e);
92
+ }
93
+ }
94
+ get datasource(): ILogDatasource {
95
+ return this.ds;
96
+ }
97
+ }
98
+ ```
99
+
100
+ ---
101
+
102
+ ## Configuración @jmlq/logger
103
+
104
+ ### Variables de entorno
105
+
106
+ El plugin no gestiona variables de entorno internamente, pero puedes implementar tu propia capa de configuración:
107
+
108
+ ```ini
109
+ # Nivel mínimo de logs
110
+ # Valores: TRACE, DEBUG, INFO, WARN, ERROR, FATAL
111
+ LOG_LEVEL=INFO
112
+ # PII Protection
113
+ LOGGER_PII_ENABLED=true
114
+ LOGGER_PII_INCLUDE_DEFAULTS=false
115
+ # FS Plugin Settings
116
+ LOGGER_FS_PATH=./logs/
117
+ ```
118
+
119
+ ### Interface de configuración del paquete
120
+
121
+ ```ts
122
+ // Path Ejemplo: src/infrastructure/logger/core/logger.bootstrap.options.ts
123
+
124
+ import { LogLevel } from "@jmlq/logger";
125
+ import { IFilesystemDatasourceOptions } from "@jmlq/logger-plugin-fs";
126
+
127
+ export interface LoggerBootstrapOptions {
128
+ minLevel: LogLevel;
129
+ pii?: {
130
+ enabled?: boolean;
131
+ whitelistKeys?: string[];
132
+ blacklistKeys?: string[];
133
+ patterns?: any[];
134
+ deep?: boolean;
135
+ includeDefaults?: boolean;
136
+ };
137
+ adapters?: {
138
+ // NOTA: Opcional, depende de las necesidades del cliente
139
+ fs?: IFilesystemDatasourceOptions;
140
+ };
141
+ }
142
+ ```
143
+
144
+ ### Implementación del Logger
145
+
146
+ ```ts
147
+ // Path Ejemplo: src/infrastructure/logger/core/logger.bootstrap.ts
148
+
149
+ import { type ILogDatasource, createLogger } from "@jmlq/logger";
150
+ import { FsAdapter } from "../fs/fs.adapter";
151
+ import { LoggerBootstrapOptions } from "./logger.bootstrap.options";
152
+
153
+ export class LoggerBootstrap {
154
+ private constructor(
155
+ private readonly _logger: ReturnType<typeof createLogger>
156
+ ) {}
157
+
158
+ static async create(opts: LoggerBootstrapOptions): Promise<LoggerBootstrap> {
159
+ const dsList: ILogDatasource[] = [];
160
+
161
+ // NOTA: Opcional, depende de las necesidades del cliente
162
+ if (opts.adapters?.fs) {
163
+ const fs = FsAdapter.create(opts.adapters.fs);
164
+ if (fs) dsList.push(fs.datasource);
165
+ }
166
+ //----
167
+
168
+ if (dsList.length === 0)
169
+ throw new Error("[logger] No hay datasources válidos.");
170
+
171
+ const logger = createLogger({
172
+ datasources: dsList,
173
+ minLevel: opts.minLevel,
174
+ redactorOptions: {
175
+ enabled: opts.pii?.enabled ?? false,
176
+ deep: opts.pii?.deep ?? true,
177
+ patterns: opts.pii?.patterns ?? [],
178
+ },
179
+ });
180
+
181
+ console.log("✅ Logger creado correctamente.\n");
182
+
183
+ return new LoggerBootstrap(logger);
184
+ }
185
+
186
+ get logger() {
187
+ return this._logger;
188
+ }
189
+
190
+ // Limpia buffers pendientes antes del cierre
191
+ async flush() {
192
+ const logs = this._logger as any;
193
+ if (typeof logs.flush === "function") await logs.flush();
194
+ }
195
+
196
+ // Cierra recursos (conexiones, handles, etc.)
197
+ async dispose() {
198
+ const logs = this._logger as any;
199
+ if (typeof logs.dispose === "function") await logs.dispose();
200
+ }
201
+ }
202
+ ```
203
+
204
+ ### Inicializador del Logger
205
+
206
+ ```ts
207
+ // Path Ejemplo: src/infrastructure/logger/index.ts
208
+
209
+ import { envs } from "../../../../config/plugins";
210
+ // NOTA: envs es un archivo donde se leen variables de entorno .env
211
+ import { LoggerBootstrap } from "./core/logger.bootstrap";
212
+ import { parseLogLevel } from "./helper";
213
+ // NOTA: parseLogLevel hace un match con los valores del objeto LogLevel de @jmlq/logger
214
+
215
+ declare global {
216
+ var __LOGGER_BOOT__: Promise<LoggerBootstrap> | undefined;
217
+ }
218
+
219
+ async function init() {
220
+ return LoggerBootstrap.create({
221
+ minLevel: parseLogLevel(envs.logger.LOGGER_LEVEL),
222
+ pii: {
223
+ enabled: envs.logger.LOGGER_PII_ENABLED,
224
+ includeDefaults: envs.logger.LOGGER_PII_INCLUDE_DEFAULTS,
225
+ deep: true,
226
+ patterns: [
227
+ {
228
+ pattern: "\\b\\d{4}-\\d{4}-\\d{4}-\\d{4}\\b",
229
+ replaceWith: "****-****-****-****",
230
+ },
231
+ {
232
+ pattern: "[\\w.-]+@[\\w.-]+",
233
+ replaceWith: "***@***",
234
+ },
235
+ ],
236
+ },
237
+ adapters: {
238
+ // NOTA: Opcional, depende de las necesidades del cliente
239
+ fs: envs.logger.LOGGER_FS_PATH
240
+ ? {
241
+ basePath: envs.logger.LOGGER_FS_PATH,
242
+ fileNamePattern: "app-{yyyy}{MM}{dd}.log",
243
+ rotation: { by: "day" },
244
+ mkdir: true,
245
+ onRotate: (oldPath, newPath) => {
246
+ console.log(
247
+ ` [Rotate] Rotación completada: ${oldPath.absolutePath} → ${newPath.absolutePath}`
248
+ );
249
+ },
250
+ onError: (err) => {
251
+ console.error(" [Error Handler]", err.message);
252
+ },
253
+ }
254
+ : undefined,
255
+ },
256
+ });
257
+ }
258
+
259
+ // 1. Es una promesa singleton de LoggerBootstrap
260
+ // 2. Usa el operador nullish-coalescing (??) para: Reutilizar globalThis.__LOGGER_BOOT__ si ya existe.
261
+ // En caso contrario crea y memoriza (= init()) la promesa si no existe aún.
262
+ // 3. Garantiza una sola inicialización global del sistema de logging (adapters, datasources, PII, etc.)
263
+ // aunque el módulo se importe múltiples veces
264
+ export const bootReady: Promise<LoggerBootstrap> =
265
+ globalThis.__LOGGER_BOOT__ ?? (globalThis.__LOGGER_BOOT__ = init());
266
+
267
+ // 1. Es una promesa que resuelve directamente al logger
268
+ // 2. Hace un map de la promesa anterior: bootReady.then(b => b.logger).
269
+ export const loggerReady = bootReady.then((b) => b.logger);
270
+
271
+ // 1. Espera a bootReady y llama boot.flush(), que a su vez pide al logger/datasources
272
+ // que vacíen buffers pendientes (útil antes de apagar el proceso o en tests).
273
+ export async function flushLogs() {
274
+ const boot = await bootReady;
275
+ await boot.flush();
276
+ }
277
+
278
+ // 1. Espera a bootReady y llama boot.dispose(), que cierra recursos
279
+ // (conexiones a MongoDB/Postgres, file handles, etc.)
280
+ export async function disposeLogs() {
281
+ const boot = await bootReady;
282
+ await boot.dispose();
283
+ }
284
+ ```
285
+
286
+ ---
287
+
288
+ ## Configuración en Frameworks
289
+
290
+ **NOTA**: Se sugiere realizarlo en la capa `presentation` de la API que implementa `@jmlq/logger` y/o sus plugins.
291
+
292
+ ### Express
293
+
294
+ #### Crear middleware (opcional):
295
+
296
+ Se puede realizar de la forma que el usuario considere conveniente
297
+
298
+ ```ts
299
+ // Path Ejemplo: src/presentation/middlewares/attach-logger.middleware.ts
300
+
301
+ import { ILogger } from "@jmlq/logger";
302
+ import { randomUUID } from "crypto";
303
+ import { Request, Response, NextFunction } from "express";
304
+
305
+ /**
306
+ * Middleware factory que adjunta contexto de logging al request.
307
+ *
308
+ * Responsabilidades:
309
+ * 1. Inyectar un logger base accesible desde cualquier controller/middleware.
310
+ * 2. Garantizar un requestId único para trazabilidad y correlación de logs.
311
+ *
312
+ * No configura el logger ni emite logs:
313
+ * solo prepara el contexto por request.
314
+ */
315
+ export function attachLogger(base: ILogger) {
316
+ /**
317
+ * Middleware de Express ejecutado en cada request HTTP.
318
+ */
319
+ return async (req: Request, _res: Response, next: NextFunction) => {
320
+ /**
321
+ * Se adjunta el logger al objeto Request para evitar:
322
+ * - imports globales
323
+ * - singletons
324
+ * - acoplamiento directo en controllers
325
+ */
326
+ req.logger = base;
327
+
328
+ /**
329
+ * Se asigna un identificador único por request.
330
+ *
331
+ * - Si el cliente ya envía "x-request-id" (ej. API Gateway, Load Balancer),
332
+ * se reutiliza para mantener trazabilidad entre servicios.
333
+ * - Si no existe, se genera un UUID local.
334
+ */
335
+ req.requestId = (req.headers["x-request-id"] as string) ?? randomUUID();
336
+
337
+ /**
338
+ * Continúa el pipeline normal de Express.
339
+ */
340
+ next();
341
+ };
342
+ }
343
+ ```
344
+
345
+ #### Adjuntar middleware en el servidor
346
+
347
+ ```ts
348
+ // Path Ejemplo: src/presentation/server.ts
349
+
350
+ import { ILogger } from "@jmlq/logger";
351
+ import express from "express";
352
+ import { attachLogger } from "./middlewares";
353
+
354
+ export function createServer(base: ILogger) {
355
+ const app = express();
356
+ app.use(express.json()); // <-- necesario para POST JSON
357
+ app.use(attachLogger(base));
358
+
359
+ app.get("/health", (_req, res) => res.json({ ok: true }));
360
+
361
+ return app;
362
+ }
363
+ ```
364
+
365
+ #### Iniciar servidor
366
+
367
+ **NOTA**: Esto se realiza en la raíz de la API
368
+
369
+ ```ts
370
+ // Path Ejemplo: src/app.ts
371
+
372
+ import { envs } from "./config/plugins";
373
+ import { disposeLogs, flushLogs, loggerReady } from "./infrastructure/logger";
374
+ import { createServer } from "./presentation/server";
375
+
376
+ async function bootstrap() {
377
+ const logger = await loggerReady;
378
+
379
+ const app = createServer(logger);
380
+ const server = app.listen(envs.PORT, envs.HOST, () => {
381
+ console.log(`🚀 Server running at http://${envs.HOST}:${envs.PORT}`);
382
+ logger.info("http.start", { host: envs.HOST, port: envs.PORT });
383
+ });
384
+
385
+ server.on("error", async (err: any) => {
386
+ logger.error("http.listen.error", {
387
+ message: err?.message,
388
+ stack: err?.stack,
389
+ });
390
+ await flushLogs().catch(() => {});
391
+ await disposeLogs().catch(() => {});
392
+ process.exit(1);
393
+ });
394
+
395
+ // 3) Señales de apagado limpio
396
+ const shutdown = async (reason: string, code = 0) => {
397
+ logger.info("app.shutdown.begin", { reason });
398
+ server.close(async () => {
399
+ try {
400
+ await flushLogs();
401
+ } catch {}
402
+ try {
403
+ await disposeLogs();
404
+ } catch {}
405
+ logger.info("app.shutdown.end", { code });
406
+ process.exit(code);
407
+ });
408
+ };
409
+
410
+ process.on("SIGINT", () => shutdown("SIGINT"));
411
+ process.on("SIGTERM", () => shutdown("SIGTERM"));
412
+
413
+ // Fallos no controlados
414
+ process.on("unhandledRejection", async (err) => {
415
+ const e = err as any;
416
+ logger.error("unhandled.rejection", {
417
+ message: e?.message,
418
+ stack: e?.stack,
419
+ });
420
+ await shutdown("unhandledRejection", 1);
421
+ });
422
+
423
+ process.on("uncaughtException", async (err) => {
424
+ logger.error("uncaught.exception", {
425
+ message: err.message,
426
+ stack: err.stack,
427
+ });
428
+ await shutdown("uncaughtException", 1);
429
+ });
430
+ }
431
+
432
+ bootstrap().catch(async (err) => {
433
+ // Falla durante el bootstrap
434
+ const logger = await loggerReady.catch(() => null);
435
+ if (logger) {
436
+ logger.error("bootstrap.fatal", {
437
+ message: (err as Error)?.message,
438
+ stack: (err as Error)?.stack,
439
+ });
440
+ await flushLogs().catch(() => {});
441
+ await disposeLogs().catch(() => {});
442
+ } else {
443
+ // último recurso si el logger no llegó a inicializar
444
+ console.error("Fatal error (no logger):", err);
445
+ }
446
+ process.exit(1);
447
+ });
448
+ ```
449
+
450
+ ---
451
+
452
+ ## ⚙️ Configuración del datasource
453
+
454
+ El datasource de filesystem acepta las siguientes opciones de configuración:
455
+
456
+ ### Opciones principales
457
+
458
+ | Opción | Tipo | Descripción | Por defecto |
459
+ | ----------------- | ---------------- | ------------------------------------------------------ | --------------------------- |
460
+ | `basePath` | `string` | Directorio base donde se guardarán los archivos de log | `"./logs/"` |
461
+ | `fileNamePattern` | `string` | Patrón para nombrar archivos con tokens de fecha | `"app-{yyyy}{MM}{dd}.log"` |
462
+ | `rotation` | `RotationConfig` | Configuración de política de rotación | `{ by: "day" }` |
463
+ | `mkdir` | `boolean` | Crear directorio base si no existe | `true` |
464
+ | `serializer` | `LogSerializer` | Serializador personalizado para el formato de líneas | Serializer JSON por defecto |
465
+
466
+ ### Configuración de rotación
467
+
468
+ ```typescript
469
+ interface RotationConfig {
470
+ by: "none" | "day" | "size";
471
+ maxSizeMB?: number; // Para rotación por tamaño
472
+ maxFiles?: number; // Límite de archivos rotados
473
+ }
474
+ ```
475
+
476
+ ### Patrones de nombre de archivo
477
+
478
+ Utiliza tokens que se reemplazan con valores de fecha (UTC):
479
+
480
+ - `{yyyy}` - Año completo (ej: 2024)
481
+ - `{MM}` - Mes con ceros (ej: 01, 12)
482
+ - `{dd}` - Día con ceros (ej: 01, 31)
483
+
484
+ **Ejemplos:**
485
+
486
+ - `"app-{yyyy}{MM}{dd}.log"` → `app-20241201.log`
487
+ - `"service-{yyyy}-{MM}-{dd}.log"` → `service-2024-12-01.log`
488
+ - `"application.log"` → `application.log` (sin rotación por fecha)
489
+
490
+ ## 🔁 Políticas de rotación
491
+
492
+ ### Rotación por fecha
493
+
494
+ Crea un archivo nuevo cada día basado en fecha UTC:
495
+
496
+ ```typescript
497
+ import { createFsDatasource } from "@jmlq/logger-plugin-fs";
498
+
499
+ const datasource = createFsDatasource({
500
+ basePath: "./logs/",
501
+ fileNamePattern: "app-{yyyy}{MM}{dd}.log",
502
+ rotation: { by: "day" },
503
+ onRotate: (oldPath, newPath) => {
504
+ console.log(
505
+ `Log rotado: ${oldPath.absolutePath} → ${newPath.absolutePath}`
506
+ );
507
+ },
508
+ });
509
+ ```
510
+
511
+ ### Rotación por tamaño
512
+
513
+ Rota cuando el archivo alcanza el tamaño máximo especificado:
514
+
515
+ ```typescript
516
+ const datasource = createFsDatasource({
517
+ basePath: "./logs/",
518
+ fileNamePattern: "app.log",
519
+ rotation: {
520
+ by: "size",
521
+ maxSizeMB: 50, // Rota al alcanzar 50MB
522
+ maxFiles: 10, // Mantiene máximo 10 archivos rotados
523
+ },
524
+ onRotate: (oldPath, newPath) => {
525
+ console.log(
526
+ `Archivo rotado por tamaño: ${oldPath.absolutePath} → ${newPath.absolutePath}`
527
+ );
528
+ },
529
+ });
530
+ ```
531
+
532
+ ### Sin rotación
533
+
534
+ Mantiene un único archivo que crece indefinidamente:
535
+
536
+ ```typescript
537
+ const datasource = createFsDatasource({
538
+ basePath: "./logs/",
539
+ fileNamePattern: "application.log",
540
+ rotation: { by: "none" },
541
+ });
542
+ ```
543
+
544
+ ## 📄 Más Información
545
+
546
+ - **[Arquitectura Detallada](./architecture.md)** - Documentación técnica completa
547
+ - **[Ejemplos](./examples/)** - Códigos de ejemplo funcionales
548
+
549
+ ## 📄 Licencia
550
+
551
+ MIT © Mauricio Lahuasi