@jmlq/logger 0.1.0-alpha.6 → 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 +232 -551
  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,626 +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
8
+ # Core del logger
9
+ npm install @jmlq/logger
26
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
27
15
  ```
28
16
 
29
- ### DOCUMENTACION
17
+ ## 🚀 Uso Básico
30
18
 
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)
19
+ ### Configuración Simple
34
20
 
35
- ---
21
+ ```typescript
22
+ import { LoggerFactory, LogLevel } from "@jmlq/logger";
36
23
 
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
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() {},
35
+ };
61
36
 
62
- # FILESYSTEM @jmlq/logger-plugin-fs
63
- LOGGER_FS_PATH=./logs
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,
45
+ },
46
+ });
64
47
 
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
+ });
65
56
  ```
66
57
 
67
- ---
58
+ ### Con Múltiples Datasources
68
59
 
69
- ### 🚀 Uso del paquete
70
-
71
- #### 1) Estructura recomendada
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
+ });
72
72
 
73
+ await logger.warn("Latencia alta", { endpoint: "/api/users", duration: 850 });
73
74
  ```
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
75
 
94
- ```
76
+ ## 🎯 Características Principales
95
77
 
96
- #### 2) Settings
78
+ ### Niveles de Log
97
79
 
98
- #### 2.1) pii.settings.ts (patrones PII + merge)
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
99
86
 
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.
87
+ ### Enmascarado PII Automático
102
88
 
103
- export type PiiReplacement = {
104
- pattern: string;
105
- replaceWith: string;
106
- flags?: string;
107
- };
89
+ El logger detecta y enmascara automáticamente datos sensibles:
108
90
 
109
- export interface PiiConfig {
110
- enabled: boolean;
111
- whitelistKeys?: string[];
112
- blacklistKeys?: string[];
113
- patterns?: PiiReplacement[];
114
- deep?: boolean;
115
- includeDefaults?: boolean;
116
- }
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
+ });
97
+ ```
117
98
 
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",
124
- },
125
- ];
126
-
127
- // patrones “del cliente/proyecto”:
128
- export const clientPiiPatterns: PiiReplacement[] = [
129
- { pattern: String.raw`\b\d{10}\b`, flags: "g", replaceWith: "[EC_DNI]" },
130
- ];
131
-
132
- // lista negra de nombres de clave que se siempre se ocultan
133
- export const redactKeys: string[] = ["password", "secret"];
134
- // lista blanca de nombres de clave que no se deben ocultar por clave
135
- export const preserveKeys: string[] = ["city"];
136
-
137
- // Helpers
138
- // 1. Filtra valores no válidos: elimina null, undefined y "" (cadena vacía).
139
- // 2. Elimina duplicados usando Set.
140
- // 3. Conserva el primero de cada valor repetido (porque Set guarda la primera aparición).
141
- export function dedupeStrings(arr: Array<string | undefined | null>): string[] {
142
- return [...new Set(arr.filter((x): x is string => !!x && x.length > 0))];
143
- }
99
+ ### Filtros y Consultas
144
100
 
145
- // 1. Construye una clave de identidad por patrón: pattern + "__" + replaceWith + "__" + (flags || "").
146
- // 2. Inserta cada elemento en un `Map` usando esa clave. Si la clave ya existe, sobrescribe el anterior (es decir, gana el último).
147
- // 3. Devuelve los valores únicos del `Map`.
148
- export function dedupePatterns(arr: PiiReplacement[]): PiiReplacement[] {
149
- const m = new Map<string, PiiReplacement>();
150
- for (const p of arr)
151
- m.set(`${p.pattern}__${p.replaceWith}__${p.flags ?? ""}`, p);
152
- return [...m.values()];
153
- }
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
+ });
154
108
 
