@richardmcquiston01/error-log-adapter 0.10.0 → 0.11.0
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 +2 -2
- package/dist/index.cjs +88 -60
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +10 -8
- package/dist/index.d.ts +10 -8
- package/dist/index.mjs +88 -60
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.d.cts
CHANGED
|
@@ -466,13 +466,6 @@ declare class FlatFileErrorAdapter<TCode extends string = string> implements Err
|
|
|
466
466
|
/** Uses the injected file system, or loads `node:fs/promises` on first use. */
|
|
467
467
|
private getFileSystem;
|
|
468
468
|
private prepareDirectory;
|
|
469
|
-
/**
|
|
470
|
-
* Moves `app.log` to `app.log.1` (shifting older files up, dropping the
|
|
471
|
-
* oldest) when the next line would push it past `maxBytes`. A failed rotation
|
|
472
|
-
* is reported, but the entry is still written so it is not lost.
|
|
473
|
-
*/
|
|
474
|
-
private rotateIfNeeded;
|
|
475
|
-
private renameIfPresent;
|
|
476
469
|
}
|
|
477
470
|
|
|
478
471
|
/** A stored record: the serialized entry plus its generated UUID. */
|
|
@@ -488,6 +481,13 @@ interface JsonFileErrorOptions {
|
|
|
488
481
|
mode?: 'ndjson' | 'array';
|
|
489
482
|
/** Array mode only: most entries to keep. The oldest are dropped first. */
|
|
490
483
|
maxEntries?: number;
|
|
484
|
+
/**
|
|
485
|
+
* NDJSON mode only: rotate the file before it would grow past this many
|
|
486
|
+
* bytes, moving it to `<path>.1` (older files shift up). Off by default.
|
|
487
|
+
*/
|
|
488
|
+
maxBytes?: number;
|
|
489
|
+
/** NDJSON mode only: rotated files to keep (`errors.ndjson.1` is newest). Defaults to `3`. Needs `maxBytes`. */
|
|
490
|
+
maxFiles?: number;
|
|
491
491
|
/** Array mode only: spaces of indentation. Defaults to `2`; `0` is compact. */
|
|
492
492
|
indent?: number;
|
|
493
493
|
/** File system to write to. Defaults to `node:fs/promises`, loaded on first use. */
|
|
@@ -506,13 +506,15 @@ declare class JsonFileErrorAdapter<TCode extends string = string> implements Err
|
|
|
506
506
|
private readonly filePath;
|
|
507
507
|
private readonly mode;
|
|
508
508
|
private readonly maxEntries;
|
|
509
|
+
private readonly maxBytes;
|
|
510
|
+
private readonly maxFiles;
|
|
509
511
|
private readonly indent;
|
|
510
512
|
private readonly createDirectory;
|
|
511
513
|
private readonly prefix;
|
|
512
514
|
private fs;
|
|
513
515
|
private directoryReady;
|
|
514
516
|
private readonly queue;
|
|
515
|
-
/** @throws {RangeError} if `maxEntries` or `indent` is invalid, or
|
|
517
|
+
/** @throws {RangeError} if `maxEntries`, `maxBytes`, `maxFiles` or `indent` is invalid, or an option is used in the wrong mode. */
|
|
516
518
|
constructor(filePath: string, options?: JsonFileErrorOptions);
|
|
517
519
|
log(entry: ErrorLogEntry<TCode>): Promise<ErrorLogResult>;
|
|
518
520
|
private write;
|
package/dist/index.d.ts
CHANGED
|
@@ -466,13 +466,6 @@ declare class FlatFileErrorAdapter<TCode extends string = string> implements Err
|
|
|
466
466
|
/** Uses the injected file system, or loads `node:fs/promises` on first use. */
|
|
467
467
|
private getFileSystem;
|
|
468
468
|
private prepareDirectory;
|
|
469
|
-
/**
|
|
470
|
-
* Moves `app.log` to `app.log.1` (shifting older files up, dropping the
|
|
471
|
-
* oldest) when the next line would push it past `maxBytes`. A failed rotation
|
|
472
|
-
* is reported, but the entry is still written so it is not lost.
|
|
473
|
-
*/
|
|
474
|
-
private rotateIfNeeded;
|
|
475
|
-
private renameIfPresent;
|
|
476
469
|
}
|
|
477
470
|
|
|
478
471
|
/** A stored record: the serialized entry plus its generated UUID. */
|
|
@@ -488,6 +481,13 @@ interface JsonFileErrorOptions {
|
|
|
488
481
|
mode?: 'ndjson' | 'array';
|
|
489
482
|
/** Array mode only: most entries to keep. The oldest are dropped first. */
|
|
490
483
|
maxEntries?: number;
|
|
484
|
+
/**
|
|
485
|
+
* NDJSON mode only: rotate the file before it would grow past this many
|
|
486
|
+
* bytes, moving it to `<path>.1` (older files shift up). Off by default.
|
|
487
|
+
*/
|
|
488
|
+
maxBytes?: number;
|
|
489
|
+
/** NDJSON mode only: rotated files to keep (`errors.ndjson.1` is newest). Defaults to `3`. Needs `maxBytes`. */
|
|
490
|
+
maxFiles?: number;
|
|
491
491
|
/** Array mode only: spaces of indentation. Defaults to `2`; `0` is compact. */
|
|
492
492
|
indent?: number;
|
|
493
493
|
/** File system to write to. Defaults to `node:fs/promises`, loaded on first use. */
|
|
@@ -506,13 +506,15 @@ declare class JsonFileErrorAdapter<TCode extends string = string> implements Err
|
|
|
506
506
|
private readonly filePath;
|
|
507
507
|
private readonly mode;
|
|
508
508
|
private readonly maxEntries;
|
|
509
|
+
private readonly maxBytes;
|
|
510
|
+
private readonly maxFiles;
|
|
509
511
|
private readonly indent;
|
|
510
512
|
private readonly createDirectory;
|
|
511
513
|
private readonly prefix;
|
|
512
514
|
private fs;
|
|
513
515
|
private directoryReady;
|
|
514
516
|
private readonly queue;
|
|
515
|
-
/** @throws {RangeError} if `maxEntries` or `indent` is invalid, or
|
|
517
|
+
/** @throws {RangeError} if `maxEntries`, `maxBytes`, `maxFiles` or `indent` is invalid, or an option is used in the wrong mode. */
|
|
516
518
|
constructor(filePath: string, options?: JsonFileErrorOptions);
|
|
517
519
|
log(entry: ErrorLogEntry<TCode>): Promise<ErrorLogResult>;
|
|
518
520
|
private write;
|
package/dist/index.mjs
CHANGED
|
@@ -920,8 +920,55 @@ function isMissingFileError(error) {
|
|
|
920
920
|
return typeof error === "object" && error !== null && error.code === "ENOENT";
|
|
921
921
|
}
|
|
922
922
|
|
|
923
|
-
// src/
|
|
923
|
+
// src/file-rotation.ts
|
|
924
924
|
var DEFAULT_MAX_FILES = 3;
|
|
925
|
+
function assertPositiveInteger(adapterName, value, name) {
|
|
926
|
+
if (!Number.isInteger(value) || value < 1) {
|
|
927
|
+
throw new RangeError(
|
|
928
|
+
`${adapterName}: ${name} must be a positive integer, received ${String(value)}.`
|
|
929
|
+
);
|
|
930
|
+
}
|
|
931
|
+
}
|
|
932
|
+
async function renameIfPresent(fs, from, to) {
|
|
933
|
+
try {
|
|
934
|
+
await fs.rename(from, to);
|
|
935
|
+
} catch (error) {
|
|
936
|
+
if (!isMissingFileError(error)) {
|
|
937
|
+
throw error;
|
|
938
|
+
}
|
|
939
|
+
}
|
|
940
|
+
}
|
|
941
|
+
async function rotateIfNeeded(fs, filePath, writeBytes, settings, report) {
|
|
942
|
+
try {
|
|
943
|
+
let currentSize;
|
|
944
|
+
try {
|
|
945
|
+
currentSize = (await fs.stat(filePath)).size;
|
|
946
|
+
} catch (error) {
|
|
947
|
+
if (isMissingFileError(error)) {
|
|
948
|
+
return;
|
|
949
|
+
}
|
|
950
|
+
throw error;
|
|
951
|
+
}
|
|
952
|
+
if (currentSize === 0 || currentSize + writeBytes <= settings.maxBytes) {
|
|
953
|
+
return;
|
|
954
|
+
}
|
|
955
|
+
await fs.rm(`${filePath}.${settings.maxFiles}`, { force: true });
|
|
956
|
+
for (let index = settings.maxFiles - 1; index >= 1; index -= 1) {
|
|
957
|
+
await renameIfPresent(
|
|
958
|
+
fs,
|
|
959
|
+
`${filePath}.${index}`,
|
|
960
|
+
`${filePath}.${index + 1}`
|
|
961
|
+
);
|
|
962
|
+
}
|
|
963
|
+
await fs.rename(filePath, `${filePath}.1`);
|
|
964
|
+
} catch (error) {
|
|
965
|
+
console.error(
|
|
966
|
+
`${report.prefix} ${report.adapterName}: could not rotate "${filePath}", so it will keep growing: ${describeError(error)}`
|
|
967
|
+
);
|
|
968
|
+
}
|
|
969
|
+
}
|
|
970
|
+
|
|
971
|
+
// src/adapters/flat-file-error-adapter.ts
|
|
925
972
|
function escapeLineBreaks(text) {
|
|
926
973
|
return text.replace(/\r?\n/g, "\\n");
|
|
927
974
|
}
|
|
@@ -948,13 +995,6 @@ function formatErrorLogLine(entry) {
|
|
|
948
995
|
`${entry.timestamp} [${entry.code}] ${source}${entry.message}${cause}`
|
|
949
996
|
);
|
|
950
997
|
}
|
|
951
|
-
function assertPositiveInteger(value, name) {
|
|
952
|
-
if (!Number.isInteger(value) || value < 1) {
|
|
953
|
-
throw new RangeError(
|
|
954
|
-
`FlatFileErrorAdapter: ${name} must be a positive integer, received ${String(value)}.`
|
|
955
|
-
);
|
|
956
|
-
}
|
|
957
|
-
}
|
|
958
998
|
var FlatFileErrorAdapter = class {
|
|
959
999
|
/** @throws {RangeError} if `maxBytes` or `maxFiles` is not a positive integer. */
|
|
960
1000
|
constructor(filePath, options = {}) {
|
|
@@ -968,9 +1008,9 @@ var FlatFileErrorAdapter = class {
|
|
|
968
1008
|
this.createDirectory = options.createDirectory ?? true;
|
|
969
1009
|
this.prefix = options.prefix ?? DEFAULT_PREFIX;
|
|
970
1010
|
if (this.maxBytes !== void 0) {
|
|
971
|
-
assertPositiveInteger(this.maxBytes, "maxBytes");
|
|
1011
|
+
assertPositiveInteger("FlatFileErrorAdapter", this.maxBytes, "maxBytes");
|
|
972
1012
|
}
|
|
973
|
-
assertPositiveInteger(this.maxFiles, "maxFiles");
|
|
1013
|
+
assertPositiveInteger("FlatFileErrorAdapter", this.maxFiles, "maxFiles");
|
|
974
1014
|
}
|
|
975
1015
|
log(entry) {
|
|
976
1016
|
return this.queue.run(() => this.write(entry));
|
|
@@ -982,7 +1022,15 @@ var FlatFileErrorAdapter = class {
|
|
|
982
1022
|
const line = formatted.endsWith("\n") ? formatted : `${formatted}
|
|
983
1023
|
`;
|
|
984
1024
|
await this.prepareDirectory(fs);
|
|
985
|
-
|
|
1025
|
+
if (this.maxBytes !== void 0) {
|
|
1026
|
+
await rotateIfNeeded(
|
|
1027
|
+
fs,
|
|
1028
|
+
this.filePath,
|
|
1029
|
+
new TextEncoder().encode(line).length,
|
|
1030
|
+
{ maxBytes: this.maxBytes, maxFiles: this.maxFiles },
|
|
1031
|
+
{ adapterName: "FlatFileErrorAdapter", prefix: this.prefix }
|
|
1032
|
+
);
|
|
1033
|
+
}
|
|
986
1034
|
await fs.appendFile(this.filePath, line);
|
|
987
1035
|
return { ok: true };
|
|
988
1036
|
} catch (error) {
|
|
@@ -1011,64 +1059,20 @@ var FlatFileErrorAdapter = class {
|
|
|
1011
1059
|
}
|
|
1012
1060
|
this.directoryReady = true;
|
|
1013
1061
|
}
|
|
1014
|
-
/**
|
|
1015
|
-
* Moves `app.log` to `app.log.1` (shifting older files up, dropping the
|
|
1016
|
-
* oldest) when the next line would push it past `maxBytes`. A failed rotation
|
|
1017
|
-
* is reported, but the entry is still written so it is not lost.
|
|
1018
|
-
*/
|
|
1019
|
-
async rotateIfNeeded(fs, lineBytes) {
|
|
1020
|
-
if (this.maxBytes === void 0) {
|
|
1021
|
-
return;
|
|
1022
|
-
}
|
|
1023
|
-
try {
|
|
1024
|
-
let currentSize;
|
|
1025
|
-
try {
|
|
1026
|
-
currentSize = (await fs.stat(this.filePath)).size;
|
|
1027
|
-
} catch (error) {
|
|
1028
|
-
if (isMissingFileError(error)) {
|
|
1029
|
-
return;
|
|
1030
|
-
}
|
|
1031
|
-
throw error;
|
|
1032
|
-
}
|
|
1033
|
-
if (currentSize === 0 || currentSize + lineBytes <= this.maxBytes) {
|
|
1034
|
-
return;
|
|
1035
|
-
}
|
|
1036
|
-
await fs.rm(`${this.filePath}.${this.maxFiles}`, { force: true });
|
|
1037
|
-
for (let index = this.maxFiles - 1; index >= 1; index -= 1) {
|
|
1038
|
-
await this.renameIfPresent(
|
|
1039
|
-
fs,
|
|
1040
|
-
`${this.filePath}.${index}`,
|
|
1041
|
-
`${this.filePath}.${index + 1}`
|
|
1042
|
-
);
|
|
1043
|
-
}
|
|
1044
|
-
await fs.rename(this.filePath, `${this.filePath}.1`);
|
|
1045
|
-
} catch (error) {
|
|
1046
|
-
console.error(
|
|
1047
|
-
`${this.prefix} FlatFileErrorAdapter: could not rotate "${this.filePath}", so it will keep growing: ${describeError(error)}`
|
|
1048
|
-
);
|
|
1049
|
-
}
|
|
1050
|
-
}
|
|
1051
|
-
async renameIfPresent(fs, from, to) {
|
|
1052
|
-
try {
|
|
1053
|
-
await fs.rename(from, to);
|
|
1054
|
-
} catch (error) {
|
|
1055
|
-
if (!isMissingFileError(error)) {
|
|
1056
|
-
throw error;
|
|
1057
|
-
}
|
|
1058
|
-
}
|
|
1059
|
-
}
|
|
1060
1062
|
};
|
|
1061
1063
|
|
|
1062
1064
|
// src/adapters/json-file-error-adapter.ts
|
|
1063
1065
|
var DEFAULT_INDENT = 2;
|
|
1064
1066
|
var JsonFileErrorAdapter = class {
|
|
1065
|
-
/** @throws {RangeError} if `maxEntries` or `indent` is invalid, or
|
|
1067
|
+
/** @throws {RangeError} if `maxEntries`, `maxBytes`, `maxFiles` or `indent` is invalid, or an option is used in the wrong mode. */
|
|
1066
1068
|
constructor(filePath, options = {}) {
|
|
1067
1069
|
this.filePath = filePath;
|
|
1068
1070
|
this.directoryReady = false;
|
|
1069
1071
|
this.queue = new SerialQueue();
|
|
1070
1072
|
this.mode = options.mode ?? "ndjson";
|
|
1071
1073
|
this.maxEntries = options.maxEntries;
|
|
1074
|
+
this.maxBytes = options.maxBytes;
|
|
1075
|
+
this.maxFiles = options.maxFiles ?? DEFAULT_MAX_FILES;
|
|
1072
1076
|
this.indent = options.indent ?? DEFAULT_INDENT;
|
|
1073
1077
|
this.fs = options.fs;
|
|
1074
1078
|
this.createDirectory = options.createDirectory ?? true;
|
|
@@ -1085,6 +1089,20 @@ var JsonFileErrorAdapter = class {
|
|
|
1085
1089
|
);
|
|
1086
1090
|
}
|
|
1087
1091
|
}
|
|
1092
|
+
if (this.maxBytes !== void 0 || options.maxFiles !== void 0) {
|
|
1093
|
+
if (this.mode !== "ndjson") {
|
|
1094
|
+
throw new RangeError(
|
|
1095
|
+
`JsonFileErrorAdapter: maxBytes and maxFiles only apply to mode "ndjson", but mode is "${this.mode}". Use mode "ndjson", or use maxEntries to limit array mode.`
|
|
1096
|
+
);
|
|
1097
|
+
}
|
|
1098
|
+
if (this.maxBytes === void 0) {
|
|
1099
|
+
throw new RangeError(
|
|
1100
|
+
"JsonFileErrorAdapter: maxFiles needs maxBytes; set maxBytes to turn rotation on."
|
|
1101
|
+
);
|
|
1102
|
+
}
|
|
1103
|
+
assertPositiveInteger("JsonFileErrorAdapter", this.maxBytes, "maxBytes");
|
|
1104
|
+
assertPositiveInteger("JsonFileErrorAdapter", this.maxFiles, "maxFiles");
|
|
1105
|
+
}
|
|
1088
1106
|
if (!Number.isInteger(this.indent) || this.indent < 0) {
|
|
1089
1107
|
throw new RangeError(
|
|
1090
1108
|
`JsonFileErrorAdapter: indent must be a non-negative integer, received ${String(this.indent)}.`
|
|
@@ -1103,8 +1121,18 @@ var JsonFileErrorAdapter = class {
|
|
|
1103
1121
|
const fs = await this.getFileSystem();
|
|
1104
1122
|
await this.prepareDirectory(fs);
|
|
1105
1123
|
if (this.mode === "ndjson") {
|
|
1106
|
-
|
|
1107
|
-
|
|
1124
|
+
const line = `${JSON.stringify(record)}
|
|
1125
|
+
`;
|
|
1126
|
+
if (this.maxBytes !== void 0) {
|
|
1127
|
+
await rotateIfNeeded(
|
|
1128
|
+
fs,
|
|
1129
|
+
this.filePath,
|
|
1130
|
+
new TextEncoder().encode(line).length,
|
|
1131
|
+
{ maxBytes: this.maxBytes, maxFiles: this.maxFiles },
|
|
1132
|
+
{ adapterName: "JsonFileErrorAdapter", prefix: this.prefix }
|
|
1133
|
+
);
|
|
1134
|
+
}
|
|
1135
|
+
await fs.appendFile(this.filePath, line);
|
|
1108
1136
|
} else {
|
|
1109
1137
|
await this.rewriteArray(fs, record);
|
|
1110
1138
|
}
|