@jmlq/logger-plugin-fs 0.1.0-alpha.6 → 0.1.0-alpha.7
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 +165 -117
- package/architecture.md +426 -0
- package/dist/application/dto/index.d.ts +2 -0
- package/dist/application/dto/index.js +2 -0
- package/dist/application/dto/rotation-check.dto.d.ts +5 -0
- package/dist/application/dto/save-log.dto.d.ts +3 -3
- package/dist/application/dto/write-operation.dto.d.ts +5 -0
- package/dist/application/factory/create-fs-datasource.factory.d.ts +3 -0
- package/dist/application/factory/create-fs-datasource.factory.js +26 -0
- package/dist/application/factory/index.d.ts +1 -0
- package/dist/{presentation → application}/factory/index.js +1 -1
- package/dist/application/services/fs-datasource.service.d.ts +7 -5
- package/dist/application/services/fs-datasource.service.js +6 -9
- package/dist/application/use-cases/append-log.use-case.d.ts +12 -0
- package/dist/application/use-cases/append-log.use-case.js +26 -0
- package/dist/application/use-cases/ensure-directory.use-case.d.ts +11 -0
- package/dist/application/use-cases/ensure-directory.use-case.js +27 -0
- package/dist/application/use-cases/index.d.ts +4 -3
- package/dist/application/use-cases/index.js +4 -3
- package/dist/application/use-cases/persist-log.use-case.d.ts +19 -0
- package/dist/application/use-cases/persist-log.use-case.js +47 -0
- package/dist/application/use-cases/rotate-if-needed.use-case.d.ts +17 -0
- package/dist/application/use-cases/rotate-if-needed.use-case.js +28 -0
- package/dist/domain/ports/clock.port.d.ts +8 -0
- package/dist/domain/ports/file-path-adapter.port.d.ts +33 -0
- package/dist/domain/ports/fs-provider.port.d.ts +61 -0
- package/dist/domain/ports/fs-provider.port.js +2 -0
- package/dist/domain/ports/index.d.ts +4 -0
- package/dist/domain/{contracts → ports}/index.js +3 -3
- package/dist/domain/ports/stream-writer.port.d.ts +42 -0
- package/dist/domain/ports/stream-writer.port.js +2 -0
- package/dist/domain/value-objects/file-path.vo.d.ts +27 -0
- package/dist/domain/value-objects/file-path.vo.js +59 -0
- package/dist/domain/value-objects/file-size.vo.d.ts +14 -0
- package/dist/domain/value-objects/file-size.vo.js +57 -0
- package/dist/domain/value-objects/index.d.ts +2 -2
- package/dist/domain/value-objects/index.js +2 -2
- package/dist/index.d.ts +4 -3
- package/dist/index.js +14 -5
- package/dist/infrastructure/adapters/file-rotator.adapter.d.ts +20 -0
- package/dist/infrastructure/adapters/file-rotator.adapter.js +105 -0
- package/dist/infrastructure/adapters/fs-provider.adapter.d.ts +17 -0
- package/dist/infrastructure/{fs/fs-provider.js → adapters/fs-provider.adapter.js} +41 -19
- package/dist/infrastructure/adapters/fs-writer.adapter.d.ts +13 -0
- package/dist/infrastructure/adapters/fs-writer.adapter.js +71 -0
- package/dist/infrastructure/{fs → adapters}/index.d.ts +2 -2
- package/dist/infrastructure/{fs → adapters}/index.js +2 -2
- package/dist/infrastructure/adapters/node-clock.adapter.d.ts +4 -0
- package/dist/infrastructure/{fs → adapters}/node-clock.adapter.js +0 -1
- package/dist/infrastructure/adapters/node-file-path.adapter.d.ts +16 -0
- package/dist/infrastructure/adapters/node-file-path.adapter.js +117 -0
- package/dist/infrastructure/filesystem/index.d.ts +4 -0
- package/dist/infrastructure/filesystem/index.js +20 -0
- package/dist/infrastructure/filesystem/polices/index.d.ts +1 -0
- package/dist/infrastructure/filesystem/polices/index.js +5 -0
- package/dist/infrastructure/filesystem/polices/rotation-policy.d.ts +29 -0
- package/dist/infrastructure/filesystem/polices/rotation-policy.js +55 -0
- package/dist/infrastructure/filesystem/ports/file-rotator.port.d.ts +32 -0
- package/dist/infrastructure/filesystem/ports/file-rotator.port.js +2 -0
- package/dist/infrastructure/filesystem/ports/index.d.ts +1 -0
- package/dist/{domain/types → infrastructure/filesystem/ports}/index.js +1 -1
- package/dist/infrastructure/filesystem/types/filesystem-datasource-options.type.d.ts +49 -0
- package/dist/infrastructure/filesystem/types/filesystem-datasource-options.type.js +2 -0
- package/dist/infrastructure/filesystem/types/filesystem-rotation.type.d.ts +19 -0
- package/dist/infrastructure/filesystem/types/filesystem-rotation.type.js +2 -0
- package/dist/infrastructure/filesystem/types/filesystem-serializer.type.d.ts +10 -0
- package/dist/infrastructure/filesystem/types/filesystem-serializer.type.js +2 -0
- package/dist/infrastructure/filesystem/types/index.d.ts +3 -0
- package/dist/infrastructure/filesystem/types/index.js +19 -0
- package/dist/infrastructure/filesystem/value-objects/file-name-pattern.vo.d.ts +22 -0
- package/dist/infrastructure/filesystem/value-objects/file-name-pattern.vo.js +37 -0
- package/dist/infrastructure/filesystem/value-objects/index.d.ts +1 -0
- package/dist/infrastructure/{datasources → filesystem/value-objects}/index.js +1 -1
- package/dist/shared/errors/file-operation.error.d.ts +12 -0
- package/dist/shared/errors/file-operation.error.js +32 -0
- package/dist/shared/errors/fs-plugin.error.d.ts +4 -0
- package/dist/shared/errors/fs-plugin.error.js +11 -0
- package/dist/shared/errors/index.d.ts +3 -0
- package/dist/shared/errors/index.js +19 -0
- package/dist/shared/errors/rotation.error.d.ts +10 -0
- package/dist/shared/errors/rotation.error.js +25 -0
- package/install.md +520 -0
- package/package.json +39 -22
- package/LICENSE +0 -21
- package/dist/application/use-cases/append-log.usecase.d.ts +0 -7
- package/dist/application/use-cases/append-log.usecase.js +0 -19
- package/dist/application/use-cases/persist-log.usecase.d.ts +0 -23
- package/dist/application/use-cases/persist-log.usecase.js +0 -74
- package/dist/application/use-cases/rotate-if-needed.usecase.d.ts +0 -10
- package/dist/application/use-cases/rotate-if-needed.usecase.js +0 -39
- package/dist/domain/contracts/clock.contract.d.ts +0 -3
- package/dist/domain/contracts/file-rotator.contract.d.ts +0 -7
- package/dist/domain/contracts/index.d.ts +0 -4
- package/dist/domain/contracts/serializer.contract.d.ts +0 -3
- package/dist/domain/contracts/stream-writer.port.d.ts +0 -6
- package/dist/domain/types/index.d.ts +0 -1
- package/dist/domain/types/options.type.d.ts +0 -11
- package/dist/domain/types/options.type.js +0 -5
- package/dist/domain/value-objects/file-name-pattern.vo.d.ts +0 -4
- package/dist/domain/value-objects/file-name-pattern.vo.js +0 -14
- package/dist/domain/value-objects/rotation-policy.vo.d.ts +0 -7
- package/dist/domain/value-objects/rotation-policy.vo.js +0 -20
- package/dist/infrastructure/datasources/fs.datasource.d.ts +0 -17
- package/dist/infrastructure/datasources/fs.datasource.js +0 -84
- package/dist/infrastructure/datasources/index.d.ts +0 -1
- package/dist/infrastructure/fs/file-rotator.adapter.d.ts +0 -22
- package/dist/infrastructure/fs/file-rotator.adapter.js +0 -51
- package/dist/infrastructure/fs/fs-provider.d.ts +0 -15
- package/dist/infrastructure/fs/fs-writer.adapter.d.ts +0 -10
- package/dist/infrastructure/fs/fs-writer.adapter.js +0 -26
- package/dist/infrastructure/fs/node-clock.adapter.d.ts +0 -4
- package/dist/infrastructure/fs/path-utils.d.ts +0 -6
- package/dist/infrastructure/fs/path-utils.js +0 -26
- package/dist/presentation/factory/create-fs-datasource.d.ts +0 -15
- package/dist/presentation/factory/create-fs-datasource.js +0 -39
- package/dist/presentation/factory/index.d.ts +0 -1
- /package/dist/{domain/contracts/clock.contract.js → application/dto/rotation-check.dto.js} +0 -0
- /package/dist/{domain/contracts/file-rotator.contract.js → application/dto/write-operation.dto.js} +0 -0
- /package/dist/domain/{contracts/serializer.contract.js → ports/clock.port.js} +0 -0
- /package/dist/domain/{contracts/stream-writer.port.js → ports/file-path-adapter.port.js} +0 -0
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
|
|
@@ -14,4 +14,6 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
|
14
14
|
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
15
|
};
|
|
16
16
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
__exportStar(require("./rotation-check.dto"), exports);
|
|
17
18
|
__exportStar(require("./save-log.dto"), exports);
|
|
19
|
+
__exportStar(require("./write-operation.dto"), exports);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import
|
|
2
|
-
export interface
|
|
3
|
-
log:
|
|
1
|
+
import { ILogProps } from "@jmlq/logger";
|
|
2
|
+
export interface ISaveLogDto {
|
|
3
|
+
log: ILogProps;
|
|
4
4
|
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createFsDatasource = createFsDatasource;
|
|
4
|
+
const adapters_1 = require("../../infrastructure/adapters");
|
|
5
|
+
const use_cases_1 = require("../use-cases");
|
|
6
|
+
const services_1 = require("../services");
|
|
7
|
+
const filesystem_1 = require("../../infrastructure/filesystem");
|
|
8
|
+
function createFsDatasource(options) {
|
|
9
|
+
// Crear Value Objects
|
|
10
|
+
const fileNamePattern = new filesystem_1.FileNamePattern(options.fileNamePattern || "app-{yyyy}{MM}{dd}.log");
|
|
11
|
+
const rotationPolicy = new filesystem_1.RotationPolicy(options.rotation?.by || "day", options.rotation?.maxSizeMB, options.rotation?.maxFiles);
|
|
12
|
+
// Crear adaptadores
|
|
13
|
+
const clockAdapter = new adapters_1.NodeClockAdapter();
|
|
14
|
+
const filePathAdapter = new adapters_1.NodeFilePathAdapter();
|
|
15
|
+
const fsProviderAdapter = new adapters_1.FsProviderAdapter();
|
|
16
|
+
const streamWriterAdapter = new adapters_1.FsWriterAdapter();
|
|
17
|
+
const fileRotatorAdapter = new adapters_1.FileRotatorAdapter(fsProviderAdapter, filePathAdapter, fileNamePattern, options.basePath);
|
|
18
|
+
// Crear casos de uso
|
|
19
|
+
const ensureDirectoryUseCase = new use_cases_1.EnsureDirectoryUseCase(fsProviderAdapter, options.basePath, options.mkdir);
|
|
20
|
+
const appendLogUseCase = new use_cases_1.AppendLogUseCase(streamWriterAdapter, options.serializer);
|
|
21
|
+
const rotateIfNeededUseCase = new use_cases_1.RotateIfNeededUseCase(fileRotatorAdapter, streamWriterAdapter, options.onRotate);
|
|
22
|
+
const persistLogUseCase = new use_cases_1.PersistLogUseCase(clockAdapter, fileRotatorAdapter, streamWriterAdapter, rotationPolicy, rotateIfNeededUseCase, appendLogUseCase, ensureDirectoryUseCase, options.onError);
|
|
23
|
+
// Inicializar directorio
|
|
24
|
+
ensureDirectoryUseCase.execute();
|
|
25
|
+
return new services_1.FsDatasourceService(persistLogUseCase, streamWriterAdapter);
|
|
26
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./create-fs-datasource.factory";
|
|
@@ -14,4 +14,4 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
|
14
14
|
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
15
|
};
|
|
16
16
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
-
__exportStar(require("./create-fs-datasource"), exports);
|
|
17
|
+
__exportStar(require("./create-fs-datasource.factory"), exports);
|
|
@@ -1,9 +1,11 @@
|
|
|
1
|
-
import
|
|
2
|
-
import {
|
|
1
|
+
import { ILogProps, ILogDatasource } from "@jmlq/logger";
|
|
2
|
+
import { IPersistLogUseCase } from "../use-cases";
|
|
3
|
+
import { IStreamWriterPort } from "../../domain/ports";
|
|
3
4
|
export declare class FsDatasourceService implements ILogDatasource {
|
|
4
|
-
private readonly
|
|
5
|
-
|
|
6
|
-
|
|
5
|
+
private readonly persistLogUseCase;
|
|
6
|
+
private readonly streamWriterPort;
|
|
7
|
+
constructor(persistLogUseCase: IPersistLogUseCase, streamWriterPort: IStreamWriterPort);
|
|
8
|
+
save(log: ILogProps): Promise<void>;
|
|
7
9
|
flush(): Promise<void>;
|
|
8
10
|
dispose(): Promise<void>;
|
|
9
11
|
}
|
|
@@ -2,21 +2,18 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.FsDatasourceService = void 0;
|
|
4
4
|
class FsDatasourceService {
|
|
5
|
-
constructor(
|
|
6
|
-
this.
|
|
5
|
+
constructor(persistLogUseCase, streamWriterPort) {
|
|
6
|
+
this.persistLogUseCase = persistLogUseCase;
|
|
7
|
+
this.streamWriterPort = streamWriterPort;
|
|
7
8
|
}
|
|
8
|
-
// Guarda un log (core llama esto). Orquesta vía PersistLogUseCase.
|
|
9
9
|
async save(log) {
|
|
10
|
-
|
|
11
|
-
await this.persistUC.execute(dto);
|
|
10
|
+
await this.persistLogUseCase.execute({ log });
|
|
12
11
|
}
|
|
13
|
-
// Espera drenaje del buffer (útil para shutdown)
|
|
14
12
|
async flush() {
|
|
15
|
-
await this.
|
|
13
|
+
await this.streamWriterPort.flush();
|
|
16
14
|
}
|
|
17
|
-
// Cierra recursos (stream)
|
|
18
15
|
async dispose() {
|
|
19
|
-
await this.
|
|
16
|
+
await this.streamWriterPort.close();
|
|
20
17
|
}
|
|
21
18
|
}
|
|
22
19
|
exports.FsDatasourceService = FsDatasourceService;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { ISaveLogDto } from "../dto/save-log.dto";
|
|
2
|
+
import { IStreamWriterPort } from "../../domain/ports/stream-writer.port";
|
|
3
|
+
import { IFsSerializer } from "../../infrastructure/filesystem";
|
|
4
|
+
export interface IAppendLogUseCase {
|
|
5
|
+
execute(dto: ISaveLogDto): Promise<void>;
|
|
6
|
+
}
|
|
7
|
+
export declare class AppendLogUseCase implements IAppendLogUseCase {
|
|
8
|
+
private readonly streamWriterPort;
|
|
9
|
+
private readonly serializer?;
|
|
10
|
+
constructor(streamWriterPort: IStreamWriterPort, serializer?: IFsSerializer | undefined);
|
|
11
|
+
execute(dto: ISaveLogDto): Promise<void>;
|
|
12
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.AppendLogUseCase = void 0;
|
|
4
|
+
class AppendLogUseCase {
|
|
5
|
+
constructor(streamWriterPort, serializer) {
|
|
6
|
+
this.streamWriterPort = streamWriterPort;
|
|
7
|
+
this.serializer = serializer;
|
|
8
|
+
}
|
|
9
|
+
async execute(dto) {
|
|
10
|
+
const { log } = dto;
|
|
11
|
+
let serializedData;
|
|
12
|
+
if (this.serializer) {
|
|
13
|
+
serializedData = this.serializer.serialize(log);
|
|
14
|
+
}
|
|
15
|
+
else {
|
|
16
|
+
// Serialización por defecto
|
|
17
|
+
serializedData = JSON.stringify(log);
|
|
18
|
+
}
|
|
19
|
+
// Agregar salto de línea si no lo tiene
|
|
20
|
+
if (!serializedData.endsWith("\n")) {
|
|
21
|
+
serializedData += "\n";
|
|
22
|
+
}
|
|
23
|
+
await this.streamWriterPort.write(serializedData);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
exports.AppendLogUseCase = AppendLogUseCase;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { IFsProviderPort } from "../../domain/ports";
|
|
2
|
+
export interface IEnsureDirectoryUseCase {
|
|
3
|
+
execute(): Promise<void>;
|
|
4
|
+
}
|
|
5
|
+
export declare class EnsureDirectoryUseCase implements IEnsureDirectoryUseCase {
|
|
6
|
+
private readonly fsProvider;
|
|
7
|
+
private readonly basePath;
|
|
8
|
+
private readonly createIfNotExists;
|
|
9
|
+
constructor(fsProvider: IFsProviderPort, basePath: string, createIfNotExists?: boolean);
|
|
10
|
+
execute(): Promise<void>;
|
|
11
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.EnsureDirectoryUseCase = void 0;
|
|
4
|
+
const errors_1 = require("../../shared/errors");
|
|
5
|
+
class EnsureDirectoryUseCase {
|
|
6
|
+
constructor(fsProvider, basePath, createIfNotExists = true) {
|
|
7
|
+
this.fsProvider = fsProvider;
|
|
8
|
+
this.basePath = basePath;
|
|
9
|
+
this.createIfNotExists = createIfNotExists;
|
|
10
|
+
}
|
|
11
|
+
async execute() {
|
|
12
|
+
if (!this.createIfNotExists) {
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
try {
|
|
16
|
+
// El basePath ES el directorio que queremos crear
|
|
17
|
+
const exists = await this.fsProvider.exists(this.basePath);
|
|
18
|
+
if (!exists) {
|
|
19
|
+
await this.fsProvider.mkdir(this.basePath, { recursive: true });
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
catch (error) {
|
|
23
|
+
throw errors_1.FileOperationError.createForMkdir(this.basePath, error);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
exports.EnsureDirectoryUseCase = EnsureDirectoryUseCase;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
-
export * from "./
|
|
2
|
-
export * from "./
|
|
3
|
-
export * from "./persist-log.
|
|
1
|
+
export * from "./append-log.use-case";
|
|
2
|
+
export * from "./ensure-directory.use-case";
|
|
3
|
+
export * from "./persist-log.use-case";
|
|
4
|
+
export * from "./rotate-if-needed.use-case";
|
|
@@ -14,6 +14,7 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
|
14
14
|
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
15
|
};
|
|
16
16
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
-
__exportStar(require("./
|
|
18
|
-
__exportStar(require("./
|
|
19
|
-
__exportStar(require("./persist-log.
|
|
17
|
+
__exportStar(require("./append-log.use-case"), exports);
|
|
18
|
+
__exportStar(require("./ensure-directory.use-case"), exports);
|
|
19
|
+
__exportStar(require("./persist-log.use-case"), exports);
|
|
20
|
+
__exportStar(require("./rotate-if-needed.use-case"), exports);
|