155
- // Builder unificado
156
- export function buildPiiConfig(
157
- opts?: PiiConfig
158
- ): Required<Omit<PiiConfig, "includeDefaults">> {
159
- const includeDefaults = opts?.includeDefaults ?? true;
160
-
161
- const patterns = dedupePatterns([
162
- ...(includeDefaults ? DEFAULT_PII_PATTERNS : []),
163
- ...clientPiiPatterns,
164
- ...(opts?.patterns ?? []),
165
- ]);
166
-
167
- const whitelistKeys = dedupeStrings([
168
- ...(opts?.whitelistKeys ?? []),
169
- ...preserveKeys,
170
- ]);
171
- const blacklistKeys = dedupeStrings([
172
- ...(opts?.blacklistKeys ?? []),
173
- ...redactKeys,
174
- ]);
175
-
176
- return {
177
- enabled: !!opts?.enabled,
178
- whitelistKeys,
179
- blacklistKeys,
180
- patterns,
181
- deep: opts?.deep ?? true,
182
- };
183
- }
109
+ // Buscar logs que contengan "usuario"
110
+ const userLogs = await logger.getLogs({
111
+ query: "usuario",
112
+ limit: 100,
113
+ });
184
114
  ```
185
115
 
186
- ##### 2.2) loglevel.settings.ts (normalización de nivel)
187
-
188
- ```ts
189
- // src/infrastructure/logger/settings/loglevel.settings.ts
190
- import { LogLevel } from "@jmlq/logger";
191
-
192
- export function toMinLevel(
193
- level: LogLevel | keyof typeof LogLevel | string
194
- ): LogLevel {
195
- if (typeof level === "number") return level as LogLevel;
196
- switch (String(level || "debug").toLowerCase()) {
197
- case "trace":
198
- return LogLevel.TRACE;
199
- case "debug":
200
- return LogLevel.DEBUG;
201
- case "info":
202
- return LogLevel.INFO;
203
- case "warn":
204
- return LogLevel.WARN;
205
- case "error":
206
- return LogLevel.ERROR;
207
- case "fatal":
208
- return LogLevel.FATAL;
209
- default:
210
- return LogLevel.DEBUG;
211
- }
212
- }
213
- ```
116
+ ## 🔧 Configuración con Variables de Entorno
214
117
 
215
- #### 3) Adapters (capa infrastructure)
118
+ ```env
119
+ # Nivel mínimo de logs
120
+ LOG_LEVEL=info
216
121
 
217
- Todos `devuelven`/`encapsulan` un `ILogDatasource` para evitar acoplar la app a clases concretas.
122
+ # Filesystem
123
+ LOGGER_FS_PATH=./logs
218
124
 
219
- **NOTA**: Solo se implementan los que se necesiten, por ejemplo:
125
+ # MongoDB
126
+ MONGO_URL=mongodb://localhost:27017
127
+ MONGO_DB_NAME=logs
220
128
 
221
- > - Si se necesita generar logs en archivos se crea adapter para `FS`.
222
- > - Si se necesita guardar logs en una base de datos no relacional se crea adapter para `mongo`.
223
- > - Si se necesita guardar logs en una base de datos relacional se crea adapter para `postgresql`.
224
- > - También se pueden combinar.
225
- > - Se debe implementar al menos un adapter.
129
+ # PostgreSQL
130
+ POSTGRES_URL=postgresql://user:pass@localhost:5432/logs
226
131
 
227
- ##### 3.1) FS (`fs.adapter.ts`)
132
+ # PII Protection
133
+ LOGGER_PII_ENABLED=true
134
+ ```
228
135
 
229
- ```ts
230
- import { createFsDatasource } from "@jmlq/logger-plugin-fs";
231
- import type { ILogDatasource } from "@jmlq/logger";
136
+ ## 📁 Ejemplos Prácticos
232
137
 
233
- export interface IFsProps {
234
- basePath: string;
235
- fileNamePattern: string;
236
- rotationPolicy: { by: "none" | "day" | "size"; maxSizeMB?: number };
237
- }
138
+ El proyecto incluye ejemplos completos en el directorio [`examples/`](examples/):
238
139
 
