@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.
- package/README.md +225 -733
- package/dist/examples/data-source-service.example.d.ts +3 -0
- package/dist/examples/data-source-service.example.js +174 -0
- package/dist/examples/flush-buffers-use-case.example.d.ts +3 -0
- package/dist/examples/flush-buffers-use-case.example.js +60 -0
- package/dist/examples/get-logs-use-case.example.d.ts +3 -0
- package/dist/examples/get-logs-use-case.example.js +110 -0
- package/dist/examples/index.example.d.ts +8 -0
- package/dist/examples/index.example.js +116 -0
- package/dist/examples/logger-factory.example.d.ts +39 -0
- package/dist/examples/logger-factory.example.js +158 -0
- package/dist/examples/normalize-message.example.d.ts +3 -0
- package/dist/examples/normalize-message.example.js +80 -0
- package/dist/examples/pii-redactor.example.d.ts +3 -0
- package/dist/examples/pii-redactor.example.js +129 -0
- package/dist/examples/save-log-use-case.example.d.ts +3 -0
- package/dist/examples/save-log-use-case.example.js +150 -0
- package/dist/examples/to-log-level.example.d.ts +3 -0
- package/dist/examples/to-log-level.example.js +49 -0
- package/dist/examples/to-pii-regex.example.d.ts +3 -0
- package/dist/examples/to-pii-regex.example.js +75 -0
- package/dist/{presentation → src/application}/factory/create-logger.d.ts +1 -1
- package/dist/{presentation → src/application}/factory/create-logger.js +3 -3
- package/dist/src/application/factory/index.d.ts +1 -0
- package/dist/{presentation → src/application}/factory/index.js +1 -1
- package/dist/src/application/factory/logger.factory.d.ts +12 -0
- package/dist/src/application/factory/logger.factory.js +74 -0
- package/dist/src/application/index.d.ts +2 -0
- package/dist/src/application/index.js +18 -0
- package/dist/src/application/use-cases/flush-buffers.use-case.d.ts +6 -0
- package/dist/{application/use-cases/flush-buffers.js → src/application/use-cases/flush-buffers.use-case.js} +3 -3
- package/dist/src/application/use-cases/get-logs.use-case.d.ts +8 -0
- package/dist/{application/use-cases/get-logs.js → src/application/use-cases/get-logs.use-case.js} +3 -3
- package/dist/src/application/use-cases/index.d.ts +3 -0
- package/dist/{application → src/application}/use-cases/index.js +3 -3
- package/dist/src/application/use-cases/save-log.use-case.d.ts +8 -0
- package/dist/src/application/use-cases/save-log.use-case.js +26 -0
- package/dist/src/domain/index.d.ts +6 -0
- package/dist/src/domain/index.js +22 -0
- package/dist/src/domain/ports/create-logger-options.port.d.ts +7 -0
- package/dist/src/domain/ports/index.d.ts +6 -0
- package/dist/src/domain/ports/index.js +22 -0
- package/dist/src/domain/ports/log-datasource.port.d.ts +9 -0
- package/dist/src/domain/ports/logger-factory-config.port.d.ts +28 -0
- package/dist/src/domain/ports/logger-service.port.d.ts +19 -0
- package/dist/{domain/contracts/logger.d.ts → src/domain/ports/logger.port.d.ts} +0 -9
- package/dist/src/domain/ports/logger.port.js +2 -0
- package/dist/src/domain/ports/pii-redactor.port.d.ts +5 -0
- package/dist/src/domain/ports/pii-redactor.port.js +2 -0
- package/dist/src/domain/request/get-logs-filter.props.d.ts +9 -0
- package/dist/src/domain/request/get-logs-filter.props.js +2 -0
- package/dist/src/domain/request/index.d.ts +5 -0
- package/dist/src/domain/request/index.js +21 -0
- package/dist/src/domain/request/log.props.d.ts +7 -0
- package/dist/src/domain/request/log.props.js +2 -0
- package/dist/src/domain/request/pii-options.props.d.ts +8 -0
- package/dist/src/domain/request/pii-options.props.js +2 -0
- package/dist/src/domain/request/pii-replacement.props.d.ts +5 -0
- package/dist/src/domain/request/pii-replacement.props.js +2 -0
- package/dist/src/domain/request/save-log.props.d.ts +7 -0
- package/dist/src/domain/request/save-log.props.js +2 -0
- package/dist/src/domain/response/index.d.ts +1 -0
- package/dist/{domain/value-objects → src/domain/response}/index.js +1 -1
- package/dist/src/domain/response/log.response.d.ts +7 -0
- package/dist/src/domain/response/log.response.js +2 -0
- package/dist/src/domain/services/index.d.ts +4 -0
- package/dist/{domain/contracts → src/domain/services}/index.js +4 -3
- package/dist/src/domain/services/log-level.service.d.ts +2 -0
- package/dist/src/domain/services/log-level.service.js +27 -0
- package/dist/src/domain/services/message-normalizer.service.d.ts +3 -0
- package/dist/src/domain/services/message-normalizer.service.js +8 -0
- package/dist/src/domain/services/pii-pattern.service.d.ts +2 -0
- package/dist/src/domain/services/pii-pattern.service.js +13 -0
- package/dist/src/domain/services/pii-redactor.d.ts +10 -0
- package/dist/{domain → src/domain}/services/pii-redactor.js +8 -17
- package/dist/src/domain/services/pii-redactor.service.d.ts +10 -0
- package/dist/src/domain/services/pii-redactor.service.js +68 -0
- package/dist/src/domain/types/index.d.ts +1 -0
- package/dist/src/domain/types/index.js +17 -0
- package/dist/src/domain/types/log-message.type.d.ts +1 -0
- package/dist/src/domain/types/log-message.type.js +2 -0
- package/dist/src/domain/value-objects/index.d.ts +1 -0
- package/dist/{domain/services → src/domain/value-objects}/index.js +1 -1
- package/dist/{domain/value-objects/log-level.d.ts → src/domain/value-objects/log-level.vo.d.ts} +0 -1
- package/dist/src/domain/value-objects/log-level.vo.js +13 -0
- package/dist/src/index.d.ts +6 -0
- package/dist/src/index.js +22 -0
- package/dist/src/infrastructure/datasources/in-memory-log.datasource.d.ts +1 -0
- package/dist/src/infrastructure/datasources/in-memory-log.datasource.js +2 -0
- package/dist/src/infrastructure/datasources/index.d.ts +1 -0
- package/dist/{infrastructure/adapters → src/infrastructure/datasources}/index.js +1 -1
- package/dist/src/infrastructure/index.d.ts +1 -0
- package/dist/{domain/types → src/infrastructure}/index.js +1 -1
- package/dist/src/infrastructure/services/data-source-error-handler.type.d.ts +5 -0
- package/dist/src/infrastructure/services/data-source-error-handler.type.js +2 -0
- package/dist/src/infrastructure/services/datasource.service.d.ts +15 -0
- package/dist/src/infrastructure/services/datasource.service.js +63 -0
- package/dist/src/infrastructure/services/index.d.ts +2 -0
- package/dist/src/infrastructure/services/index.js +18 -0
- package/dist/tests/application/factory/logger-factory.spec.d.ts +1 -0
- package/dist/tests/application/factory/logger-factory.spec.js +161 -0
- package/dist/tests/application/use-cases/flush-buffers.use-case.spec.d.ts +1 -0
- package/dist/tests/application/use-cases/flush-buffers.use-case.spec.js +38 -0
- package/dist/tests/application/use-cases/get-logs.use-case.spec.d.ts +1 -0
- package/dist/tests/application/use-cases/get-logs.use-case.spec.js +114 -0
- package/dist/tests/application/use-cases/save-log.use-case.spec.d.ts +1 -0
- package/dist/tests/application/use-cases/save-log.use-case.spec.js +138 -0
- package/dist/tests/domain/services/log-level.service.spec.d.ts +1 -0
- package/dist/tests/domain/services/log-level.service.spec.js +68 -0
- package/dist/tests/domain/services/normalize-message.spec.d.ts +1 -0
- package/dist/tests/domain/services/normalize-message.spec.js +83 -0
- package/dist/tests/domain/services/pii-redactor.spec.d.ts +1 -0
- package/dist/tests/domain/services/pii-redactor.spec.js +170 -0
- package/dist/tests/domain/services/to-pii-regex.spec.d.ts +1 -0
- package/dist/tests/domain/services/to-pii-regex.spec.js +82 -0
- package/dist/tests/infrastructure/services/datasource.service.spec.d.ts +1 -0
- package/dist/tests/infrastructure/services/datasource.service.spec.js +128 -0
- package/dist/tests/test-utils/create-pii-redactor-mock.d.ts +5 -0
- package/dist/tests/test-utils/create-pii-redactor-mock.js +10 -0
- package/package.json +27 -19
- package/LICENSE +0 -21
- package/dist/application/use-cases/flush-buffers.d.ts +0 -6
- package/dist/application/use-cases/get-logs.d.ts +0 -7
- package/dist/application/use-cases/index.d.ts +0 -3
- package/dist/application/use-cases/save-log.d.ts +0 -13
- package/dist/application/use-cases/save-log.js +0 -30
- package/dist/domain/contracts/index.d.ts +0 -3
- package/dist/domain/contracts/log.datasource.d.ts +0 -8
- package/dist/domain/contracts/pii.d.ts +0 -5
- package/dist/domain/services/index.d.ts +0 -1
- package/dist/domain/services/pii-redactor.d.ts +0 -10
- package/dist/domain/types/index.d.ts +0 -1
- package/dist/domain/types/log.types.d.ts +0 -28
- package/dist/domain/value-objects/index.d.ts +0 -1
- package/dist/domain/value-objects/log-level.js +0 -37
- package/dist/index.d.ts +0 -5
- package/dist/index.js +0 -10
- package/dist/infrastructure/adapters/composite.datasource.d.ts +0 -11
- package/dist/infrastructure/adapters/composite.datasource.js +0 -46
- package/dist/infrastructure/adapters/index.d.ts +0 -1
- package/dist/presentation/factory/index.d.ts +0 -1
- /package/dist/{domain/contracts/log.datasource.js → src/domain/ports/create-logger-options.port.js} +0 -0
- /package/dist/{domain/contracts/logger.js → src/domain/ports/log-datasource.port.js} +0 -0
- /package/dist/{domain/contracts/pii.js → src/domain/ports/logger-factory-config.port.js} +0 -0
- /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
|
-
|
|
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
|
-
#
|
|
12
|
-
npm
|
|
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
|
-
###
|
|
19
|
+
### Configuración Simple
|
|
70
20
|
|
|
71
|
-
|
|
21
|
+
```typescript
|
|
22
|
+
import { LoggerFactory, LogLevel } from "@jmlq/logger";
|
|
72
23
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
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
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
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
|
-
//
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
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
|
-
|
|
188
|
-
|
|
189
|
-
> > - `g` (**global**): Encuentra `todas las coincidencias` en el texto, no solo la primera.
|
|
58
|
+
### Con Múltiples Datasources
|
|
190
59
|
|
|
191
|
-
```
|
|
192
|
-
|
|
193
|
-
|
|
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
|
-
|
|
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
|
-
|
|
76
|
+
## 🎯 Características Principales
|
|
202
77
|
|
|
203
|
-
|
|
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
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
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
|
-
|
|
87
|
+
### Enmascarado PII Automático
|
|
217
88
|
|
|
218
|
-
|
|
219
|
-
const texto = `uno
|
|
220
|
-
FOO
|
|
221
|
-
tres`;
|
|
89
|
+
El logger detecta y enmascara automáticamente datos sensibles:
|
|
222
90
|
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
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
|
-
|
|
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
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
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
|
-
|
|
249
|
-
const
|
|
250
|
-
|
|
109
|
+
// Buscar logs que contengan "usuario"
|
|
110
|
+
const userLogs = await logger.getLogs({
|
|
111
|
+
query: "usuario",
|
|
112
|
+
limit: 100,
|
|
113
|
+
});
|
|
251
114
|
```
|
|
252
115
|
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
```ts
|
|
256
|
-
const regexSticky = /\d{2}/y;
|
|
116
|
+
## 🔧 Configuración con Variables de Entorno
|
|
257
117
|
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
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
|
-
|
|
122
|
+
# Filesystem
|
|
123
|
+
LOGGER_FS_PATH=./logs
|
|
276
124
|
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
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
|
-
|
|
286
|
-
|
|
287
|
-
// {5} → significa exactamente 5 repeticiones seguidas.
|
|
288
|
-
```
|
|
129
|
+
# PostgreSQL
|
|
130
|
+
POSTGRES_URL=postgresql://user:pass@localhost:5432/logs
|
|
289
131
|
|
|
290
|
-
|
|
291
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
```
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
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
|
-
|
|
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
|
-
|
|
361
|
-
|
|
362
|
-
|
|
153
|
+
```typescript
|
|
154
|
+
import express from "express";
|
|
155
|
+
import { LoggerFactory, LogLevel } from "@jmlq/logger";
|
|
363
156
|
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
157
|
+
const app = express();
|
|
158
|
+
const logger = LoggerFactory.create({
|
|
159
|
+
datasources: /* tu datasource */,
|
|
160
|
+
minLevel: LogLevel.INFO
|
|
161
|
+
});
|
|
369
162
|
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
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
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
//
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
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
|
-
|
|
405
|
-
|
|
406
|
-
Todos `devuelven`/`encapsulan` un `ILogDatasource` para evitar acoplar la app a clases concretas.
|
|
187
|
+
### Ejemplo NestJS
|
|
407
188
|
|
|
408
|
-
|
|
189
|
+
```typescript
|
|
190
|
+
@Injectable()
|
|
191
|
+
export class AppService {
|
|
192
|
+
constructor(@Inject("LOGGER") private readonly logger: ILoggerService) {}
|
|
409
193
|
|
|
410
|
-
|
|
411
|
-
|
|
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
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
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
|
-
|
|
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
|
-
|
|
210
|
+
## 🏗️ Arquitectura
|
|
454
211
|
|
|
455
|
-
|
|
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
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
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
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
}
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
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
|
-
|
|
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
|
-
|
|
611
|
-
|
|
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
|
-
|
|
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
|
-
|
|
286
|
+
- Asegúrate de configurar al menos un datasource
|
|
287
|
+
- Verifica las variables de entorno
|
|
780
288
|
|
|
781
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
##
|
|
305
|
+
## 📝 Licencia
|
|
814
306
|
|
|
815
307
|
MIT © Mauricio Lahuasi
|