@jmlq/logger-plugin-fs 0.1.0-alpha.0 → 0.1.0-alpha.10
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 +229 -0
- package/architecture.md +426 -0
- package/dist/application/dto/index.d.ts +1 -0
- package/dist/application/dto/index.js +17 -0
- package/dist/application/dto/rotate-if-needed.request.d.ts +10 -0
- package/dist/application/dto/rotate-if-needed.request.js +2 -0
- package/dist/application/factory/create-fs-datasource.factory.d.ts +16 -0
- package/dist/application/factory/create-fs-datasource.factory.js +51 -0
- package/dist/application/factory/index.d.ts +1 -0
- package/dist/application/factory/index.js +17 -0
- package/dist/application/use-cases/append-log.use-case.d.ts +30 -0
- package/dist/application/use-cases/append-log.use-case.js +39 -0
- package/dist/application/use-cases/ensure-directory.use-case.d.ts +58 -0
- package/dist/application/use-cases/ensure-directory.use-case.js +64 -0
- package/dist/application/use-cases/find-logs-use-case.d.ts +17 -0
- package/dist/application/use-cases/find-logs-use-case.js +64 -0
- package/dist/application/use-cases/index.d.ts +5 -0
- package/dist/application/use-cases/index.js +21 -0
- package/dist/application/use-cases/persist-log.use-case.d.ts +96 -0
- package/dist/application/use-cases/persist-log.use-case.js +105 -0
- package/dist/application/use-cases/rotate-if-needed.use-case.d.ts +55 -0
- package/dist/application/use-cases/rotate-if-needed.use-case.js +61 -0
- package/dist/domain/model/index.d.ts +1 -0
- package/dist/domain/model/index.js +17 -0
- package/dist/domain/model/log-entry.model.d.ts +8 -0
- package/dist/domain/model/log-entry.model.js +2 -0
- package/dist/domain/ports/file/file-path.port.d.ts +38 -0
- package/dist/domain/ports/file/file-path.port.js +2 -0
- package/dist/domain/ports/file/file-rotator.port.d.ts +42 -0
- package/dist/domain/ports/file/file-rotator.port.js +2 -0
- package/dist/domain/ports/file/index.d.ts +2 -0
- package/dist/domain/ports/file/index.js +18 -0
- package/dist/domain/ports/file/log-stream-writer.port.d.ts +70 -0
- package/dist/domain/ports/file/log-stream-writer.port.js +2 -0
- package/dist/domain/ports/filesystem-provider.port.d.ts +61 -0
- package/dist/domain/ports/filesystem-provider.port.js +2 -0
- package/dist/domain/ports/index.d.ts +5 -0
- package/dist/domain/ports/index.js +21 -0
- package/dist/domain/ports/logs/find/index.d.ts +2 -0
- package/dist/domain/ports/logs/find/index.js +18 -0
- package/dist/domain/ports/logs/find/log-file-line-reader.port.d.ts +3 -0
- package/dist/domain/ports/logs/find/log-file-line-reader.port.js +2 -0
- package/dist/domain/ports/logs/find/log-file-numerator.port.d.ts +3 -0
- package/dist/domain/ports/logs/find/log-file-numerator.port.js +2 -0
- package/dist/domain/ports/logs/index.d.ts +2 -0
- package/dist/domain/ports/logs/index.js +18 -0
- package/dist/domain/ports/logs/log-datasource.port.d.ts +10 -0
- package/dist/domain/ports/logs/log-datasource.port.js +2 -0
- package/dist/domain/ports/system-clock.port.d.ts +8 -0
- package/dist/domain/ports/system-clock.port.js +2 -0
- package/dist/domain/request/index.d.ts +2 -0
- package/dist/domain/request/index.js +18 -0
- package/dist/domain/request/log-filter.request.d.ts +9 -0
- package/dist/domain/request/log-filter.request.js +2 -0
- package/dist/domain/request/save-log.request.d.ts +7 -0
- package/dist/domain/request/save-log.request.js +2 -0
- package/dist/domain/response/index.d.ts +1 -0
- package/dist/domain/response/index.js +17 -0
- package/dist/domain/response/log.response.d.ts +8 -0
- package/dist/domain/response/log.response.js +2 -0
- package/dist/domain/types/fs-rotation-by.type.d.ts +8 -0
- package/dist/domain/types/fs-rotation-by.type.js +2 -0
- package/dist/domain/types/index.d.ts +1 -0
- package/dist/domain/types/index.js +17 -0
- package/dist/domain/value-objects/file-name-pattern.vo.d.ts +36 -0
- package/dist/domain/value-objects/file-name-pattern.vo.js +53 -0
- package/dist/domain/value-objects/file-path.vo.d.ts +91 -0
- package/dist/domain/value-objects/file-path.vo.js +100 -0
- package/dist/domain/value-objects/file-rotation-policy.vo.d.ts +51 -0
- package/dist/domain/value-objects/file-rotation-policy.vo.js +76 -0
- package/dist/domain/value-objects/file-size.vo.d.ts +75 -0
- package/dist/domain/value-objects/file-size.vo.js +114 -0
- package/dist/domain/value-objects/index.d.ts +5 -0
- package/dist/domain/value-objects/index.js +21 -0
- package/dist/domain/value-objects/log-level.vo.d.ts +8 -0
- package/dist/domain/value-objects/log-level.vo.js +13 -0
- package/dist/index.d.ts +3 -11
- package/dist/index.js +10 -61
- package/dist/infrastructure/adapters/file-rotator.adapter.d.ts +79 -0
- package/dist/infrastructure/adapters/file-rotator.adapter.js +171 -0
- package/dist/infrastructure/adapters/fileSystem-datasource.adapter.d.ts +26 -0
- package/dist/infrastructure/adapters/fileSystem-datasource.adapter.js +45 -0
- package/dist/infrastructure/adapters/filesystem-log-file-enumerator.adapter.d.ts +6 -0
- package/dist/infrastructure/adapters/filesystem-log-file-enumerator.adapter.js +54 -0
- package/dist/infrastructure/adapters/filesystem-log-file-line-reader.adapter.d.ts +4 -0
- package/dist/infrastructure/adapters/filesystem-log-file-line-reader.adapter.js +53 -0
- package/dist/infrastructure/adapters/filesystem-provider.adapter.d.ts +122 -0
- package/dist/infrastructure/adapters/filesystem-provider.adapter.js +182 -0
- package/dist/infrastructure/adapters/index.d.ts +8 -0
- package/dist/infrastructure/adapters/index.js +24 -0
- package/dist/infrastructure/adapters/log-stream-writer.adapter.d.ts +80 -0
- package/dist/infrastructure/adapters/log-stream-writer.adapter.js +163 -0
- package/dist/infrastructure/adapters/system-clock.adapter.d.ts +25 -0
- package/dist/infrastructure/adapters/system-clock.adapter.js +30 -0
- package/dist/infrastructure/adapters/system-file-path.adapter.d.ts +47 -0
- package/dist/infrastructure/adapters/system-file-path.adapter.js +141 -0
- package/dist/infrastructure/errors/file-operation.error.d.ts +28 -0
- package/dist/infrastructure/errors/file-operation.error.js +54 -0
- package/dist/infrastructure/errors/index.d.ts +1 -0
- package/dist/infrastructure/errors/index.js +17 -0
- package/dist/infrastructure/errors/types/file-operation-error-options.type.d.ts +8 -0
- package/dist/infrastructure/errors/types/file-operation-error-options.type.js +2 -0
- package/dist/infrastructure/errors/types/file-operation.type.d.ts +1 -0
- package/dist/infrastructure/errors/types/file-operation.type.js +2 -0
- package/dist/infrastructure/errors/types/fs-error-scope.type.d.ts +1 -0
- package/dist/infrastructure/errors/types/fs-error-scope.type.js +2 -0
- package/dist/infrastructure/errors/types/index.d.ts +3 -0
- package/dist/infrastructure/errors/types/index.js +19 -0
- package/dist/infrastructure/filesystem/index.d.ts +1 -0
- package/dist/infrastructure/filesystem/index.js +17 -0
- package/dist/infrastructure/filesystem/types/filesystem-datasource-options.type.d.ts +45 -0
- package/dist/infrastructure/filesystem/types/filesystem-datasource-options.type.js +2 -0
- package/dist/infrastructure/filesystem/types/filesystem-rotation.type.d.ts +12 -0
- package/dist/infrastructure/filesystem/types/filesystem-rotation.type.js +2 -0
- package/dist/infrastructure/filesystem/types/index.d.ts +2 -0
- package/dist/infrastructure/filesystem/types/index.js +18 -0
- package/install.md +520 -0
- package/package.json +40 -12
package/README.md
ADDED
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
# @jmlq/logger-plugin-fs
|
|
2
|
+
|
|
3
|
+
Plugin de **sistema de archivos** para [`@jmlq/logger`](https://www.npmjs.com/package/@jmlq/logger). Permite persistir logs en archivos con soporte para rotación automática, manejo de backpressure y configuración flexible de formato y estructura de archivos.
|
|
4
|
+
|
|
5
|
+
## 📦 Instalación
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @jmlq/logger @jmlq/logger-plugin-fs
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
### Dependencias
|
|
12
|
+
|
|
13
|
+
El plugin requiere como peer dependency:
|
|
14
|
+
|
|
15
|
+
- `@jmlq/logger` >= 0.1.0-alpha.12
|
|
16
|
+
|
|
17
|
+
> **Nota:** Este plugin requiere [`@jmlq/logger`](https://www.npmjs.com/package/@jmlq/logger) como dependencia principal.
|
|
18
|
+
|
|
19
|
+
## 🚀 Uso rápido
|
|
20
|
+
|
|
21
|
+
```typescript
|
|
22
|
+
import { LoggerFactory } from "@jmlq/logger";
|
|
23
|
+
import { createFsDatasource } from "@jmlq/logger-plugin-fs";
|
|
24
|
+
|
|
25
|
+
// Crear datasource de filesystem
|
|
26
|
+
const fsDatasource = createFsDatasource({
|
|
27
|
+
basePath: "./logs",
|
|
28
|
+
fileNamePattern: "app-{yyyy}{MM}{dd}.log",
|
|
29
|
+
rotation: { by: "day" },
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
// Crear logger usando la factory
|
|
33
|
+
const logger = LoggerFactory.create([fsDatasource]);
|
|
34
|
+
|
|
35
|
+
// Usar el logger
|
|
36
|
+
logger.info("Aplicación iniciada", { timestamp: new Date(), pid: process.pid });
|
|
37
|
+
logger.error("Error de conexión", { service: "database", retries: 3 });
|
|
38
|
+
|
|
39
|
+
// Cierre elegante
|
|
40
|
+
process.on("SIGTERM", async () => {
|
|
41
|
+
await logger.flush();
|
|
42
|
+
await logger.dispose();
|
|
43
|
+
process.exit(0);
|
|
44
|
+
});
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## ⚙️ Configuración del datasource
|
|
48
|
+
|
|
49
|
+
El datasource de filesystem acepta las siguientes opciones de configuración:
|
|
50
|
+
|
|
51
|
+
### Opciones principales
|
|
52
|
+
|
|
53
|
+
| Opción | Tipo | Descripción | Por defecto |
|
|
54
|
+
| ----------------- | ---------------- | ------------------------------------------------------ | --------------------------- |
|
|
55
|
+
| `basePath` | `string` | Directorio base donde se guardarán los archivos de log | `"./logs"` |
|
|
56
|
+
| `fileNamePattern` | `string` | Patrón para nombrar archivos con tokens de fecha | `"app-{yyyy}{MM}{dd}.log"` |
|
|
57
|
+
| `rotation` | `RotationConfig` | Configuración de política de rotación | `{ by: "day" }` |
|
|
58
|
+
| `mkdir` | `boolean` | Crear directorio base si no existe | `true` |
|
|
59
|
+
| `serializer` | `LogSerializer` | Serializador personalizado para el formato de líneas | Serializer JSON por defecto |
|
|
60
|
+
|
|
61
|
+
### Configuración de rotación
|
|
62
|
+
|
|
63
|
+
```typescript
|
|
64
|
+
interface RotationConfig {
|
|
65
|
+
by: "none" | "day" | "size";
|
|
66
|
+
maxSizeMB?: number; // Para rotación por tamaño
|
|
67
|
+
maxFiles?: number; // Límite de archivos rotados
|
|
68
|
+
}
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
### Patrones de nombre de archivo
|
|
72
|
+
|
|
73
|
+
Utiliza tokens que se reemplazan con valores de fecha (UTC):
|
|
74
|
+
|
|
75
|
+
- `{yyyy}` - Año completo (ej: 2024)
|
|
76
|
+
- `{MM}` - Mes con ceros (ej: 01, 12)
|
|
77
|
+
- `{dd}` - Día con ceros (ej: 01, 31)
|
|
78
|
+
|
|
79
|
+
**Ejemplos:**
|
|
80
|
+
|
|
81
|
+
- `"app-{yyyy}{MM}{dd}.log"` → `app-20241201.log`
|
|
82
|
+
- `"service-{yyyy}-{MM}-{dd}.log"` → `service-2024-12-01.log`
|
|
83
|
+
- `"application.log"` → `application.log` (sin rotación por fecha)
|
|
84
|
+
|
|
85
|
+
## 🔁 Políticas de rotación
|
|
86
|
+
|
|
87
|
+
### Rotación por fecha
|
|
88
|
+
|
|
89
|
+
Crea un archivo nuevo cada día basado en fecha UTC:
|
|
90
|
+
|
|
91
|
+
```typescript
|
|
92
|
+
import { createFsDatasource } from "@jmlq/logger-plugin-fs";
|
|
93
|
+
|
|
94
|
+
const datasource = createFsDatasource({
|
|
95
|
+
basePath: "./logs",
|
|
96
|
+
fileNamePattern: "app-{yyyy}{MM}{dd}.log",
|
|
97
|
+
rotation: { by: "day" },
|
|
98
|
+
onRotate: (oldPath, newPath) => {
|
|
99
|
+
console.log(
|
|
100
|
+
`Log rotado: ${oldPath.absolutePath} → ${newPath.absolutePath}`
|
|
101
|
+
);
|
|
102
|
+
},
|
|
103
|
+
});
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
### Rotación por tamaño
|
|
107
|
+
|
|
108
|
+
Rota cuando el archivo alcanza el tamaño máximo especificado:
|
|
109
|
+
|
|
110
|
+
```typescript
|
|
111
|
+
const datasource = createFsDatasource({
|
|
112
|
+
basePath: "./logs",
|
|
113
|
+
fileNamePattern: "app.log",
|
|
114
|
+
rotation: {
|
|
115
|
+
by: "size",
|
|
116
|
+
maxSizeMB: 50, // Rota al alcanzar 50MB
|
|
117
|
+
maxFiles: 10, // Mantiene máximo 10 archivos rotados
|
|
118
|
+
},
|
|
119
|
+
onRotate: (oldPath, newPath) => {
|
|
120
|
+
console.log(
|
|
121
|
+
`Archivo rotado por tamaño: ${oldPath.absolutePath} → ${newPath.absolutePath}`
|
|
122
|
+
);
|
|
123
|
+
},
|
|
124
|
+
});
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
### Sin rotación
|
|
128
|
+
|
|
129
|
+
Mantiene un único archivo que crece indefinidamente:
|
|
130
|
+
|
|
131
|
+
```typescript
|
|
132
|
+
const datasource = createFsDatasource({
|
|
133
|
+
basePath: "./logs",
|
|
134
|
+
fileNamePattern: "application.log",
|
|
135
|
+
rotation: { by: "none" },
|
|
136
|
+
});
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
## 🧩 Ejemplo completo
|
|
140
|
+
|
|
141
|
+
```typescript
|
|
142
|
+
import { createFsDatasource } from "@jmlq/logger-plugin-fs";
|
|
143
|
+
import { LogLevel } from "@jmlq/logger";
|
|
144
|
+
|
|
145
|
+
const datasource = createFsDatasource({
|
|
146
|
+
basePath: "./logs",
|
|
147
|
+
fileNamePattern: "app-{yyyy}-{MM}-{dd}.log",
|
|
148
|
+
rotation: { by: "day" },
|
|
149
|
+
mkdir: true,
|
|
150
|
+
serializer: {
|
|
151
|
+
serialize(log: any) {
|
|
152
|
+
return JSON.stringify(log, null, 2);
|
|
153
|
+
},
|
|
154
|
+
},
|
|
155
|
+
onRotate: (oldPath, newPath) => {
|
|
156
|
+
console.log(`Rotación: ${oldPath.absolutePath} → ${newPath.absolutePath}`);
|
|
157
|
+
},
|
|
158
|
+
onError: (err) => {
|
|
159
|
+
console.error("Error en datasource:", err.message);
|
|
160
|
+
},
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
// Guardar logs
|
|
164
|
+
await datasource.save({
|
|
165
|
+
level: LogLevel.INFO,
|
|
166
|
+
message: "Servidor iniciado correctamente",
|
|
167
|
+
timestamp: Date.now(),
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
await datasource.save({
|
|
171
|
+
level: LogLevel.DEBUG,
|
|
172
|
+
message: "Conectando a base de datos...",
|
|
173
|
+
timestamp: Date.now(),
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
// Cierre
|
|
177
|
+
await datasource.flush();
|
|
178
|
+
await datasource.dispose();
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
## 🧪 Testing
|
|
182
|
+
|
|
183
|
+
```typescript
|
|
184
|
+
import { createFsDatasource } from "@jmlq/logger-plugin-fs";
|
|
185
|
+
import * as fs from "fs";
|
|
186
|
+
import * as path from "path";
|
|
187
|
+
|
|
188
|
+
describe("FileSystem Datasource", () => {
|
|
189
|
+
const testLogsPath = "./test-logs";
|
|
190
|
+
|
|
191
|
+
beforeEach(() => {
|
|
192
|
+
if (fs.existsSync(testLogsPath)) {
|
|
193
|
+
fs.rmSync(testLogsPath, { recursive: true });
|
|
194
|
+
}
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
test("debe crear archivo de log", async () => {
|
|
198
|
+
const datasource = createFsDatasource({
|
|
199
|
+
basePath: testLogsPath,
|
|
200
|
+
fileNamePattern: "test.log",
|
|
201
|
+
rotation: { by: "none" },
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
await datasource.save({
|
|
205
|
+
level: "info",
|
|
206
|
+
message: "Test message",
|
|
207
|
+
timestamp: new Date(),
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
await datasource.flush();
|
|
211
|
+
|
|
212
|
+
const logFile = path.join(testLogsPath, "test.log");
|
|
213
|
+
expect(fs.existsSync(logFile)).toBe(true);
|
|
214
|
+
|
|
215
|
+
const content = fs.readFileSync(logFile, "utf8");
|
|
216
|
+
expect(content).toContain("Test message");
|
|
217
|
+
});
|
|
218
|
+
});
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
## 📄 Más Información
|
|
222
|
+
|
|
223
|
+
- **[Arquitectura Detallada](./architecture.md)** - Documentación técnica completa
|
|
224
|
+
- **[Guía de Instalación](./install.md)** - Configuración paso a paso
|
|
225
|
+
- **[Ejemplos](./examples/)** - Códigos de ejemplo funcionales
|
|
226
|
+
|
|
227
|
+
## 📄 Licencia
|
|
228
|
+
|
|
229
|
+
MIT © Mauricio Lahuasi
|
package/architecture.md
ADDED
|
@@ -0,0 +1,426 @@
|
|
|
1
|
+
# Arquitectura del Plugin - @jmlq/logger-plugin-fs
|
|
2
|
+
|
|
3
|
+
## Visión General del Plugin
|
|
4
|
+
|
|
5
|
+
`@jmlq/logger-plugin-fs` es un plugin datasource especializado que resuelve la persistencia de logs en sistema de archivos para el ecosistema `@jmlq/logger`. Su arquitectura está diseñada bajo principios de Clean Architecture, proporcionando un sistema robusto de escritura de logs con capacidades avanzadas de rotación automática.
|
|
6
|
+
|
|
7
|
+
El plugin aporta al ecosistema `@jmlq/logger` la capacidad de persistir logs en archivos con gestión inteligente de rotación, asegurando que las aplicaciones puedan mantener historial de logs sin comprometer el rendimiento ni el espacio en disco. Su diseño modular permite diferentes estrategias de rotación y serialización según las necesidades específicas de cada aplicación.
|
|
8
|
+
|
|
9
|
+
Como datasource, el plugin implementa la interfaz `ILogDatasource` de `@jmlq/logger`, actuando como un adaptador entre el logger principal y el sistema de archivos, encapsulando toda la complejidad de gestión de archivos, rotación y escritura asíncrona.
|
|
10
|
+
|
|
11
|
+
## Principios de Arquitectura
|
|
12
|
+
|
|
13
|
+
### Clean Architecture
|
|
14
|
+
|
|
15
|
+
El plugin implementa Clean Architecture con separación clara de responsabilidades en capas concéntricas, donde las dependencias fluyen hacia adentro (Infrastructure → Application → Domain).
|
|
16
|
+
|
|
17
|
+
### Ports & Adapters (Hexagonal Architecture)
|
|
18
|
+
|
|
19
|
+
Utiliza el patrón Ports & Adapters para aislar la lógica de negocio de las implementaciones concretas del filesystem, permitiendo testabilidad y flexibilidad en las implementaciones.
|
|
20
|
+
|
|
21
|
+
### Separation of Concerns
|
|
22
|
+
|
|
23
|
+
Cada componente tiene una responsabilidad específica y bien definida, desde la gestión de Value Objects hasta la escritura física de archivos.
|
|
24
|
+
|
|
25
|
+
### Dependency Inversion
|
|
26
|
+
|
|
27
|
+
Las capas internas definen interfaces (ports) que las capas externas implementan, invirtiendo las dependencias y manteniendo la independencia del dominio.
|
|
28
|
+
|
|
29
|
+
## Estructura del Proyecto
|
|
30
|
+
|
|
31
|
+
### `/src/domain/` - Capa de Dominio
|
|
32
|
+
|
|
33
|
+
**Responsabilidad**: Contiene la lógica de negocio pura y las abstracciones fundamentales del plugin.
|
|
34
|
+
|
|
35
|
+
**Contenido**:
|
|
36
|
+
|
|
37
|
+
- `/ports/` - Interfaces que definen contratos con infraestructura
|
|
38
|
+
- `/value-objects/` - Objetos de valor inmutables del dominio
|
|
39
|
+
|
|
40
|
+
### `/src/application/` - Capa de Aplicación
|
|
41
|
+
|
|
42
|
+
**Responsabilidad**: Orquesta los casos de uso del plugin y coordina entre dominio e infraestructura.
|
|
43
|
+
|
|
44
|
+
**Contenido**:
|
|
45
|
+
|
|
46
|
+
- `/dto/` - Data Transfer Objects para casos de uso
|
|
47
|
+
- `/factory/` - Factories para creación de componentes
|
|
48
|
+
- `/services/` - Servicios de aplicación que implementan `ILogDatasource`
|
|
49
|
+
- `/use-cases/` - Casos de uso específicos del plugin
|
|
50
|
+
|
|
51
|
+
### `/src/infrastructure/` - Capa de Infraestructura
|
|
52
|
+
|
|
53
|
+
**Responsabilidad**: Implementaciones concretas de los ports del dominio y adaptadores específicos.
|
|
54
|
+
|
|
55
|
+
**Contenido**:
|
|
56
|
+
|
|
57
|
+
- `/adapters/` - Implementaciones concretas de ports del dominio
|
|
58
|
+
- `/filesystem/` - Tipos, políticas y ports específicos del filesystem
|
|
59
|
+
|
|
60
|
+
### `/src/shared/` - Utilitarios Compartidos
|
|
61
|
+
|
|
62
|
+
**Responsabilidad**: Errores y utilidades transversales al plugin.
|
|
63
|
+
|
|
64
|
+
**Contenido**:
|
|
65
|
+
|
|
66
|
+
- `/errors/` - Jerarquía de errores específicos del plugin
|
|
67
|
+
|
|
68
|
+
## Componentes Internos
|
|
69
|
+
|
|
70
|
+
### Value Objects del Dominio
|
|
71
|
+
|
|
72
|
+
#### FilePath
|
|
73
|
+
|
|
74
|
+
```typescript
|
|
75
|
+
class FilePath {
|
|
76
|
+
constructor(props: IFilePathProps);
|
|
77
|
+
get absolutePath(): string;
|
|
78
|
+
get directory(): string;
|
|
79
|
+
get filename(): string;
|
|
80
|
+
get extension(): string;
|
|
81
|
+
get basename(): string;
|
|
82
|
+
equals(other: FilePath): boolean;
|
|
83
|
+
}
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
**Responsabilidad**: Encapsula una ruta de archivo completa como objeto inmutable, garantizando consistencia en la representación de paths a través del sistema.
|
|
87
|
+
|
|
88
|
+
#### FileSize
|
|
89
|
+
|
|
90
|
+
```typescript
|
|
91
|
+
class FileSize {
|
|
92
|
+
constructor(bytes: number);
|
|
93
|
+
get bytes(): number;
|
|
94
|
+
get megabytes(): number;
|
|
95
|
+
isGreaterThan(other: FileSize): boolean;
|
|
96
|
+
equals(other: FileSize): boolean;
|
|
97
|
+
}
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
**Responsabilidad**: Representa el tamaño de archivos como concepto del dominio, proporcionando métodos de comparación y conversión de unidades.
|
|
101
|
+
|
|
102
|
+
### Value Objects de Infraestructura
|
|
103
|
+
|
|
104
|
+
#### FileNamePattern
|
|
105
|
+
|
|
106
|
+
```typescript
|
|
107
|
+
class FileNamePattern {
|
|
108
|
+
constructor(pattern: string);
|
|
109
|
+
get pattern(): string;
|
|
110
|
+
hasDateTokens(): boolean;
|
|
111
|
+
getTokens(): string[];
|
|
112
|
+
}
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
**Responsabilidad**: Encapsula patrones de nombres de archivo con placeholders para fechas, permitiendo generación dinámica de nombres basados en timestamps.
|
|
116
|
+
|
|
117
|
+
#### RotationPolicy
|
|
118
|
+
|
|
119
|
+
```typescript
|
|
120
|
+
class RotationPolicy {
|
|
121
|
+
constructor(by: FsRotationBy, maxSizeMB?: number, maxFiles?: number);
|
|
122
|
+
get by(): FsRotationBy;
|
|
123
|
+
get maxSizeMB(): number | undefined;
|
|
124
|
+
get maxFiles(): number | undefined;
|
|
125
|
+
shouldRotateBySize(currentSize: FileSize): boolean;
|
|
126
|
+
}
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
**Responsabilidad**: Define las reglas de rotación de archivos, encapsulando la lógica de cuándo y cómo rotar archivos según diferentes estrategias.
|
|
130
|
+
|
|
131
|
+
### Tipos del Sistema
|
|
132
|
+
|
|
133
|
+
#### FsRotationBy
|
|
134
|
+
|
|
135
|
+
```typescript
|
|
136
|
+
type FsRotationBy = "none" | "day" | "size";
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
**Responsabilidad**: Define las estrategias de rotación disponibles en el sistema.
|
|
140
|
+
|
|
141
|
+
### Adaptadores de Infraestructura
|
|
142
|
+
|
|
143
|
+
#### FsWriterAdapter
|
|
144
|
+
|
|
145
|
+
```typescript
|
|
146
|
+
class FsWriterAdapter implements IStreamWriterPort {
|
|
147
|
+
async write(data: string): Promise<boolean>;
|
|
148
|
+
async open(path: FilePath): Promise<void>;
|
|
149
|
+
async close(): Promise<void>;
|
|
150
|
+
async flush(): Promise<void>;
|
|
151
|
+
isOpen(): boolean;
|
|
152
|
+
getCurrentPath(): FilePath | null;
|
|
153
|
+
}
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
**Responsabilidad**: Maneja la escritura física en archivos usando Node.js WriteStream, con gestión de estado y control de flujo.
|
|
157
|
+
|
|
158
|
+
#### FileRotatorAdapter
|
|
159
|
+
|
|
160
|
+
```typescript
|
|
161
|
+
class FileRotatorAdapter implements IFileRotatorPort {
|
|
162
|
+
getCurrentPath(): FilePath | null;
|
|
163
|
+
getExpectedPathForDate(date: Date): FilePath;
|
|
164
|
+
async getFileSize(filePath: FilePath): Promise<FileSize>;
|
|
165
|
+
async shouldRotate(
|
|
166
|
+
policy: RotationPolicy,
|
|
167
|
+
currentDate: Date
|
|
168
|
+
): Promise<boolean>;
|
|
169
|
+
}
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
**Responsabilidad**: Implementa la lógica de rotación de archivos, generación de paths basados en fechas y evaluación de políticas de rotación.
|
|
173
|
+
|
|
174
|
+
#### FsProviderAdapter
|
|
175
|
+
|
|
176
|
+
```typescript
|
|
177
|
+
class FsProviderAdapter implements IFsProviderPort
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
**Responsabilidad**: Abstrae operaciones del filesystem (stat, readdir, mkdir) proporcionando una interfaz uniforme para el dominio.
|
|
181
|
+
|
|
182
|
+
#### NodeClockAdapter
|
|
183
|
+
|
|
184
|
+
```typescript
|
|
185
|
+
class NodeClockAdapter implements IClockPort
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
**Responsabilidad**: Proporciona acceso al tiempo del sistema de forma abstraída.
|
|
189
|
+
|
|
190
|
+
#### NodeFilePathAdapter
|
|
191
|
+
|
|
192
|
+
```typescript
|
|
193
|
+
class NodeFilePathAdapter implements IFilePathAdapterPort
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
**Responsabilidad**: Maneja operaciones de manipulación de paths usando el módulo `path` de Node.js.
|
|
197
|
+
|
|
198
|
+
### Services de Aplicación
|
|
199
|
+
|
|
200
|
+
#### FsDatasourceService
|
|
201
|
+
|
|
202
|
+
```typescript
|
|
203
|
+
class FsDatasourceService implements ILogDatasource {
|
|
204
|
+
async save(log: ILogProps): Promise<void>;
|
|
205
|
+
async flush(): Promise<void>;
|
|
206
|
+
async dispose(): Promise<void>;
|
|
207
|
+
}
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
**Responsabilidad**: Implementa la interfaz `ILogDatasource` del logger principal, actuando como punto de entrada del plugin y delegando a los casos de uso correspondientes.
|
|
211
|
+
|
|
212
|
+
### Factory Principal
|
|
213
|
+
|
|
214
|
+
#### createFsDatasource
|
|
215
|
+
|
|
216
|
+
```typescript
|
|
217
|
+
function createFsDatasource(
|
|
218
|
+
options: IFilesystemDatasourceOptions
|
|
219
|
+
): ILogDatasource;
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
**Responsabilidad**: Factory function que ensambla todos los componentes del plugin, creando e inyectando dependencias según la configuración proporcionada.
|
|
223
|
+
|
|
224
|
+
## Política de Rotación
|
|
225
|
+
|
|
226
|
+
### Estrategias Implementadas
|
|
227
|
+
|
|
228
|
+
#### Rotación por Día (`"day"`)
|
|
229
|
+
|
|
230
|
+
La rotación se basa en cambios de fecha. El sistema evalúa si el archivo actual corresponde a la fecha presente:
|
|
231
|
+
|
|
232
|
+
1. **Evaluación**: Compara el path actual con el path esperado para la fecha actual
|
|
233
|
+
2. **Decisión**: Si difieren, se requiere rotación
|
|
234
|
+
3. **Proceso**: Cierra el stream actual y abre uno nuevo con el path actualizado
|
|
235
|
+
|
|
236
|
+
#### Rotación por Tamaño (`"size"`)
|
|
237
|
+
|
|
238
|
+
La rotación se activa cuando el archivo supera el límite configurado:
|
|
239
|
+
|
|
240
|
+
1. **Evaluación**: Compara el tamaño actual del archivo con `maxSizeMB` usando `FileSize.isGreaterThan()`
|
|
241
|
+
2. **Decisión**: Se rota cuando el tamaño es mayor o igual al límite
|
|
242
|
+
3. **Proceso**: Se crea un nuevo archivo y el anterior se mantiene según `maxFiles`
|
|
243
|
+
|
|
244
|
+
#### Sin Rotación (`"none"`)
|
|
245
|
+
|
|
246
|
+
El archivo crece indefinidamente sin intervención del sistema de rotación.
|
|
247
|
+
|
|
248
|
+
### Generación de Nombres de Archivos
|
|
249
|
+
|
|
250
|
+
El sistema utiliza `FileNamePattern` para generar nombres dinámicamente:
|
|
251
|
+
|
|
252
|
+
**Placeholders soportados**:
|
|
253
|
+
|
|
254
|
+
- `{yyyy}` - Año de 4 dígitos
|
|
255
|
+
- `{MM}` - Mes con padding (01-12)
|
|
256
|
+
- `{dd}` - Día con padding (01-31)
|
|
257
|
+
- `{HH}` - Hora con padding (00-23)
|
|
258
|
+
- `{mm}` - Minutos con padding (00-59)
|
|
259
|
+
- `{ss}` - Segundos con padding (00-59)
|
|
260
|
+
|
|
261
|
+
**Proceso**:
|
|
262
|
+
|
|
263
|
+
1. `FileRotatorAdapter.buildPathForDate()` toma la fecha actual
|
|
264
|
+
2. Reemplaza cada placeholder con el valor correspondiente de la fecha
|
|
265
|
+
3. Combina con `basePath` usando `NodeFilePathAdapter`
|
|
266
|
+
4. Retorna un `FilePath` completo
|
|
267
|
+
|
|
268
|
+
## Flujo Interno del Datasource
|
|
269
|
+
|
|
270
|
+
### Pipeline de Escritura de Logs
|
|
271
|
+
|
|
272
|
+
```
|
|
273
|
+
ILogProps → ISaveLogDto → PersistLogUseCase → Filesystem
|
|
274
|
+
↓ ↓ ↓ ↓
|
|
275
|
+
[Logger] [Application] [Domain] [Infrastructure]
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
### Secuencia Detallada
|
|
279
|
+
|
|
280
|
+
1. **Recepción**: `FsDatasourceService.save()` recibe `ILogProps` desde el logger principal
|
|
281
|
+
|
|
282
|
+
2. **Transformación**: Se encapsula en `ISaveLogDto` para el caso de uso
|
|
283
|
+
|
|
284
|
+
3. **Orquestación**: `PersistLogUseCase.execute()` coordina el proceso:
|
|
285
|
+
|
|
286
|
+
- Asegura que el directorio existe (`EnsureDirectoryUseCase`)
|
|
287
|
+
- Evalúa necesidad de rotación (`RotateIfNeededUseCase`)
|
|
288
|
+
- Verifica estado del stream de escritura
|
|
289
|
+
- Delega escritura (`AppendLogUseCase`)
|
|
290
|
+
|
|
291
|
+
4. **Evaluación de Rotación**: `RotateIfNeededUseCase`:
|
|
292
|
+
|
|
293
|
+
- Consulta `FileRotatorAdapter.shouldRotate()`
|
|
294
|
+
- Si requiere rotación, cierra stream actual y abre uno nuevo
|
|
295
|
+
- Ejecuta callback `onRotate` si está configurado
|
|
296
|
+
|
|
297
|
+
5. **Escritura**: `AppendLogUseCase`:
|
|
298
|
+
|
|
299
|
+
- Serializa el log usando `IFsSerializer`
|
|
300
|
+
- Escribe en el stream actual via `FsWriterAdapter`
|
|
301
|
+
|
|
302
|
+
6. **Gestión de Stream**: `FsWriterAdapter`:
|
|
303
|
+
- Maneja WriteStream de Node.js
|
|
304
|
+
- Controla backpressure y drenaje
|
|
305
|
+
- Gestiona apertura/cierre de archivos
|
|
306
|
+
|
|
307
|
+
## Dependencias entre Capas
|
|
308
|
+
|
|
309
|
+
```
|
|
310
|
+
┌─────────────────────────────────────────────────────┐
|
|
311
|
+
│ Infrastructure │
|
|
312
|
+
│ ┌─────────────────┐ ┌─────────────────────────┐ │
|
|
313
|
+
│ │ Adapters │ │ Filesystem │ │
|
|
314
|
+
│ │ - FsWriter │ │ - RotationPolicy │ │
|
|
315
|
+
│ │ - FileRotator │ │ - FileNamePattern │ │
|
|
316
|
+
│ │ - FsProvider │ │ - Types & Ports │ │
|
|
317
|
+
│ │ - NodeClock │ │ │ │
|
|
318
|
+
│ │ - NodeFilePath │ │ │ │
|
|
319
|
+
│ └─────────────────┘ └─────────────────────────┘ │
|
|
320
|
+
└─────────────────────────────────────────────────────┘
|
|
321
|
+
│
|
|
322
|
+
▼
|
|
323
|
+
┌─────────────────────────────────────────────────────┐
|
|
324
|
+
│ Application │
|
|
325
|
+
│ ┌─────────────────┐ ┌─────────────────────────┐ │
|
|
326
|
+
│ │ Use Cases │ │ Factory & Service │ │
|
|
327
|
+
│ │ - PersistLog │ │ - createFsDatasource │ │
|
|
328
|
+
│ │ - AppendLog │ │ - FsDatasourceService │ │
|
|
329
|
+
│ │ - RotateIfNeed │ │ │ │
|
|
330
|
+
│ │ - EnsureDir │ │ │ │
|
|
331
|
+
│ └─────────────────┘ └─────────────────────────┘ │
|
|
332
|
+
└─────────────────────────────────────────────────────┘
|
|
333
|
+
│
|
|
334
|
+
▼
|
|
335
|
+
┌─────────────────────────────────────────────────────┐
|
|
336
|
+
│ Domain │
|
|
337
|
+
│ ┌─────────────────┐ ┌─────────────────────────┐ │
|
|
338
|
+
│ │ Value Objects │ │ Ports │ │
|
|
339
|
+
│ │ - FilePath │ │ - IStreamWriterPort │ │
|
|
340
|
+
│ │ - FileSize │ │ - IFileRotatorPort │ │
|
|
341
|
+
│ │ │ │ - IFsProviderPort │ │
|
|
342
|
+
│ │ │ │ - IClockPort │ │
|
|
343
|
+
│ └─────────────────┘ └─────────────────────────┘ │
|
|
344
|
+
└─────────────────────────────────────────────────────┘
|
|
345
|
+
```
|
|
346
|
+
|
|
347
|
+
## Patrones Técnicos Implementados
|
|
348
|
+
|
|
349
|
+
### Adapter Pattern
|
|
350
|
+
|
|
351
|
+
Los adaptadores (`FsWriterAdapter`, `FileRotatorAdapter`, etc.) implementan interfaces del dominio, adaptando APIs externas (Node.js filesystem) a las necesidades del plugin.
|
|
352
|
+
|
|
353
|
+
### Strategy Pattern
|
|
354
|
+
|
|
355
|
+
`RotationPolicy` encapsula diferentes estrategias de rotación (`"day"`, `"size"`, `"none"`) que pueden intercambiarse dinámicamente.
|
|
356
|
+
|
|
357
|
+
### Value Object Pattern
|
|
358
|
+
|
|
359
|
+
`FilePath`, `FileSize`, `FileNamePattern` y `RotationPolicy` implementan el patrón Value Object, proporcionando inmutabilidad y encapsulación de conceptos del dominio.
|
|
360
|
+
|
|
361
|
+
### Factory Pattern
|
|
362
|
+
|
|
363
|
+
`createFsDatasource` implementa el patrón Factory, encapsulando la complejidad de creación y configuración de dependencias.
|
|
364
|
+
|
|
365
|
+
### Port & Adapter Pattern
|
|
366
|
+
|
|
367
|
+
Las interfaces en `/domain/ports/` definen contratos que las implementaciones en `/infrastructure/adapters/` cumplen, permitiendo inversión de dependencias.
|
|
368
|
+
|
|
369
|
+
## Interacción con @jmlq/logger
|
|
370
|
+
|
|
371
|
+
### Integración como Datasource
|
|
372
|
+
|
|
373
|
+
El plugin se integra implementando `ILogDatasource`:
|
|
374
|
+
|
|
375
|
+
```typescript
|
|
376
|
+
import { createFsDatasource } from "@jmlq/logger-plugin-fs";
|
|
377
|
+
import { LoggerFactory } from "@jmlq/logger";
|
|
378
|
+
|
|
379
|
+
const fsDatasource = createFsDatasource({
|
|
380
|
+
basePath: "./logs",
|
|
381
|
+
rotation: { by: "day" },
|
|
382
|
+
});
|
|
383
|
+
|
|
384
|
+
const logger = LoggerFactory.create({
|
|
385
|
+
datasources: [fsDatasource],
|
|
386
|
+
});
|
|
387
|
+
```
|
|
388
|
+
|
|
389
|
+
### Flujo de Integración
|
|
390
|
+
|
|
391
|
+
1. `LoggerFactory` registra el datasource creado por `createFsDatasource`
|
|
392
|
+
2. Cuando se ejecuta `logger.info()`, `logger.error()`, etc., el logger principal invoca `FsDatasourceService.save()`
|
|
393
|
+
3. El plugin procesa el log a través de su arquitectura interna
|
|
394
|
+
4. El resultado se persiste en el filesystem según la configuración
|
|
395
|
+
|
|
396
|
+
### Lifecycle Management
|
|
397
|
+
|
|
398
|
+
```typescript
|
|
399
|
+
// Flush de buffers pendientes
|
|
400
|
+
await fsDatasource.flush();
|
|
401
|
+
|
|
402
|
+
// Cierre limpio de recursos
|
|
403
|
+
await fsDatasource.dispose();
|
|
404
|
+
```
|
|
405
|
+
|
|
406
|
+
## Consideraciones Técnicas
|
|
407
|
+
|
|
408
|
+
### Gestión de Concurrencia
|
|
409
|
+
|
|
410
|
+
El plugin maneja escritura asíncrona usando WriteStream de Node.js con control de backpressure a través de eventos `drain` y gestión de estado interno.
|
|
411
|
+
|
|
412
|
+
### Manejo de Errores
|
|
413
|
+
|
|
414
|
+
La arquitectura proporciona tres niveles de error:
|
|
415
|
+
|
|
416
|
+
- `FsPluginError`: Errores generales del plugin
|
|
417
|
+
- `FileOperationError`: Errores específicos de operaciones de archivo
|
|
418
|
+
- `RotationError`: Errores relacionados con rotación de archivos
|
|
419
|
+
|
|
420
|
+
### Limitaciones Arquitecturales
|
|
421
|
+
|
|
422
|
+
1. **Dependencia de Node.js**: El plugin está acoplado al runtime de Node.js para operaciones de filesystem
|
|
423
|
+
2. **Rotación Síncrona por Fecha**: La evaluación de rotación por día se basa en comparación de paths, no en metadata de archivos
|
|
424
|
+
3. **Sin Compresión**: No incluye compresión automática de archivos rotados
|
|
425
|
+
4. **Estrategias de Rotación Limitadas**: Solo soporta rotación por día, tamaño o ninguna
|
|
426
|
+
5. **Sin Limpieza Automática**: La limpieza de archivos antiguos debe implementarse externamente usando `maxFiles` como referencia
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./rotate-if-needed.request";
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
__exportStar(require("./rotate-if-needed.request"), exports);
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { FileRotationPolicy } from "../../domain/value-objects";
|
|
2
|
+
/**
|
|
3
|
+
* DTO de entrada para el caso de uso RotateIfNeeded.
|
|
4
|
+
* - currentDate: fecha/hora actual (inyectada desde un clock para tests deterministas)
|
|
5
|
+
* - rotationPolicy: política de rotación (VO de dominio)
|
|
6
|
+
*/
|
|
7
|
+
export interface RotateIfNeededRequest {
|
|
8
|
+
currentDate: Date;
|
|
9
|
+
rotationPolicy: FileRotationPolicy;
|
|
10
|
+
}
|