239
- export class FsAdapter {
240
- private constructor(private readonly ds: ILogDatasource) {}
241
- static create(opts: IFsProps): FsAdapter | undefined {
242
- try {
243
- const ds = createFsDatasource({
244
- basePath: opts.basePath,
245
- mkdir: true,
246
- fileNamePattern: opts.fileNamePattern,
247
- rotation: opts.rotationPolicy,
248
- onRotate: (oldP, newP) =>
249
- console.log("[fs] rotated:", oldP, "->", newP),
250
- onError: (e) => console.error("[fs] error:", e),
251
- });
252
- console.log("[logger] Conectado a FS para logs");
253
- return new FsAdapter(ds);
254
- } catch (e: any) {
255
- console.warn("[logger] FS deshabilitado:", e?.message ?? e);
256
- }
257
- }
258
- get datasource(): ILogDatasource {
259
- return this.ds;
260
- }
261
- }
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
262
149
  ```
263
150
 
264
- ##### 3.2) Mongo (`mongo.adapter.ts`)
151
+ ### Ejemplo Express
265
152
 
266
- ```ts
267
- import type { ILogDatasource } from "@jmlq/logger";
268
- import { createMongoInfra, MongoDatasource } from "@jmlq/logger-plugin-mongo";
153
+ ```typescript
154
+ import express from "express";
155
+ import { LoggerFactory, LogLevel } from "@jmlq/logger";
269
156
 
270
- export interface IMongoProps {
271
- url: string;
272
- dbName: string;
273
- collectionName?: string;
274
- retentionDays?: number | null;
275
- }
157
+ const app = express();
158
+ const logger = LoggerFactory.create({
159
+ datasources: /* tu datasource */,
160
+ minLevel: LogLevel.INFO
161
+ });
276
162
 
277
- export class MongoAdapter {
278
- private constructor(private readonly ds: ILogDatasource) {}
279
- static async create(opts: IMongoProps): Promise<MongoAdapter | undefined> {
280
- try {
281
- const infra = await createMongoInfra({
282
- url: opts.url,
283
- dbName: opts.dbName,
284
- collectionName: opts.collectionName ?? "logs",
285
- createIfMissing: true,
286
- ensureIndexes: true,
287
- retentionDays: opts.retentionDays ?? 0,
288
- extraIndexes: [{ key: { "meta.userId": 1 } }],
289
- });
290
- return new MongoAdapter(new MongoDatasource(infra.collection));
291
- } catch (e: any) {
292
- console.warn("[logger] Mongo deshabilitado:", e?.message ?? e);
293
- }
294
- }
295
- get datasource(): ILogDatasource {
296
- return this.ds;
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
+ });
171
+
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" });
297
183
  }
298
- }
184
+ });
299
185
  ```
300
186
 
301
- ##### 3.2) Postgresql (`postgresql.adapter.ts`)
187
+ ### Ejemplo NestJS
302
188
 
