@jmlq/logger 0.1.0-alpha.13 → 0.1.0-alpha.16

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 CHANGED
@@ -16,195 +16,185 @@ npm install @jmlq/logger-plugin-postgresql # Para PostgreSQL
16
16
 
17
17
  ## 🚀 Uso Básico
18
18
 
19
- ### Configuración Simple
19
+ Con esta configuración se busca:
20
20
 
21
- ```typescript
22
- import { LoggerFactory, LogLevel } from "@jmlq/logger";
21
+ - Tener un **logger centralizado** basado en `@jmlq/logger`.
22
+ - Enviar logs a uno o varios de estos destinos (**opcionales** y combinables):
23
+ - Sistema de archivos (`@jmlq/logger-plugin-fs`)
24
+ - MongoDB (`@jmlq/logger-plugin-mongo`)
25
+ - PostgreSQL (`@jmlq/logger-plugin-postgresql`)
26
+ - Integrar el logger con:
27
+ - Express (por request) mediante un middleware (`req.logger` + `req.requestId`).
28
+ - El ciclo de vida de la app: arranque, apagado limpio y fallos no controlados.
23
29
 
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
- };
36
-
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
- });
30
+ ---
47
31
 
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
- });
56
- ```
32
+ ## 🎯 Características Principales
57
33
 
58
- ### Con Múltiples Datasources
34
+ ### Niveles de Log
59
35
 
60
- ```typescript
61
- import { LoggerFactory } from "@jmlq/logger";
62
- import { FileSystemDatasource } from "@jmlq/logger-plugin-fs";
63
- import { MongoDatasource } from "@jmlq/logger-plugin-mongo";
36
+ - `TRACE` (10) - Información muy detallada
37
+ - `DEBUG` (20) - Información de depuración
38
+ - `INFO` (30) - Información general
39
+ - `WARN` (40) - Advertencias
40
+ - `ERROR` (50) - Errores
41
+ - `FATAL` (60) - Errores críticos
64
42
 
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
- });
43
+ ### Enmascarado PII Automático
72
44
 
73
- await logger.warn("Latencia alta", { endpoint: "/api/users", duration: 850 });
74
- ```
45
+ El logger detecta y enmascara automáticamente datos sensibles utilizando patrones personalizados:
75
46
 
76
- ## 🎯 Características Principales
47
+ ```js
77
48
 
78
- ### Niveles de Log
49
+ patterns: [
50
+ // Tarjetas de crédito
51
+ {
52
+ pattern: "\\b\\d{4}[-\\s]?\\d{4}[-\\s]?\\d{4}[-\\s]?\\d{4}\\b",
53
+ replaceWith: "****-****-****-****",
54
+ },
55
+ // Números de cuenta bancaria
56
+ {
57
+ pattern: "\\b\\d{10,20}\\b",
58
+ replaceWith: "****ACCOUNT****",
59
+ },
60
+ // Tokens JWT
61
+ {
62
+ pattern: "eyJ[A-Za-z0-9-_=]+\\.[A-Za-z0-9-_=]+\\.?[A-Za-z0-9-_.+/=]*",
63
+ replaceWith: "****JWT****",
64
+ },
65
+ ],
79
66
 
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
67
+ ```
86
68
 
87
- ### Enmascarado PII Automático
69
+ Por ejemplo se tiene la siguiente información:
88
70
 
89
- El logger detecta y enmascara automáticamente datos sensibles:
71
+ ```js
72
+ [
73
+ {
74
+ id: "1",
75
+ name: "John Doe",
76
+ email: "john.doe@example.com",
77
+ card: "1234-5678-9012-3456",
78
+ },
79
+ {
80
+ id: "2",
81
+ name: "Jane Doe",
82
+ email: "jane.doe@example.com",
83
+ card: "6258-3842-9524-3251",
84
+ },
85
+ ];
86
+ ```
90
87
 
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
- });
88
+ Los logs registrarán lo siguiente:
89
+
90
+ > - **File .log** (`@jmlq/logger-plugin-fs`)
91
+
92
+ ```js
93
+ {
94
+ "level": 30,
95
+ "message": "Users listed successfully",
96
+ "meta": {
97
+ "count": 2,
98
+ "result": [
99
+ {
100
+ "id": "1",
101
+ "name": "John Doe",
102
+ "email": "***@***",
103
+ "card": "****-****-****-****"
104
+ },
105
+ {
106
+ "id": "2",
107
+ "name": "Jane Doe",
108
+ "email": "***@***",
109
+ "card": "****-****-****-****"
110
+ }
111
+ ]
112
+ },
113
+ "timestamp": 1765400661565,
114
+ "_id": "6939e0556925736129e1008d"
115
+ }
97
116
  ```
