@jmlq/logger 0.1.0-alpha.7 → 0.1.0-alpha.8

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 (145) hide show
  1. package/README.md +225 -733
  2. package/dist/examples/data-source-service.example.d.ts +3 -0
  3. package/dist/examples/data-source-service.example.js +174 -0
  4. package/dist/examples/flush-buffers-use-case.example.d.ts +3 -0
  5. package/dist/examples/flush-buffers-use-case.example.js +60 -0
  6. package/dist/examples/get-logs-use-case.example.d.ts +3 -0
  7. package/dist/examples/get-logs-use-case.example.js +110 -0
  8. package/dist/examples/index.example.d.ts +8 -0
  9. package/dist/examples/index.example.js +116 -0
  10. package/dist/examples/logger-factory.example.d.ts +39 -0
  11. package/dist/examples/logger-factory.example.js +158 -0
  12. package/dist/examples/normalize-message.example.d.ts +3 -0
  13. package/dist/examples/normalize-message.example.js +80 -0
  14. package/dist/examples/pii-redactor.example.d.ts +3 -0
  15. package/dist/examples/pii-redactor.example.js +129 -0
  16. package/dist/examples/save-log-use-case.example.d.ts +3 -0
  17. package/dist/examples/save-log-use-case.example.js +150 -0
  18. package/dist/examples/to-log-level.example.d.ts +3 -0
  19. package/dist/examples/to-log-level.example.js +49 -0
  20. package/dist/examples/to-pii-regex.example.d.ts +3 -0
  21. package/dist/examples/to-pii-regex.example.js +75 -0
  22. package/dist/{presentation → src/application}/factory/create-logger.d.ts +1 -1
  23. package/dist/{presentation → src/application}/factory/create-logger.js +3 -3
  24. package/dist/src/application/factory/index.d.ts +1 -0
  25. package/dist/{presentation → src/application}/factory/index.js +1 -1
  26. package/dist/src/application/factory/logger.factory.d.ts +12 -0
  27. package/dist/src/application/factory/logger.factory.js +74 -0
  28. package/dist/src/application/index.d.ts +2 -0
  29. package/dist/src/application/index.js +18 -0
  30. package/dist/src/application/use-cases/flush-buffers.use-case.d.ts +6 -0
  31. package/dist/{application/use-cases/flush-buffers.js → src/application/use-cases/flush-buffers.use-case.js} +3 -3
  32. package/dist/src/application/use-cases/get-logs.use-case.d.ts +8 -0
  33. package/dist/{application/use-cases/get-logs.js → src/application/use-cases/get-logs.use-case.js} +3 -3
  34. package/dist/src/application/use-cases/index.d.ts +3 -0
  35. package/dist/{application → src/application}/use-cases/index.js +3 -3
  36. package/dist/src/application/use-cases/save-log.use-case.d.ts +8 -0
  37. package/dist/src/application/use-cases/save-log.use-case.js +26 -0
  38. package/dist/src/domain/index.d.ts +6 -0
  39. package/dist/src/domain/index.js +22 -0
  40. package/dist/src/domain/ports/create-logger-options.port.d.ts +7 -0
  41. package/dist/src/domain/ports/index.d.ts +6 -0
  42. package/dist/src/domain/ports/index.js +22 -0
  43. package/dist/src/domain/ports/log-datasource.port.d.ts +9 -0
  44. package/dist/src/domain/ports/logger-factory-config.port.d.ts +28 -0
  45. package/dist/src/domain/ports/logger-service.port.d.ts +19 -0
  46. package/dist/{domain/contracts/logger.d.ts → src/domain/ports/logger.port.d.ts} +0 -9
  47. package/dist/src/domain/ports/logger.port.js +2 -0
  48. package/dist/src/domain/ports/pii-redactor.port.d.ts +5 -0
  49. package/dist/src/domain/ports/pii-redactor.port.js +2 -0
  50. package/dist/src/domain/request/get-logs-filter.props.d.ts +9 -0
  51. package/dist/src/domain/request/get-logs-filter.props.js +2 -0
  52. package/dist/src/domain/request/index.d.ts +5 -0
  53. package/dist/src/domain/request/index.js +21 -0
  54. package/dist/src/domain/request/log.props.d.ts +7 -0
  55. package/dist/src/domain/request/log.props.js +2 -0
  56. package/dist/src/domain/request/pii-options.props.d.ts +8 -0
  57. package/dist/src/domain/request/pii-options.props.js +2 -0
  58. package/dist/src/domain/request/pii-replacement.props.d.ts +5 -0
  59. package/dist/src/domain/request/pii-replacement.props.js +2 -0
  60. package/dist/src/domain/request/save-log.props.d.ts +7 -0
  61. package/dist/src/domain/request/save-log.props.js +2 -0
  62. package/dist/src/domain/response/index.d.ts +1 -0
  63. package/dist/{domain/value-objects → src/domain/response}/index.js +1 -1
  64. package/dist/src/domain/response/log.response.d.ts +7 -0
  65. package/dist/src/domain/response/log.response.js +2 -0
  66. package/dist/src/domain/services/index.d.ts +4 -0
  67. package/dist/{domain/contracts → src/domain/services}/index.js +4 -3
  68. package/dist/src/domain/services/log-level.service.d.ts +2 -0
  69. package/dist/src/domain/services/log-level.service.js +27 -0
  70. package/dist/src/domain/services/message-normalizer.service.d.ts +3 -0
  71. package/dist/src/domain/services/message-normalizer.service.js +8 -0
  72. package/dist/src/domain/services/pii-pattern.service.d.ts +2 -0
  73. package/dist/src/domain/services/pii-pattern.service.js +13 -0
  74. package/dist/src/domain/services/pii-redactor.d.ts +10 -0
  75. package/dist/{domain → src/domain}/services/pii-redactor.js +8 -17
  76. package/dist/src/domain/services/pii-redactor.service.d.ts +10 -0
  77. package/dist/src/domain/services/pii-redactor.service.js +68 -0
  78. package/dist/src/domain/types/index.d.ts +1 -0
  79. package/dist/src/domain/types/index.js +17 -0
  80. package/dist/src/domain/types/log-message.type.d.ts +1 -0
  81. package/dist/src/domain/types/log-message.type.js +2 -0
  82. package/dist/src/domain/value-objects/index.d.ts +1 -0
  83. package/dist/{domain/services → src/domain/value-objects}/index.js +1 -1
  84. package/dist/{domain/value-objects/log-level.d.ts → src/domain/value-objects/log-level.vo.d.ts} +0 -1
  85. package/dist/src/domain/value-objects/log-level.vo.js +13 -0
  86. package/dist/src/index.d.ts +6 -0
  87. package/dist/src/index.js +22 -0
  88. package/dist/src/infrastructure/datasources/in-memory-log.datasource.d.ts +1 -0
  89. package/dist/src/infrastructure/datasources/in-memory-log.datasource.js +2 -0
  90. package/dist/src/infrastructure/datasources/index.d.ts +1 -0
  91. package/dist/{infrastructure/adapters → src/infrastructure/datasources}/index.js +1 -1
  92. package/dist/src/infrastructure/index.d.ts +1 -0
  93. package/dist/{domain/types → src/infrastructure}/index.js +1 -1
  94. package/dist/src/infrastructure/services/data-source-error-handler.type.d.ts +5 -0
  95. package/dist/src/infrastructure/services/data-source-error-handler.type.js +2 -0
  96. package/dist/src/infrastructure/services/datasource.service.d.ts +15 -0
  97. package/dist/src/infrastructure/services/datasource.service.js +63 -0
  98. package/dist/src/infrastructure/services/index.d.ts +2 -0
  99. package/dist/src/infrastructure/services/index.js +18 -0
  100. package/dist/tests/application/factory/logger-factory.spec.d.ts +1 -0
  101. package/dist/tests/application/factory/logger-factory.spec.js +161 -0
  102. package/dist/tests/application/use-cases/flush-buffers.use-case.spec.d.ts +1 -0
  103. package/dist/tests/application/use-cases/flush-buffers.use-case.spec.js +38 -0
  104. package/dist/tests/application/use-cases/get-logs.use-case.spec.d.ts +1 -0
  105. package/dist/tests/application/use-cases/get-logs.use-case.spec.js +114 -0
  106. package/dist/tests/application/use-cases/save-log.use-case.spec.d.ts +1 -0
  107. package/dist/tests/application/use-cases/save-log.use-case.spec.js +138 -0
  108. package/dist/tests/domain/services/log-level.service.spec.d.ts +1 -0
  109. package/dist/tests/domain/services/log-level.service.spec.js +68 -0
  110. package/dist/tests/domain/services/normalize-message.spec.d.ts +1 -0
  111. package/dist/tests/domain/services/normalize-message.spec.js +83 -0
  112. package/dist/tests/domain/services/pii-redactor.spec.d.ts +1 -0
  113. package/dist/tests/domain/services/pii-redactor.spec.js +170 -0
  114. package/dist/tests/domain/services/to-pii-regex.spec.d.ts +1 -0
  115. package/dist/tests/domain/services/to-pii-regex.spec.js +82 -0
  116. package/dist/tests/infrastructure/services/datasource.service.spec.d.ts +1 -0
  117. package/dist/tests/infrastructure/services/datasource.service.spec.js +128 -0
  118. package/dist/tests/test-utils/create-pii-redactor-mock.d.ts +5 -0
  119. package/dist/tests/test-utils/create-pii-redactor-mock.js +10 -0
  120. package/package.json +27 -19
  121. package/LICENSE +0 -21
  122. package/dist/application/use-cases/flush-buffers.d.ts +0 -6
  123. package/dist/application/use-cases/get-logs.d.ts +0 -7
  124. package/dist/application/use-cases/index.d.ts +0 -3
  125. package/dist/application/use-cases/save-log.d.ts +0 -13
  126. package/dist/application/use-cases/save-log.js +0 -30
  127. package/dist/domain/contracts/index.d.ts +0 -3
  128. package/dist/domain/contracts/log.datasource.d.ts +0 -8
  129. package/dist/domain/contracts/pii.d.ts +0 -5
  130. package/dist/domain/services/index.d.ts +0 -1
  131. package/dist/domain/services/pii-redactor.d.ts +0 -10
  132. package/dist/domain/types/index.d.ts +0 -1
  133. package/dist/domain/types/log.types.d.ts +0 -28
  134. package/dist/domain/value-objects/index.d.ts +0 -1
  135. package/dist/domain/value-objects/log-level.js +0 -37
  136. package/dist/index.d.ts +0 -5
  137. package/dist/index.js +0 -10
  138. package/dist/infrastructure/adapters/composite.datasource.d.ts +0 -11
  139. package/dist/infrastructure/adapters/composite.datasource.js +0 -46
  140. package/dist/infrastructure/adapters/index.d.ts +0 -1
  141. package/dist/presentation/factory/index.d.ts +0 -1
  142. /package/dist/{domain/contracts/log.datasource.js → src/domain/ports/create-logger-options.port.js} +0 -0
  143. /package/dist/{domain/contracts/logger.js → src/domain/ports/log-datasource.port.js} +0 -0
  144. /package/dist/{domain/contracts/pii.js → src/domain/ports/logger-factory-config.port.js} +0 -0
  145. /package/dist/{domain/types/log.types.js → src/domain/ports/logger-service.port.js} +0 -0
