@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.
- package/README.md +551 -0
- package/architecture.md +299 -0
- package/dist/application/dto/index.d.ts +1 -0
- package/dist/application/dto/index.js +17 -0
- package/dist/application/dto/rotate-if-needed.request.d.ts +10 -0
- package/dist/application/dto/rotate-if-needed.request.js +2 -0
- package/dist/application/factory/create-fs-datasource.factory.d.ts +16 -0
- package/dist/application/factory/create-fs-datasource.factory.js +51 -0
- package/dist/application/factory/index.d.ts +1 -0
- package/dist/application/factory/index.js +17 -0
- package/dist/application/types/filesystem-datasource-options.type.d.ts +45 -0
- package/dist/application/types/filesystem-datasource-options.type.js +2 -0
- package/dist/application/types/index.d.ts +1 -0
- package/dist/application/types/index.js +17 -0
- package/dist/application/use-cases/append-log.use-case.d.ts +30 -0
- package/dist/application/use-cases/append-log.use-case.js +39 -0
- package/dist/application/use-cases/ensure-directory.use-case.d.ts +58 -0
- package/dist/application/use-cases/ensure-directory.use-case.js +64 -0
- package/dist/application/use-cases/find-logs-use-case.d.ts +17 -0
- package/dist/application/use-cases/find-logs-use-case.js +64 -0
- package/dist/application/use-cases/index.d.ts +5 -0
- package/dist/application/use-cases/index.js +21 -0
- package/dist/application/use-cases/persist-log.use-case.d.ts +96 -0
- package/dist/application/use-cases/persist-log.use-case.js +105 -0
- package/dist/application/use-cases/rotate-if-needed.use-case.d.ts +55 -0
- package/dist/application/use-cases/rotate-if-needed.use-case.js +61 -0
- package/dist/domain/model/index.d.ts +1 -0
- package/dist/domain/model/index.js +17 -0
- package/dist/domain/model/log-entry.model.d.ts +8 -0
- package/dist/domain/model/log-entry.model.js +2 -0
- package/dist/domain/ports/file/file-path.port.d.ts +38 -0
- package/dist/domain/ports/file/file-path.port.js +2 -0
- package/dist/domain/ports/file/file-rotator.port.d.ts +42 -0
- package/dist/domain/ports/file/file-rotator.port.js +2 -0
- package/dist/domain/ports/file/index.d.ts +2 -0
- package/dist/domain/ports/file/index.js +18 -0
- package/dist/domain/ports/file/log-stream-writer.port.d.ts +70 -0
- package/dist/domain/ports/file/log-stream-writer.port.js +2 -0
- package/dist/domain/ports/filesystem-provider.port.d.ts +61 -0
- package/dist/domain/ports/filesystem-provider.port.js +2 -0
- package/dist/domain/ports/index.d.ts +5 -0
- package/dist/domain/ports/index.js +21 -0
- package/dist/domain/ports/logs/find/index.d.ts +2 -0
- package/dist/domain/ports/logs/find/index.js +18 -0
- package/dist/domain/ports/logs/find/log-file-line-reader.port.d.ts +3 -0
- package/dist/domain/ports/logs/find/log-file-line-reader.port.js +2 -0
- package/dist/domain/ports/logs/find/log-file-numerator.port.d.ts +3 -0
- package/dist/domain/ports/logs/find/log-file-numerator.port.js +2 -0
- package/dist/domain/ports/logs/index.d.ts +2 -0
- package/dist/domain/ports/logs/index.js +18 -0
- package/dist/domain/ports/logs/log-datasource.port.d.ts +10 -0
- package/dist/domain/ports/logs/log-datasource.port.js +2 -0
- package/dist/domain/ports/system-clock.port.d.ts +8 -0
- package/dist/domain/ports/system-clock.port.js +2 -0
- package/dist/domain/request/index.d.ts +2 -0
- package/dist/domain/request/index.js +18 -0
- package/dist/domain/request/log-filter.request.d.ts +9 -0
- package/dist/domain/request/log-filter.request.js +2 -0
- package/dist/domain/request/save-log.request.d.ts +7 -0
- package/dist/domain/request/save-log.request.js +2 -0
- package/dist/domain/response/index.d.ts +1 -0
- package/dist/domain/response/index.js +17 -0
- package/dist/domain/response/log.response.d.ts +8 -0
- package/dist/domain/response/log.response.js +2 -0
- package/dist/domain/types/fs-rotation-by.type.d.ts +8 -0
- package/dist/domain/types/fs-rotation-by.type.js +2 -0
- package/dist/domain/types/index.d.ts +1 -0
- package/dist/domain/types/index.js +17 -0
- package/dist/domain/value-objects/file-name-pattern.vo.d.ts +36 -0
- package/dist/domain/value-objects/file-name-pattern.vo.js +53 -0
- package/dist/domain/value-objects/file-path.vo.d.ts +91 -0
- package/dist/domain/value-objects/file-path.vo.js +100 -0
- package/dist/domain/value-objects/file-rotation-policy.vo.d.ts +51 -0
- package/dist/domain/value-objects/file-rotation-policy.vo.js +76 -0
- package/dist/domain/value-objects/file-size.vo.d.ts +75 -0
- package/dist/domain/value-objects/file-size.vo.js +114 -0
- package/dist/domain/value-objects/index.d.ts +5 -0
- package/dist/domain/value-objects/index.js +21 -0
- package/dist/domain/value-objects/log-level.vo.d.ts +8 -0
- package/dist/domain/value-objects/log-level.vo.js +13 -0
- package/dist/index.d.ts +4 -13
- package/dist/index.js +10 -38
- package/dist/infrastructure/adapters/file-rotator.adapter.d.ts +79 -0
- package/dist/infrastructure/adapters/file-rotator.adapter.js +171 -0
- package/dist/infrastructure/adapters/fileSystem-datasource.adapter.d.ts +26 -0
- package/dist/infrastructure/adapters/fileSystem-datasource.adapter.js +45 -0
- package/dist/infrastructure/adapters/filesystem-log-file-enumerator.adapter.d.ts +6 -0
- package/dist/infrastructure/adapters/filesystem-log-file-enumerator.adapter.js +54 -0
- package/dist/infrastructure/adapters/filesystem-log-file-line-reader.adapter.d.ts +4 -0
- package/dist/infrastructure/adapters/filesystem-log-file-line-reader.adapter.js +53 -0
- package/dist/infrastructure/adapters/filesystem-provider.adapter.d.ts +122 -0
- package/dist/infrastructure/adapters/filesystem-provider.adapter.js +182 -0
- package/dist/infrastructure/adapters/index.d.ts +8 -0
- package/dist/infrastructure/adapters/index.js +24 -0
- package/dist/infrastructure/adapters/log-stream-writer.adapter.d.ts +80 -0
- package/dist/infrastructure/adapters/log-stream-writer.adapter.js +163 -0
- package/dist/infrastructure/adapters/system-clock.adapter.d.ts +25 -0
- package/dist/infrastructure/adapters/system-clock.adapter.js +30 -0
- package/dist/infrastructure/adapters/system-file-path.adapter.d.ts +47 -0
- package/dist/infrastructure/adapters/system-file-path.adapter.js +141 -0
- package/dist/infrastructure/errors/file-operation.error.d.ts +28 -0
- package/dist/infrastructure/errors/file-operation.error.js +54 -0
- package/dist/infrastructure/errors/index.d.ts +1 -0
- package/dist/infrastructure/errors/index.js +17 -0
- package/dist/infrastructure/errors/types/file-operation-error-options.type.d.ts +8 -0
- package/dist/infrastructure/errors/types/file-operation-error-options.type.js +2 -0
- package/dist/infrastructure/errors/types/file-operation.type.d.ts +1 -0
- package/dist/infrastructure/errors/types/file-operation.type.js +2 -0
- package/dist/infrastructure/errors/types/fs-error-scope.type.d.ts +1 -0
- package/dist/infrastructure/errors/types/fs-error-scope.type.js +2 -0
- package/dist/infrastructure/errors/types/index.d.ts +3 -0
- package/dist/infrastructure/errors/types/index.js +19 -0
- package/dist/infrastructure/filesystem/index.d.ts +1 -0
- package/dist/infrastructure/filesystem/index.js +17 -0
- package/dist/infrastructure/filesystem/types/filesystem-rotation.type.d.ts +12 -0
- package/dist/infrastructure/filesystem/types/filesystem-rotation.type.js +2 -0
- package/dist/infrastructure/filesystem/types/index.d.ts +1 -0
- package/dist/infrastructure/filesystem/types/index.js +17 -0
- 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
|