@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
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.FileNamePattern = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* representa el patrón de nombrado de archivos de log
|
|
6
|
+
*/
|
|
7
|
+
class FileNamePattern {
|
|
8
|
+
constructor(pattern) {
|
|
9
|
+
if (!pattern || typeof pattern !== "string") {
|
|
10
|
+
throw new Error("FileNamePattern: pattern must be a non-empty string");
|
|
11
|
+
}
|
|
12
|
+
this._pattern = pattern.trim();
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Retorna el valor inmutable
|
|
16
|
+
*/
|
|
17
|
+
get pattern() {
|
|
18
|
+
return this._pattern;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Indica si el patrón contiene tokens de fecha
|
|
22
|
+
* Útil para detectar si el nombre del archivo depende de fecha
|
|
23
|
+
* @returns true si el patrón contiene tokens de fecha, false en caso contrario
|
|
24
|
+
*/
|
|
25
|
+
hasDateTokens() {
|
|
26
|
+
return /\{(yyyy|MM|dd|HH|mm|ss)\}/.test(this._pattern);
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Devuelve todos los tokens encerrados en llaves presentes en el patrón
|
|
30
|
+
* @returns Array de tokens encontrados en el patrón
|
|
31
|
+
*/
|
|
32
|
+
getTokens() {
|
|
33
|
+
const matches = this._pattern.match(/\{([^}]+)\}/g);
|
|
34
|
+
return matches ? matches.map((m) => m.slice(1, -1)) : [];
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
exports.FileNamePattern = FileNamePattern;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./file-name-pattern.vo";
|
|
@@ -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("./
|
|
17
|
+
__exportStar(require("./file-name-pattern.vo"), exports);
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export declare class FileOperationError extends Error {
|
|
2
|
+
readonly operation: string;
|
|
3
|
+
readonly filePath?: string | undefined;
|
|
4
|
+
readonly cause?: Error | undefined;
|
|
5
|
+
constructor(message: string, operation: string, filePath?: string | undefined, cause?: Error | undefined);
|
|
6
|
+
static create(operation: string, filePath: string, cause?: Error): FileOperationError;
|
|
7
|
+
static createForRead(filePath: string, cause?: Error): FileOperationError;
|
|
8
|
+
static createForWrite(filePath: string, cause?: Error): FileOperationError;
|
|
9
|
+
static createForDelete(filePath: string, cause?: Error): FileOperationError;
|
|
10
|
+
static createForMkdir(filePath: string, cause?: Error): FileOperationError;
|
|
11
|
+
static createForMove(filePath: string, cause?: Error): FileOperationError;
|
|
12
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.FileOperationError = void 0;
|
|
4
|
+
class FileOperationError extends Error {
|
|
5
|
+
constructor(message, operation, filePath, cause) {
|
|
6
|
+
super(message);
|
|
7
|
+
this.operation = operation;
|
|
8
|
+
this.filePath = filePath;
|
|
9
|
+
this.cause = cause;
|
|
10
|
+
this.name = "FileOperationError";
|
|
11
|
+
}
|
|
12
|
+
static create(operation, filePath, cause) {
|
|
13
|
+
const message = `Failed to ${operation} file: ${filePath}`;
|
|
14
|
+
return new FileOperationError(message, operation, filePath, cause);
|
|
15
|
+
}
|
|
16
|
+
static createForRead(filePath, cause) {
|
|
17
|
+
return FileOperationError.create("read", filePath, cause);
|
|
18
|
+
}
|
|
19
|
+
static createForWrite(filePath, cause) {
|
|
20
|
+
return FileOperationError.create("write", filePath, cause);
|
|
21
|
+
}
|
|
22
|
+
static createForDelete(filePath, cause) {
|
|
23
|
+
return FileOperationError.create("delete", filePath, cause);
|
|
24
|
+
}
|
|
25
|
+
static createForMkdir(filePath, cause) {
|
|
26
|
+
return FileOperationError.create("create directory", filePath, cause);
|
|
27
|
+
}
|
|
28
|
+
static createForMove(filePath, cause) {
|
|
29
|
+
return FileOperationError.create("move", filePath, cause);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
exports.FileOperationError = FileOperationError;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.FsPluginError = void 0;
|
|
4
|
+
class FsPluginError extends Error {
|
|
5
|
+
constructor(message, cause) {
|
|
6
|
+
super(message);
|
|
7
|
+
this.cause = cause;
|
|
8
|
+
this.name = "FsPluginError";
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
exports.FsPluginError = FsPluginError;
|
|
@@ -0,0 +1,19 @@
|
|
|
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("./file-operation.error"), exports);
|
|
18
|
+
__exportStar(require("./fs-plugin.error"), exports);
|
|
19
|
+
__exportStar(require("./rotation.error"), exports);
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export declare class RotationError extends Error {
|
|
2
|
+
readonly rotationType: string;
|
|
3
|
+
readonly filePath?: string | undefined;
|
|
4
|
+
readonly cause?: Error | undefined;
|
|
5
|
+
constructor(message: string, rotationType: string, filePath?: string | undefined, cause?: Error | undefined);
|
|
6
|
+
static createForPolicyViolation(policy: string, reason: string): RotationError;
|
|
7
|
+
static createForFileRotation(filePath: string, cause?: Error): RotationError;
|
|
8
|
+
static createForSizeCheck(filePath: string, cause?: Error): RotationError;
|
|
9
|
+
static createForDateCheck(filePath: string, cause?: Error): RotationError;
|
|
10
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.RotationError = void 0;
|
|
4
|
+
class RotationError extends Error {
|
|
5
|
+
constructor(message, rotationType, filePath, cause) {
|
|
6
|
+
super(message);
|
|
7
|
+
this.rotationType = rotationType;
|
|
8
|
+
this.filePath = filePath;
|
|
9
|
+
this.cause = cause;
|
|
10
|
+
this.name = "RotationError";
|
|
11
|
+
}
|
|
12
|
+
static createForPolicyViolation(policy, reason) {
|
|
13
|
+
return new RotationError(`Rotation policy '${policy}' violation: ${reason}`, policy);
|
|
14
|
+
}
|
|
15
|
+
static createForFileRotation(filePath, cause) {
|
|
16
|
+
return new RotationError(`Failed to rotate file: ${filePath}`, "file", filePath, cause);
|
|
17
|
+
}
|
|
18
|
+
static createForSizeCheck(filePath, cause) {
|
|
19
|
+
return new RotationError(`Failed to check file size for rotation: ${filePath}`, "size-check", filePath, cause);
|
|
20
|
+
}
|
|
21
|
+
static createForDateCheck(filePath, cause) {
|
|
22
|
+
return new RotationError(`Failed to check file date for rotation: ${filePath}`, "date-check", filePath, cause);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
exports.RotationError = RotationError;
|
package/install.md
ADDED
|
@@ -0,0 +1,520 @@
|
|
|
1
|
+
# Instalación y Configuración - @jmlq/logger-plugin-fs
|
|
2
|
+
|
|
3
|
+
## Introducción Técnica
|
|
4
|
+
|
|
5
|
+
`@jmlq/logger-plugin-fs` es un plugin para el sistema de logging `@jmlq/logger` que proporciona persistencia en sistema de archivos con capacidades avanzadas de rotación. Implementa Clean Architecture y está diseñado para aplicaciones Node.js que requieren logging persistente con gestión automática de archivos.
|
|
6
|
+
|
|
7
|
+
El plugin se integra como un datasource del logger principal, permitiendo escribir logs en archivos con políticas de rotación configurables (diaria, por tamaño, o sin rotación). Incluye funcionalidades como creación automática de directorios, serialización personalizable y callbacks para eventos de rotación.
|
|
8
|
+
|
|
9
|
+
## Instalación
|
|
10
|
+
|
|
11
|
+
### Dependencias Requeridas
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
npm install @jmlq/logger @jmlq/logger-plugin-fs
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
### Dependencias de Peer
|
|
18
|
+
|
|
19
|
+
El plugin requiere como peer dependency:
|
|
20
|
+
|
|
21
|
+
- `@jmlq/logger` >= 0.1.0-alpha.12
|
|
22
|
+
|
|
23
|
+
## Configuración del FileSystemDatasource
|
|
24
|
+
|
|
25
|
+
### Factory Principal
|
|
26
|
+
|
|
27
|
+
El plugin proporciona la función `createFsDatasource` que actúa como factory principal:
|
|
28
|
+
|
|
29
|
+
```typescript
|
|
30
|
+
import { createFsDatasource } from '@jmlq/logger-plugin-fs';
|
|
31
|
+
import { IFilesystemDatasourceOptions } from '@jmlq/logger-plugin-fs';
|
|
32
|
+
|
|
33
|
+
const datasource = createFsDatasource(options: IFilesystemDatasourceOptions);
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
### Parámetros de Configuración
|
|
37
|
+
|
|
38
|
+
#### IFilesystemDatasourceOptions
|
|
39
|
+
|
|
40
|
+
```typescript
|
|
41
|
+
interface IFilesystemDatasourceOptions {
|
|
42
|
+
basePath: string; // OBLIGATORIO
|
|
43
|
+
mkdir?: boolean; // Opcional, default: undefined
|
|
44
|
+
fileNamePattern?: string; // Opcional, default: "app-{yyyy}{MM}{dd}.log"
|
|
45
|
+
rotation?: IFileRotationConfig; // Opcional
|
|
46
|
+
serializer?: IFsSerializer; // Opcional
|
|
47
|
+
onRotate?: (oldPath: FilePath, newPath: FilePath) => void | Promise<void>;
|
|
48
|
+
onError?: (error: Error) => void | Promise<void>;
|
|
49
|
+
}
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
#### Descripción de Parámetros
|
|
53
|
+
|
|
54
|
+
- **`basePath`** (string): Directorio base donde se crearán los archivos de log. Es el único parámetro obligatorio.
|
|
55
|
+
|
|
56
|
+
- **`mkdir`** (boolean, opcional): Si es `true`, crea automáticamente el directorio `basePath` usando `fs.mkdir({ recursive: true })` si no existe.
|
|
57
|
+
|
|
58
|
+
- **`fileNamePattern`** (string, opcional): Patrón para nombres de archivo. Default: `"app-{yyyy}{MM}{dd}.log"`. Admite placeholders:
|
|
59
|
+
|
|
60
|
+
- `{yyyy}` - Año de 4 dígitos
|
|
61
|
+
- `{MM}` - Mes de 2 dígitos (01-12)
|
|
62
|
+
- `{dd}` - Día de 2 dígitos (01-31)
|
|
63
|
+
|
|
64
|
+
- **`rotation`** (IFileRotationConfig, opcional): Configuración de política de rotación.
|
|
65
|
+
|
|
66
|
+
- **`serializer`** (IFsSerializer, opcional): Estrategia personalizada de serialización de logs a string.
|
|
67
|
+
|
|
68
|
+
- **`onRotate`** (función, opcional): Callback ejecutado cuando se rota un archivo. Útil para notificaciones o envío a servicios externos.
|
|
69
|
+
|
|
70
|
+
- **`onError`** (función, opcional): Callback centralizado para manejo de errores del datasource.
|
|
71
|
+
|
|
72
|
+
## Política de Rotación (RotationPolicy)
|
|
73
|
+
|
|
74
|
+
### Configuración IFileRotationConfig
|
|
75
|
+
|
|
76
|
+
```typescript
|
|
77
|
+
interface IFileRotationConfig {
|
|
78
|
+
by: FsRotationBy; // "none" | "day" | "size"
|
|
79
|
+
maxSizeMB?: number; // Solo para by: "size"
|
|
80
|
+
maxFiles?: number; // Número máximo de archivos rotados a mantener
|
|
81
|
+
}
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
### Tipos de Rotación Soportados
|
|
85
|
+
|
|
86
|
+
#### 1. Sin Rotación (`"none"`)
|
|
87
|
+
|
|
88
|
+
```typescript
|
|
89
|
+
const datasource = createFsDatasource({
|
|
90
|
+
basePath: "./logs",
|
|
91
|
+
rotation: {
|
|
92
|
+
by: "none",
|
|
93
|
+
},
|
|
94
|
+
});
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
El archivo crece indefinidamente sin rotación.
|
|
98
|
+
|
|
99
|
+
#### 2. Rotación Diaria (`"day"`)
|
|
100
|
+
|
|
101
|
+
```typescript
|
|
102
|
+
const datasource = createFsDatasource({
|
|
103
|
+
basePath: "./logs",
|
|
104
|
+
fileNamePattern: "app-{yyyy}-{MM}-{dd}.log",
|
|
105
|
+
rotation: {
|
|
106
|
+
by: "day",
|
|
107
|
+
maxFiles: 7, // Mantener últimos 7 días
|
|
108
|
+
},
|
|
109
|
+
});
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
Crea un archivo nuevo cada día basado en el patrón de fecha.
|
|
113
|
+
|
|
114
|
+
#### 3. Rotación por Tamaño (`"size"`)
|
|
115
|
+
|
|
116
|
+
```typescript
|
|
117
|
+
const datasource = createFsDatasource({
|
|
118
|
+
basePath: "./logs",
|
|
119
|
+
rotation: {
|
|
120
|
+
by: "size",
|
|
121
|
+
maxSizeMB: 10, // OBLIGATORIO para rotación por tamaño
|
|
122
|
+
maxFiles: 5, // Mantener últimos 5 archivos
|
|
123
|
+
},
|
|
124
|
+
});
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
**Importante**: Para `by: "size"`, el parámetro `maxSizeMB` debe ser un número positivo.
|
|
128
|
+
|
|
129
|
+
## Serialización Personalizada
|
|
130
|
+
|
|
131
|
+
### Interface IFsSerializer
|
|
132
|
+
|
|
133
|
+
```typescript
|
|
134
|
+
interface IFsSerializer {
|
|
135
|
+
serialize(entry: unknown): string;
|
|
136
|
+
}
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
### Implementación de Serializer Personalizado
|
|
140
|
+
|
|
141
|
+
```typescript
|
|
142
|
+
const customSerializer: IFsSerializer = {
|
|
143
|
+
serialize(log: any): string {
|
|
144
|
+
// Ejemplo: formato personalizado con timestamp ISO
|
|
145
|
+
const timestamp = new Date(log.timestamp).toISOString();
|
|
146
|
+
return `[${timestamp}] ${log.level.toUpperCase()}: ${log.message}\n`;
|
|
147
|
+
},
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
const datasource = createFsDatasource({
|
|
151
|
+
basePath: "./logs",
|
|
152
|
+
serializer: customSerializer,
|
|
153
|
+
});
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
## Ejemplos Técnicos Completos
|
|
157
|
+
|
|
158
|
+
### Configuración Básica
|
|
159
|
+
|
|
160
|
+
```typescript
|
|
161
|
+
import { createFsDatasource } from "@jmlq/logger-plugin-fs";
|
|
162
|
+
import { LoggerFactory } from "@jmlq/logger";
|
|
163
|
+
|
|
164
|
+
// Crear datasource con configuración mínima
|
|
165
|
+
const fsDatasource = createFsDatasource({
|
|
166
|
+
basePath: "./logs",
|
|
167
|
+
mkdir: true,
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
// Integrar con LoggerFactory
|
|
171
|
+
const logger = LoggerFactory.create({
|
|
172
|
+
datasources: [fsDatasource],
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
// Uso básico
|
|
176
|
+
logger.info("Aplicación iniciada");
|
|
177
|
+
logger.error("Error de conexión a base de datos");
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
### Configuración con Rotación por Día y Callbacks
|
|
181
|
+
|
|
182
|
+
```typescript
|
|
183
|
+
import { createFsDatasource } from "@jmlq/logger-plugin-fs";
|
|
184
|
+
import { LoggerFactory } from "@jmlq/logger";
|
|
185
|
+
import { FilePath } from "@jmlq/logger-plugin-fs";
|
|
186
|
+
|
|
187
|
+
const fsDatasource = createFsDatasource({
|
|
188
|
+
basePath: "./logs/production",
|
|
189
|
+
fileNamePattern: "app-{yyyy}-{MM}-{dd}.log",
|
|
190
|
+
mkdir: true,
|
|
191
|
+
rotation: {
|
|
192
|
+
by: "day",
|
|
193
|
+
maxFiles: 30, // Mantener logs de últimos 30 días
|
|
194
|
+
},
|
|
195
|
+
serializer: {
|
|
196
|
+
serialize(log: any): string {
|
|
197
|
+
return (
|
|
198
|
+
JSON.stringify({
|
|
199
|
+
timestamp: new Date(log.timestamp).toISOString(),
|
|
200
|
+
level: log.level,
|
|
201
|
+
message: log.message,
|
|
202
|
+
...log.extra,
|
|
203
|
+
}) + "\n"
|
|
204
|
+
);
|
|
205
|
+
},
|
|
206
|
+
},
|
|
207
|
+
onRotate: async (oldPath: FilePath, newPath: FilePath) => {
|
|
208
|
+
console.log(
|
|
209
|
+
`Log rotado: ${oldPath.absolutePath} → ${newPath.absolutePath}`
|
|
210
|
+
);
|
|
211
|
+
// Aquí podrías enviar el archivo a S3, comprimir, etc.
|
|
212
|
+
},
|
|
213
|
+
onError: async (error: Error) => {
|
|
214
|
+
console.error("Error en filesystem datasource:", error.message);
|
|
215
|
+
// Enviar métricas de error, notificar administradores, etc.
|
|
216
|
+
},
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
const logger = LoggerFactory.create({
|
|
220
|
+
datasources: [fsDatasource],
|
|
221
|
+
});
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
### Configuración con Rotación por Tamaño
|
|
225
|
+
|
|
226
|
+
```typescript
|
|
227
|
+
import { createFsDatasource } from "@jmlq/logger-plugin-fs";
|
|
228
|
+
import { LoggerFactory } from "@jmlq/logger";
|
|
229
|
+
|
|
230
|
+
const fsDatasource = createFsDatasource({
|
|
231
|
+
basePath: "./logs/high-volume",
|
|
232
|
+
fileNamePattern: "app.log",
|
|
233
|
+
mkdir: true,
|
|
234
|
+
rotation: {
|
|
235
|
+
by: "size",
|
|
236
|
+
maxSizeMB: 50, // Rotar cuando el archivo alcance 50MB
|
|
237
|
+
maxFiles: 10, // Mantener últimos 10 archivos rotados
|
|
238
|
+
},
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
const logger = LoggerFactory.create({
|
|
242
|
+
datasources: [fsDatasource],
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
// Para aplicaciones con alto volumen de logs
|
|
246
|
+
for (let i = 0; i < 10000; i++) {
|
|
247
|
+
logger.debug(`Procesando registro ${i}`, {
|
|
248
|
+
userId: i,
|
|
249
|
+
operation: "bulk-process",
|
|
250
|
+
});
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
// Forzar escritura al archivo
|
|
254
|
+
await fsDatasource.flush();
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
## Uso en Frameworks
|
|
258
|
+
|
|
259
|
+
### Integración con Express
|
|
260
|
+
|
|
261
|
+
```typescript
|
|
262
|
+
import express from "express";
|
|
263
|
+
import { createFsDatasource } from "@jmlq/logger-plugin-fs";
|
|
264
|
+
import { LoggerFactory, LogLevel } from "@jmlq/logger";
|
|
265
|
+
|
|
266
|
+
// Configurar datasource para Express
|
|
267
|
+
const fsDatasource = createFsDatasource({
|
|
268
|
+
basePath: "./logs/express",
|
|
269
|
+
fileNamePattern: "express-{yyyy}-{MM}-{dd}.log",
|
|
270
|
+
mkdir: true,
|
|
271
|
+
rotation: {
|
|
272
|
+
by: "day",
|
|
273
|
+
maxFiles: 15,
|
|
274
|
+
},
|
|
275
|
+
serializer: {
|
|
276
|
+
serialize(log: any): string {
|
|
277
|
+
return `${new Date(log.timestamp).toISOString()} [${log.level}] ${
|
|
278
|
+
log.message
|
|
279
|
+
}\n`;
|
|
280
|
+
},
|
|
281
|
+
},
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
const logger = LoggerFactory.create({
|
|
285
|
+
datasources: [fsDatasource],
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
const app = express();
|
|
289
|
+
|
|
290
|
+
// Middleware de logging
|
|
291
|
+
app.use((req, res, next) => {
|
|
292
|
+
logger.info(`${req.method} ${req.path}`, {
|
|
293
|
+
ip: req.ip,
|
|
294
|
+
userAgent: req.get("User-Agent"),
|
|
295
|
+
});
|
|
296
|
+
next();
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
// Manejo de errores con logging
|
|
300
|
+
app.use(
|
|
301
|
+
(
|
|
302
|
+
err: Error,
|
|
303
|
+
req: express.Request,
|
|
304
|
+
res: express.Response,
|
|
305
|
+
next: express.NextFunction
|
|
306
|
+
) => {
|
|
307
|
+
logger.error("Error en aplicación Express", {
|
|
308
|
+
error: err.message,
|
|
309
|
+
stack: err.stack,
|
|
310
|
+
url: req.url,
|
|
311
|
+
method: req.method,
|
|
312
|
+
});
|
|
313
|
+
res.status(500).json({ error: "Internal Server Error" });
|
|
314
|
+
}
|
|
315
|
+
);
|
|
316
|
+
|
|
317
|
+
app.listen(3000, () => {
|
|
318
|
+
logger.info("Servidor Express iniciado en puerto 3000");
|
|
319
|
+
});
|
|
320
|
+
|
|
321
|
+
// Cerrar correctamente el datasource al terminar la aplicación
|
|
322
|
+
process.on("SIGINT", async () => {
|
|
323
|
+
logger.info("Cerrando aplicación...");
|
|
324
|
+
await fsDatasource.flush();
|
|
325
|
+
await fsDatasource.dispose();
|
|
326
|
+
process.exit(0);
|
|
327
|
+
});
|
|
328
|
+
```
|
|
329
|
+
|
|
330
|
+
### Integración con NestJS
|
|
331
|
+
|
|
332
|
+
```typescript
|
|
333
|
+
import { Injectable, Module, OnModuleDestroy } from "@nestjs/common";
|
|
334
|
+
import { createFsDatasource } from "@jmlq/logger-plugin-fs";
|
|
335
|
+
import { LoggerFactory, ILogDatasource } from "@jmlq/logger";
|
|
336
|
+
|
|
337
|
+
@Injectable()
|
|
338
|
+
export class LoggingService implements OnModuleDestroy {
|
|
339
|
+
private readonly fsDatasource: ILogDatasource;
|
|
340
|
+
private readonly logger: any;
|
|
341
|
+
|
|
342
|
+
constructor() {
|
|
343
|
+
this.fsDatasource = createFsDatasource({
|
|
344
|
+
basePath: "./logs/nestjs",
|
|
345
|
+
fileNamePattern: "nest-{yyyy}-{MM}-{dd}.log",
|
|
346
|
+
mkdir: true,
|
|
347
|
+
rotation: {
|
|
348
|
+
by: "day",
|
|
349
|
+
maxFiles: 30,
|
|
350
|
+
},
|
|
351
|
+
serializer: {
|
|
352
|
+
serialize(log: any): string {
|
|
353
|
+
return (
|
|
354
|
+
JSON.stringify({
|
|
355
|
+
timestamp: new Date(log.timestamp).toISOString(),
|
|
356
|
+
level: log.level,
|
|
357
|
+
message: log.message,
|
|
358
|
+
context: log.context || "Application",
|
|
359
|
+
...log.extra,
|
|
360
|
+
}) + "\n"
|
|
361
|
+
);
|
|
362
|
+
},
|
|
363
|
+
},
|
|
364
|
+
onError: async (error: Error) => {
|
|
365
|
+
console.error("[FS Logger Plugin]", error.message);
|
|
366
|
+
},
|
|
367
|
+
});
|
|
368
|
+
|
|
369
|
+
this.logger = LoggerFactory.create({
|
|
370
|
+
datasources: [this.fsDatasource],
|
|
371
|
+
});
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
log(message: string, context?: string, extra?: any) {
|
|
375
|
+
this.logger.info(message, { context, ...extra });
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
error(message: string, trace?: string, context?: string, extra?: any) {
|
|
379
|
+
this.logger.error(message, { trace, context, ...extra });
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
warn(message: string, context?: string, extra?: any) {
|
|
383
|
+
this.logger.warn(message, { context, ...extra });
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
debug(message: string, context?: string, extra?: any) {
|
|
387
|
+
this.logger.debug(message, { context, ...extra });
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
async onModuleDestroy() {
|
|
391
|
+
await this.fsDatasource.flush();
|
|
392
|
+
await this.fsDatasource.dispose();
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
@Module({
|
|
397
|
+
providers: [LoggingService],
|
|
398
|
+
exports: [LoggingService],
|
|
399
|
+
})
|
|
400
|
+
export class LoggingModule {}
|
|
401
|
+
```
|
|
402
|
+
|
|
403
|
+
## Variables de Entorno
|
|
404
|
+
|
|
405
|
+
El plugin no maneja variables de entorno directamente. Para usarlas, mapéalas manualmente en la configuración:
|
|
406
|
+
|
|
407
|
+
```typescript
|
|
408
|
+
import { createFsDatasource } from "@jmlq/logger-plugin-fs";
|
|
409
|
+
|
|
410
|
+
const fsDatasource = createFsDatasource({
|
|
411
|
+
basePath: process.env.LOG_DIR || "./logs",
|
|
412
|
+
mkdir: process.env.NODE_ENV !== "development",
|
|
413
|
+
fileNamePattern: process.env.LOG_FILE_PATTERN || "app-{yyyy}-{MM}-{dd}.log",
|
|
414
|
+
rotation: {
|
|
415
|
+
by: (process.env.LOG_ROTATION_TYPE as any) || "day",
|
|
416
|
+
maxSizeMB: process.env.LOG_MAX_SIZE_MB
|
|
417
|
+
? parseInt(process.env.LOG_MAX_SIZE_MB)
|
|
418
|
+
: undefined,
|
|
419
|
+
maxFiles: process.env.LOG_MAX_FILES
|
|
420
|
+
? parseInt(process.env.LOG_MAX_FILES)
|
|
421
|
+
: 7,
|
|
422
|
+
},
|
|
423
|
+
});
|
|
424
|
+
```
|
|
425
|
+
|
|
426
|
+
Ejemplo de archivo `.env`:
|
|
427
|
+
|
|
428
|
+
```env
|
|
429
|
+
LOG_DIR=./logs/production
|
|
430
|
+
LOG_FILE_PATTERN=myapp-{yyyy}-{MM}-{dd}.log
|
|
431
|
+
LOG_ROTATION_TYPE=day
|
|
432
|
+
LOG_MAX_FILES=30
|
|
433
|
+
# Para rotación por tamaño:
|
|
434
|
+
# LOG_ROTATION_TYPE=size
|
|
435
|
+
# LOG_MAX_SIZE_MB=100
|
|
436
|
+
```
|
|
437
|
+
|
|
438
|
+
## Consideraciones Técnicas
|
|
439
|
+
|
|
440
|
+
### Permisos del Sistema de Archivos
|
|
441
|
+
|
|
442
|
+
- Asegúrate de que el proceso Node.js tenga permisos de escritura en el directorio `basePath`.
|
|
443
|
+
- Para producción, considera usar directorios como `/var/log/tu-app` en sistemas Unix.
|
|
444
|
+
- En contenedores Docker, monta volúmenes para persistir los logs:
|
|
445
|
+
|
|
446
|
+
```dockerfile
|
|
447
|
+
VOLUME ["/app/logs"]
|
|
448
|
+
```
|
|
449
|
+
|
|
450
|
+
### Gestión de Recursos
|
|
451
|
+
|
|
452
|
+
#### Flush Explícito
|
|
453
|
+
|
|
454
|
+
```typescript
|
|
455
|
+
// Forzar escritura de buffers pendientes
|
|
456
|
+
await datasource.flush();
|
|
457
|
+
```
|
|
458
|
+
|
|
459
|
+
#### Cierre Correcto
|
|
460
|
+
|
|
461
|
+
```typescript
|
|
462
|
+
// Cerrar streams y liberar recursos
|
|
463
|
+
await datasource.dispose();
|
|
464
|
+
```
|
|
465
|
+
|
|
466
|
+
#### Manejo de Shutdown Graceful
|
|
467
|
+
|
|
468
|
+
```typescript
|
|
469
|
+
process.on("SIGTERM", async () => {
|
|
470
|
+
await datasource.flush();
|
|
471
|
+
await datasource.dispose();
|
|
472
|
+
process.exit(0);
|
|
473
|
+
});
|
|
474
|
+
```
|
|
475
|
+
|
|
476
|
+
### Rendimiento y Recomendaciones
|
|
477
|
+
|
|
478
|
+
- **Rotación por tamaño**: Más eficiente para aplicaciones con volumen variable de logs.
|
|
479
|
+
- **Rotación diaria**: Mejor para aplicaciones con logs predecibles y análisis temporal.
|
|
480
|
+
- **Serializer personalizado**: Optimiza el formato según tus necesidades de análisis posterior.
|
|
481
|
+
- **Callbacks `onRotate`**: Úsalos para automatizar tareas como compresión o envío a servicios externos.
|
|
482
|
+
- **Callback `onError`**: Implementa logging secundario o métricas de monitoreo.
|
|
483
|
+
|
|
484
|
+
### Limitaciones Conocidas
|
|
485
|
+
|
|
486
|
+
- El plugin funciona únicamente con Node.js (no browser).
|
|
487
|
+
- La rotación por tamaño requiere especificar `maxSizeMB` obligatoriamente.
|
|
488
|
+
- Los placeholders en `fileNamePattern` están limitados a: `{yyyy}`, `{MM}`, `{dd}`.
|
|
489
|
+
- La rotación se basa en la fecha del sistema local.
|
|
490
|
+
|
|
491
|
+
## Troubleshooting
|
|
492
|
+
|
|
493
|
+
### Error: "maxSizeMB must be positive for size rotation"
|
|
494
|
+
|
|
495
|
+
Especifica `maxSizeMB` mayor a 0 cuando uses `rotation.by = "size"`:
|
|
496
|
+
|
|
497
|
+
```typescript
|
|
498
|
+
rotation: {
|
|
499
|
+
by: 'size',
|
|
500
|
+
maxSizeMB: 10 // Obligatorio y > 0
|
|
501
|
+
}
|
|
502
|
+
```
|
|
503
|
+
|
|
504
|
+
### Error: Permisos insuficientes
|
|
505
|
+
|
|
506
|
+
Verifica permisos del directorio:
|
|
507
|
+
|
|
508
|
+
```bash
|
|
509
|
+
chmod 755 /ruta/a/logs
|
|
510
|
+
chown node:node /ruta/a/logs
|
|
511
|
+
```
|
|
512
|
+
|
|
513
|
+
### Logs no aparecen inmediatamente
|
|
514
|
+
|
|
515
|
+
Llama `flush()` para forzar escritura:
|
|
516
|
+
|
|
517
|
+
```typescript
|
|
518
|
+
logger.info("Mensaje importante");
|
|
519
|
+
await datasource.flush(); // Fuerza escritura inmediata
|
|
520
|
+
```
|