package/README.md CHANGED
@@ -1,815 +1,307 @@
1
1
  # @jmlq/logger
2
2
 
3
- Paquete de `logging` extensible y desacoplado, diseñado con principios de Arquitectura Limpia.
4
- Permite registrar logs en múltiples destinos (`archivos`, `MongoDB`, `PostgreSQL`) mediante **plugins** y soporta enmascarado de datos sensibles (PII).
5
-
6
- ---
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).
7
4
 
8
5
  ## 📦 Instalación
9
6
 
10
7
  ```bash
11
- # Instalar el core
12
- npm i @jmlq/logger
13
-
14
- # Instalar plugins opcionales según el backend de persistencia
15
- npm i @jmlq/logger-plugin-fs
16
- npm i @jmlq/logger-plugin-mongo
17
- npm i @jmlq/logger-plugin-postgresql
18
-
19
- ```
20
-
21
- Si usas Mongo o Postgres en tu app cliente, instala además:
22
-
23
- ```bash
24
- npm i mongodb@^6.19.0
25
- npm i pg@^8.16.3
26
-
27
- ```
28
-
29
- ### DOCUMENTACION
30
-
31
- > - [`@jmlq/logger-plugin-fs`](https://www.npmjs.com/package/@jmlq/logger-plugin-fs)
32
- > - [`@jmlq/logger-plugin-mongo`](https://www.npmjs.com/package/@jmlq/logger-plugin-mongo)
33
- > - [`@jmlq/logger-plugin-postgresql`](https://www.npmjs.com/package/@jmlq/logger-plugin-postgresql)
34
-
35
- ---
36
-
37
- ## 🧩 Configuración
38
-
39
- ### 🔐 Variables de Entorno (.env)
40
-
41
- ```ini
42
- # @jmlq/logger
43
- # Nivel mínimo por entorno (dev: debug, prod: warn)
44
- LOGGER_LEVEL=debug
45
- # PII (Personally Identifiable Information)
46
- LOGGER_PII_ENABLED=true
47
- LOGGER_PII_INCLUDE_DEFAULTS=true
48
-
49
- # MONGO DB @jmlq/logger-plugin-mongo
50
- MONGO_URL=mongodb://<user>:<password>@localhost:<port>
51
- MONGO_DB_NAME=<db-name>
52
- MONGO_COLLECTION=<collection-name>
53
- LOGGER_MONGO_RETENTION_DAYS=30
54
-
55
- # POSTGRESQL @jmlq/logger-plugin-postgresql
56
- POSTGRES_URL="postgresql://mlahuasi:123456@localhost:5432/NOC"
57
- POSTGRES_DB=<db-name>
58
- POSTGRES_SCHEMA=public
59
- POSTGRES_TABLE=<table-name>
60
- LOGGER_PG_RETENTION_DAYS=30
61
-
62
- # FILESYSTEM @jmlq/logger-plugin-fs
63
- LOGGER_FS_PATH=./logs
8
+ # Core del logger
9
+ npm install @jmlq/logger
64
10
 
11
+ # Plugins opcionales (instala según necesites)
12
+ npm install @jmlq/logger-plugin-fs # Para archivos
13
+ npm install @jmlq/logger-plugin-mongo # Para MongoDB
14
+ npm install @jmlq/logger-plugin-postgresql # Para PostgreSQL
65
15
  ```
66
16
 
67
- ---
17
+ ## 🚀 Uso Básico
68
18
 
69
- ### 🚀 Uso del paquete
19
+ ### Configuración Simple
70
20
 
71
- #### 1) Estructura recomendada
21
+ ```typescript
22
+ import { LoggerFactory, LogLevel } from "@jmlq/logger";
72
23
 
73
- ```
74
- src/
75
- ├─ infrastructure/
76
- ├─ logger/
77
- │ │ ├─ adapters/
78
- │ │ ├─ fs.adapter.ts
79
- │ │ ├─ mongo.adapter.ts
80
- │ │ │ └─ postgresql.adapter.ts
81
- │ ├─ settings/
82
- │ │ ├─ pii.settings.ts
83
- │ │ └─ loglevel.settings.ts
84
- │ │ └─ bootstrap.ts # LoggerBootstrap (orquesta adapters + PII)
85
- │ └─ plugins/
86
- │ ├─ env.plugin.ts
87
- │ └─ index.ts
88
- ├─ config/
89
- │ └─ logger/
90
- │ └─ index.ts # singleton global: loggerReady / flush / dispose
91
- └─ presentation/
92
- └─ server.ts # uso del logger en Express
93
-
94
- ```
95
-
96
- #### 2) Settings
97
-
98
- #### 2.1) pii.settings.ts (patrones PII + merge)
99
-
100
- ```ts
101
- //Se definine reglas de redacción PII (buscar-y-reemplazar con regex) que el logger aplica antes de persistir/emitir un log.
102
-
103
- export type PiiReplacement = {
104
- pattern: string;
105
- replaceWith: string;
106
- flags?: string; // regex (g, i, m, s, u, y)
24
+ // Crear un datasource en memoria (para testing/desarrollo)
25
+ const memoryDatasource = {
26
+ name: "memory",
27
+ async save(log) {
28
+ console.log("LOG:", log);
29
+ },
30
+ async find(filter) {
31
+ return [];
32
+ },
33
+ async flush() {},
34
+ async dispose() {},
107
35
  };
108
36
 
109
- export interface PiiConfig {
110
- enabled: boolean;
111
- whitelistKeys?: string[];
112
- blacklistKeys?: string[];
113
- patterns?: PiiReplacement[];
114
- deep?: boolean;
115
- includeDefaults?: boolean;
116
- }
117
-
118
- // patrones “de fábrica” (del sistema):
119
- export const DEFAULT_PII_PATTERNS: PiiReplacement[] = [
120
- {
121
- pattern: String.raw`[^@\n\r ]+@[^@\n\r ]+`,
122
- replaceWith: "[EMAIL]",
123
- flags: "g",
37
+ // Crear el logger
38
+ const logger = LoggerFactory.create({
39
+ datasources: memoryDatasource,
40
+ minLevel: LogLevel.INFO,
41
+ redactorOptions: {
42
+ enabled: true,
43
+ deep: true,
44
+ includeDefaults: true,
124
45
  },
125
- ];
126
- // Ver abajo explicación flags
127
-
128
- // patrones “del cliente/proyecto”:
129
- export const clientPiiPatterns: PiiReplacement[] = [
130
- { pattern: String.raw`\b\d{10}\b`, flags: "g", replaceWith: "[EC_DNI]" },
131
- ];
132
-
133
- // lista negra de nombres de clave que se siempre se ocultan
134
- export const redactKeys: string[] = ["password", "secret"];
135
- // lista blanca de nombres de clave que no se deben ocultar por clave
136
- export const preserveKeys: string[] = ["city"];
137
-
138
- // Helpers
139
- // 1. Filtra valores no válidos: elimina null, undefined y "" (cadena vacía).
140
- // 2. Elimina duplicados usando Set.
141
- // 3. Conserva el primero de cada valor repetido (porque Set guarda la primera aparición).
142
- export function dedupeStrings(arr: Array<string | undefined | null>): string[] {
143
- return [...new Set(arr.filter((x): x is string => !!x && x.length > 0))];
144
- }
145
-
146
- // 1. Construye una clave de identidad por patrón: pattern + "__" + replaceWith + "__" + (flags || "").
147
- // 2. Inserta cada elemento en un `Map` usando esa clave. Si la clave ya existe, sobrescribe el anterior (es decir, gana el último).
148
- // 3. Devuelve los valores únicos del `Map`.
149
- export function dedupePatterns(arr: PiiReplacement[]): PiiReplacement[] {
150
- const m = new Map<string, PiiReplacement>();
151
- for (const p of arr)
152
- m.set(`${p.pattern}__${p.replaceWith}__${p.flags ?? ""}`, p);
153
- return [...m.values()];
154
- }
46
+ });
155
47
 
156
- // Builder unificado
157
- export function buildPiiConfig(
158
- opts?: PiiConfig
159
- ): Required<Omit<PiiConfig, "includeDefaults">> {
160
- const includeDefaults = opts?.includeDefaults ?? true;
161
-
162
- const patterns = dedupePatterns([
163
- ...(includeDefaults ? DEFAULT_PII_PATTERNS : []),
164
- ...clientPiiPatterns,
165
- ...(opts?.patterns ?? []),
166
- ]);
167
-
168
- const whitelistKeys = dedupeStrings([
169
- ...(opts?.whitelistKeys ?? []),
170
- ...preserveKeys,
171
- ]);
172
- const blacklistKeys = dedupeStrings([
173
- ...(opts?.blacklistKeys ?? []),
174
- ...redactKeys,
175
- ]);
176
-
177
- return {
178
- enabled: !!opts?.enabled,
179
- whitelistKeys,
180
- blacklistKeys,
181
- patterns,
182
- deep: opts?.deep ?? true,
183
- };
184
- }
48
+ // Usar el logger
49
+ await logger.info("Usuario conectado", {
50
+ userId: "123",
51
+ email: "user@example.com",
52
+ });
53
+ await logger.error("Error en el sistema", {
54
+ error: "Database connection failed",
55
+ });
185
56
  ```
186
57
 
187
- > - **REGEX FLAGS**
188
-
189
- > > - `g` (**global**): Encuentra `todas las coincidencias` en el texto, no solo la primera.
58
+ ### Con Múltiples Datasources
190
59
 
191
- ```ts
192
- const regex = /\d{2}/g;
193
- "123456".match(regex); // ["12", "34", "56"]
60
+ ```typescript
61
+ import { LoggerFactory } from "@jmlq/logger";
62
+ import { FileSystemDatasource } from "@jmlq/logger-plugin-fs";
63
+ import { MongoDatasource } from "@jmlq/logger-plugin-mongo";
64
+
65
+ const logger = LoggerFactory.create({
66
+ datasources: [
67
+ new FileSystemDatasource({ basePath: "./logs" }),
68
+ new MongoDatasource({ url: "mongodb://localhost:27017", dbName: "logs" }),
69
+ ],
70
+ minLevel: LogLevel.INFO,
71
+ });
194
72
 
195
- // /\d{2}/ busca pares de dígitos.
196
- // "12" en índices 0-1
197
- // "34" en índices 2-3
198
- // "56" en índices 4-5
73
+ await logger.warn("Latencia alta", { endpoint: "/api/users", duration: 850 });
199
74
  ```
200
75
 
201
- > > - `i` (**ignore case**): Ignora mayúsculas/minúsculas.
76
+ ## 🎯 Características Principales
202
77
 
203
- ```ts
204
- // Sin "i" (sensible a mayúsculas/minúsculas)
205
- const regexCaseSensitive = /secret/;
206
- "SECRET".match(regexCaseSensitive); // null
207
- "secret".match(regexCaseSensitive); // ["secret"]
78
+ ### Niveles de Log
208
79
 
209
- // Con "i" (ignora mayúsculas/minúsculas)
210
- const regexIgnoreCase = /secret/i;
211
- "SECRET".match(regexIgnoreCase); // ["SECRET"]
212
- "Secret".match(regexIgnoreCase); // ["Secret"]
213
- "sEcReT".match(regexIgnoreCase); // ["sEcReT"]
214
- ```
80
+ - `TRACE` (0) - Información muy detallada
81
+ - `DEBUG` (1) - Información de depuración
82
+ - `INFO` (2) - Información general
83
+ - `WARN` (3) - Advertencias
84
+ - `ERROR` (4) - Errores
85
+ - `FATAL` (5) - Errores críticos
215
86
 
216
- > > - `m` (**multiline**): Permite que `^` y `$` funcionen en cada línea, no solo al inicio/fin del string completo.
87
+ ### Enmascarado PII Automático
217
88
 
218
- ```ts
219
- const texto = `uno
220
- FOO
221
- tres`;
89
+ El logger detecta y enmascara automáticamente datos sensibles:
222
90
 
223
- // Sin "m": ^ solo reconoce el inicio de *todo* el string
224
- const regexNormal = /^FOO/;
225
- console.log(texto.match(regexNormal)); // null
226
-
227
- // Con "m": ^ reconoce también el inicio de cada línea
228
- const regexMultiline = /^FOO/m;
229
- console.log(texto.match(regexMultiline)); // ["FOO"]
91
+ ```typescript
92
+ await logger.info("Pago procesado", {
93
+ email: "user@example.com", // → user@[EMAIL]
94
+ card: "4111-1111-1111-1111", // → ****-****-****-****
95
+ password: "secret123", // [REDACTED]
96
+ });
230
97
  ```
231
98
 
232
- > > - `s` (**dotAll**): Permite que `.` coincida también con saltos de línea (`\n`).
233
-
234
- ```ts
235
- const texto = "a\nb";
236
-
237
- // Sin "s": el punto (.) no captura saltos de línea
238
- const regexNormal = /a.b/;
239
- texto.match(regexNormal); // null
99
+ ### Filtros y Consultas
240
100
 
241
- // Con "s": el punto (.) sí captura saltos de línea
242
- const regexDotAll = /a.b/s;
243
- texto.match(regexDotAll); // ["a\nb"]
244
- ```
245
-
246
- > > - `u` (**unicode**): Habilita soporte Unicode completo en regex (ejemplo, emojis o caracteres fuera del BMP).
101
+ ```typescript
102
+ // Obtener logs de errores de las últimas 24 horas
103
+ const errors = await logger.getLogs({
104
+ levelMin: LogLevel.ERROR,
105
+ since: Date.now() - 24 * 60 * 60 * 1000,
106
+ limit: 50,
107
+ });
247
108
 
248
- ```ts
249
- const regex = /\u{1F600}/u; // 😀
250
- "😀".match(regex); // ["😀"]
109
+ // Buscar logs que contengan "usuario"
110
+ const userLogs = await logger.getLogs({
111
+ query: "usuario",
112
+ limit: 100,
113
+ });
251
114
  ```
252
115
 
253
- > > - `y` (**sticky**): Solo encuentra coincidencias en la posición exacta del índice actual (lastIndex).
254
-
255
- ```ts
256
- const regexSticky = /\d{2}/y;
116
+ ## 🔧 Configuración con Variables de Entorno
257
117
 
258
- regexSticky.lastIndex = 0;
259
- regexSticky.exec("123456");
260
- // ["12"]
261
-
262
- regexSticky.lastIndex = 2;
263
- regexSticky.exec("123456");
264
- // ["34"]
265
-
266
- regexSticky.lastIndex = 4;
267
- regexSticky.exec("123456");
268
- // ["56"]
269
-
270
- regexSticky.lastIndex = 1;
271
- regexSticky.exec("123456");
272
- // null (porque en índice 1 hay "2", pero necesita empezar justo ahí y no hay 2 dígitos completos desde esa posición)
273
- ```
118
+ ```env
119
+ # Nivel mínimo de logs
120
+ LOG_LEVEL=info
274
121
 
275
- Ejemplos:
122
+ # Filesystem
123
+ LOGGER_FS_PATH=./logs
276
124
 
277
- ```ts
278
- const text = "Usuario: 12345, Otro: 67890";
279
- const rules: PiiReplacement[] = [
280
- { pattern: "\\d{5}", replaceWith: "[ID]", flags: "g" },
281
- ];
282
- console.log(redact(text, rules));
283
- // Usuario: [ID], Otro: [ID]
125
+ # MongoDB
126
+ MONGO_URL=mongodb://localhost:27017
127
+ MONGO_DB_NAME=logs
284
128
 
285
- // Donde:
286
- // \d → significa un dígito (0–9).
287
- // {5} → significa exactamente 5 repeticiones seguidas.
288
- ```
129
+ # PostgreSQL
130
+ POSTGRES_URL=postgresql://user:pass@localhost:5432/logs
289
131
 
290
- ```ts
291
- const text = "Password=1234; PASSWORD=5678; password=9999";
292
- const rules: PiiReplacement[] = [
293
- { pattern: "password=\\d+", replaceWith: "password=[REDACTED]", flags: "gi" },
294
- ];
295
- console.log(redact(text, rules));
296
- // password=[REDACTED]; password=[REDACTED]; password=[REDACTED]
297
-
298
- // Donde:
299
- // \d → significa un dígito (0–9).
300
- // + → uno o más dígitos consecutivos.
132
+ # PII Protection
133
+ LOGGER_PII_ENABLED=true
301
134
  ```
302
135
 
303
- ```ts
304
- const text = `
305
- linea1: ok
306
- secret=12345
307
- linea3: done
308
- `;
309
-
310
- const rules: PiiReplacement[] = [
311
- { pattern: "^secret=.*$", replaceWith: "secret=[REDACTED]", flags: "m" },
312
- ];
313
- console.log(redact(text, rules));
314
- /*
315
- linea1: ok
316
- secret=[REDACTED]
317
- linea3: done
318
- */
319
-
320
- // Donde
321
- // . → cualquier carácter (excepto salto de línea, a menos que uses el flag s).
322
- // * → cero o más repeticiones del carácter anterior (.).
323
- // $ → final de la línea o final de la cadena (dependiendo si usas flag m).
324
- ```
136
+ ## 📁 Ejemplos Prácticos
325
137
 
326
- ```ts
327
- const text = "BEGIN\n12345\nEND";
328
- const rules: PiiReplacement[] = [
329
- { pattern: "BEGIN.*END", replaceWith: "[BLOCK REDACTED]", flags: "s" },
330
- ];
331
- console.log(redact(text, rules));
332
- // [BLOCK REDACTED]
333
-
334
- // Donde
335
- // . → cualquier carácter (excepto salto de línea, a menos que uses el flag s).
336
- // * → cero o más repeticiones del carácter anterior (.).
337
- ```
138
+ El proyecto incluye ejemplos completos en el directorio [`examples/`](examples/):
338
139
 
339
- ```ts
340
- const text = "Cliente: 😀 secreto=123";
341
- const rules: PiiReplacement[] = [
342
- { pattern: "\\p{Emoji}", replaceWith: "[EMOJI]", flags: "gu" },
343
- ];
344
- console.log(redact(text, rules));
345
- // Cliente: [EMOJI] secreto=123
346
-
347
- // Donde
348
- // \p{...} → en regex con flag u (unicode), permite usar propiedades Unicode.
349
- // \p{Emoji} → coincide con cualquier carácter que esté clasificado en Unicode como un emoji.
140
+ ```bash
141
+ # Ver todos los ejemplos disponibles
142
+ npm run example:help
143
+
144
+ # Ejecutar ejemplos específicos
145
+ npm run example:factories # LoggerFactory
146
+ npm run example:use-cases # SaveLog, GetLogs, FlushBuffers
147
+ npm run example:domain-services # PII, normalización
148
+ npm run example:all # Todos los ejemplos
350
149
  ```
351
150
 
352
- ```ts
353
- const text = "ID=1234 ID=5678";
354
- const regexRule: PiiReplacement = {
355
- pattern: "ID=\\d{4}",
356
- replaceWith: "ID=[REDACTED]",
357
- flags: "y",
358
- };
151
+ ### Ejemplo Express
359
152
 
360
- const regex = new RegExp(regexRule.pattern, regexRule.flags);
361
- regex.lastIndex = 0;
362
- console.log(regex.exec(text)); // ["ID=1234"]
153
+ ```typescript
154
+ import express from "express";
155
+ import { LoggerFactory, LogLevel } from "@jmlq/logger";
363
156
 
364
- regex.lastIndex = 7;
365
- console.log(regex.exec(text)); // ["ID=5678"]
366
-
367
- regex.lastIndex = 3;
368
- console.log(regex.exec(text)); // null (porque no empieza justo ahí)
157
+ const app = express();
158
+ const logger = LoggerFactory.create({
159
+ datasources: /* tu datasource */,
160
+ minLevel: LogLevel.INFO
161
+ });
369
162
 
370
- // Donde:
371
- // \d significa un dígito (0–9).
372
- // {4} → significa exactamente 4 repeticiones consecutivas.
373
- ```
163
+ app.use(async (req, res, next) => {
164
+ await logger.info("Request iniciado", {
165
+ method: req.method,
166
+ url: req.url,
167
+ ip: req.ip
168
+ });
169
+ next();
170
+ });
374
171
 
375
- ##### 2.2) loglevel.settings.ts (normalización de nivel)
376
-
377
- ```ts
378
- // src/infrastructure/logger/settings/loglevel.settings.ts
379
- import { LogLevel } from "@jmlq/logger";
380
-
381
- export function toMinLevel(
382
- level: LogLevel | keyof typeof LogLevel | string
383
- ): LogLevel {
384
- if (typeof level === "number") return level as LogLevel;
385
- switch (String(level || "debug").toLowerCase()) {
386
- case "trace":
387
- return LogLevel.TRACE;
388
- case "debug":
389
- return LogLevel.DEBUG;
390
- case "info":
391
- return LogLevel.INFO;
392
- case "warn":
393
- return LogLevel.WARN;
394
- case "error":
395
- return LogLevel.ERROR;
396
- case "fatal":
397
- return LogLevel.FATAL;
398
- default:
399
- return LogLevel.DEBUG;
172
+ app.get("/users/:id", async (req, res) => {
173
+ try {
174
+ await logger.info("Obteniendo usuario", { userId: req.params.id });
175
+ // ... tu lógica
176
+ res.json({ user: data });
177
+ } catch (error) {
178
+ await logger.error("Error al obtener usuario", {
179
+ userId: req.params.id,
180
+ error: error.message
181
+ });
182
+ res.status(500).json({ error: "Internal server error" });
400
183
  }
401
- }
184
+ });
402
185
  ```
403
186
 
404
- #### 3) Adapters (capa infrastructure)
405
-
406
- Todos `devuelven`/`encapsulan` un `ILogDatasource` para evitar acoplar la app a clases concretas.
187
+ ### Ejemplo NestJS
407
188
 
408
- **NOTA**: Solo se implementan los que se necesiten, por ejemplo:
189
+ ```typescript
190
+ @Injectable()
191
+ export class AppService {
192
+ constructor(@Inject("LOGGER") private readonly logger: ILoggerService) {}
409
193
 
410
- > - Si se necesita generar logs en archivos se crea adapter para `FS`.
411
- > - Si se necesita guardar logs en una base de datos no relacional se crea adapter para `mongo`.
412
- > - Si se necesita guardar logs en una base de datos relacional se crea adapter para `postgresql`.
413
- > - También se pueden combinar.
414
- > - Se debe implementar al menos un adapter.
415
-
416
- ##### 3.1) FS (`fs.adapter.ts`)
417
-
418
- ```ts
419
- import { createFsDatasource } from "@jmlq/logger-plugin-fs";
420
- import type { ILogDatasource } from "@jmlq/logger";
421
-
422
- export interface IFsProps {
423
- basePath: string;
424
- fileNamePattern: string;
425
- rotationPolicy: { by: "none" | "day" | "size"; maxSizeMB?: number };
426
- }
427
-
428
- export class FsAdapter {
429
- private constructor(private readonly ds: ILogDatasource) {}
430
- static create(opts: IFsProps): FsAdapter | undefined {
194
+ async createUser(userData: any) {
195
+ await this.logger.info("Creando usuario", { userData });
431
196
  try {
432
- const ds = createFsDatasource({
433
- basePath: opts.basePath,
434
- mkdir: true,
435
- fileNamePattern: opts.fileNamePattern,
436
- rotation: opts.rotationPolicy,
437
- onRotate: (oldP, newP) =>
438
- console.log("[fs] rotated:", oldP, "->", newP),
439
- onError: (e) => console.error("[fs] error:", e),
197
+ // ... lógica
198
+ await this.logger.info("Usuario creado", { userId: result.id });
199
+ return result;
200
+ } catch (error) {
201
+ await this.logger.error("Error creando usuario", {
202
+ error: error.message,
440
203
  });
441
- console.log("[logger] Conectado a FS para logs");
442
- return new FsAdapter(ds);
443
- } catch (e: any) {
444
- console.warn("[logger] FS deshabilitado:", e?.message ?? e);
204
+ throw error;
445
205
  }
446
206
  }
447
- get datasource(): ILogDatasource {
448
- return this.ds;
449
- }
450
207
  }
451
208
  ```
452
209
 
453
- ##### 3.2) Mongo (`mongo.adapter.ts`)
210
+ ## 🏗️ Arquitectura
454
211
 
455
- ```ts
456
- import type { ILogDatasource } from "@jmlq/logger";
457
- import { createMongoInfra, MongoDatasource } from "@jmlq/logger-plugin-mongo";
212
+ El paquete sigue **Arquitectura Limpia**:
458
213
 
459
- export interface IMongoProps {
460
- url: string;
461
- dbName: string;
462
- collectionName?: string;
463
- retentionDays?: number | null;
464
- }
465
-
466
- export class MongoAdapter {
467
- private constructor(private readonly ds: ILogDatasource) {}
468
- static async create(opts: IMongoProps): Promise<MongoAdapter | undefined> {
469
- try {
470
- const infra = await createMongoInfra({
471
- url: opts.url,
472
- dbName: opts.dbName,
473
- collectionName: opts.collectionName ?? "logs",
474
- createIfMissing: true,
475
- ensureIndexes: true,
476
- retentionDays: opts.retentionDays ?? 0,
477
- extraIndexes: [{ key: { "meta.userId": 1 } }],
478
- });
479
- return new MongoAdapter(new MongoDatasource(infra.collection));
480
- } catch (e: any) {
481
- console.warn("[logger] Mongo deshabilitado:", e?.message ?? e);
482
- }
483
- }
484
- get datasource(): ILogDatasource {
485
- return this.ds;
486
- }
487
- }
488
214
  ```
489
-
490
- ##### 3.2) Postgresql (`postgresql.adapter.ts`)
491
-
492
- ```ts
493
- import type { ILogDatasource } from "@jmlq/logger";
494
- import { createPostgresDatasource } from "@jmlq/logger-plugin-postgresql";
495
-
496
- export interface IPostgresqlProps {
497
- url: string;
498
- schema: string;
499
- table?: string;
500
- retentionDays?: number | null;
501
- }
502
-
503
- export class PostgresqlAdapter {
504
- private constructor(private readonly ds: ILogDatasource) {}
505
- static async create(
506
- opts: IPostgresqlProps
507
- ): Promise<PostgresqlAdapter | undefined> {
508
- try {
509
- const ps = await createPostgresDatasource({
510
- connectionString: opts.url,
511
- schema: opts.schema,
512
- table: opts.table ?? "logs",
513
- createIfMissing: true,
514
- retentionDays: opts.retentionDays ?? 0,
515
- });
516
- console.log("[logger] Conectado a PostgreSQL para logs");
517
- return new PostgresqlAdapter(ps);
518
- } catch (e: any) {
519
- console.warn("[logger] Postgres deshabilitado:", e?.message ?? e);
520
- }
521
- }
522
- get datasource(): ILogDatasource {
523
- return this.ds;
524
- }
525
- }
215
+ src/
216
+ ├─ domain/ # Reglas de negocio puras
217
+ │ ├─ entities/
218
+ │ ├─ value-objects/ # LogLevel
219
+ │ ├─ services/ # PiiRedactor, MessageNormalizer
220
+ │ ├─ ports/ # Interfaces/contratos
221
+ │ └─ types/
222
+ ├─ application/ # Casos de uso
223
+ ├─ use-cases/ # SaveLog, GetLogs, FlushBuffers
224
+ └─ factory/ # LoggerFactory
225
+ └─ infrastructure/ # Implementaciones concretas
226
+ └─ services/ # DataSourceService (composite)
526
227
  ```
527
228
 
528
- #### 4) `LoggerBootstrap` (orquestador de adapters + PII)
529
-
530
- ```ts
531
- // src/infrastructure/logger/bootstrap.ts
532
- import {
533
- createLogger,
534
- CompositeDatasource,
535
- type ILogDatasource,
536
- } from "@jmlq/logger";
537
- import { buildPiiConfig } from "./settings/pii.settings";
538
- import { toMinLevel } from "./settings/loglevel.settings";
539
- // NOTA: Opcionales depende de las necesidades del cliente
540
- import { FsAdapter, type IFsProps } from "./adapters/fs.adapter";
541
- import { MongoAdapter, type IMongoProps } from "./adapters/mongo.adapter";
542
- import {
543
- PostgresqlAdapter,
544
- type IPostgresqlProps,
545
- } from "./adapters/postgresql.adapter";
546
-
547
- export interface LoggerBootstrapOptions {
548
- minLevel: string | number;
549
- pii?: {
550
- enabled?: boolean;
551
- whitelistKeys?: string[];
552
- blacklistKeys?: string[];
553
- patterns?: any[];
554
- deep?: boolean;
555
- includeDefaults?: boolean;
556
- };
557
- adapters?: {
558
- // NOTA: Opcionales depende de las necesidades del cliente
559
- fs?: IFsProps;
560
- mongo?: IMongoProps;
561
- postgres?: IPostgresqlProps;
562
- };
563
- }
564
-
565
- export class LoggerBootstrap {
566
- private constructor(
567
- private readonly _logger: ReturnType<typeof createLogger>,
568
- private readonly _ds: ILogDatasource
569
- ) {}
570
-
571
- static async create(opts: LoggerBootstrapOptions): Promise<LoggerBootstrap> {
572
- const dsList: ILogDatasource[] = [];
573
-
574
- // NOTA: Opcionales depende de las necesidades del cliente
575
- if (opts.adapters?.fs) {
576
- const fs = FsAdapter.create(opts.adapters.fs);
577
- if (fs) dsList.push(fs.datasource);
578
- }
579
- if (opts.adapters?.mongo) {
580
- const mg = await MongoAdapter.create(opts.adapters.mongo);
581
- if (mg) dsList.push(mg.datasource);
582
- }
583
- if (opts.adapters?.postgres) {
584
- const pg = await PostgresqlAdapter.create(opts.adapters.postgres);
585
- if (pg) dsList.push(pg.datasource);
586
- }
587
- //----
588
-
589
- if (dsList.length === 0)
590
- throw new Error("[logger] No hay datasources válidos.");
591
- const datasource =
592
- dsList.length === 1 ? dsList[0] : new CompositeDatasource(dsList);
593
-
594
- const pii = buildPiiConfig({
595
- enabled: opts.pii?.enabled ?? false,
596
- includeDefaults: opts.pii?.includeDefaults ?? true,
597
- whitelistKeys: opts.pii?.whitelistKeys,
598
- blacklistKeys: opts.pii?.blacklistKeys,
599
- patterns: opts.pii?.patterns,
600
- deep: opts.pii?.deep ?? true,
229
+ ### Casos de Uso Principales
230
+
231
+ - **[`SaveLogUseCase`](src/application/use-cases/save-log.use-case.ts)** - Guarda logs aplicando filtros de nivel y PII
232
+ - **[`GetLogsUseCase`](src/application/use-cases/get-logs.use-case.ts)** - Recupera logs con filtros
233
+ - **[`FlushBuffersUseCase`](src/application/use-cases/flush-buffers.use-case.ts)** - Vacía buffers de datasources
234
+
235
+ ### Servicios de Dominio
236
+
237
+ - **[`PiiRedactor`](src/domain/services/pii-redactor.service.ts)** - Enmascara datos sensibles
238
+ - **[`MessageNormalizer`](src/domain/services/message-normalizer.service.ts)** - Normaliza mensajes de log
239
+ - **[`LogLevelService`](src/domain/services/log-level.service.ts)** - Maneja niveles de log
240
+
241
+ ## 🔌 Plugins Disponibles
242
+
243
+ | Plugin | Descripción | NPM |
244
+ | -------------------------------- | -------------------------- | --------------------------------------------------------------- |
245
+ | `@jmlq/logger-plugin-fs` | Persistencia en archivos | [npm](https://npmjs.com/package/@jmlq/logger-plugin-fs) |
246
+ | `@jmlq/logger-plugin-mongo` | Persistencia en MongoDB | [npm](https://npmjs.com/package/@jmlq/logger-plugin-mongo) |
247
+ | `@jmlq/logger-plugin-postgresql` | Persistencia en PostgreSQL | [npm](https://npmjs.com/package/@jmlq/logger-plugin-postgresql) |
248
+
249
+ ## 🧪 Testing
250
+
251
+ ```typescript
252
+ import { LoggerFactory, LogLevel } from "@jmlq/logger";
253
+
254
+ describe("Logger Tests", () => {
255
+ it("debe enmascarar PII correctamente", async () => {
256
+ const logs: any[] = [];
257
+ const mockDatasource = {
258
+ name: "mock",
259
+ async save(log: any) {
260
+ logs.push(log);
261
+ },
262
+ async find() {
263
+ return [];
264
+ },
265
+ async flush() {},
266
+ async dispose() {},
267
+ };
268
+
269
+ const logger = LoggerFactory.create({
270
+ datasources: mockDatasource,
271
+ redactorOptions: { enabled: true },
601
272
  });
602
273
 
603
- const logger = createLogger(datasource, {
604
- minLevel: toMinLevel(opts.minLevel),
605
- pii,
606
- });
607
- return new LoggerBootstrap(logger, datasource);
608
- }
274
+ await logger.info("Usuario: user@example.com");
609
275
 
610
- get logger() {
611
- return this._logger;
612
- }
613
- async flush() {
614
- const any = this._logger as any;
615
- if (typeof any.flush === "function") await any.flush();
616
- }
617
- async dispose() {
618
- const any = this._logger as any;
619
- if (typeof any.dispose === "function") await any.dispose();
620
- }
621
- }
622
- ```
623
-
624
- #### 5) Configuración global del logger (singleton)
625
-
626
- ```ts
627
- // src/config/logger/index.ts
628
- import { LoggerBootstrap } from "../../infrastructure/logger/bootstrap";
629
- import { envs } from "../../infrastructure/plugins";
630
-
631
- declare global {
632
- var __LOGGER_BOOT__: Promise<LoggerBootstrap> | undefined;
633
- }
634
-
635
- async function init() {
636
- return LoggerBootstrap.create({
637
- minLevel: envs.logger.LOGGER_LEVEL ?? "debug",
638
- pii: {
639
- enabled: envs.logger.LOGGER_PII_ENABLED,
640
- includeDefaults: envs.logger.LOGGER_PII_INCLUDE_DEFAULTS,
641
- deep: true,
642
- },
643
- adapters: {
644
- // NOTA: Opcionales depende de las necesidades del cliente
645
- fs: envs.logger.LOGGER_FS_PATH
646
- ? {
647
- basePath: envs.logger.LOGGER_FS_PATH,
648
- fileNamePattern: "app-{yyyy}{MM}{dd}.log",
649
- rotationPolicy: { by: "day" },
650
- }
651
- : undefined,
652
- mongo: envs.logger.MONGO_URL
653
- ? {
654
- url: envs.logger.MONGO_URL!,
655
- dbName: envs.logger.MONGO_DB_NAME!,
656
- collectionName: envs.logger.MONGO_COLLECTION ?? "logs",
657
- retentionDays: Number(envs.logger.LOGGER_MONGO_RETENTION_DAYS) || 0,
658
- }
659
- : undefined,
660
- postgres: envs.logger.POSTGRES_URL
661
- ? {
662
- url: envs.logger.POSTGRES_URL!,
663
- schema: envs.logger.POSTGRES_SCHEMA ?? "public",
664
- table: envs.logger.POSTGRES_TABLE ?? "logs",
665
- retentionDays: Number(envs.logger.LOGGER_PG_RETENTION_DAYS) || 0,
666
- }
667
- : undefined,
668
- // ---
669
- },
670
- });
671
- }
672
-
673
- // 1. Es una promesa singleton de LoggerBootstrap
674
- // 2. usa el operador nullish-coalescing (??) para: Reusar globalThis.__LOGGER_BOOT__ si ya existe. En caso contrario crea y memoriza (= init()) la promesa si no existe aún.
675
- // 3. Garantiza una sola inicialización global del sistema de logging (adapters, datasources, PII, etc.) aunque el módulo se importe múltiples veces
676
- export const bootReady: Promise<LoggerBootstrap> =
677
- globalThis.__LOGGER_BOOT__ ?? (globalThis.__LOGGER_BOOT__ = init());
678
-
679
- // 1. Es una promesa que resuelve directamente al logger
680
- // 2. Hace un map de la promesa anterior: bootReady.then(b => b.logger).
681
- export const loggerReady = bootReady.then((b) => b.logger);
682
-
683
- // 1. Espera a bootReady y llama boot.flush(), que a su vez pide al logger/datasources que vacíen buffers pendientes (útil antes de apagar el proceso o en tests).
684
- export async function flushLogs() {
685
- const boot = await bootReady;
686
- await boot.flush();
687
- }
688
-
689
- // 1. Espera a bootReady y llama boot.dispose(), que cierra recursos (conexiones a Mongo/Postgres, file handles, etc.)
690
- export async function disposeLogs() {
691
- const boot = await bootReady;
692
- await boot.dispose();
693
- }
694
- ```
695
-
696
- #### 6) Uso en la aplicación (Express)
697
-
698
- ```ts
699
- // src/presentation/server.ts
700
- import express, { Request, Response, NextFunction } from "express";
701
- import { loggerReady } from "../config/logger";
702
-
703
- function attachLogger() {
704
- const boot = loggerReady;
705
- return async (req: Request, _res: Response, next: NextFunction) => {
706
- // @ts-expect-error: extensión ad-hoc
707
- req.logger = await boot;
708
- // @ts-expect-error: idem
709
- req.requestId = (req.headers["x-request-id"] as string) ?? randomUUID();
710
- next();
711
- };
712
- }
713
-
714
- export function createServer() {
715
- const app = express();
716
- app.use(express.json());
717
- app.use(attachLogger());
718
-
719
- app.get("/health", (_req, res) =>
720
- res
721
- .status(200)
722
- .json({ ok: true, service: "ml-dev-test", timestamp: Date.now() })
723
- );
724
-
725
- app.get("/debug/log-demo", async (req, res) => {
726
- // @ts-expect-error: logger agregado por attachLogger
727
- const logger = req.logger;
728
- await logger.info(
729
- "Pago con tarjeta 4111 1111 1111 1111 del email demo@correo.com",
730
- {
731
- password: "abc123",
732
- phone: "0987654321",
733
- city: "Quito",
734
- }
735
- );
736
- res.json({ ok: true });
276
+ expect(logs[0].message).not.toContain("user@example.com");
277
+ expect(logs[0].message).toContain("[EMAIL]");
737
278
  });
738
-
739
- // Error handler con logging
740
- app.use(
741
- async (err: any, req: Request, res: Response, _next: NextFunction) => {
742
- const status = err?.statusCode ?? 500;
743
- // @ts-expect-error
744
- const logger = req.logger ?? (await loggerReady);
745
- await logger.error("http_error", {
746
- message: err?.message,
747
- stack: err?.stack,
748
- status,
749
- });
750
- res
751
- .status(status)
752
- .json({ error: err?.message ?? "Internal Server Error" });
753
- }
754
- );
755
-
756
- return app;
757
- }
279
+ });
758
280
  ```
759
281
 
760
- ### 🔎 Notas importantes
761
-
762
- > - `loggerReady` es una Promise → debe resolverse con `await`.
763
- > - `flushLogs()` y `disposeLogs()` deben usarse en procesos que cierran conexiones (ej. `SIGINT`, `SIGTERM`).
764
- > - Los patrones definidos en `pii.ts` se combinan con los patrones por defecto cuando `LOGGER_PII_INCLUDE_DEFAULTS=true`.
765
- > - El logger puede trabajar con **un único datasource** o con **CompositeDatasource** para múltiples.
766
-
767
- ---
768
-
769
- ### 🧪 Escenarios
770
-
771
- > - **Solo FS** → logs locales en `./logs/app.log`.
772
- > - **Solo MongoDB** → logs en colección `logs`.
773
- > - **Solo PostgreSQL** → logs en tabla `logs`.
774
- > - **Combinado** → fan-out a varios destinos simultáneamente.
775
- > - **Extensión** → implementar `ILogDatasource`.
282
+ ## 🚨 Troubleshooting
776
283
 
777
- ---
284
+ **Error: No datasources válidos**
778
285
 
779
- ## 🧯 Troubleshooting
286
+ - Asegúrate de configurar al menos un datasource
287
+ - Verifica las variables de entorno
780
288
 
781
- > - **No se inicializa ningún datasource** → definir al menos una variable (`LOGGER_FS_PATH`, `MONGO_URL`, `POSTGRES_URL`).
782
- > - **MongoDB Auth** → incluir `authSource=admin` en la URL si se usan usuarios root.
783
- > - **Postgres** → ejecutar `ensurePostgresSchema()` para crear tabla logs si no existe.
784
- > - **Alto volumen de logs** → implementar `flush()` o batching en el datasource.
289
+ **MongoDB no conecta**
785
290
 
786
- ---
291
+ - Verifica la URL de conexión
292
+ - Incluye `authSource=admin` si usas usuarios root
787
293
 
788
- ## 🧪 Tests
789
-
790
- ```ts
791
- import { createLogger, LogLevel } from "@jmlq/logger";
792
- import { FileSystemDatasource } from "@jmlq/logger-plugin-fs";
793
-
794
- test("logger redacta PII en FS", async () => {
795
- const fsDs = new FileSystemDatasource({ filePath: "./logs/test.log" });
796
- const logger = createLogger(fsDs, {
797
- minLevel: LogLevel.DEBUG,
798
- pii: { enabled: true },
799
- });
800
-
801
- await logger.info("Inicio de sesión", { user: "demo", password: "123456" });
802
-
803
- // Luego verificar que el archivo test.log no contiene la contraseña en claro
804
- });
805
- ```
294
+ **Alto uso de memoria**
806
295
 
807
- ---
296
+ - Implementa límites en las consultas
297
+ - Usa `flush()` periódicamente para vaciar buffers
808
298
 
809
- ### [LEER MAS...](./ARQUITECTURA.md)
299
+ ## 📄 Más Información
810
300
 
811
- ---
301
+ - **[Arquitectura Detallada](./ARQUITECTURA.md)** - Documentación técnica completa
302
+ - **[Guía de Instalación](./install.md)** - Configuración paso a paso
303
+ - **[Ejemplos](./examples/)** - Códigos de ejemplo funcionales
812
304
 
813
- ## 📄 Licencia
305
+ ## 📝 Licencia
814
306
 
815
307
  MIT © Mauricio Lahuasi