@warlock.js/logger 5.29.0 → 5.31.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/CHANGELOG.md +48 -2
- package/README.md +14 -17
- package/cjs/index.cjs +569 -57
- package/cjs/index.cjs.map +1 -1
- package/esm/channels/console-log.d.mts +1 -1
- package/esm/channels/console-log.d.mts.map +1 -1
- package/esm/channels/console-log.mjs +1 -1
- package/esm/channels/console-log.mjs.map +1 -1
- package/esm/channels/file-log.d.mts +129 -13
- package/esm/channels/file-log.d.mts.map +1 -1
- package/esm/channels/file-log.mjs +204 -23
- package/esm/channels/file-log.mjs.map +1 -1
- package/esm/channels/file-name-pattern.d.mts +16 -0
- package/esm/channels/file-name-pattern.d.mts.map +1 -0
- package/esm/channels/file-name-pattern.mjs +41 -0
- package/esm/channels/file-name-pattern.mjs.map +1 -0
- package/esm/channels/index.mjs +1 -1
- package/esm/channels/json-file-log.d.mts +1 -1
- package/esm/channels/json-file-log.d.mts.map +1 -1
- package/esm/channels/json-file-log.mjs +25 -6
- package/esm/channels/json-file-log.mjs.map +1 -1
- package/esm/channels/sentry-log.d.mts +1 -1
- package/esm/channels/sentry-log.d.mts.map +1 -1
- package/esm/channels/sentry-log.mjs +1 -1
- package/esm/channels/sentry-log.mjs.map +1 -1
- package/esm/index.d.mts +8 -5
- package/esm/index.mjs +5 -3
- package/esm/log-channel.d.mts +24 -2
- package/esm/log-channel.d.mts.map +1 -1
- package/esm/log-channel.mjs +23 -1
- package/esm/log-channel.mjs.map +1 -1
- package/esm/logger.d.mts +78 -18
- package/esm/logger.d.mts.map +1 -1
- package/esm/logger.mjs +163 -22
- package/esm/logger.mjs.map +1 -1
- package/esm/redact/default-keys.d.mts +1 -1
- package/esm/redact/default-keys.d.mts.map +1 -1
- package/esm/redact/default-keys.mjs +1 -1
- package/esm/redact/default-keys.mjs.map +1 -1
- package/esm/redact/escape-log-value.d.mts +26 -0
- package/esm/redact/escape-log-value.d.mts.map +1 -0
- package/esm/redact/escape-log-value.mjs +37 -0
- package/esm/redact/escape-log-value.mjs.map +1 -0
- package/esm/redact/index.mjs +2 -0
- package/esm/redact/mask-email.d.mts +40 -0
- package/esm/redact/mask-email.d.mts.map +1 -0
- package/esm/redact/mask-email.mjs +70 -0
- package/esm/redact/mask-email.mjs.map +1 -0
- package/esm/redact/redact.d.mts +1 -1
- package/esm/redact/redact.d.mts.map +1 -1
- package/esm/redact/redact.mjs +1 -1
- package/esm/redact/redact.mjs.map +1 -1
- package/esm/types.d.mts +10 -2
- package/esm/types.d.mts.map +1 -1
- package/esm/utils/capture-unhandled-errors.d.mts +1 -1
- package/esm/utils/capture-unhandled-errors.d.mts.map +1 -1
- package/esm/utils/capture-unhandled-errors.mjs +1 -1
- package/esm/utils/capture-unhandled-errors.mjs.map +1 -1
- package/esm/utils/clear-message.d.mts +1 -1
- package/esm/utils/clear-message.d.mts.map +1 -1
- package/esm/utils/clear-message.mjs +1 -1
- package/esm/utils/clear-message.mjs.map +1 -1
- package/esm/utils/safe-json-stringify.d.mts +1 -1
- package/esm/utils/safe-json-stringify.d.mts.map +1 -1
- package/esm/utils/safe-json-stringify.mjs +1 -1
- package/esm/utils/safe-json-stringify.mjs.map +1 -1
- package/llms-full.txt +247 -147
- package/llms.txt +2 -2
- package/package.json +4 -3
- package/skills/capture-unhandled-errors/SKILL.md +9 -10
- package/skills/configure-logger/SKILL.md +7 -10
- package/skills/filter-log-entries/SKILL.md +2 -2
- package/skills/flush-logs-on-shutdown/SKILL.md +34 -15
- package/skills/index.md +4 -1
- package/skills/logger-basics/SKILL.md +21 -24
- package/skills/overview/SKILL.md +17 -6
- package/skills/pick-log-channel/SKILL.md +36 -24
- package/skills/redact-sensitive-log-fields/SKILL.md +48 -18
- package/skills/ship-logs-to-sentry/SKILL.md +18 -18
- package/skills/test-logging-code/SKILL.md +4 -2
- package/skills/write-custom-log-channel/SKILL.md +51 -18
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { BasicLogConfigurations, LoggingData } from "../types.mjs";
|
|
2
2
|
import { LogChannel } from "../log-channel.mjs";
|
|
3
3
|
|
|
4
|
-
//#region
|
|
4
|
+
//#region ../../packages/logger/src/channels/console-log.d.ts
|
|
5
5
|
type ConsoleLogConfig = BasicLogConfigurations & {
|
|
6
6
|
/**
|
|
7
7
|
* Render the log entry's `context` object on a second line after the main
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"console-log.d.mts","names":[],"sources":["
|
|
1
|
+
{"version":3,"file":"console-log.d.mts","names":[],"sources":["../../../../../../../../packages/logger/src/channels/console-log.ts"],"mappings":";;;;KAKY,gBAAA,GAAmB,sBAAsB;;AAArD;;;;;;;;EAUE,WAAA;EA6EW;;;;;;EAtEX,YAAA;AAAA;AAAA,cAsEW,UAAA,SAAmB,UAAA,CAAW,gBAAA;EAAA;;;EAIlC,IAAA;EAUU;;;EALV,QAAA;;;;EAKA,GAAA,CAAI,IAAA,EAAM,WAAA;AAAA"}
|
|
@@ -2,7 +2,7 @@ import { LogChannel } from "../log-channel.mjs";
|
|
|
2
2
|
import { colors } from "@mongez/copper";
|
|
3
3
|
import { inspect } from "util";
|
|
4
4
|
|
|
5
|
-
//#region
|
|
5
|
+
//#region ../../packages/logger/src/channels/console-log.ts
|
|
6
6
|
/**
|
|
7
7
|
* The longest level name (`success`) sets the tag column width. Padding every
|
|
8
8
|
* level name to this width keeps the timestamp / module / action columns
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"console-log.mjs","names":[],"sources":["
|
|
1
|
+
{"version":3,"file":"console-log.mjs","names":[],"sources":["../../../../../../../../packages/logger/src/channels/console-log.ts"],"sourcesContent":["import { colors } from \"@mongez/copper\";\nimport { inspect } from \"util\";\nimport { LogChannel } from \"../log-channel\";\nimport type { BasicLogConfigurations, LoggingData, LogLevel } from \"../types\";\n\nexport type ConsoleLogConfig = BasicLogConfigurations & {\n /**\n * Render the log entry's `context` object on a second line after the main\n * message. When `false`, context is silently dropped (the historical\n * behavior). When `true`, contexts are pretty-printed with `util.inspect`\n * — colored, depth-limited, ideal for development. Persistent channels\n * (`FileLog`, `JSONFileLog`) always retain context regardless of this flag.\n *\n * @default false\n */\n showContext?: boolean;\n /**\n * Depth passed to `util.inspect` when rendering context. Only applies when\n * `showContext` is enabled.\n *\n * @default 4\n */\n contextDepth?: number;\n};\n\n/**\n * The longest level name (`success`) sets the tag column width. Padding every\n * level name to this width keeps the timestamp / module / action columns\n * aligned across lines. The level icons are bare (text-style, single-cell)\n * glyphs, so padding the name alone produces true columns.\n */\nconst LEVEL_NAME_WIDTH = 7;\n\n/**\n * Per-level console presentation: the colored, fixed-width `{icon} {name}` tag\n * and the function that colors the message body.\n */\ntype LevelStyle = {\n tag: string;\n message: (message: LoggingData[\"message\"]) => string;\n};\n\n/**\n * Build a single-color, fixed-width tag — `{icon} {name}` with the name padded\n * so every tag spans the same number of columns.\n */\nfunction buildTag(icon: string, name: string, color: (text: string) => string): string {\n return color(`${icon} ${name.padEnd(LEVEL_NAME_WIDTH)}`);\n}\n\n/**\n * Console styling per log level. The `fatal` tag is a bright-red background\n * badge — the same column width as the others (`\" ☠ fatal \"` is 9 cells, like\n * `{icon} {name.padEnd(7)}`), but impossible to miss in a wall of red `error`\n * lines, where a plain red tag would blend in.\n */\nconst LEVEL_STYLES: Record<LogLevel, LevelStyle> = {\n debug: {\n tag: buildTag(\"⚙\", \"debug\", colors.magentaBright),\n message: colors.magentaBright,\n },\n info: {\n tag: buildTag(\"ℹ\", \"info\", colors.blueBright),\n message: colors.blueBright,\n },\n warn: {\n tag: buildTag(\"⚠\", \"warn\", colors.yellow),\n message: colors.yellowBright,\n },\n error: {\n tag: buildTag(\"✗\", \"error\", colors.red),\n message: colors.redBright,\n },\n success: {\n tag: buildTag(\"✓\", \"success\", colors.green),\n message: colors.greenBright,\n },\n fatal: {\n tag: colors.bgRedBright(colors.whiteBright(colors.bold(\" ☠ fatal \"))),\n message: colors.redBright.bold,\n },\n};\n\n/**\n * Fallback for an unrecognized level — a plain `[log]` tag padded to the same\n * width so the columns stay aligned.\n */\nconst DEFAULT_STYLE: LevelStyle = {\n tag: \"[log]\".padEnd(LEVEL_NAME_WIDTH + 2),\n message: (message) => message as string,\n};\n\nexport class ConsoleLog extends LogChannel<ConsoleLogConfig> {\n /**\n * {@inheritdoc}\n */\n public name = \"console\";\n\n /**\n * Determine if channel is logging in terminal\n */\n public terminal = true;\n\n /**\n * {@inheritdoc}\n */\n public log(data: LoggingData) {\n const { module, action, message, type: level } = data;\n\n if (!this.shouldBeLogged(data)) return;\n\n // Console shows time-only (HH:mm:ss.SSS) — within a dev session the date\n // rarely changes, so the full ISO date + `T`/`Z` are just noise. Persistent\n // channels (FileLog / JSONFileLog) keep the full ISO timestamp.\n const time = new Date().toISOString().slice(11, 23); // i.e. \"10:22:00.000\"\n\n const style = LEVEL_STYLES[level] ?? DEFAULT_STYLE;\n\n // The slate timestamp recedes so the colored level + message lead the eye;\n // module (cyan) and action (magenta) stay colored for subsystem scanning.\n // `slate` (256-color neutral gray) reads cleaner than bright-black `gray`,\n // which sits too close to the background on most terminal themes.\n console.log(\n style.tag,\n colors.slate(`(${time})`),\n colors.cyan(`[${module}]`),\n colors.magenta(`[${action}]`),\n style.message(message),\n );\n\n if (typeof message === \"object\") {\n console.log(message);\n }\n\n // Render context on a second line when explicitly enabled. We only\n // attempt rendering if there's anything meaningful to show — empty\n // objects clutter the terminal without adding signal.\n if (this.config(\"showContext\") && data.context && Object.keys(data.context).length > 0) {\n const depth = this.config(\"contextDepth\") ?? 4;\n console.log(\n colors.slate(\" ↳\"),\n inspect(data.context, { colors: true, depth, breakLength: 80 }),\n );\n }\n }\n}\n"],"mappings":";;;;;;;;;;;AA+BA,MAAM,mBAAmB;;;;;AAezB,SAAS,SAAS,MAAc,MAAc,OAAyC;CACrF,OAAO,MAAM,GAAG,KAAK,GAAG,KAAK,OAAO,gBAAgB,GAAG;AACzD;;;;;;;AAQA,MAAM,eAA6C;CACjD,OAAO;EACL,KAAK,SAAS,KAAK,SAAS,OAAO,aAAa;EAChD,SAAS,OAAO;CAClB;CACA,MAAM;EACJ,KAAK,SAAS,KAAK,QAAQ,OAAO,UAAU;EAC5C,SAAS,OAAO;CAClB;CACA,MAAM;EACJ,KAAK,SAAS,KAAK,QAAQ,OAAO,MAAM;EACxC,SAAS,OAAO;CAClB;CACA,OAAO;EACL,KAAK,SAAS,KAAK,SAAS,OAAO,GAAG;EACtC,SAAS,OAAO;CAClB;CACA,SAAS;EACP,KAAK,SAAS,KAAK,WAAW,OAAO,KAAK;EAC1C,SAAS,OAAO;CAClB;CACA,OAAO;EACL,KAAK,OAAO,YAAY,OAAO,YAAY,OAAO,KAAK,WAAW,CAAC,CAAC;EACpE,SAAS,OAAO,UAAU;CAC5B;AACF;;;;;AAMA,MAAM,gBAA4B;CAChC,KAAK,QAAQ,OAAO,CAAoB;CACxC,UAAU,YAAY;AACxB;AAEA,IAAa,aAAb,cAAgC,WAA6B;;;cAI7C;kBAKI;;;;;CAKlB,AAAO,IAAI,MAAmB;EAC5B,MAAM,EAAE,QAAQ,QAAQ,SAAS,MAAM,UAAU;EAEjD,IAAI,CAAC,KAAK,eAAe,IAAI,GAAG;EAKhC,MAAM,wBAAO,IAAI,KAAK,EAAC,CAAC,YAAY,CAAC,CAAC,MAAM,IAAI,EAAE;EAElD,MAAM,QAAQ,aAAa,UAAU;EAMrC,QAAQ,IACN,MAAM,KACN,OAAO,MAAM,IAAI,KAAK,EAAE,GACxB,OAAO,KAAK,IAAI,OAAO,EAAE,GACzB,OAAO,QAAQ,IAAI,OAAO,EAAE,GAC5B,MAAM,QAAQ,OAAO,CACvB;EAEA,IAAI,OAAO,YAAY,UACrB,QAAQ,IAAI,OAAO;EAMrB,IAAI,KAAK,OAAO,aAAa,KAAK,KAAK,WAAW,OAAO,KAAK,KAAK,OAAO,CAAC,CAAC,SAAS,GAAG;GACtF,MAAM,QAAQ,KAAK,OAAO,cAAc,KAAK;GAC7C,QAAQ,IACN,OAAO,MAAM,KAAK,GAClB,QAAQ,KAAK,SAAS;IAAE,QAAQ;IAAM;IAAO,aAAa;GAAG,CAAC,CAChE;EACF;CACF;AACF"}
|
|
@@ -1,18 +1,61 @@
|
|
|
1
1
|
import { BasicLogConfigurations, LogContract, LogLevel, LogMessage, LoggingData } from "../types.mjs";
|
|
2
2
|
import { LogChannel } from "../log-channel.mjs";
|
|
3
|
+
import { FileNameOption } from "./file-name-pattern.mjs";
|
|
3
4
|
|
|
4
|
-
//#region
|
|
5
|
+
//#region ../../packages/logger/src/channels/file-log.d.ts
|
|
6
|
+
/** Default file name of a `daily` chunk: sortable ISO date. */
|
|
7
|
+
declare const DAILY_FILE_NAME = "{YYYY}-{MM}-{DD}";
|
|
8
|
+
/** Default file name of an `hourly` chunk: sortable ISO date and hour. */
|
|
9
|
+
declare const HOURLY_FILE_NAME = "{YYYY}-{MM}-{DD}-{HH}";
|
|
10
|
+
/** Default date part of a size-rotated file's name. */
|
|
11
|
+
declare const ROTATE_FILE_NAME = "{YYYY}-{MM}-{DD}";
|
|
12
|
+
/**
|
|
13
|
+
* Which old log files `FileLog` removes, on boot and whenever it starts a new
|
|
14
|
+
* file (a size rotation or a dated name rolling over). Only files in the log
|
|
15
|
+
* directory (each group directory with `groupBy`) whose names match the
|
|
16
|
+
* channel's file name pattern are considered; the file being written is
|
|
17
|
+
* always kept.
|
|
18
|
+
*/
|
|
19
|
+
type FileLogRetention = {
|
|
20
|
+
/**
|
|
21
|
+
* Keep at most this many matching files per directory, newest (by
|
|
22
|
+
* modification time) first, counting the file being written.
|
|
23
|
+
*/
|
|
24
|
+
maxFiles?: number;
|
|
25
|
+
/**
|
|
26
|
+
* Remove matching files last modified more than this many days ago.
|
|
27
|
+
*/
|
|
28
|
+
maxAgeDays?: number;
|
|
29
|
+
/**
|
|
30
|
+
* Which file names (with extension) belong to this channel. Defaults to one
|
|
31
|
+
* derived from `fileName` and `rotateFileName`; required when `fileName` is
|
|
32
|
+
* a function.
|
|
33
|
+
*/
|
|
34
|
+
match?: RegExp;
|
|
35
|
+
};
|
|
5
36
|
type FileLogConfig = BasicLogConfigurations & {
|
|
6
37
|
storagePath?: string;
|
|
7
38
|
/**
|
|
8
|
-
*
|
|
39
|
+
* Base name, without extension: the file name of a `single` chunk and the
|
|
40
|
+
* `{name}` token of a `fileName` pattern.
|
|
41
|
+
*
|
|
42
|
+
* @default app
|
|
9
43
|
*/
|
|
10
44
|
name?: string;
|
|
11
45
|
/**
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
46
|
+
* The file name, without extension: a pattern such as
|
|
47
|
+
* `"app-{YYYY}-{MM}-{DD}"` (tokens `{YYYY}` `{MM}` `{DD}` `{HH}` `{mm}`
|
|
48
|
+
* `{name}`, local time) or a function of the current date. Evaluated at
|
|
49
|
+
* every write, so a dated name starts a new file when it changes.
|
|
50
|
+
*
|
|
51
|
+
* @default by chunk: `single` → name, `daily` → `{YYYY}-{MM}-{DD}`, `hourly` → `{YYYY}-{MM}-{DD}-{HH}`
|
|
52
|
+
*/
|
|
53
|
+
fileName?: FileNameOption;
|
|
54
|
+
/**
|
|
55
|
+
* chunk mode: picks the default `fileName`.
|
|
56
|
+
* `single` writes one file named `name`, `daily` one file per day
|
|
57
|
+
* (`YYYY-MM-DD`), `hourly` one per hour (`YYYY-MM-DD-HH`). Size rotation
|
|
58
|
+
* applies to each.
|
|
16
59
|
* @default single
|
|
17
60
|
*/
|
|
18
61
|
chunk?: "single" | "daily" | "hourly";
|
|
@@ -29,17 +72,26 @@ type FileLogConfig = BasicLogConfigurations & {
|
|
|
29
72
|
*/
|
|
30
73
|
extension?: string;
|
|
31
74
|
/**
|
|
32
|
-
*
|
|
75
|
+
* Middle part of a size-rotated file's name:
|
|
76
|
+
* `<file name>-<rotateFileName>-<epoch ms>.<extension>`. Same forms as
|
|
77
|
+
* `fileName` (a token pattern or a function of the rotation date); text
|
|
78
|
+
* without tokens is used as is.
|
|
33
79
|
*
|
|
34
|
-
* @default
|
|
80
|
+
* @default {YYYY}-{MM}-{DD}
|
|
35
81
|
*/
|
|
36
|
-
rotateFileName?:
|
|
82
|
+
rotateFileName?: FileNameOption;
|
|
37
83
|
/**
|
|
38
84
|
* Max file size before rotating the file
|
|
39
85
|
*
|
|
40
86
|
* @default 10MB
|
|
41
87
|
*/
|
|
42
88
|
maxFileSize?: number;
|
|
89
|
+
/**
|
|
90
|
+
* Remove old log files on boot and whenever a new file is started.
|
|
91
|
+
*
|
|
92
|
+
* @default none (nothing is removed)
|
|
93
|
+
*/
|
|
94
|
+
retention?: FileLogRetention;
|
|
43
95
|
/**
|
|
44
96
|
* Set the max messages that needs to be added before writing to the file
|
|
45
97
|
*
|
|
@@ -99,14 +151,36 @@ declare class FileLog extends LogChannel<FileLogConfig> implements LogContract {
|
|
|
99
151
|
* would otherwise leak one timer per channel.
|
|
100
152
|
*/
|
|
101
153
|
protected flushIntervalHandle?: NodeJS.Timeout;
|
|
154
|
+
/**
|
|
155
|
+
* Set when a size rotation started a new file, so the next write prunes.
|
|
156
|
+
*/
|
|
157
|
+
protected retentionDue: boolean;
|
|
158
|
+
/**
|
|
159
|
+
* File name of the last write, to notice a dated name rolling over.
|
|
160
|
+
*/
|
|
161
|
+
protected lastFileName?: string;
|
|
162
|
+
/**
|
|
163
|
+
* The running (or last) prune, so prunes never overlap.
|
|
164
|
+
*/
|
|
165
|
+
protected pruning?: Promise<void>;
|
|
166
|
+
/**
|
|
167
|
+
* Whether the "function `fileName` needs `retention.match`" error was
|
|
168
|
+
* already reported.
|
|
169
|
+
*/
|
|
170
|
+
protected missingMatchReported: boolean;
|
|
171
|
+
/**
|
|
172
|
+
* {@inheritdoc}
|
|
173
|
+
*/
|
|
174
|
+
protected setConfigurations(configurations: FileLogConfig): this;
|
|
102
175
|
/**
|
|
103
176
|
* Check file size for file rotation
|
|
104
177
|
*/
|
|
105
178
|
protected checkAndRotateFile(filePath?: string): Promise<void>;
|
|
106
179
|
/**
|
|
107
|
-
* Rotate log file
|
|
180
|
+
* Rotate the given log file (the active one by default) aside as
|
|
181
|
+
* `<file name>-<rotateFileName>-<epoch ms>.<extension>` in its directory.
|
|
108
182
|
*/
|
|
109
|
-
protected rotateLogFile(): Promise<void>;
|
|
183
|
+
protected rotateLogFile(filePath?: string): Promise<void>;
|
|
110
184
|
/**
|
|
111
185
|
* Flush messages
|
|
112
186
|
*
|
|
@@ -133,7 +207,16 @@ declare class FileLog extends LogChannel<FileLogConfig> implements LogContract {
|
|
|
133
207
|
*/
|
|
134
208
|
protected get maxMessagesToWrite(): number;
|
|
135
209
|
/**
|
|
136
|
-
*
|
|
210
|
+
* The configured `name` option.
|
|
211
|
+
*/
|
|
212
|
+
protected get baseName(): string;
|
|
213
|
+
/**
|
|
214
|
+
* The `fileName` option, or the chunk's default. `undefined` means the
|
|
215
|
+
* plain `name` of a `single` chunk, used as is (braces included).
|
|
216
|
+
*/
|
|
217
|
+
protected get fileNamePattern(): FileNameOption | undefined;
|
|
218
|
+
/**
|
|
219
|
+
* Current file name, without extension
|
|
137
220
|
*/
|
|
138
221
|
get fileName(): string;
|
|
139
222
|
/**
|
|
@@ -152,6 +235,39 @@ declare class FileLog extends LogChannel<FileLogConfig> implements LogContract {
|
|
|
152
235
|
* {@inheritdoc}
|
|
153
236
|
*/
|
|
154
237
|
protected init(): Promise<void>;
|
|
238
|
+
/**
|
|
239
|
+
* Remove old log files as configured by `retention`. Runs on boot and
|
|
240
|
+
* whenever a new file is started; call it yourself to prune on demand.
|
|
241
|
+
* Never rejects: failures go to the channel error handler.
|
|
242
|
+
*/
|
|
243
|
+
prune(): Promise<void>;
|
|
244
|
+
/**
|
|
245
|
+
* Prune once a write started a new file: after a size rotation, or when a
|
|
246
|
+
* dated file name rolled over. Never throws.
|
|
247
|
+
*/
|
|
248
|
+
protected afterWrite(): Promise<void>;
|
|
249
|
+
/**
|
|
250
|
+
* Apply `retention` to every directory this channel writes to.
|
|
251
|
+
*/
|
|
252
|
+
protected pruneFiles(): Promise<void>;
|
|
253
|
+
/**
|
|
254
|
+
* The expression a file name must match to be pruned, or `undefined` when
|
|
255
|
+
* none can be derived (a function `fileName` without `retention.match`).
|
|
256
|
+
*/
|
|
257
|
+
protected retentionMatcher(retention: FileLogRetention): RegExp | undefined;
|
|
258
|
+
/**
|
|
259
|
+
* The log directory, or each group directory when `groupBy` is set.
|
|
260
|
+
*/
|
|
261
|
+
protected retentionDirectories(): Promise<string[]>;
|
|
262
|
+
/**
|
|
263
|
+
* Remove the matching files of one directory that `retention` no longer
|
|
264
|
+
* keeps. Each failure is reported and the rest still run.
|
|
265
|
+
*/
|
|
266
|
+
protected pruneDirectory(directory: string, matcher: RegExp, retention: FileLogRetention, activeFile: string): Promise<void>;
|
|
267
|
+
/**
|
|
268
|
+
* Send a retention failure to the channel error handler.
|
|
269
|
+
*/
|
|
270
|
+
protected reportRetentionError(error: unknown, target: string): void;
|
|
155
271
|
/**
|
|
156
272
|
* Synchronously flush messages
|
|
157
273
|
*/
|
|
@@ -200,5 +316,5 @@ declare class FileLog extends LogChannel<FileLogConfig> implements LogContract {
|
|
|
200
316
|
protected write(filePath: string, content: string): Promise<unknown>;
|
|
201
317
|
}
|
|
202
318
|
//#endregion
|
|
203
|
-
export { FileLog, FileLogConfig };
|
|
319
|
+
export { DAILY_FILE_NAME, FileLog, FileLogConfig, FileLogRetention, HOURLY_FILE_NAME, ROTATE_FILE_NAME };
|
|
204
320
|
//# sourceMappingURL=file-log.d.mts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"file-log.d.mts","names":[],"sources":["
|
|
1
|
+
{"version":3,"file":"file-log.d.mts","names":[],"sources":["../../../../../../../../packages/logger/src/channels/file-log.ts"],"mappings":";;;;;;cAuBa,eAAA;;cAGA,gBAAA;;cAGA,gBAAA;AAHb;;;;AAA6B;AAG7B;;AAHA,KAcY,gBAAA;EAXiB;AAAA;AAW7B;;EAKE,QAAA;EAUc;;;EANd,UAAA;EAMQ;;AAAM;AAGhB;;EAHE,KAAA,GAAQ,MAAM;AAAA;AAAA,KAGJ,aAAA,GAAgB,sBAAA;EAC1B,WAAA;EAyDY;;;;;;EAlDZ,IAAA;EASA;;;;;;;;EAAA,QAAA,GAAW,cAAA;EAyCC;;;;;;;EAjCZ,KAAA;EA2DM;AAAA;AAIR;;;EAzDE,MAAA;EAkEoB;;;;;EA5DpB,SAAA;EA+H4C;;;;;;;;EAtH5C,cAAA,GAAiB,cAAA;EA4WwC;;;;;EAtWzD,WAAA;EAqiBuB;;;;;EA/hBvB,SAAA,GAAY,gBAAA;EA8Be;;;;;EAxB3B,kBAAA;EAwBgE;;;;;;;EAhBhE,OAAA;EAmCiC;;;;;EA7BjC,MAAA,GAAS,QAAA;EAiEC;;;EA7DV,UAAA;IACE,IAAA;IACA,IAAA;EAAA;AAAA;AAAA,cAIS,OAAA,SAAgB,UAAA,CAAW,aAAA,aAA0B,WAAA;EAqF7B;;;EAjF5B,IAAA;EAsG+C;;;EAAA,UAjG5C,QAAA,EAAU,UAAA;EA2KN;;;EAAA,UAtKJ,eAAA,EAAiB,MAAA,SAAe,UAAA;EAwM/B;;;EAAA,UAnMD,qBAAA,EAAuB,aAAA;EAmOjB;;;EAAA,UAhNN,aAAA;EA2OM;;;EAAA,UAtON,SAAA;EAuRA;;;;;EAAA,UAhRA,mBAAA,GAAsB,MAAA,CAAO,OAAA;EAmVvB;;;EAAA,UA9UN,YAAA;EAiVG;;;EAAA,UA5UH,YAAA;EAuYA;;;EAAA,UAlYA,OAAA,GAAU,OAAA;EA+aP;;;;EAAA,UAzaH,oBAAA;EA0bwB;;;EAAA,UArbxB,iBAAA,CAAkB,cAAA,EAAgB,aAAA;EAif9B;;;EAAA,UAxeE,kBAAA,CAAmB,QAAA,YAAwB,OAAA;EA2gBb;;;;EAAA,UAtf9B,aAAA,CAAc,QAAA,YAAwB,OAAA;EAoiBC;;AAAA;;;;;;EAAA,UAngB7C,gBAAA;;;;;;;;EAkBH,OAAA;;;;MAYI,QAAA;;;;gBAWG,kBAAA;;;;gBAOA,QAAA;;;;;gBAQA,eAAA,IAAmB,cAAA;;;;MAmBtB,QAAA;;;;MAWA,SAAA;;;;gBAOG,OAAA;;;;MAOH,WAAA;;;;YAOK,IAAA,IAAI,OAAA;;;;;;EAeb,KAAA,IAAS,OAAA;;;;;YAYA,UAAA,IAAc,OAAA;;;;YAuBd,UAAA,IAAc,OAAA;;;;;YA0BpB,gBAAA,CAAiB,SAAA,EAAW,gBAAA,GAAmB,MAAA;;;;YAqCzC,oBAAA,IAAwB,OAAA;;;;;YA8BxB,cAAA,CACd,SAAA,UACA,OAAA,EAAS,MAAA,EACT,SAAA,EAAW,gBAAA,EACX,UAAA,WACC,OAAA;;;;YAyDO,oBAAA,CAAqB,KAAA,WAAgB,MAAA;;;;EAYxC,SAAA;;;;;;;;;;EAiCM,KAAA,IAAS,OAAA;;;;EAiBT,GAAA,CAAI,IAAA,EAAM,WAAA,GAAW,OAAA;;;;YAyClB,8BAAA,IAA8B,OAAA;;;;YASpC,MAAA;;;;gBAUI,uBAAA;;;;YAOE,mBAAA,IAAmB,OAAA;;;;YA4BnB,0BAAA,IAA8B,OAAA;;;;YAgCpC,sBAAA;;;;YAcM,KAAA,CAAM,QAAA,UAAkB,OAAA,WAAe,OAAA;AAAA"}
|
|
@@ -1,11 +1,19 @@
|
|
|
1
1
|
import { LogChannel } from "../log-channel.mjs";
|
|
2
|
+
import { escapeRegExp, fileNamePatternSource, renderFileName } from "./file-name-pattern.mjs";
|
|
2
3
|
import { ensureDirectoryAsync } from "@warlock.js/fs";
|
|
3
4
|
import dayjs from "dayjs";
|
|
4
5
|
import fs from "fs";
|
|
5
6
|
import { EOL } from "os";
|
|
6
7
|
import path from "path";
|
|
7
8
|
|
|
8
|
-
//#region
|
|
9
|
+
//#region ../../packages/logger/src/channels/file-log.ts
|
|
10
|
+
/** Default file name of a `daily` chunk: sortable ISO date. */
|
|
11
|
+
const DAILY_FILE_NAME = "{YYYY}-{MM}-{DD}";
|
|
12
|
+
/** Default file name of an `hourly` chunk: sortable ISO date and hour. */
|
|
13
|
+
const HOURLY_FILE_NAME = "{YYYY}-{MM}-{DD}-{HH}";
|
|
14
|
+
/** Default date part of a size-rotated file's name. */
|
|
15
|
+
const ROTATE_FILE_NAME = "{YYYY}-{MM}-{DD}";
|
|
16
|
+
const DAY_MS = 1440 * 60 * 1e3;
|
|
9
17
|
var FileLog = class extends LogChannel {
|
|
10
18
|
constructor(..._args) {
|
|
11
19
|
super(..._args);
|
|
@@ -21,9 +29,7 @@ var FileLog = class extends LogChannel {
|
|
|
21
29
|
maxMessagesToWrite: 100,
|
|
22
30
|
filter: () => true,
|
|
23
31
|
maxFileSize: 10 * 1024 * 1024,
|
|
24
|
-
|
|
25
|
-
return dayjs().format("DD-MM-YYYY");
|
|
26
|
-
},
|
|
32
|
+
rotateFileName: ROTATE_FILE_NAME,
|
|
27
33
|
dateFormat: {
|
|
28
34
|
date: "DD-MM-YYYY",
|
|
29
35
|
time: "HH:mm:ss"
|
|
@@ -31,6 +37,15 @@ var FileLog = class extends LogChannel {
|
|
|
31
37
|
};
|
|
32
38
|
this.lastWriteTime = Date.now();
|
|
33
39
|
this.isWriting = false;
|
|
40
|
+
this.retentionDue = false;
|
|
41
|
+
this.missingMatchReported = false;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* {@inheritdoc}
|
|
45
|
+
*/
|
|
46
|
+
setConfigurations(configurations) {
|
|
47
|
+
assertRetention(configurations.retention);
|
|
48
|
+
return super.setConfigurations(configurations);
|
|
34
49
|
}
|
|
35
50
|
/**
|
|
36
51
|
* Check file size for file rotation
|
|
@@ -38,19 +53,24 @@ var FileLog = class extends LogChannel {
|
|
|
38
53
|
async checkAndRotateFile(filePath = this.filePath) {
|
|
39
54
|
if (!this.config("rotate")) return;
|
|
40
55
|
try {
|
|
41
|
-
if ((await fs.promises.stat(filePath)).size >= this.config("maxFileSize")) await this.rotateLogFile();
|
|
56
|
+
if ((await fs.promises.stat(filePath)).size >= this.config("maxFileSize")) await this.rotateLogFile(filePath);
|
|
42
57
|
} catch (error) {
|
|
43
58
|
if (error.code !== "ENOENT") console.error("Error checking log file:", error);
|
|
44
59
|
}
|
|
45
60
|
}
|
|
46
61
|
/**
|
|
47
|
-
* Rotate log file
|
|
62
|
+
* Rotate the given log file (the active one by default) aside as
|
|
63
|
+
* `<file name>-<rotateFileName>-<epoch ms>.<extension>` in its directory.
|
|
48
64
|
*/
|
|
49
|
-
async rotateLogFile() {
|
|
50
|
-
const
|
|
65
|
+
async rotateLogFile(filePath = this.filePath) {
|
|
66
|
+
const now = /* @__PURE__ */ new Date();
|
|
51
67
|
const extension = this.extension;
|
|
52
|
-
const
|
|
53
|
-
|
|
68
|
+
const baseName = path.basename(filePath, `.${extension}`);
|
|
69
|
+
const rotateName = renderFileName(this.config("rotateFileName") ?? "{YYYY}-{MM}-{DD}", now, this.baseName);
|
|
70
|
+
const rotatedFilePath = path.join(path.dirname(filePath), `${baseName}-${rotateName}-${now.getTime()}.${extension}`);
|
|
71
|
+
await fs.promises.rename(filePath, rotatedFilePath).then(() => {
|
|
72
|
+
this.retentionDue = true;
|
|
73
|
+
}, (error) => {
|
|
54
74
|
console.error("Error rotating file:", error);
|
|
55
75
|
});
|
|
56
76
|
}
|
|
@@ -96,17 +116,32 @@ var FileLog = class extends LogChannel {
|
|
|
96
116
|
return this.config("maxMessagesToWrite");
|
|
97
117
|
}
|
|
98
118
|
/**
|
|
99
|
-
*
|
|
119
|
+
* The configured `name` option.
|
|
100
120
|
*/
|
|
101
|
-
get
|
|
121
|
+
get baseName() {
|
|
122
|
+
return this.config("name") ?? "app";
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* The `fileName` option, or the chunk's default. `undefined` means the
|
|
126
|
+
* plain `name` of a `single` chunk, used as is (braces included).
|
|
127
|
+
*/
|
|
128
|
+
get fileNamePattern() {
|
|
129
|
+
const configured = this.config("fileName");
|
|
130
|
+
if (configured !== void 0) return configured;
|
|
102
131
|
switch (this.config("chunk")) {
|
|
103
|
-
case "
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
case "hourly": return dayjs().format("DD-MM-YYYY-HH-00-00-a");
|
|
132
|
+
case "daily": return DAILY_FILE_NAME;
|
|
133
|
+
case "hourly": return HOURLY_FILE_NAME;
|
|
134
|
+
default: return;
|
|
107
135
|
}
|
|
108
136
|
}
|
|
109
137
|
/**
|
|
138
|
+
* Current file name, without extension
|
|
139
|
+
*/
|
|
140
|
+
get fileName() {
|
|
141
|
+
const pattern = this.fileNamePattern;
|
|
142
|
+
return pattern === void 0 ? this.baseName : renderFileName(pattern, /* @__PURE__ */ new Date(), this.baseName);
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
110
145
|
* Get file extension
|
|
111
146
|
*/
|
|
112
147
|
get extension() {
|
|
@@ -131,6 +166,138 @@ var FileLog = class extends LogChannel {
|
|
|
131
166
|
const logsDirectory = this.storagePath;
|
|
132
167
|
await ensureDirectoryAsync(logsDirectory);
|
|
133
168
|
this.initMessageFlush();
|
|
169
|
+
await this.prune();
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* Remove old log files as configured by `retention`. Runs on boot and
|
|
173
|
+
* whenever a new file is started; call it yourself to prune on demand.
|
|
174
|
+
* Never rejects: failures go to the channel error handler.
|
|
175
|
+
*/
|
|
176
|
+
prune() {
|
|
177
|
+
const run = (this.pruning ?? Promise.resolve()).then(() => this.pruneFiles());
|
|
178
|
+
this.pruning = run;
|
|
179
|
+
return run;
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* Prune once a write started a new file: after a size rotation, or when a
|
|
183
|
+
* dated file name rolled over. Never throws.
|
|
184
|
+
*/
|
|
185
|
+
async afterWrite() {
|
|
186
|
+
let fileName;
|
|
187
|
+
try {
|
|
188
|
+
fileName = this.fileName;
|
|
189
|
+
} catch {
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
const rolledOver = this.lastFileName !== void 0 && this.lastFileName !== fileName;
|
|
193
|
+
this.lastFileName = fileName;
|
|
194
|
+
if (!rolledOver && !this.retentionDue) return;
|
|
195
|
+
this.retentionDue = false;
|
|
196
|
+
await this.prune();
|
|
197
|
+
}
|
|
198
|
+
/**
|
|
199
|
+
* Apply `retention` to every directory this channel writes to.
|
|
200
|
+
*/
|
|
201
|
+
async pruneFiles() {
|
|
202
|
+
const retention = this.config("retention");
|
|
203
|
+
if (!retention || retention.maxFiles === void 0 && retention.maxAgeDays === void 0) return;
|
|
204
|
+
try {
|
|
205
|
+
const matcher = this.retentionMatcher(retention);
|
|
206
|
+
if (!matcher) return;
|
|
207
|
+
const activeFile = `${this.fileName}.${this.extension}`;
|
|
208
|
+
for (const directory of await this.retentionDirectories()) await this.pruneDirectory(directory, matcher, retention, activeFile);
|
|
209
|
+
} catch (error) {
|
|
210
|
+
this.reportRetentionError(error, this.storagePath);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
/**
|
|
214
|
+
* The expression a file name must match to be pruned, or `undefined` when
|
|
215
|
+
* none can be derived (a function `fileName` without `retention.match`).
|
|
216
|
+
*/
|
|
217
|
+
retentionMatcher(retention) {
|
|
218
|
+
if (retention.match) return new RegExp(retention.match.source, retention.match.flags.replace(/[gy]/g, ""));
|
|
219
|
+
const pattern = this.fileNamePattern;
|
|
220
|
+
if (typeof pattern === "function") {
|
|
221
|
+
if (!this.missingMatchReported) {
|
|
222
|
+
this.missingMatchReported = true;
|
|
223
|
+
this.reportRetentionError(/* @__PURE__ */ new Error("FileLog retention needs `retention.match` when `fileName` is a function; no files were pruned"), this.storagePath);
|
|
224
|
+
}
|
|
225
|
+
return;
|
|
226
|
+
}
|
|
227
|
+
const name = this.baseName;
|
|
228
|
+
const fileSource = pattern === void 0 ? escapeRegExp(name) : fileNamePatternSource(pattern, name);
|
|
229
|
+
const rotateName = this.config("rotateFileName") ?? "{YYYY}-{MM}-{DD}";
|
|
230
|
+
const rotateSource = typeof rotateName === "function" ? ".+" : fileNamePatternSource(rotateName, name);
|
|
231
|
+
return new RegExp(`^${fileSource}(?:-${rotateSource}-\\d{13,})?\\.${escapeRegExp(this.extension)}$`);
|
|
232
|
+
}
|
|
233
|
+
/**
|
|
234
|
+
* The log directory, or each group directory when `groupBy` is set.
|
|
235
|
+
*/
|
|
236
|
+
async retentionDirectories() {
|
|
237
|
+
let directories = [this.storagePath];
|
|
238
|
+
const depth = this.config("groupBy")?.length ?? 0;
|
|
239
|
+
for (let level = 0; level < depth; level++) {
|
|
240
|
+
const next = [];
|
|
241
|
+
for (const directory of directories) {
|
|
242
|
+
const entries = await fs.promises.readdir(directory, { withFileTypes: true }).catch((error) => {
|
|
243
|
+
if (error.code !== "ENOENT") this.reportRetentionError(error, directory);
|
|
244
|
+
return [];
|
|
245
|
+
});
|
|
246
|
+
for (const entry of entries) if (entry.isDirectory()) next.push(path.join(directory, entry.name));
|
|
247
|
+
}
|
|
248
|
+
directories = next;
|
|
249
|
+
}
|
|
250
|
+
return directories;
|
|
251
|
+
}
|
|
252
|
+
/**
|
|
253
|
+
* Remove the matching files of one directory that `retention` no longer
|
|
254
|
+
* keeps. Each failure is reported and the rest still run.
|
|
255
|
+
*/
|
|
256
|
+
async pruneDirectory(directory, matcher, retention, activeFile) {
|
|
257
|
+
let names;
|
|
258
|
+
try {
|
|
259
|
+
names = await fs.promises.readdir(directory);
|
|
260
|
+
} catch (error) {
|
|
261
|
+
if (error.code !== "ENOENT") this.reportRetentionError(error, directory);
|
|
262
|
+
return;
|
|
263
|
+
}
|
|
264
|
+
const candidates = [];
|
|
265
|
+
for (const name of names) {
|
|
266
|
+
if (name === activeFile || !matcher.test(name)) continue;
|
|
267
|
+
const filePath = path.join(directory, name);
|
|
268
|
+
try {
|
|
269
|
+
const stats = await fs.promises.stat(filePath);
|
|
270
|
+
if (stats.isFile()) candidates.push({
|
|
271
|
+
filePath,
|
|
272
|
+
name,
|
|
273
|
+
modifiedAt: stats.mtimeMs
|
|
274
|
+
});
|
|
275
|
+
} catch (error) {
|
|
276
|
+
if (error.code !== "ENOENT") this.reportRetentionError(error, filePath);
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
candidates.sort((a, b) => b.modifiedAt - a.modifiedAt || (a.name < b.name ? 1 : -1));
|
|
280
|
+
const keep = retention.maxFiles === void 0 ? Infinity : retention.maxFiles - (names.includes(activeFile) ? 1 : 0);
|
|
281
|
+
const oldestKept = retention.maxAgeDays === void 0 ? -Infinity : Date.now() - retention.maxAgeDays * DAY_MS;
|
|
282
|
+
for (const [index, candidate] of candidates.entries()) {
|
|
283
|
+
if (index < keep && candidate.modifiedAt >= oldestKept) continue;
|
|
284
|
+
try {
|
|
285
|
+
await fs.promises.unlink(candidate.filePath);
|
|
286
|
+
} catch (error) {
|
|
287
|
+
if (error.code !== "ENOENT") this.reportRetentionError(error, candidate.filePath);
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
/**
|
|
292
|
+
* Send a retention failure to the channel error handler.
|
|
293
|
+
*/
|
|
294
|
+
reportRetentionError(error, target) {
|
|
295
|
+
this.reportError(error, {
|
|
296
|
+
type: "error",
|
|
297
|
+
module: "logger",
|
|
298
|
+
action: "retention",
|
|
299
|
+
message: `Log retention failed for ${target}`
|
|
300
|
+
});
|
|
134
301
|
}
|
|
135
302
|
/**
|
|
136
303
|
* Synchronously flush messages
|
|
@@ -222,14 +389,17 @@ var FileLog = class extends LogChannel {
|
|
|
222
389
|
if (this.messages.length === 0 || this.isWriting || !this.isInitialized) return;
|
|
223
390
|
this.isWriting = true;
|
|
224
391
|
if (this.messagedShouldBeGrouped) return await this.writeGroupedMessagesToFile();
|
|
225
|
-
await this.checkAndRotateFile();
|
|
226
392
|
try {
|
|
227
|
-
|
|
393
|
+
const filePath = this.filePath;
|
|
394
|
+
await this.checkAndRotateFile(filePath);
|
|
395
|
+
await this.write(filePath, this.content);
|
|
228
396
|
this.onSave();
|
|
229
397
|
} catch (error) {
|
|
230
398
|
console.error("Failed to write log:", error);
|
|
231
399
|
this.isWriting = false;
|
|
400
|
+
return;
|
|
232
401
|
}
|
|
402
|
+
await this.afterWrite();
|
|
233
403
|
}
|
|
234
404
|
/**
|
|
235
405
|
* Write grouped messages to the file
|
|
@@ -238,11 +408,11 @@ var FileLog = class extends LogChannel {
|
|
|
238
408
|
this.prepareGroupedMessages();
|
|
239
409
|
for (const [key, groupMessages] of Object.entries(this.groupedMessages)) {
|
|
240
410
|
const directoryPath = path.join(this.storagePath, key);
|
|
241
|
-
await ensureDirectoryAsync(directoryPath);
|
|
242
|
-
const filePath = path.join(directoryPath, `${this.fileName}.${this.extension}`);
|
|
243
|
-
await this.checkAndRotateFile(filePath);
|
|
244
|
-
const content = groupMessages.map((message) => message.content).join(EOL) + EOL;
|
|
245
411
|
try {
|
|
412
|
+
await ensureDirectoryAsync(directoryPath);
|
|
413
|
+
const filePath = path.join(directoryPath, `${this.fileName}.${this.extension}`);
|
|
414
|
+
await this.checkAndRotateFile(filePath);
|
|
415
|
+
const content = groupMessages.map((message) => message.content).join(EOL) + EOL;
|
|
246
416
|
await this.write(filePath, content);
|
|
247
417
|
} catch (error) {
|
|
248
418
|
console.error("Failed to write log:", error);
|
|
@@ -250,6 +420,7 @@ var FileLog = class extends LogChannel {
|
|
|
250
420
|
}
|
|
251
421
|
this.onSave();
|
|
252
422
|
this.isWriting = false;
|
|
423
|
+
await this.afterWrite();
|
|
253
424
|
}
|
|
254
425
|
/**
|
|
255
426
|
* Prepare grouped messages
|
|
@@ -275,7 +446,17 @@ var FileLog = class extends LogChannel {
|
|
|
275
446
|
});
|
|
276
447
|
}
|
|
277
448
|
};
|
|
449
|
+
/**
|
|
450
|
+
* Reject a `retention` that could never keep or remove anything sensibly.
|
|
451
|
+
*/
|
|
452
|
+
function assertRetention(retention) {
|
|
453
|
+
if (!retention) return;
|
|
454
|
+
const { maxFiles, maxAgeDays, match } = retention;
|
|
455
|
+
if (maxFiles !== void 0 && (!Number.isInteger(maxFiles) || maxFiles < 1)) throw new TypeError("FileLog retention.maxFiles must be a positive integer");
|
|
456
|
+
if (maxAgeDays !== void 0 && (!Number.isFinite(maxAgeDays) || maxAgeDays <= 0)) throw new TypeError("FileLog retention.maxAgeDays must be a positive number");
|
|
457
|
+
if (match !== void 0 && !(match instanceof RegExp)) throw new TypeError("FileLog retention.match must be a RegExp");
|
|
458
|
+
}
|
|
278
459
|
|
|
279
460
|
//#endregion
|
|
280
|
-
export { FileLog };
|
|
461
|
+
export { DAILY_FILE_NAME, FileLog, HOURLY_FILE_NAME, ROTATE_FILE_NAME };
|
|
281
462
|
//# sourceMappingURL=file-log.mjs.map
|