303
- ```ts
304
- import type { ILogDatasource } from "@jmlq/logger";
305
- import { createPostgresDatasource } from "@jmlq/logger-plugin-postgresql";
306
-
307
- export interface IPostgresqlProps {
308
- url: string;
309
- schema: string;
310
- table?: string;
311
- retentionDays?: number | null;
312
- }
189
+ ```typescript
190
+ @Injectable()
191
+ export class AppService {
192
+ constructor(@Inject("LOGGER") private readonly logger: ILoggerService) {}
313
193
 
314
- export class PostgresqlAdapter {
315
- private constructor(private readonly ds: ILogDatasource) {}
316
- static async create(
317
- opts: IPostgresqlProps
318
- ): Promise<PostgresqlAdapter | undefined> {
194
+ async createUser(userData: any) {
195
+ await this.logger.info("Creando usuario", { userData });
319
196
  try {
320
- const ps = await createPostgresDatasource({
321
- connectionString: opts.url,
322
- schema: opts.schema,
323
- table: opts.table ?? "logs",
324
- createIfMissing: true,
325
- retentionDays: opts.retentionDays ?? 0,
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,
326
203
  });
327
- console.log("[logger] Conectado a PostgreSQL para logs");
328
- return new PostgresqlAdapter(ps);
329
- } catch (e: any) {
330
- console.warn("[logger] Postgres deshabilitado:", e?.message ?? e);
204
+ throw error;
331
205
  }
332
206
  }
333
- get datasource(): ILogDatasource {
334
- return this.ds;
335
- }
336
207
  }
337
208
  ```
338
209
 
339
- #### 4) `LoggerBootstrap` (orquestador de adapters + PII)
340
-
341
- ```ts
342
- // src/infrastructure/logger/bootstrap.ts
343
- import {
344
- createLogger,
345
- CompositeDatasource,
346
- type ILogDatasource,
347
- } from "@jmlq/logger";
348
- import { buildPiiConfig } from "./settings/pii.settings";
349
- import { toMinLevel } from "./settings/loglevel.settings";
350
- // NOTA: Opcionales depende de las necesidades del cliente
351
- import { FsAdapter, type IFsProps } from "./adapters/fs.adapter";
352
- import { MongoAdapter, type IMongoProps } from "./adapters/mongo.adapter";
353
- import {
354
- PostgresqlAdapter,
355
- type IPostgresqlProps,
356
- } from "./adapters/postgresql.adapter";
357
-
358
- export interface LoggerBootstrapOptions {
359
- minLevel: string | number;
360
- pii?: {
361
- enabled?: boolean;
362
- whitelistKeys?: string[];
363
- blacklistKeys?: string[];
364
- patterns?: any[];
365
- deep?: boolean;
366
- includeDefaults?: boolean;
367
- };
368
- adapters?: {
369
- // NOTA: Opcionales depende de las necesidades del cliente
370
- fs?: IFsProps;
371
- mongo?: IMongoProps;
372
- postgres?: IPostgresqlProps;
373
- };
374
- }
375
-
376
- export class LoggerBootstrap {
377
- private constructor(
378
- private readonly _logger: ReturnType<typeof createLogger>,
379
- private readonly _ds: ILogDatasource
380
- ) {}
210
+ ## 🏗️ Arquitectura
381
211
 
382
- static async create(opts: LoggerBootstrapOptions): Promise<LoggerBootstrap> {
383
- const dsList: ILogDatasource[] = [];
212
+ El paquete sigue **Arquitectura Limpia**:
384
213
 
385
- // NOTA: Opcionales depende de las necesidades del cliente
386
- if (opts.adapters?.fs) {
387
- const fs = FsAdapter.create(opts.adapters.fs);
388
- if (fs) dsList.push(fs.datasource);
389
- }
390
- if (opts.adapters?.mongo) {
391
- const mg = await MongoAdapter.create(opts.adapters.mongo);
392
- if (mg) dsList.push(mg.datasource);
393
- }
394
- if (opts.adapters?.postgres) {
395
- const pg = await PostgresqlAdapter.create(opts.adapters.postgres);
396
- if (pg) dsList.push(pg.datasource);
397
- }
398
- //----
399
-
400
- if (dsList.length === 0)
401
- throw new Error("[logger] No hay datasources válidos.");
402
- const datasource =
403
- dsList.length === 1 ? dsList[0] : new CompositeDatasource(dsList);
404
-
405
- const pii = buildPiiConfig({
406
- enabled: opts.pii?.enabled ?? false,
407
- includeDefaults: opts.pii?.includeDefaults ?? true,
408
- whitelistKeys: opts.pii?.whitelistKeys,
409
- blacklistKeys: opts.pii?.blacklistKeys,
410
- patterns: opts.pii?.patterns,
411
- deep: opts.pii?.deep ?? true,
412
- });
413
-
414
- const logger = createLogger(datasource, {
415
- minLevel: toMinLevel(opts.minLevel),
416
- pii,
417
- });
418
- return new LoggerBootstrap(logger, datasource);
419
- }
420
-
421
- get logger() {
422
- return this._logger;
423
- }
424
- async flush() {
425
- const any = this._logger as any;
426
- if (typeof any.flush === "function") await any.flush();
427
- }
428
- async dispose() {
429
- const any = this._logger as any;
430
- if (typeof any.dispose === "function") await any.dispose();
431
- }
432
- }
433
214
  ```
434
-
435
- #### 5) Configuración global del logger (singleton)
436
-
437
- ```ts
438
- // src/config/logger/index.ts
439
- import { LoggerBootstrap } from "../../infrastructure/logger/bootstrap";
440
- import { envs } from "../../infrastructure/plugins";
441
-
442
- declare global {
443
- var __LOGGER_BOOT__: Promise<LoggerBootstrap> | undefined;
444
- }
445
-
446
- async function init() {
447
- return LoggerBootstrap.create({
448
- minLevel: envs.logger.LOGGER_LEVEL ?? "debug",
449
- pii: {
450
- enabled: envs.logger.LOGGER_PII_ENABLED,
451
- includeDefaults: envs.logger.LOGGER_PII_INCLUDE_DEFAULTS,
452
- deep: true,
453
- },
454
- adapters: {
455
- // NOTA: Opcionales depende de las necesidades del cliente
456
- fs: envs.logger.LOGGER_FS_PATH
457
- ? {
458
- basePath: envs.logger.LOGGER_FS_PATH,
459
- fileNamePattern: "app-{yyyy}{MM}{dd}.log",
460
- rotationPolicy: { by: "day" },
461
- }
462
- : undefined,
463
- mongo: envs.logger.MONGO_URL
464
- ? {
465
- url: envs.logger.MONGO_URL!,
466
- dbName: envs.logger.MONGO_DB_NAME!,
467
- collectionName: envs.logger.MONGO_COLLECTION ?? "logs",
468
- retentionDays: Number(envs.logger.LOGGER_MONGO_RETENTION_DAYS) || 0,
469
- }
470
- : undefined,
471
- postgres: envs.logger.POSTGRES_URL
472
- ? {
473
- url: envs.logger.POSTGRES_URL!,
474
- schema: envs.logger.POSTGRES_SCHEMA ?? "public",
475
- table: envs.logger.POSTGRES_TABLE ?? "logs",
476
- retentionDays: Number(envs.logger.LOGGER_PG_RETENTION_DAYS) || 0,
477
- }
478
- : undefined,
479
- // ---
480
- },
481
- });
482
- }
483
-
484
- // 1. Es una promesa singleton de LoggerBootstrap
485
- // 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.
486
- // 3. Garantiza una sola inicialización global del sistema de logging (adapters, datasources, PII, etc.) aunque el módulo se importe múltiples veces
487
- export const bootReady: Promise<LoggerBootstrap> =
488
- globalThis.__LOGGER_BOOT__ ?? (globalThis.__LOGGER_BOOT__ = init());
489
-
490
- // 1. Es una promesa que resuelve directamente al logger
491
- // 2. Hace un map de la promesa anterior: bootReady.then(b => b.logger).
492
- export const loggerReady = bootReady.then((b) => b.logger);
493
-
494
- // 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).
495
- export async function flushLogs() {
496
- const boot = await bootReady;
497
- await boot.flush();
498
- }
499
-
500
- // 1. Espera a bootReady y llama boot.dispose(), que cierra recursos (conexiones a Mongo/Postgres, file handles, etc.)
501
- export async function disposeLogs() {
502
- const boot = await bootReady;
503
- await boot.dispose();
504
- }
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)
505
227
  ```
506
228
 
507
- #### 6) Uso en la aplicación (Express)
508
-
509
- ```ts
510
- // src/presentation/server.ts
511
- import express, { Request, Response, NextFunction } from "express";
512
- import { loggerReady } from "../config/logger";
513
-
514
- function attachLogger() {
515
- const boot = loggerReady;
516
- return async (req: Request, _res: Response, next: NextFunction) => {
517
- // @ts-expect-error: extensión ad-hoc
518
- req.logger = await boot;
519
- // @ts-expect-error: idem
520
- req.requestId = (req.headers["x-request-id"] as string) ?? randomUUID();
521
- next();
522
- };
523
- }
524
-
525
- export function createServer() {
526
- const app = express();
527
- app.use(express.json());
528
- app.use(attachLogger());
529
-
530
- app.get("/health", (_req, res) =>
531
- res
532
- .status(200)
533
- .json({ ok: true, service: "ml-dev-test", timestamp: Date.now() })
534
- );
535
-
536
- app.get("/debug/log-demo", async (req, res) => {
537
- // @ts-expect-error: logger agregado por attachLogger
538
- const logger = req.logger;
539
- await logger.info(
540
- "Pago con tarjeta 4111 1111 1111 1111 del email demo@correo.com",
541
- {
542
- password: "abc123",
543
- phone: "0987654321",
544
- city: "Quito",
545
- }
546
- );
547
- res.json({ ok: true });
548
- });
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 },
272
+ });
549
273
 