98
117
 
118
+ > - **MongoDB** (`@jmlq/logger-plugin-mongo`)
119
+
120
+ ![MongoDB log](./assets/mongo-log.png)
121
+
122
+ > - **MongoDB** (`@jmlq/logger-plugin-mongo`)
123
+
124
+ ![Postgresql log](./assets/pg-log.png)
125
+
99
126
  ### Filtros y Consultas
100
127
 
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
- });
128
+ #### 1. Obtener todos los logs de nivel WARN o superior (en este caso retorna WARN, ERROR y FATAL)
108
129
 
109
- // Buscar logs que contengan "usuario"
110
- const userLogs = await logger.getLogs({
111
- query: "usuario",
112
- limit: 100,
130
+ ```ts
131
+ const errors = await _req.logger?.getLogs({
132
+ levelMin: parseLogLevel("WARN"),
113
133
  });
114
134
  ```
115
135
 
116
- ## 🔧 Configuración con Variables de Entorno
136
+ Resultado:
117
137
 
118
- ```env
119
- # Nivel mínimo de logs
120
- LOG_LEVEL=info
138
+ ```ini
139
+ [2025-12-10T21:19:58.804Z] [50] app.error {"name":"AppError","code":"INTERNAL","message":"Unhandled exception","retryable":false,"meta":null,"cause":{"cause":{}},"stack":"AppError: Unhandled exception\n at AppError.internal (D:\\Fuentes\\Core\\ml-dev-rest-api\\dist\\shared\\errors\\app-error.js:61:16)\n at D:\\Fuentes\\Core\\ml-dev-rest-api\\dist\\presentation\\middlewares\\http\\error.middleware.js:36:40\n at Layer.handleError (D:\\Fuentes\\Core\\ml-dev-rest-api\\node_modules\\router\\lib\\layer.js:116:17)\n
140
+ at trimPrefix (D:\\Fuentes\\Core\\ml-dev-rest-api\\node_modules\\router\\index.js:340:13)\n at D:\\Fuentes\\Core\\ml-dev-rest-api\\node_modules\\router\\index.js:297:9\n at processParams (D:\\Fuentes\\Core\\ml-dev-rest-api\\node_modules\\router\\index.js:582:12)\n at Immediate.next (D:\\Fuentes\\Core\\ml-dev-rest-api\\node_modules\\router\\index.js:291:5)\n at Immediate._onImmediate (D:\\Fuentes\\Core\\ml-dev-rest-api\\node_modules\\router\\index.js:688:15)\n at process.processImmediate (node:internal/timers:485:21)"}
141
+ [2025-12-10T21:19:58.804Z] [50] app.error {"code":"INTERNAL","name":"AppError","cause":{"cause":{}},"stack":"AppError: Unhandled exception\n at AppError.internal (D:\\Fuentes\\Core\\ml-dev-rest-api\\dist\\shared\\errors\\app-error.js:61:16)\n at D:\\Fuentes\\Core\\ml-dev-rest-api\\dist\\presentation\\middlewares\\http\\error.middleware.js:36:40\n at Layer.handleError (D:\\Fuentes\\Core\\ml-dev-rest-api\\node_modules\\router\\lib\\layer.js:116:17)\n at trimPrefix (D:\\Fuentes\\Core\\ml-dev-rest-api\\node_modules\\router\\index.js:340:13)\n at D:\\Fuentes\\Core\\ml-dev-rest-api\\node_modules\\router\\index.js:297:9\n at processParams (D:\\Fuentes\\Core\\ml-dev-rest-api\\node_modules\\router\\index.js:582:12)\n at Immediate.next (D:\\Fuentes\\Core\\ml-dev-rest-api\\node_modules\\router\\index.js:291:5)\n at Immediate._onImmediate (D:\\Fuentes\\Core\\ml-dev-rest-api\\node_modules\\router\\index.js:688:15)\n at process.processImmediate (node:internal/timers:485:21)","message":"Unhandled exception","retryable":false}
121
142
 
122
- # Filesystem
123
- LOGGER_FS_PATH=./logs
143
+ ```
124
144
 
125
- # MongoDB
126
- MONGO_URL=mongodb://localhost:27017
127
- MONGO_DB_NAME=logs
145
+ **NOTA**: `parseLogLevel` es un helper para convertir un `string` a `LogLevel`.
128
146
 
129
- # PostgreSQL
130
- POSTGRES_URL=postgresql://user:pass@localhost:5432/logs
147
+ #### 2. Filtrar por rango de fechas
131
148
 
132
- # PII Protection
133
- LOGGER_PII_ENABLED=true
149
+ ```ts
150
+ const yesterday = Date.now() - 24 * 60 * 60 * 1000;
151
+ const recentLogs = await _req.logger?.getLogs({
152
+ since: yesterday,
153
+ until: Date.now(),
154
+ });
134
155
  ```
135
156
 
136
- ## 📁 Ejemplos Prácticos
137
-
138
- El proyecto incluye ejemplos completos en el directorio [`examples/`](examples/):
157
+ Resultado:
139
158
 
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
159
+ ```ini
160
+ [2025-12-10T22:21:58.500Z] [30] app.shutdown.begin {"reason":"SIGINT"}
161
+ [2025-12-10T21:12:59.216Z] [50] app.error {"name":"AppError","code":"INTERNAL","message":"Unhandled exception","retryable":false,"meta":null,"cause":{"cause":{}},"stack":"AppError: Unhandled exception\n at AppError.internal (D:\\Fuentes\\Core\\ml-dev-rest-api\\dist\\shared\\errors\\app-error.js:61:16)\n at D:\\Fuentes\\Core\\ml-dev-rest-api\\dist\\presentation\\middlewares\\http\\error.middleware.js:36:40\n at Layer.handleError (D:\\Fuentes\\Core\\ml-dev-rest-api\\node_modules\\router\\lib\\layer.js:116:17)\n at trimPrefix (D:\\Fuentes\\Core\\ml-dev-rest-api\\node_modules\\router\\index.js:340:13)\n at D:\\Fuentes\\Core\\ml-dev-rest-api\\node_modules\\router\\index.js:297:9\n at processParams (D:\\Fuentes\\Core\\ml-dev-rest-api\\node_modules\\router\\index.js:582:12)\n at Immediate.next (D:\\Fuentes\\Core\\ml-dev-rest-api\\node_modules\\router\\index.js:291:5)\n at Immediate._onImmediate (D:\\Fuentes\\Core\\ml-dev-rest-api\\node_modules\\router\\index.js:688:15)\n at process.processImmediate (node:internal/timers:485:21)"}
162
+ [2025-12-10T21:04:21.565Z] [30] Users listed successfully {"count":2,"result":[{"id":"1","name":"John Doe","email":"***@***","card":"****-****-****-****"},{"id":"2","name":"Jane Doe","email":"***@***","card":"****-****-****-****"}]}
163
+ [2025-12-10T21:04:21.565Z] [30] Users listed successfully {"count":2,"result":[{"id":"1","card":"****-****-****-****","name":"John Doe","email":"***@***"},{"id":"2","card":"****-****-****-****","name":"Jane Doe","email":"***@***"}]}
164
+ [2025-12-10T21:03:32.910Z] [30] http.start {"host":"127.0.0.1","port":3000}
149
165
  ```
150
166
 
151
- ### Ejemplo Express
152
-
153
- ```typescript
154
- import express from "express";
155
- import { LoggerFactory, LogLevel } from "@jmlq/logger";
167
+ #### 3. Búsqueda por texto (en el campo mensaje del log)
156
168
 
157
- const app = express();
158
- const logger = LoggerFactory.create({
159
- datasources: /* tu datasource */,
160
- minLevel: LogLevel.INFO
169
+ ```ts
170
+ const userLogs = await _req.logger?.getLogs({
171
+ query: "Users",
161
172
  });
173
+ ```
162
174
 
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
- });
175
+ Resultado:
171
176
 
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" });
183
- }
177
+ ```ini
178
+ [2025-12-10T22:58:09.874Z] [30] Users listed successfully {"count":2,"result":[{"id":"1","card":"****-****-****-****","name":"John Doe","email":"***@***"},{"id":"2","card":"****-****-****-****","name":"Jane Doe","email":"***@***"}]}
179
+ [2025-12-10T22:57:28.483Z] [30] Users listed successfully {"count":2,"result":[{"id":"1","name":"John Doe","email":"***@***","card":"****-****-****-****"},{"id":"2","name":"Jane Doe","email":"***@***","card":"****-****-****-****"}]}
180
+ ```
181
+
182
+ #### 4. Combinación de filtros
183
+
184
+ ```ts
185
+ const logs = await _req.logger?.getLogs({
186
+ levelMin: parseLogLevel("INFO"),
187
+ since: Date.now() - 2 * 60 * 60 * 1000, // Últimas 2 horas
188
+ query: "successfully",
184
189
  });
185
190
  ```
186
191
 
187
- ### Ejemplo NestJS
188
-
189
- ```typescript
190
- @Injectable()
191
- export class AppService {
192
- constructor(@Inject("LOGGER") private readonly logger: ILoggerService) {}
193
-
194
- async createUser(userData: any) {
195
- await this.logger.info("Creando usuario", { userData });
196
- try {
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,
203
- });
204
- throw error;
205
- }
206
- }
207
- }
192
+ Resultado:
193
+
194
+ ```ini
195
+ [2025-12-10T23:05:31.791Z] [30] Users listed successfully {"count":2,"result":[{"id":"1","name":"John Doe","email":"***@***","card":"****-****-****-****"},{"id":"2","name":"Jane Doe","email":"***@***","card":"****-****-****-****"}]}
196
+ [2025-12-10T23:05:31.791Z] [30] Users listed successfully {"count":2,"result":[{"id":"1","card":"****-****-****-****","name":"John Doe","email":"***@***"},{"id":"2","card":"****-****-****-****","name":"Jane Doe","email":"***@***"}]}
197
+ [2025-12-10T22:58:09.874Z] [30] Users listed successfully {"count":2,"result":[{"id":"1","name":"John Doe","email":"***@***","card":"****-****-****-****"},{"id":"2","name":"Jane Doe","email":"***@***","card":"****-****-****-****"}]}
208
198
  ```
209
199
 
210
200
  ## 🏗️ Arquitectura
@@ -246,56 +236,6 @@ src/
246
236
  | `@jmlq/logger-plugin-mongo` | Persistencia en MongoDB | [npm](https://npmjs.com/package/@jmlq/logger-plugin-mongo) |
247
237
  | `@jmlq/logger-plugin-postgresql` | Persistencia en PostgreSQL | [npm](https://npmjs.com/package/@jmlq/logger-plugin-postgresql) |
248
238
 
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
- });
273
-
274
- await logger.info("Usuario: user@example.com");
275
-
276
- expect(logs[0].message).not.toContain("user@example.com");
277
- expect(logs[0].message).toContain("[EMAIL]");
278
- });
279
- });
280
- ```
281
-
282
- ## 🚨 Troubleshooting
283
-
284
- **Error: No datasources válidos**
285
-
286
- - Asegúrate de configurar al menos un datasource
287
- - Verifica las variables de entorno
288
-
289
- **MongoDB no conecta**
290
-
291
- - Verifica la URL de conexión
292
- - Incluye `authSource=admin` si usas usuarios root
293
-
294
- **Alto uso de memoria**
295
-
296
- - Implementa límites en las consultas
297
- - Usa `flush()` periódicamente para vaciar buffers
298
-
299
239
  ## 📄 Más Información
300
240
 
301
241
  - **[Arquitectura Detallada](./architecture.md)** - Documentación técnica completa
@@ -43,6 +43,34 @@ class DataSourceService {
43
43
  logs.sort((a, b) => b.timestamp - a.timestamp);
44
44
  return logs;
45
45
  }
46
+ // async find(filter?: IGetLogsFilterProps): Promise<ILogResponse[]> {
47
+ // let results = [...this.logs];
48
+ // if (filter?.levelMin !== undefined) {
49
+ // results = results.filter((log) => log.level >= filter.levelMin!);
50
+ // }
51
+ // if (filter?.since !== undefined) {
52
+ // results = results.filter((log) => log.timestamp >= filter.since!);
53
+ // }
54
+ // if (filter?.until !== undefined) {
55
+ // results = results.filter((log) => log.timestamp <= filter.until!);
56
+ // }
57
+ // if (filter?.query) {
58
+ // const q = filter.query.toLowerCase();
59
+ // results = results.filter((log) => {
60
+ // const msg =
61
+ // typeof log.message === "string"
62
+ // ? log.message.toLowerCase()
63
+ // : JSON.stringify(log.message).toLowerCase();
64
+ // return msg.includes(q);
65
+ // });
66
+ // }
67
+ // // Ordenar por timestamp ascendente (opcional pero útil para visualizar)
68
+ // results.sort((a, b) => a.timestamp - b.timestamp);
69
+ // // Paginación
70
+ // const offset = filter?.offset ?? 0;
71
+ // const limit = filter?.limit ?? results.length;
72
+ // return results.slice(offset, offset + limit);
73
+ // }
46
74
  async flush() {
47
75
  const results = await Promise.allSettled(this.targets.map((ds) => ds.flush?.()));
48
76
  results.forEach((r, i) => {