@loglayer/transport-log-file-rotation 1.0.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Theo Gravity
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,74 @@
1
+ # @loglayer/transport-log-file-rotation
2
+
3
+ [![NPM Version](https://img.shields.io/npm/v/%40loglayer%2Ftransport-log-file-rotation)](https://www.npmjs.com/package/@loglayer/transport-log-file-rotation)
4
+
5
+ A transport for [LogLayer](https://github.com/loglayer/loglayer) that writes logs to files with automatic rotation based on size or time. Built on top of [file-stream-rotator](https://github.com/rogerc/file-stream-rotator/).
6
+
7
+ ## Features
8
+
9
+ - Automatic log file rotation based on time (hourly, daily)
10
+ - Size-based rotation with support for KB, MB, and GB units
11
+ - Support for date patterns in filenames using numerical values
12
+ - Compression of rotated log files using gzip
13
+ - Maximum file count or age-based retention
14
+ - Automatic cleanup of old log files
15
+ - Batch processing of logs for improved performance
16
+
17
+ ## Installation
18
+
19
+ ```sh
20
+ # npm
21
+ npm i loglayer @loglayer/transport-log-file-rotation serialize-error
22
+
23
+ # pnpm
24
+ pnpm add loglayer @loglayer/transport-log-file-rotation serialize-error
25
+
26
+ # yarn
27
+ yarn add loglayer @loglayer/transport-log-file-rotation serialize-error
28
+ ```
29
+
30
+ ## Usage
31
+
32
+ ```typescript
33
+ import { LogLayer } from "loglayer";
34
+ import { LogFileRotationTransport } from "@loglayer/transport-log-file-rotation";
35
+ import { serializeError } from "serialize-error";
36
+
37
+ const logger = new LogLayer({
38
+ errorSerializer: serializeError,
39
+ transport: [
40
+ new LogFileRotationTransport({
41
+ filename: "./logs/app.log",
42
+ }),
43
+ ],
44
+ });
45
+
46
+ logger.info("Application started");
47
+ ```
48
+
49
+ ### Filename Uniqueness
50
+ Each instance of `LogFileRotationTransport` must have a unique filename to prevent possible race conditions. If you try to create multiple transport instances with the same filename, an error will be thrown. If you need multiple loggers to write to the same file, they should share the same transport instance:
51
+
52
+ ```typescript
53
+ // Create a single transport instance
54
+ const fileTransport = new LogFileRotationTransport({
55
+ filename: "./logs/app-%DATE%.log",
56
+ dateFormat: "YMD",
57
+ frequency: "daily"
58
+ });
59
+
60
+ // Share it between multiple loggers
61
+ const logger1 = new LogLayer({
62
+ transport: [fileTransport]
63
+ });
64
+
65
+ const logger2 = new LogLayer({
66
+ transport: [fileTransport]
67
+ });
68
+ ```
69
+
70
+ Child loggers do not have this problem as they inherit the transport instance from their parent logger.
71
+
72
+ ## Documentation
73
+
74
+ For detailed documentation, including all configuration options and advanced features, visit the [LogLayer documentation](https://loglayer.dev/transports/log-file-rotation.html).
package/dist/index.cjs ADDED
@@ -0,0 +1,370 @@
1
+ "use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; } var _class;// src/LogFileRotationTransport.ts
2
+ var _fs = require('fs');
3
+ var _promises = require('fs/promises');
4
+ var _promises3 = require('stream/promises');
5
+ var _zlib = require('zlib');
6
+ var _transport = require('@loglayer/transport');
7
+ var _filestreamrotator = require('file-stream-rotator'); var _filestreamrotator2 = _interopRequireDefault(_filestreamrotator);
8
+ var LogFileRotationTransport = (_class = class _LogFileRotationTransport extends _transport.LoggerlessTransport {
9
+ /** Registry of active filenames to prevent multiple transports writing to the same file */
10
+ static __initStatic() {this.activeFilenames = /* @__PURE__ */ new Set()}
11
+ /** The current write stream for the log file */
12
+
13
+ /** Custom field names for log entries */
14
+
15
+ /** Delimiter between log entries */
16
+
17
+ /** Function to generate timestamps for log entries */
18
+
19
+ /** Custom mapping for log levels */
20
+
21
+ /** Whether to compress rotated files */
22
+
23
+ /** Whether a file is currently being compressed */
24
+
25
+ /** The base filename pattern for log files */
26
+
27
+ /** Whether batch processing is enabled */
28
+
29
+ /** Maximum number of log entries to queue before writing */
30
+
31
+ /** Maximum time in milliseconds to wait before writing queued logs */
32
+
33
+ /** Queue of log entries waiting to be written */
34
+
35
+ /** Timer for batch flush timeout */
36
+
37
+ /** Whether the transport is being disposed */
38
+
39
+ /** Event callbacks for various file stream events */
40
+
41
+ /** Frequency of rotation (daily, hourly, etc.) */
42
+
43
+ /** Whether to enable verbose mode */
44
+
45
+ /** Date format for filename patterns */
46
+
47
+ /** Size threshold for rotation */
48
+
49
+ /** Maximum number of log files to keep */
50
+
51
+ /** Path to the audit file */
52
+
53
+ /** File extension for log files */
54
+
55
+ /** Whether to create a symlink to current log */
56
+
57
+ /** Name of the symlink file */
58
+
59
+ /** Whether to use UTC time in filenames */
60
+
61
+ /** Hash algorithm for audit file */
62
+
63
+ /** Options for file streams */
64
+
65
+ /** File mode to be used when creating log files */
66
+
67
+ /**
68
+ * Generates the options for FileStreamRotator consistently across the transport
69
+ * @returns FileStreamRotatorOptions object
70
+ * @private
71
+ */
72
+ getRotatorOptions() {
73
+ return {
74
+ filename: this.filename,
75
+ frequency: this.frequency,
76
+ verbose: _nullishCoalesce(this.verbose, () => ( false)),
77
+ date_format: this.dateFormat,
78
+ size: this.size,
79
+ max_logs: _optionalChain([this, 'access', _ => _.maxLogs, 'optionalAccess', _2 => _2.toString, 'call', _3 => _3()]),
80
+ audit_file: this.auditFile || void 0,
81
+ end_stream: true,
82
+ extension: this.extension,
83
+ create_symlink: this.createSymlink,
84
+ symlink_name: this.symlinkName,
85
+ utc: this.utc,
86
+ audit_hash_type: this.auditHashType,
87
+ file_options: {
88
+ flags: "a",
89
+ encoding: "utf8",
90
+ mode: _nullishCoalesce(this.fileMode, () => ( 416)),
91
+ ...this.fileOptions
92
+ }
93
+ };
94
+ }
95
+ /**
96
+ * Creates a new LogFileRotationTransport instance.
97
+ * @param params - Configuration options for the transport
98
+ * @throws {Error} If the filename is already in use by another transport instance
99
+ */
100
+ constructor(params) {
101
+ super(params);
102
+ if (_LogFileRotationTransport.activeFilenames.has(params.filename)) {
103
+ throw new Error(
104
+ `LogFileRotationTransport: Filename "${params.filename}" is already in use by another instance. To use the same file for multiple loggers, share the same transport instance between them.`
105
+ );
106
+ }
107
+ this.filename = params.filename;
108
+ _LogFileRotationTransport.activeFilenames.add(this.filename);
109
+ this.fieldNames = {
110
+ level: _nullishCoalesce(_optionalChain([params, 'access', _4 => _4.fieldNames, 'optionalAccess', _5 => _5.level]), () => ( "level")),
111
+ message: _nullishCoalesce(_optionalChain([params, 'access', _6 => _6.fieldNames, 'optionalAccess', _7 => _7.message]), () => ( "message")),
112
+ timestamp: _nullishCoalesce(_optionalChain([params, 'access', _8 => _8.fieldNames, 'optionalAccess', _9 => _9.timestamp]), () => ( "timestamp"))
113
+ };
114
+ this.delimiter = _nullishCoalesce(params.delimiter, () => ( "\n"));
115
+ this.timestampFn = _nullishCoalesce(params.timestampFn, () => ( (() => (/* @__PURE__ */ new Date()).toISOString())));
116
+ this.levelMap = _nullishCoalesce(params.levelMap, () => ( {}));
117
+ this.compressOnRotate = _nullishCoalesce(params.compressOnRotate, () => ( false));
118
+ this.isCompressing = false;
119
+ this.batchEnabled = !!params.batch;
120
+ this.batchSize = _nullishCoalesce(_optionalChain([params, 'access', _10 => _10.batch, 'optionalAccess', _11 => _11.size]), () => ( 1e3));
121
+ this.batchTimeout = _nullishCoalesce(_optionalChain([params, 'access', _12 => _12.batch, 'optionalAccess', _13 => _13.timeout]), () => ( 5e3));
122
+ this.batchQueue = [];
123
+ this.batchTimer = null;
124
+ this.isDisposing = false;
125
+ this.callbacks = params.callbacks;
126
+ this.frequency = params.frequency;
127
+ this.verbose = params.verbose;
128
+ this.dateFormat = params.dateFormat;
129
+ this.size = params.size;
130
+ this.maxLogs = params.maxLogs;
131
+ this.auditFile = params.auditFile;
132
+ this.extension = params.extension;
133
+ this.createSymlink = params.createSymlink;
134
+ this.symlinkName = params.symlinkName;
135
+ this.utc = params.utc;
136
+ this.auditHashType = params.auditHashType;
137
+ this.fileOptions = params.fileOptions;
138
+ this.fileMode = params.fileMode;
139
+ if (this.batchEnabled) {
140
+ process.on("beforeExit", () => {
141
+ if (!this.isDisposing) {
142
+ this.flush();
143
+ }
144
+ });
145
+ const handleSignal = (signal) => {
146
+ if (!this.isDisposing) {
147
+ this.flushSync();
148
+ _LogFileRotationTransport.activeFilenames.delete(this.filename);
149
+ process.exit(signal === "SIGINT" ? 130 : 143);
150
+ }
151
+ };
152
+ process.on("SIGINT", () => handleSignal("SIGINT"));
153
+ process.on("SIGTERM", () => handleSignal("SIGTERM"));
154
+ }
155
+ if (!this.batchEnabled) {
156
+ this.initStream(this.getRotatorOptions());
157
+ }
158
+ }
159
+ /**
160
+ * Initializes the write stream and sets up event listeners.
161
+ * This is called either immediately if batching is disabled,
162
+ * or lazily when the first batch needs to be written if batching is enabled.
163
+ * @param options - Options for the file stream rotator
164
+ * @private
165
+ */
166
+ initStream(options) {
167
+ this.stream = _filestreamrotator2.default.getStream(options);
168
+ if (this.callbacks) {
169
+ const { onRotate, onNew, onOpen, onClose, onError, onFinish, onLogRemoved } = this.callbacks;
170
+ if (this.compressOnRotate) {
171
+ this.stream.on("rotate", async (oldFile, newFile) => {
172
+ try {
173
+ this.isCompressing = true;
174
+ const compressedPath = await this.compressFile(oldFile);
175
+ await _promises.unlink.call(void 0, oldFile);
176
+ _optionalChain([onRotate, 'optionalCall', _14 => _14(compressedPath, newFile)]);
177
+ } catch (error) {
178
+ _optionalChain([this, 'access', _15 => _15.callbacks, 'optionalAccess', _16 => _16.onError, 'optionalCall', _17 => _17(error)]);
179
+ } finally {
180
+ this.isCompressing = false;
181
+ }
182
+ });
183
+ } else if (onRotate) {
184
+ this.stream.on("rotate", onRotate);
185
+ }
186
+ if (onNew) {
187
+ this.stream.on("new", onNew);
188
+ }
189
+ if (onOpen) {
190
+ this.stream.on("open", onOpen);
191
+ }
192
+ if (onClose) {
193
+ this.stream.on("close", onClose);
194
+ }
195
+ if (onError) {
196
+ this.stream.on("error", onError);
197
+ }
198
+ if (onFinish) {
199
+ this.stream.on("finish", onFinish);
200
+ }
201
+ if (onLogRemoved) {
202
+ this.stream.on("logRemoved", onLogRemoved);
203
+ }
204
+ }
205
+ }
206
+ /**
207
+ * Generates a unique path for a compressed log file.
208
+ * If a file with .gz extension already exists, appends timestamp and counter.
209
+ * @param filePath - The original log file path
210
+ * @returns The unique path for the compressed file
211
+ * @private
212
+ */
213
+ async getUniqueCompressedFilePath(filePath) {
214
+ let finalPath = `${filePath}.gz`;
215
+ let counter = 0;
216
+ try {
217
+ while (true) {
218
+ try {
219
+ await _promises.access.call(void 0, finalPath);
220
+ counter++;
221
+ finalPath = `${filePath}.${Date.now()}.${counter}.gz`;
222
+ } catch (e) {
223
+ break;
224
+ }
225
+ }
226
+ } catch (error) {
227
+ finalPath = `${filePath}.${Date.now()}.gz`;
228
+ }
229
+ return finalPath;
230
+ }
231
+ /**
232
+ * Compresses a log file using gzip.
233
+ * @param filePath - Path to the file to compress
234
+ * @returns Path to the compressed file
235
+ * @private
236
+ */
237
+ async compressFile(filePath) {
238
+ const gzPath = await this.getUniqueCompressedFilePath(filePath);
239
+ const gzip = _zlib.createGzip.call(void 0, );
240
+ const source = _fs.createReadStream.call(void 0, filePath);
241
+ const destination = _fs.createWriteStream.call(void 0, gzPath);
242
+ await _promises3.pipeline.call(void 0, source, gzip, destination);
243
+ return gzPath;
244
+ }
245
+ /**
246
+ * Flushes queued log entries to disk asynchronously.
247
+ * This is used for normal batch processing operations.
248
+ * @private
249
+ */
250
+ flush() {
251
+ if (!this.batchEnabled || this.batchQueue.length === 0) {
252
+ return;
253
+ }
254
+ if (this.batchTimer) {
255
+ clearTimeout(this.batchTimer);
256
+ this.batchTimer = null;
257
+ }
258
+ if (!this.stream) {
259
+ this.initStream(this.getRotatorOptions());
260
+ }
261
+ const batchContent = this.batchQueue.join("");
262
+ this.stream.write(batchContent);
263
+ this.batchQueue = [];
264
+ }
265
+ /**
266
+ * Synchronously flush logs to disk.
267
+ * This is used during process termination (SIGINT/SIGTERM) to ensure logs are written
268
+ * before the process exits. This method uses synchronous file I/O to guarantee that
269
+ * logs are written even during abrupt process termination.
270
+ * @private
271
+ */
272
+ flushSync() {
273
+ if (!this.batchEnabled || this.batchQueue.length === 0) {
274
+ return;
275
+ }
276
+ if (this.batchTimer) {
277
+ clearTimeout(this.batchTimer);
278
+ this.batchTimer = null;
279
+ }
280
+ if (!this.stream) {
281
+ this.initStream(this.getRotatorOptions());
282
+ }
283
+ const batchContent = this.batchQueue.join("");
284
+ const rotator = this.stream;
285
+ if (rotator.currentFile) {
286
+ _fs.writeFileSync.call(void 0, rotator.currentFile, batchContent, { flag: "a" });
287
+ }
288
+ this.batchQueue = [];
289
+ }
290
+ /**
291
+ * Schedules a batch flush operation.
292
+ * This creates a timer that will flush the batch after the configured timeout.
293
+ * The timer is unref'd to prevent keeping the process alive.
294
+ * @private
295
+ */
296
+ scheduleBatchFlush() {
297
+ if (!this.batchTimer && !this.isDisposing) {
298
+ this.batchTimer = setTimeout(() => {
299
+ this.flush();
300
+ }, this.batchTimeout);
301
+ if (this.batchTimer.unref) {
302
+ this.batchTimer.unref();
303
+ }
304
+ }
305
+ }
306
+ /**
307
+ * Processes and writes a log entry.
308
+ * If batching is enabled, the entry is queued and written based on batch settings.
309
+ * If batching is disabled, the entry is written immediately.
310
+ * @param params - The log entry parameters
311
+ * @returns The original messages array
312
+ */
313
+ shipToLogger({ logLevel, messages, data, hasData }) {
314
+ const logEntry = {
315
+ [this.fieldNames.level]: _nullishCoalesce(this.levelMap[logLevel], () => ( logLevel)),
316
+ [this.fieldNames.message]: messages.join(" ") || "",
317
+ [this.fieldNames.timestamp]: this.timestampFn(),
318
+ ...hasData ? data : {}
319
+ };
320
+ const logString = `${JSON.stringify(logEntry)}${this.delimiter}`;
321
+ if (this.batchEnabled) {
322
+ this.batchQueue.push(logString);
323
+ if (this.batchQueue.length >= this.batchSize) {
324
+ this.flush();
325
+ } else {
326
+ this.scheduleBatchFlush();
327
+ }
328
+ } else {
329
+ this.stream.write(logString);
330
+ }
331
+ return messages;
332
+ }
333
+ /**
334
+ * Disposes of the transport, cleaning up resources and flushing any remaining logs.
335
+ * This method:
336
+ * 1. Prevents new batch flushes from being scheduled
337
+ * 2. Cancels any pending batch flush
338
+ * 3. Flushes any remaining logs
339
+ * 4. Waits for any in-progress compression to complete
340
+ * 5. Closes the write stream
341
+ * 6. Removes the filename from the registry
342
+ */
343
+ [Symbol.dispose]() {
344
+ if (this.stream || this.batchEnabled) {
345
+ this.isDisposing = true;
346
+ if (this.batchTimer) {
347
+ clearTimeout(this.batchTimer);
348
+ this.batchTimer = null;
349
+ }
350
+ if (this.batchEnabled) {
351
+ this.flush();
352
+ }
353
+ const checkAndEnd = () => {
354
+ if (!this.isCompressing) {
355
+ if (this.stream) {
356
+ this.stream.end();
357
+ }
358
+ _LogFileRotationTransport.activeFilenames.delete(this.filename);
359
+ } else {
360
+ setTimeout(checkAndEnd, 100);
361
+ }
362
+ };
363
+ checkAndEnd();
364
+ }
365
+ }
366
+ }, _class.__initStatic(), _class);
367
+
368
+
369
+ exports.LogFileRotationTransport = LogFileRotationTransport;
370
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["/home/runner/work/loglayer/loglayer/packages/transports/log-file-rotation/dist/index.cjs","../src/LogFileRotationTransport.ts"],"names":[],"mappings":"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","file":"/home/runner/work/loglayer/loglayer/packages/transports/log-file-rotation/dist/index.cjs","sourcesContent":[null,"import type { WriteStream } from \"node:fs\";\nimport { createReadStream, createWriteStream, writeFileSync } from \"node:fs\";\nimport { access, unlink } from \"node:fs/promises\";\nimport { pipeline } from \"node:stream/promises\";\nimport { createGzip } from \"node:zlib\";\nimport type { LogLayerTransportParams, LoggerlessTransportConfig } from \"@loglayer/transport\";\nimport { LoggerlessTransport } from \"@loglayer/transport\";\nimport FileStreamRotator from \"file-stream-rotator\";\n\ninterface FileStreamRotatorOptions {\n filename: string;\n frequency?: string;\n verbose?: boolean;\n date_format?: string;\n size?: string;\n max_logs?: string;\n audit_file?: string;\n end_stream?: boolean;\n extension?: string;\n create_symlink?: boolean;\n symlink_name?: string;\n utc?: boolean;\n audit_hash_type?: \"md5\" | \"sha256\";\n file_options?: {\n flags?: string;\n encoding?: string;\n mode?: number;\n };\n}\n\nexport interface LogFileRotationCallbacks {\n /**\n * Called when a log file is rotated\n * @param oldFile - The path to the old log file\n * @param newFile - The path to the new log file\n */\n onRotate?: (oldFile: string, newFile: string) => void;\n /**\n * Called when a new log file is created\n * @param newFile - The path to the new log file\n */\n onNew?: (newFile: string) => void;\n /**\n * Called when a log file is opened\n */\n onOpen?: () => void;\n /**\n * Called when a log file is closed\n */\n onClose?: () => void;\n /**\n * Called when an error occurs\n * @param error - The error that occurred\n */\n onError?: (error: Error) => void;\n /**\n * Called when the stream is finished\n */\n onFinish?: () => void;\n /**\n * Called when a log file is removed due to retention policy\n * @param info - Information about the removed log file\n */\n onLogRemoved?: (info: { date: number; name: string; hash: string }) => void;\n}\n\nexport interface LogFileRotationFieldNames {\n /**\n * Field name for the log level\n * @default \"level\"\n */\n level?: string;\n /**\n * Field name for the log message\n * @default \"message\"\n */\n message?: string;\n /**\n * Field name for the timestamp\n * @default \"timestamp\"\n */\n timestamp?: string;\n}\n\nexport interface LogFileRotationLevelMap {\n /**\n * Mapping for the 'fatal' log level\n * @example 60 or \"FATAL\"\n */\n fatal?: string | number;\n /**\n * Mapping for the 'error' log level\n * @example 50 or \"ERROR\"\n */\n error?: string | number;\n /**\n * Mapping for the 'warn' log level\n * @example 40 or \"WARN\"\n */\n warn?: string | number;\n /**\n * Mapping for the 'info' log level\n * @example 30 or \"INFO\"\n */\n info?: string | number;\n /**\n * Mapping for the 'debug' log level\n * @example 20 or \"DEBUG\"\n */\n debug?: string | number;\n /**\n * Mapping for the 'trace' log level\n * @example 10 or \"TRACE\"\n */\n trace?: string | number;\n}\n\nexport interface LogFileRotationBatchConfig {\n /**\n * Maximum number of log entries to queue before writing.\n * Default: 1000\n */\n size?: number;\n /**\n * Maximum time in milliseconds to wait before writing queued logs.\n * Default: 5000 (5 seconds)\n */\n timeout?: number;\n}\n\nexport interface LogFileRotationTransportConfig extends LoggerlessTransportConfig {\n /**\n * The filename pattern to use for the log files.\n * Supports date format using numerical values.\n * Example: \"./logs/application-%DATE%.log\"\n */\n filename: string;\n /**\n * The frequency of rotation. Can be:\n * - 'daily' for daily rotation\n * - 'date' for rotation on date format change\n * - '[1-30]m' for rotation every X minutes\n * - '[1-12]h' for rotation every X hours\n */\n frequency?: string;\n /**\n * The date format to use in the filename.\n * Uses single characters for each date component:\n * - 'Y' for full year\n * - 'M' for month\n * - 'D' for day\n * - 'H' for hour\n * - 'm' for minutes\n * - 's' for seconds\n *\n * Common patterns:\n * - For daily rotation: use \"YMD\" (creates files like app-20240117.log)\n * - For hourly/minute rotation: use \"YMDHm\" (creates files like app-202401171430.log)\n *\n * @default \"YMD\"\n */\n dateFormat?: string;\n /**\n * The size at which to rotate.\n * Examples: \"10M\", \"100K\", \"100B\"\n * If frequency is specified, this will be ignored.\n */\n size?: string;\n /**\n * Maximum number of logs to keep.\n * Can be a number of files or days (e.g., \"10d\" for 10 days)\n */\n maxLogs?: string | number;\n /**\n * Location to store the log audit file.\n * If not set, it will be stored in the root of the application.\n */\n auditFile?: string;\n /**\n * File extension to be appended to the filename.\n * Useful when using size restrictions as the rotation adds a count at the end.\n */\n extension?: string;\n /**\n * Create a tailable symlink to the current active log file.\n * Default: false\n */\n createSymlink?: boolean;\n /**\n * Name to use when creating the symbolic link.\n * Default: 'current.log'\n */\n symlinkName?: string;\n /**\n * Use UTC time for date in filename.\n * Default: false\n */\n utc?: boolean;\n /**\n * Use specified hashing algorithm for audit.\n * Default: 'md5'\n * Use 'sha256' for FIPS compliance.\n */\n auditHashType?: \"md5\" | \"sha256\";\n /**\n * File mode to be used when creating log files.\n * Default: 0o640 (user read/write, group read, others none)\n */\n fileMode?: number;\n /**\n * Options passed to the file stream.\n * See: https://nodejs.org/api/fs.html#fs_fs_createwritestream_path_options\n */\n fileOptions?: {\n flags?: string;\n encoding?: string;\n mode?: number;\n };\n /**\n * Event callbacks for various file stream events\n */\n callbacks?: LogFileRotationCallbacks;\n /**\n * Custom field names for the log entry JSON\n * Default: { level: \"level\", message: \"message\", data: \"data\", timestamp: \"timestamp\" }\n */\n fieldNames?: LogFileRotationFieldNames;\n /**\n * Delimiter between log entries.\n * Default: \"\\n\"\n */\n delimiter?: string;\n /**\n * Custom function to generate timestamps for log entries.\n * Can return either a string (e.g., ISO string) or a number (e.g., Unix timestamp)\n * If not provided, defaults to new Date().toISOString()\n */\n timestampFn?: () => string | number;\n /**\n * Custom mapping for log levels.\n * Each log level can be mapped to either a string or number.\n * Example: { error: 50, warn: 40, info: 30, debug: 20, trace: 10, fatal: 60 }\n * Example: { error: \"ERROR\", warn: \"WARN\", info: \"INFO\", debug: \"DEBUG\", trace: \"TRACE\", fatal: \"FATAL\" }\n */\n levelMap?: LogFileRotationLevelMap;\n /**\n * Whether to compress rotated log files using gzip.\n * When enabled, rotated files will be compressed with .gz extension.\n * Default: false\n */\n compressOnRotate?: boolean;\n /**\n * Whether to enable verbose mode in the underlying file-stream-rotator.\n * When enabled, the rotator will log detailed information about its operations.\n * Default: false\n */\n verbose?: boolean;\n /**\n * Batch processing configuration.\n * If defined, batch processing will be enabled.\n * When batching is enabled, logs are queued in memory and written to disk in batches.\n * Queued logs are automatically flushed in the following situations:\n * - When the batch size is reached\n * - When the batch timeout is reached\n * - When the transport is disposed\n * - When the process exits (including SIGINT and SIGTERM signals)\n */\n batch?: LogFileRotationBatchConfig;\n}\n\n/**\n * A transport that writes logs to rotating files with support for time-based and size-based rotation.\n * Features include:\n * - Automatic log file rotation based on time (hourly, daily) or size\n * - Support for date patterns in filenames using numerical values (YYYY, MM, DD, etc.)\n * - Size-based rotation with support for KB, MB, and GB units\n * - Compression of rotated log files using gzip\n * - Maximum file count or age-based retention\n * - Automatic cleanup of old log files\n * - Batch processing of logs for improved performance\n * - Safe handling of process termination signals\n *\n * Each instance must have a unique filename to prevent race conditions.\n * If you need multiple loggers to write to the same file, share the same transport instance between them.\n */\nexport class LogFileRotationTransport extends LoggerlessTransport implements Disposable {\n /** Registry of active filenames to prevent multiple transports writing to the same file */\n private static activeFilenames = new Set<string>();\n /** The current write stream for the log file */\n private stream: WriteStream;\n /** Custom field names for log entries */\n private fieldNames: Required<LogFileRotationFieldNames>;\n /** Delimiter between log entries */\n private delimiter: string;\n /** Function to generate timestamps for log entries */\n private timestampFn: () => string | number;\n /** Custom mapping for log levels */\n private levelMap: LogFileRotationLevelMap;\n /** Whether to compress rotated files */\n private compressOnRotate: boolean;\n /** Whether a file is currently being compressed */\n private isCompressing: boolean;\n /** The base filename pattern for log files */\n private filename: string;\n /** Whether batch processing is enabled */\n private batchEnabled: boolean;\n /** Maximum number of log entries to queue before writing */\n private batchSize: number;\n /** Maximum time in milliseconds to wait before writing queued logs */\n private batchTimeout: number;\n /** Queue of log entries waiting to be written */\n private batchQueue: string[];\n /** Timer for batch flush timeout */\n private batchTimer: NodeJS.Timeout | null;\n /** Whether the transport is being disposed */\n private isDisposing: boolean;\n /** Event callbacks for various file stream events */\n private callbacks?: LogFileRotationCallbacks;\n /** Frequency of rotation (daily, hourly, etc.) */\n private frequency?: string;\n /** Whether to enable verbose mode */\n private verbose?: boolean;\n /** Date format for filename patterns */\n private dateFormat?: string;\n /** Size threshold for rotation */\n private size?: string;\n /** Maximum number of log files to keep */\n private maxLogs?: string | number;\n /** Path to the audit file */\n private auditFile?: string;\n /** File extension for log files */\n private extension?: string;\n /** Whether to create a symlink to current log */\n private createSymlink?: boolean;\n /** Name of the symlink file */\n private symlinkName?: string;\n /** Whether to use UTC time in filenames */\n private utc?: boolean;\n /** Hash algorithm for audit file */\n private auditHashType?: \"md5\" | \"sha256\";\n /** Options for file streams */\n private fileOptions?: {\n flags?: string;\n encoding?: string;\n mode?: number;\n };\n /** File mode to be used when creating log files */\n private fileMode?: number;\n\n /**\n * Generates the options for FileStreamRotator consistently across the transport\n * @returns FileStreamRotatorOptions object\n * @private\n */\n private getRotatorOptions(): FileStreamRotatorOptions {\n return {\n filename: this.filename,\n frequency: this.frequency,\n verbose: this.verbose ?? false,\n date_format: this.dateFormat,\n size: this.size,\n max_logs: this.maxLogs?.toString(),\n audit_file: this.auditFile || undefined,\n end_stream: true,\n extension: this.extension,\n create_symlink: this.createSymlink,\n symlink_name: this.symlinkName,\n utc: this.utc,\n audit_hash_type: this.auditHashType,\n file_options: {\n flags: \"a\",\n encoding: \"utf8\",\n mode: this.fileMode ?? 0o640,\n ...this.fileOptions,\n },\n };\n }\n\n /**\n * Creates a new LogFileRotationTransport instance.\n * @param params - Configuration options for the transport\n * @throws {Error} If the filename is already in use by another transport instance\n */\n constructor(params: LogFileRotationTransportConfig) {\n super(params);\n\n // Check if filename is already in use\n if (LogFileRotationTransport.activeFilenames.has(params.filename)) {\n throw new Error(\n `LogFileRotationTransport: Filename \"${params.filename}\" is already in use by another instance. To use the same file for multiple loggers, share the same transport instance between them.`,\n );\n }\n\n // Register the filename\n this.filename = params.filename;\n LogFileRotationTransport.activeFilenames.add(this.filename);\n\n // Set up field names with defaults\n this.fieldNames = {\n level: params.fieldNames?.level ?? \"level\",\n message: params.fieldNames?.message ?? \"message\",\n timestamp: params.fieldNames?.timestamp ?? \"timestamp\",\n };\n\n // Set up delimiter\n this.delimiter = params.delimiter ?? \"\\n\";\n\n // Set up timestamp function\n this.timestampFn = params.timestampFn ?? (() => new Date().toISOString());\n\n // Set up level mapping\n this.levelMap = params.levelMap ?? {};\n\n // Set up compression\n this.compressOnRotate = params.compressOnRotate ?? false;\n this.isCompressing = false;\n\n // Set up batching\n this.batchEnabled = !!params.batch;\n this.batchSize = params.batch?.size ?? 1000;\n this.batchTimeout = params.batch?.timeout ?? 5000;\n this.batchQueue = [];\n this.batchTimer = null;\n this.isDisposing = false;\n\n // Store other options\n this.callbacks = params.callbacks;\n this.frequency = params.frequency;\n this.verbose = params.verbose;\n this.dateFormat = params.dateFormat;\n this.size = params.size;\n this.maxLogs = params.maxLogs;\n this.auditFile = params.auditFile;\n this.extension = params.extension;\n this.createSymlink = params.createSymlink;\n this.symlinkName = params.symlinkName;\n this.utc = params.utc;\n this.auditHashType = params.auditHashType;\n this.fileOptions = params.fileOptions;\n this.fileMode = params.fileMode;\n\n // Set up exit handler for flushing\n if (this.batchEnabled) {\n // Handle normal process exit\n process.on(\"beforeExit\", () => {\n if (!this.isDisposing) {\n this.flush();\n }\n });\n\n // Handle SIGINT (Ctrl+C) and SIGTERM\n const handleSignal = (signal: string) => {\n if (!this.isDisposing) {\n // Synchronously flush logs to ensure they're written before exit\n this.flushSync();\n // Remove the filename from registry\n LogFileRotationTransport.activeFilenames.delete(this.filename);\n // Exit with the original signal\n process.exit(signal === \"SIGINT\" ? 130 : 143);\n }\n };\n\n process.on(\"SIGINT\", () => handleSignal(\"SIGINT\"));\n process.on(\"SIGTERM\", () => handleSignal(\"SIGTERM\"));\n }\n\n // Only create the stream if not in batch mode or if we have logs to write\n if (!this.batchEnabled) {\n this.initStream(this.getRotatorOptions());\n }\n }\n\n /**\n * Initializes the write stream and sets up event listeners.\n * This is called either immediately if batching is disabled,\n * or lazily when the first batch needs to be written if batching is enabled.\n * @param options - Options for the file stream rotator\n * @private\n */\n private initStream(options: FileStreamRotatorOptions): void {\n // FileStreamRotator.getStream() returns a WriteStream-compatible object\n this.stream = FileStreamRotator.getStream(options) as unknown as WriteStream;\n\n // Set up event listeners if callbacks are provided\n if (this.callbacks) {\n const { onRotate, onNew, onOpen, onClose, onError, onFinish, onLogRemoved } = this.callbacks;\n\n // Wrap the onRotate callback to handle compression\n if (this.compressOnRotate) {\n this.stream.on(\"rotate\", async (oldFile: string, newFile: string) => {\n try {\n this.isCompressing = true;\n const compressedPath = await this.compressFile(oldFile);\n await unlink(oldFile);\n onRotate?.(compressedPath, newFile);\n } catch (error) {\n this.callbacks?.onError?.(error as Error);\n } finally {\n this.isCompressing = false;\n }\n });\n } else if (onRotate) {\n this.stream.on(\"rotate\", onRotate);\n }\n\n if (onNew) {\n this.stream.on(\"new\", onNew);\n }\n if (onOpen) {\n this.stream.on(\"open\", onOpen);\n }\n if (onClose) {\n this.stream.on(\"close\", onClose);\n }\n if (onError) {\n this.stream.on(\"error\", onError);\n }\n if (onFinish) {\n this.stream.on(\"finish\", onFinish);\n }\n if (onLogRemoved) {\n this.stream.on(\"logRemoved\", onLogRemoved);\n }\n }\n }\n\n /**\n * Generates a unique path for a compressed log file.\n * If a file with .gz extension already exists, appends timestamp and counter.\n * @param filePath - The original log file path\n * @returns The unique path for the compressed file\n * @private\n */\n private async getUniqueCompressedFilePath(filePath: string): Promise<string> {\n let finalPath = `${filePath}.gz`;\n let counter = 0;\n\n try {\n while (true) {\n try {\n await access(finalPath);\n counter++;\n finalPath = `${filePath}.${Date.now()}.${counter}.gz`;\n } catch {\n break;\n }\n }\n } catch (error) {\n finalPath = `${filePath}.${Date.now()}.gz`;\n }\n\n return finalPath;\n }\n\n /**\n * Compresses a log file using gzip.\n * @param filePath - Path to the file to compress\n * @returns Path to the compressed file\n * @private\n */\n private async compressFile(filePath: string): Promise<string> {\n const gzPath = await this.getUniqueCompressedFilePath(filePath);\n const gzip = createGzip();\n const source = createReadStream(filePath);\n const destination = createWriteStream(gzPath);\n\n await pipeline(source, gzip, destination);\n return gzPath;\n }\n\n /**\n * Flushes queued log entries to disk asynchronously.\n * This is used for normal batch processing operations.\n * @private\n */\n private flush(): void {\n if (!this.batchEnabled || this.batchQueue.length === 0) {\n return;\n }\n\n if (this.batchTimer) {\n clearTimeout(this.batchTimer);\n this.batchTimer = null;\n }\n\n // Initialize stream if it hasn't been created yet\n if (!this.stream) {\n this.initStream(this.getRotatorOptions());\n }\n\n const batchContent = this.batchQueue.join(\"\");\n this.stream.write(batchContent);\n this.batchQueue = [];\n }\n\n /**\n * Synchronously flush logs to disk.\n * This is used during process termination (SIGINT/SIGTERM) to ensure logs are written\n * before the process exits. This method uses synchronous file I/O to guarantee that\n * logs are written even during abrupt process termination.\n * @private\n */\n private flushSync(): void {\n if (!this.batchEnabled || this.batchQueue.length === 0) {\n return;\n }\n\n if (this.batchTimer) {\n clearTimeout(this.batchTimer);\n this.batchTimer = null;\n }\n\n // Initialize stream if it hasn't been created yet\n if (!this.stream) {\n this.initStream(this.getRotatorOptions());\n }\n\n const batchContent = this.batchQueue.join(\"\");\n // Use writeFileSync to ensure logs are written before process exit\n const rotator = this.stream as unknown as { currentFile: string };\n if (rotator.currentFile) {\n writeFileSync(rotator.currentFile, batchContent, { flag: \"a\" });\n }\n this.batchQueue = [];\n }\n\n /**\n * Schedules a batch flush operation.\n * This creates a timer that will flush the batch after the configured timeout.\n * The timer is unref'd to prevent keeping the process alive.\n * @private\n */\n private scheduleBatchFlush(): void {\n if (!this.batchTimer && !this.isDisposing) {\n this.batchTimer = setTimeout(() => {\n this.flush();\n }, this.batchTimeout);\n\n // Prevent timer from keeping the process alive\n if (this.batchTimer.unref) {\n this.batchTimer.unref();\n }\n }\n }\n\n /**\n * Processes and writes a log entry.\n * If batching is enabled, the entry is queued and written based on batch settings.\n * If batching is disabled, the entry is written immediately.\n * @param params - The log entry parameters\n * @returns The original messages array\n */\n shipToLogger({ logLevel, messages, data, hasData }: LogLayerTransportParams) {\n const logEntry = {\n [this.fieldNames.level]: this.levelMap[logLevel as keyof LogFileRotationLevelMap] ?? logLevel,\n [this.fieldNames.message]: messages.join(\" \") || \"\",\n [this.fieldNames.timestamp]: this.timestampFn(),\n ...(hasData ? data : {}),\n };\n\n const logString = `${JSON.stringify(logEntry)}${this.delimiter}`;\n\n if (this.batchEnabled) {\n this.batchQueue.push(logString);\n\n if (this.batchQueue.length >= this.batchSize) {\n this.flush();\n } else {\n this.scheduleBatchFlush();\n }\n } else {\n this.stream.write(logString);\n }\n\n return messages;\n }\n\n /**\n * Disposes of the transport, cleaning up resources and flushing any remaining logs.\n * This method:\n * 1. Prevents new batch flushes from being scheduled\n * 2. Cancels any pending batch flush\n * 3. Flushes any remaining logs\n * 4. Waits for any in-progress compression to complete\n * 5. Closes the write stream\n * 6. Removes the filename from the registry\n */\n [Symbol.dispose](): void {\n if (this.stream || this.batchEnabled) {\n this.isDisposing = true;\n\n if (this.batchTimer) {\n clearTimeout(this.batchTimer);\n this.batchTimer = null;\n }\n\n // Flush any remaining logs\n if (this.batchEnabled) {\n this.flush();\n }\n\n const checkAndEnd = () => {\n if (!this.isCompressing) {\n if (this.stream) {\n this.stream.end();\n }\n // Remove the filename from registry when disposed\n LogFileRotationTransport.activeFilenames.delete(this.filename);\n } else {\n setTimeout(checkAndEnd, 100);\n }\n };\n checkAndEnd();\n }\n }\n}\n"]}