550
- // Error handler con logging
551
- app.use(
552
- async (err: any, req: Request, res: Response, _next: NextFunction) => {
553
- const status = err?.statusCode ?? 500;
554
- // @ts-expect-error
555
- const logger = req.logger ?? (await loggerReady);
556
- await logger.error("http_error", {
557
- message: err?.message,
558
- stack: err?.stack,
559
- status,
560
- });
561
- res
562
- .status(status)
563
- .json({ error: err?.message ?? "Internal Server Error" });
564
- }
565
- );
274
+ await logger.info("Usuario: user@example.com");
566
275
 
567
- return app;
568
- }
276
+ expect(logs[0].message).not.toContain("user@example.com");
277
+ expect(logs[0].message).toContain("[EMAIL]");
278
+ });
279
+ });
569
280
  ```
570
281
 
571
- ### 🔎 Notas importantes
572
-
573
- > - `loggerReady` es una Promise → debe resolverse con `await`.
574
- > - `flushLogs()` y `disposeLogs()` deben usarse en procesos que cierran conexiones (ej. `SIGINT`, `SIGTERM`).
575
- > - Los patrones definidos en `pii.ts` se combinan con los patrones por defecto cuando `LOGGER_PII_INCLUDE_DEFAULTS=true`.
576
- > - El logger puede trabajar con **un único datasource** o con **CompositeDatasource** para múltiples.
577
-
578
- ---
579
-
580
- ### 🧪 Escenarios
282
+ ## 🚨 Troubleshooting
581
283
 
582
- > - **Solo FS** → logs locales en `./logs/app.log`.
583
- > - **Solo MongoDB** → logs en colección `logs`.
584
- > - **Solo PostgreSQL** → logs en tabla `logs`.
585
- > - **Combinado** → fan-out a varios destinos simultáneamente.
586
- > - **Extensión** → implementar `ILogDatasource`.
284
+ **Error: No datasources válidos**
587
285
 
588
- ---
286
+ - Asegúrate de configurar al menos un datasource
287
+ - Verifica las variables de entorno
589
288
 
590
- ## 🧯 Troubleshooting
289
+ **MongoDB no conecta**
591
290
 
592
- > - **No se inicializa ningún datasource** → definir al menos una variable (`LOGGER_FS_PATH`, `MONGO_URL`, `POSTGRES_URL`).
593
- > - **MongoDB Auth** → incluir `authSource=admin` en la URL si se usan usuarios root.
594
- > - **Postgres** → ejecutar `ensurePostgresSchema()` para crear tabla logs si no existe.
595
- > - **Alto volumen de logs** → implementar `flush()` o batching en el datasource.
291
+ - Verifica la URL de conexión
292
+ - Incluye `authSource=admin` si usas usuarios root
596
293
 
597
- ---
598
-
599
- ## 🧪 Tests
600
-
601
- ```ts
602
- import { createLogger, LogLevel } from "@jmlq/logger";
603
- import { FileSystemDatasource } from "@jmlq/logger-plugin-fs";
604
-
605
- test("logger redacta PII en FS", async () => {
606
- const fsDs = new FileSystemDatasource({ filePath: "./logs/test.log" });
607
- const logger = createLogger(fsDs, {
608
- minLevel: LogLevel.DEBUG,
609
- pii: { enabled: true },
610
- });
611
-
612
- await logger.info("Inicio de sesión", { user: "demo", password: "123456" });
613
-
614
- // Luego verificar que el archivo test.log no contiene la contraseña en claro
615
- });
616
- ```
294
+ **Alto uso de memoria**
617
295
 
618
- ---
296
+ - Implementa límites en las consultas
297
+ - Usa `flush()` periódicamente para vaciar buffers
619
298
 
620
- ### [LEER MAS...](./ARQUITECTURA.md)
299
+ ## 📄 Más Información
621
300
 
622
- ---
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
623
304
 
624
- ## 📄 Licencia
305
+ ## 📝 Licencia
625
306
 
626
307
  MIT © Mauricio Lahuasi