@loglayer/transport-log-file-rotation 2.1.5 → 2.1.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/dist/index.cjs +365 -370
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +398 -391
- package/dist/index.d.ts +398 -391
- package/dist/index.js +336 -370
- package/dist/index.js.map +1 -1
- package/package.json +8 -8
package/dist/index.cjs
CHANGED
|
@@ -1,374 +1,369 @@
|
|
|
1
|
-
|
|
2
|
-
var
|
|
3
|
-
var
|
|
4
|
-
var
|
|
5
|
-
var
|
|
6
|
-
var
|
|
7
|
-
var
|
|
8
|
-
var
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
/** Whether a file is currently being compressed */
|
|
24
|
-
|
|
25
|
-
/** The base filename pattern for log files */
|
|
26
|
-
|
|
27
|
-
/** Static data to be included in every log entry */
|
|
28
|
-
|
|
29
|
-
/** Whether batch processing is enabled */
|
|
30
|
-
|
|
31
|
-
/** Maximum number of log entries to queue before writing */
|
|
32
|
-
|
|
33
|
-
/** Maximum time in milliseconds to wait before writing queued logs */
|
|
34
|
-
|
|
35
|
-
/** Queue of log entries waiting to be written */
|
|
36
|
-
|
|
37
|
-
/** Timer for batch flush timeout */
|
|
38
|
-
|
|
39
|
-
/** Whether the transport is being disposed */
|
|
40
|
-
|
|
41
|
-
/** Event callbacks for various file stream events */
|
|
42
|
-
|
|
43
|
-
/** Frequency of rotation (daily, hourly, etc.) */
|
|
44
|
-
|
|
45
|
-
/** Whether to enable verbose mode */
|
|
46
|
-
|
|
47
|
-
/** Date format for filename patterns */
|
|
48
|
-
|
|
49
|
-
/** Size threshold for rotation */
|
|
50
|
-
|
|
51
|
-
/** Maximum number of log files to keep */
|
|
52
|
-
|
|
53
|
-
/** Path to the audit file */
|
|
54
|
-
|
|
55
|
-
/** File extension for log files */
|
|
56
|
-
|
|
57
|
-
/** Whether to create a symlink to current log */
|
|
58
|
-
|
|
59
|
-
/** Name of the symlink file */
|
|
60
|
-
|
|
61
|
-
/** Whether to use UTC time in filenames */
|
|
62
|
-
|
|
63
|
-
/** Hash algorithm for audit file */
|
|
64
|
-
|
|
65
|
-
/** Options for file streams */
|
|
66
|
-
|
|
67
|
-
/** File mode to be used when creating log files */
|
|
68
|
-
|
|
69
|
-
/**
|
|
70
|
-
* Generates the options for FileStreamRotator consistently across the transport
|
|
71
|
-
* @returns FileStreamRotatorOptions object
|
|
72
|
-
* @private
|
|
73
|
-
*/
|
|
74
|
-
getRotatorOptions() {
|
|
75
|
-
return {
|
|
76
|
-
filename: this.filename,
|
|
77
|
-
frequency: this.frequency,
|
|
78
|
-
verbose: _nullishCoalesce(this.verbose, () => ( false)),
|
|
79
|
-
date_format: this.dateFormat,
|
|
80
|
-
size: this.size,
|
|
81
|
-
max_logs: _optionalChain([this, 'access', _ => _.maxLogs, 'optionalAccess', _2 => _2.toString, 'call', _3 => _3()]),
|
|
82
|
-
audit_file: this.auditFile || void 0,
|
|
83
|
-
end_stream: true,
|
|
84
|
-
extension: this.extension,
|
|
85
|
-
create_symlink: this.createSymlink,
|
|
86
|
-
symlink_name: this.symlinkName,
|
|
87
|
-
utc: this.utc,
|
|
88
|
-
audit_hash_type: this.auditHashType,
|
|
89
|
-
file_options: {
|
|
90
|
-
flags: "a",
|
|
91
|
-
encoding: "utf8",
|
|
92
|
-
mode: _nullishCoalesce(this.fileMode, () => ( 416)),
|
|
93
|
-
...this.fileOptions
|
|
94
|
-
}
|
|
95
|
-
};
|
|
96
|
-
}
|
|
97
|
-
/**
|
|
98
|
-
* Creates a new LogFileRotationTransport instance.
|
|
99
|
-
* @param params - Configuration options for the transport
|
|
100
|
-
* @throws {Error} If the filename is already in use by another transport instance
|
|
101
|
-
*/
|
|
102
|
-
constructor(params) {
|
|
103
|
-
super(params);
|
|
104
|
-
if (_LogFileRotationTransport.activeFilenames.has(params.filename)) {
|
|
105
|
-
throw new Error(
|
|
106
|
-
`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.`
|
|
107
|
-
);
|
|
108
|
-
}
|
|
109
|
-
this.filename = params.filename;
|
|
110
|
-
_LogFileRotationTransport.activeFilenames.add(this.filename);
|
|
111
|
-
this.fieldNames = {
|
|
112
|
-
level: _nullishCoalesce(_optionalChain([params, 'access', _4 => _4.fieldNames, 'optionalAccess', _5 => _5.level]), () => ( "level")),
|
|
113
|
-
message: _nullishCoalesce(_optionalChain([params, 'access', _6 => _6.fieldNames, 'optionalAccess', _7 => _7.message]), () => ( "message")),
|
|
114
|
-
timestamp: _nullishCoalesce(_optionalChain([params, 'access', _8 => _8.fieldNames, 'optionalAccess', _9 => _9.timestamp]), () => ( "timestamp"))
|
|
115
|
-
};
|
|
116
|
-
this.delimiter = _nullishCoalesce(params.delimiter, () => ( "\n"));
|
|
117
|
-
this.timestampFn = _nullishCoalesce(params.timestampFn, () => ( (() => (/* @__PURE__ */ new Date()).toISOString())));
|
|
118
|
-
this.levelMap = _nullishCoalesce(params.levelMap, () => ( {}));
|
|
119
|
-
this.compressOnRotate = _nullishCoalesce(params.compressOnRotate, () => ( false));
|
|
120
|
-
this.isCompressing = false;
|
|
121
|
-
this.batchEnabled = !!params.batch;
|
|
122
|
-
this.batchSize = _nullishCoalesce(_optionalChain([params, 'access', _10 => _10.batch, 'optionalAccess', _11 => _11.size]), () => ( 1e3));
|
|
123
|
-
this.batchTimeout = _nullishCoalesce(_optionalChain([params, 'access', _12 => _12.batch, 'optionalAccess', _13 => _13.timeout]), () => ( 5e3));
|
|
124
|
-
this.batchQueue = [];
|
|
125
|
-
this.batchTimer = null;
|
|
126
|
-
this.isDisposing = false;
|
|
127
|
-
this.callbacks = params.callbacks;
|
|
128
|
-
this.frequency = params.frequency;
|
|
129
|
-
this.verbose = params.verbose;
|
|
130
|
-
this.dateFormat = params.dateFormat;
|
|
131
|
-
this.size = params.size;
|
|
132
|
-
this.maxLogs = params.maxLogs;
|
|
133
|
-
this.auditFile = params.auditFile;
|
|
134
|
-
this.extension = params.extension;
|
|
135
|
-
this.createSymlink = params.createSymlink;
|
|
136
|
-
this.symlinkName = params.symlinkName;
|
|
137
|
-
this.utc = params.utc;
|
|
138
|
-
this.auditHashType = params.auditHashType;
|
|
139
|
-
this.fileOptions = params.fileOptions;
|
|
140
|
-
this.fileMode = params.fileMode;
|
|
141
|
-
this.staticData = params.staticData;
|
|
142
|
-
if (this.batchEnabled) {
|
|
143
|
-
process.on("beforeExit", () => {
|
|
144
|
-
if (!this.isDisposing) {
|
|
145
|
-
this.flush();
|
|
146
|
-
}
|
|
147
|
-
});
|
|
148
|
-
const handleSignal = (signal) => {
|
|
149
|
-
if (!this.isDisposing) {
|
|
150
|
-
this.flushSync();
|
|
151
|
-
_LogFileRotationTransport.activeFilenames.delete(this.filename);
|
|
152
|
-
process.exit(signal === "SIGINT" ? 130 : 143);
|
|
153
|
-
}
|
|
154
|
-
};
|
|
155
|
-
process.on("SIGINT", () => handleSignal("SIGINT"));
|
|
156
|
-
process.on("SIGTERM", () => handleSignal("SIGTERM"));
|
|
157
|
-
}
|
|
158
|
-
if (!this.batchEnabled) {
|
|
159
|
-
this.initStream(this.getRotatorOptions());
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
/**
|
|
163
|
-
* Initializes the write stream and sets up event listeners.
|
|
164
|
-
* This is called either immediately if batching is disabled,
|
|
165
|
-
* or lazily when the first batch needs to be written if batching is enabled.
|
|
166
|
-
* @param options - Options for the file stream rotator
|
|
167
|
-
* @private
|
|
168
|
-
*/
|
|
169
|
-
initStream(options) {
|
|
170
|
-
this.stream = _filestreamrotator2.default.getStream(options);
|
|
171
|
-
if (this.callbacks) {
|
|
172
|
-
const { onRotate, onNew, onOpen, onClose, onError, onFinish, onLogRemoved } = this.callbacks;
|
|
173
|
-
if (this.compressOnRotate) {
|
|
174
|
-
this.stream.on("rotate", async (oldFile, newFile) => {
|
|
175
|
-
try {
|
|
176
|
-
this.isCompressing = true;
|
|
177
|
-
const compressedPath = await this.compressFile(oldFile);
|
|
178
|
-
await _promises.unlink.call(void 0, oldFile);
|
|
179
|
-
_optionalChain([onRotate, 'optionalCall', _14 => _14(compressedPath, newFile)]);
|
|
180
|
-
} catch (error) {
|
|
181
|
-
_optionalChain([this, 'access', _15 => _15.callbacks, 'optionalAccess', _16 => _16.onError, 'optionalCall', _17 => _17(error)]);
|
|
182
|
-
} finally {
|
|
183
|
-
this.isCompressing = false;
|
|
184
|
-
}
|
|
185
|
-
});
|
|
186
|
-
} else if (onRotate) {
|
|
187
|
-
this.stream.on("rotate", onRotate);
|
|
188
|
-
}
|
|
189
|
-
if (onNew) {
|
|
190
|
-
this.stream.on("new", onNew);
|
|
191
|
-
}
|
|
192
|
-
if (onOpen) {
|
|
193
|
-
this.stream.on("open", onOpen);
|
|
194
|
-
}
|
|
195
|
-
if (onClose) {
|
|
196
|
-
this.stream.on("close", onClose);
|
|
197
|
-
}
|
|
198
|
-
if (onError) {
|
|
199
|
-
this.stream.on("error", onError);
|
|
200
|
-
}
|
|
201
|
-
if (onFinish) {
|
|
202
|
-
this.stream.on("finish", onFinish);
|
|
203
|
-
}
|
|
204
|
-
if (onLogRemoved) {
|
|
205
|
-
this.stream.on("logRemoved", onLogRemoved);
|
|
206
|
-
}
|
|
207
|
-
}
|
|
208
|
-
}
|
|
209
|
-
/**
|
|
210
|
-
* Generates a unique path for a compressed log file.
|
|
211
|
-
* If a file with .gz extension already exists, appends timestamp and counter.
|
|
212
|
-
* @param filePath - The original log file path
|
|
213
|
-
* @returns The unique path for the compressed file
|
|
214
|
-
* @private
|
|
215
|
-
*/
|
|
216
|
-
async getUniqueCompressedFilePath(filePath) {
|
|
217
|
-
let finalPath = `${filePath}.gz`;
|
|
218
|
-
let counter = 0;
|
|
219
|
-
try {
|
|
220
|
-
while (true) {
|
|
221
|
-
try {
|
|
222
|
-
await _promises.access.call(void 0, finalPath);
|
|
223
|
-
counter++;
|
|
224
|
-
finalPath = `${filePath}.${Date.now()}.${counter}.gz`;
|
|
225
|
-
} catch (e) {
|
|
226
|
-
break;
|
|
227
|
-
}
|
|
228
|
-
}
|
|
229
|
-
} catch (_error) {
|
|
230
|
-
finalPath = `${filePath}.${Date.now()}.gz`;
|
|
231
|
-
}
|
|
232
|
-
return finalPath;
|
|
233
|
-
}
|
|
234
|
-
/**
|
|
235
|
-
* Compresses a log file using gzip.
|
|
236
|
-
* @param filePath - Path to the file to compress
|
|
237
|
-
* @returns Path to the compressed file
|
|
238
|
-
* @private
|
|
239
|
-
*/
|
|
240
|
-
async compressFile(filePath) {
|
|
241
|
-
const gzPath = await this.getUniqueCompressedFilePath(filePath);
|
|
242
|
-
const gzip = _zlib.createGzip.call(void 0, );
|
|
243
|
-
const source = _fs.createReadStream.call(void 0, filePath);
|
|
244
|
-
const destination = _fs.createWriteStream.call(void 0, gzPath);
|
|
245
|
-
await _promises3.pipeline.call(void 0, source, gzip, destination);
|
|
246
|
-
return gzPath;
|
|
247
|
-
}
|
|
248
|
-
/**
|
|
249
|
-
* Flushes queued log entries to disk asynchronously.
|
|
250
|
-
* This is used for normal batch processing operations.
|
|
251
|
-
* @private
|
|
252
|
-
*/
|
|
253
|
-
flush() {
|
|
254
|
-
if (!this.batchEnabled || this.batchQueue.length === 0) {
|
|
255
|
-
return;
|
|
256
|
-
}
|
|
257
|
-
if (this.batchTimer) {
|
|
258
|
-
clearTimeout(this.batchTimer);
|
|
259
|
-
this.batchTimer = null;
|
|
260
|
-
}
|
|
261
|
-
if (!this.stream) {
|
|
262
|
-
this.initStream(this.getRotatorOptions());
|
|
263
|
-
}
|
|
264
|
-
const batchContent = this.batchQueue.join("");
|
|
265
|
-
this.stream.write(batchContent);
|
|
266
|
-
this.batchQueue = [];
|
|
267
|
-
}
|
|
268
|
-
/**
|
|
269
|
-
* Synchronously flush logs to disk.
|
|
270
|
-
* This is used during process termination (SIGINT/SIGTERM) to ensure logs are written
|
|
271
|
-
* before the process exits. This method uses synchronous file I/O to guarantee that
|
|
272
|
-
* logs are written even during abrupt process termination.
|
|
273
|
-
* @private
|
|
274
|
-
*/
|
|
275
|
-
flushSync() {
|
|
276
|
-
if (!this.batchEnabled || this.batchQueue.length === 0) {
|
|
277
|
-
return;
|
|
278
|
-
}
|
|
279
|
-
if (this.batchTimer) {
|
|
280
|
-
clearTimeout(this.batchTimer);
|
|
281
|
-
this.batchTimer = null;
|
|
282
|
-
}
|
|
283
|
-
if (!this.stream) {
|
|
284
|
-
this.initStream(this.getRotatorOptions());
|
|
285
|
-
}
|
|
286
|
-
const batchContent = this.batchQueue.join("");
|
|
287
|
-
const rotator = this.stream;
|
|
288
|
-
if (rotator.currentFile) {
|
|
289
|
-
_fs.writeFileSync.call(void 0, rotator.currentFile, batchContent, { flag: "a" });
|
|
290
|
-
}
|
|
291
|
-
this.batchQueue = [];
|
|
292
|
-
}
|
|
293
|
-
/**
|
|
294
|
-
* Schedules a batch flush operation.
|
|
295
|
-
* This creates a timer that will flush the batch after the configured timeout.
|
|
296
|
-
* The timer is unref'd to prevent keeping the process alive.
|
|
297
|
-
* @private
|
|
298
|
-
*/
|
|
299
|
-
scheduleBatchFlush() {
|
|
300
|
-
if (!this.batchTimer && !this.isDisposing) {
|
|
301
|
-
this.batchTimer = setTimeout(() => {
|
|
302
|
-
this.flush();
|
|
303
|
-
}, this.batchTimeout);
|
|
304
|
-
if (this.batchTimer.unref) {
|
|
305
|
-
this.batchTimer.unref();
|
|
306
|
-
}
|
|
307
|
-
}
|
|
308
|
-
}
|
|
309
|
-
/**
|
|
310
|
-
* Processes and writes a log entry.
|
|
311
|
-
* If batching is enabled, the entry is queued and written based on batch settings.
|
|
312
|
-
* If batching is disabled, the entry is written immediately.
|
|
313
|
-
* @param params - The log entry parameters
|
|
314
|
-
* @returns The original messages array
|
|
315
|
-
*/
|
|
316
|
-
shipToLogger({ logLevel, messages, data, hasData }) {
|
|
317
|
-
const logEntry = {
|
|
318
|
-
[this.fieldNames.level]: _nullishCoalesce(this.levelMap[logLevel], () => ( logLevel)),
|
|
319
|
-
[this.fieldNames.message]: messages.join(" ") || "",
|
|
320
|
-
[this.fieldNames.timestamp]: this.timestampFn(),
|
|
321
|
-
...this.staticData ? typeof this.staticData === "function" ? this.staticData() : this.staticData : {},
|
|
322
|
-
...hasData ? data : {}
|
|
323
|
-
};
|
|
324
|
-
const logString = `${JSON.stringify(logEntry)}${this.delimiter}`;
|
|
325
|
-
if (this.batchEnabled) {
|
|
326
|
-
this.batchQueue.push(logString);
|
|
327
|
-
if (this.batchQueue.length >= this.batchSize) {
|
|
328
|
-
this.flush();
|
|
329
|
-
} else {
|
|
330
|
-
this.scheduleBatchFlush();
|
|
331
|
-
}
|
|
332
|
-
} else {
|
|
333
|
-
this.stream.write(logString);
|
|
334
|
-
}
|
|
335
|
-
return messages;
|
|
336
|
-
}
|
|
337
|
-
/**
|
|
338
|
-
* Disposes of the transport, cleaning up resources and flushing any remaining logs.
|
|
339
|
-
* This method:
|
|
340
|
-
* 1. Prevents new batch flushes from being scheduled
|
|
341
|
-
* 2. Cancels any pending batch flush
|
|
342
|
-
* 3. Flushes any remaining logs
|
|
343
|
-
* 4. Waits for any in-progress compression to complete
|
|
344
|
-
* 5. Closes the write stream
|
|
345
|
-
* 6. Removes the filename from the registry
|
|
346
|
-
*/
|
|
347
|
-
[Symbol.dispose]() {
|
|
348
|
-
if (this.stream || this.batchEnabled) {
|
|
349
|
-
this.isDisposing = true;
|
|
350
|
-
if (this.batchTimer) {
|
|
351
|
-
clearTimeout(this.batchTimer);
|
|
352
|
-
this.batchTimer = null;
|
|
353
|
-
}
|
|
354
|
-
if (this.batchEnabled) {
|
|
355
|
-
this.flush();
|
|
356
|
-
}
|
|
357
|
-
const checkAndEnd = () => {
|
|
358
|
-
if (!this.isCompressing) {
|
|
359
|
-
if (this.stream) {
|
|
360
|
-
this.stream.end();
|
|
361
|
-
}
|
|
362
|
-
_LogFileRotationTransport.activeFilenames.delete(this.filename);
|
|
363
|
-
} else {
|
|
364
|
-
setTimeout(checkAndEnd, 100);
|
|
365
|
-
}
|
|
366
|
-
};
|
|
367
|
-
checkAndEnd();
|
|
368
|
-
}
|
|
369
|
-
}
|
|
370
|
-
}, _class.__initStatic(), _class);
|
|
1
|
+
//#region rolldown:runtime
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __copyProps = (to, from, except, desc) => {
|
|
9
|
+
if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
|
|
10
|
+
key = keys[i];
|
|
11
|
+
if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
|
|
12
|
+
get: ((k) => from[k]).bind(null, key),
|
|
13
|
+
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
|
|
19
|
+
value: mod,
|
|
20
|
+
enumerable: true
|
|
21
|
+
}) : target, mod));
|
|
371
22
|
|
|
23
|
+
//#endregion
|
|
24
|
+
let node_fs = require("node:fs");
|
|
25
|
+
node_fs = __toESM(node_fs);
|
|
26
|
+
let node_fs_promises = require("node:fs/promises");
|
|
27
|
+
node_fs_promises = __toESM(node_fs_promises);
|
|
28
|
+
let node_stream_promises = require("node:stream/promises");
|
|
29
|
+
node_stream_promises = __toESM(node_stream_promises);
|
|
30
|
+
let node_zlib = require("node:zlib");
|
|
31
|
+
node_zlib = __toESM(node_zlib);
|
|
32
|
+
let __loglayer_transport = require("@loglayer/transport");
|
|
33
|
+
__loglayer_transport = __toESM(__loglayer_transport);
|
|
34
|
+
let file_stream_rotator = require("file-stream-rotator");
|
|
35
|
+
file_stream_rotator = __toESM(file_stream_rotator);
|
|
372
36
|
|
|
37
|
+
//#region src/LogFileRotationTransport.ts
|
|
38
|
+
/**
|
|
39
|
+
* A transport that writes logs to rotating files with support for time-based and size-based rotation.
|
|
40
|
+
* Features include:
|
|
41
|
+
* - Automatic log file rotation based on time (hourly, daily) or size
|
|
42
|
+
* - Support for date patterns in filenames using numerical values (YYYY, MM, DD, etc.)
|
|
43
|
+
* - Size-based rotation with support for KB, MB, and GB units
|
|
44
|
+
* - Compression of rotated log files using gzip
|
|
45
|
+
* - Maximum file count or age-based retention
|
|
46
|
+
* - Automatic cleanup of old log files
|
|
47
|
+
* - Batch processing of logs for improved performance
|
|
48
|
+
* - Safe handling of process termination signals
|
|
49
|
+
*
|
|
50
|
+
* Each instance must have a unique filename to prevent race conditions.
|
|
51
|
+
* If you need multiple loggers to write to the same file, share the same transport instance between them.
|
|
52
|
+
*/
|
|
53
|
+
var LogFileRotationTransport = class LogFileRotationTransport extends __loglayer_transport.LoggerlessTransport {
|
|
54
|
+
/** Registry of active filenames to prevent multiple transports writing to the same file */
|
|
55
|
+
static activeFilenames = /* @__PURE__ */ new Set();
|
|
56
|
+
/** The current write stream for the log file */
|
|
57
|
+
stream;
|
|
58
|
+
/** Custom field names for log entries */
|
|
59
|
+
fieldNames;
|
|
60
|
+
/** Delimiter between log entries */
|
|
61
|
+
delimiter;
|
|
62
|
+
/** Function to generate timestamps for log entries */
|
|
63
|
+
timestampFn;
|
|
64
|
+
/** Custom mapping for log levels */
|
|
65
|
+
levelMap;
|
|
66
|
+
/** Whether to compress rotated files */
|
|
67
|
+
compressOnRotate;
|
|
68
|
+
/** Whether a file is currently being compressed */
|
|
69
|
+
isCompressing;
|
|
70
|
+
/** The base filename pattern for log files */
|
|
71
|
+
filename;
|
|
72
|
+
/** Static data to be included in every log entry */
|
|
73
|
+
staticData;
|
|
74
|
+
/** Whether batch processing is enabled */
|
|
75
|
+
batchEnabled;
|
|
76
|
+
/** Maximum number of log entries to queue before writing */
|
|
77
|
+
batchSize;
|
|
78
|
+
/** Maximum time in milliseconds to wait before writing queued logs */
|
|
79
|
+
batchTimeout;
|
|
80
|
+
/** Queue of log entries waiting to be written */
|
|
81
|
+
batchQueue;
|
|
82
|
+
/** Timer for batch flush timeout */
|
|
83
|
+
batchTimer;
|
|
84
|
+
/** Whether the transport is being disposed */
|
|
85
|
+
isDisposing;
|
|
86
|
+
/** Event callbacks for various file stream events */
|
|
87
|
+
callbacks;
|
|
88
|
+
/** Frequency of rotation (daily, hourly, etc.) */
|
|
89
|
+
frequency;
|
|
90
|
+
/** Whether to enable verbose mode */
|
|
91
|
+
verbose;
|
|
92
|
+
/** Date format for filename patterns */
|
|
93
|
+
dateFormat;
|
|
94
|
+
/** Size threshold for rotation */
|
|
95
|
+
size;
|
|
96
|
+
/** Maximum number of log files to keep */
|
|
97
|
+
maxLogs;
|
|
98
|
+
/** Path to the audit file */
|
|
99
|
+
auditFile;
|
|
100
|
+
/** File extension for log files */
|
|
101
|
+
extension;
|
|
102
|
+
/** Whether to create a symlink to current log */
|
|
103
|
+
createSymlink;
|
|
104
|
+
/** Name of the symlink file */
|
|
105
|
+
symlinkName;
|
|
106
|
+
/** Whether to use UTC time in filenames */
|
|
107
|
+
utc;
|
|
108
|
+
/** Hash algorithm for audit file */
|
|
109
|
+
auditHashType;
|
|
110
|
+
/** Options for file streams */
|
|
111
|
+
fileOptions;
|
|
112
|
+
/** File mode to be used when creating log files */
|
|
113
|
+
fileMode;
|
|
114
|
+
/**
|
|
115
|
+
* Generates the options for FileStreamRotator consistently across the transport
|
|
116
|
+
* @returns FileStreamRotatorOptions object
|
|
117
|
+
* @private
|
|
118
|
+
*/
|
|
119
|
+
getRotatorOptions() {
|
|
120
|
+
return {
|
|
121
|
+
filename: this.filename,
|
|
122
|
+
frequency: this.frequency,
|
|
123
|
+
verbose: this.verbose ?? false,
|
|
124
|
+
date_format: this.dateFormat,
|
|
125
|
+
size: this.size,
|
|
126
|
+
max_logs: this.maxLogs?.toString(),
|
|
127
|
+
audit_file: this.auditFile || void 0,
|
|
128
|
+
end_stream: true,
|
|
129
|
+
extension: this.extension,
|
|
130
|
+
create_symlink: this.createSymlink,
|
|
131
|
+
symlink_name: this.symlinkName,
|
|
132
|
+
utc: this.utc,
|
|
133
|
+
audit_hash_type: this.auditHashType,
|
|
134
|
+
file_options: {
|
|
135
|
+
flags: "a",
|
|
136
|
+
encoding: "utf8",
|
|
137
|
+
mode: this.fileMode ?? 416,
|
|
138
|
+
...this.fileOptions
|
|
139
|
+
}
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Creates a new LogFileRotationTransport instance.
|
|
144
|
+
* @param params - Configuration options for the transport
|
|
145
|
+
* @throws {Error} If the filename is already in use by another transport instance
|
|
146
|
+
*/
|
|
147
|
+
constructor(params) {
|
|
148
|
+
super(params);
|
|
149
|
+
if (LogFileRotationTransport.activeFilenames.has(params.filename)) throw new Error(`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.`);
|
|
150
|
+
this.filename = params.filename;
|
|
151
|
+
LogFileRotationTransport.activeFilenames.add(this.filename);
|
|
152
|
+
this.fieldNames = {
|
|
153
|
+
level: params.fieldNames?.level ?? "level",
|
|
154
|
+
message: params.fieldNames?.message ?? "message",
|
|
155
|
+
timestamp: params.fieldNames?.timestamp ?? "timestamp"
|
|
156
|
+
};
|
|
157
|
+
this.delimiter = params.delimiter ?? "\n";
|
|
158
|
+
this.timestampFn = params.timestampFn ?? (() => (/* @__PURE__ */ new Date()).toISOString());
|
|
159
|
+
this.levelMap = params.levelMap ?? {};
|
|
160
|
+
this.compressOnRotate = params.compressOnRotate ?? false;
|
|
161
|
+
this.isCompressing = false;
|
|
162
|
+
this.batchEnabled = !!params.batch;
|
|
163
|
+
this.batchSize = params.batch?.size ?? 1e3;
|
|
164
|
+
this.batchTimeout = params.batch?.timeout ?? 5e3;
|
|
165
|
+
this.batchQueue = [];
|
|
166
|
+
this.batchTimer = null;
|
|
167
|
+
this.isDisposing = false;
|
|
168
|
+
this.callbacks = params.callbacks;
|
|
169
|
+
this.frequency = params.frequency;
|
|
170
|
+
this.verbose = params.verbose;
|
|
171
|
+
this.dateFormat = params.dateFormat;
|
|
172
|
+
this.size = params.size;
|
|
173
|
+
this.maxLogs = params.maxLogs;
|
|
174
|
+
this.auditFile = params.auditFile;
|
|
175
|
+
this.extension = params.extension;
|
|
176
|
+
this.createSymlink = params.createSymlink;
|
|
177
|
+
this.symlinkName = params.symlinkName;
|
|
178
|
+
this.utc = params.utc;
|
|
179
|
+
this.auditHashType = params.auditHashType;
|
|
180
|
+
this.fileOptions = params.fileOptions;
|
|
181
|
+
this.fileMode = params.fileMode;
|
|
182
|
+
this.staticData = params.staticData;
|
|
183
|
+
if (this.batchEnabled) {
|
|
184
|
+
process.on("beforeExit", () => {
|
|
185
|
+
if (!this.isDisposing) this.flush();
|
|
186
|
+
});
|
|
187
|
+
const handleSignal = (signal) => {
|
|
188
|
+
if (!this.isDisposing) {
|
|
189
|
+
this.flushSync();
|
|
190
|
+
LogFileRotationTransport.activeFilenames.delete(this.filename);
|
|
191
|
+
process.exit(signal === "SIGINT" ? 130 : 143);
|
|
192
|
+
}
|
|
193
|
+
};
|
|
194
|
+
process.on("SIGINT", () => handleSignal("SIGINT"));
|
|
195
|
+
process.on("SIGTERM", () => handleSignal("SIGTERM"));
|
|
196
|
+
}
|
|
197
|
+
if (!this.batchEnabled) this.initStream(this.getRotatorOptions());
|
|
198
|
+
}
|
|
199
|
+
/**
|
|
200
|
+
* Initializes the write stream and sets up event listeners.
|
|
201
|
+
* This is called either immediately if batching is disabled,
|
|
202
|
+
* or lazily when the first batch needs to be written if batching is enabled.
|
|
203
|
+
* @param options - Options for the file stream rotator
|
|
204
|
+
* @private
|
|
205
|
+
*/
|
|
206
|
+
initStream(options) {
|
|
207
|
+
this.stream = file_stream_rotator.default.getStream(options);
|
|
208
|
+
if (this.callbacks) {
|
|
209
|
+
const { onRotate, onNew, onOpen, onClose, onError, onFinish, onLogRemoved } = this.callbacks;
|
|
210
|
+
if (this.compressOnRotate) this.stream.on("rotate", async (oldFile, newFile) => {
|
|
211
|
+
try {
|
|
212
|
+
this.isCompressing = true;
|
|
213
|
+
const compressedPath = await this.compressFile(oldFile);
|
|
214
|
+
await (0, node_fs_promises.unlink)(oldFile);
|
|
215
|
+
onRotate?.(compressedPath, newFile);
|
|
216
|
+
} catch (error) {
|
|
217
|
+
this.callbacks?.onError?.(error);
|
|
218
|
+
} finally {
|
|
219
|
+
this.isCompressing = false;
|
|
220
|
+
}
|
|
221
|
+
});
|
|
222
|
+
else if (onRotate) this.stream.on("rotate", onRotate);
|
|
223
|
+
if (onNew) this.stream.on("new", onNew);
|
|
224
|
+
if (onOpen) this.stream.on("open", onOpen);
|
|
225
|
+
if (onClose) this.stream.on("close", onClose);
|
|
226
|
+
if (onError) this.stream.on("error", onError);
|
|
227
|
+
if (onFinish) this.stream.on("finish", onFinish);
|
|
228
|
+
if (onLogRemoved) this.stream.on("logRemoved", onLogRemoved);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
/**
|
|
232
|
+
* Generates a unique path for a compressed log file.
|
|
233
|
+
* If a file with .gz extension already exists, appends timestamp and counter.
|
|
234
|
+
* @param filePath - The original log file path
|
|
235
|
+
* @returns The unique path for the compressed file
|
|
236
|
+
* @private
|
|
237
|
+
*/
|
|
238
|
+
async getUniqueCompressedFilePath(filePath) {
|
|
239
|
+
let finalPath = `${filePath}.gz`;
|
|
240
|
+
let counter = 0;
|
|
241
|
+
try {
|
|
242
|
+
while (true) try {
|
|
243
|
+
await (0, node_fs_promises.access)(finalPath);
|
|
244
|
+
counter++;
|
|
245
|
+
finalPath = `${filePath}.${Date.now()}.${counter}.gz`;
|
|
246
|
+
} catch {
|
|
247
|
+
break;
|
|
248
|
+
}
|
|
249
|
+
} catch (_error) {
|
|
250
|
+
finalPath = `${filePath}.${Date.now()}.gz`;
|
|
251
|
+
}
|
|
252
|
+
return finalPath;
|
|
253
|
+
}
|
|
254
|
+
/**
|
|
255
|
+
* Compresses a log file using gzip.
|
|
256
|
+
* @param filePath - Path to the file to compress
|
|
257
|
+
* @returns Path to the compressed file
|
|
258
|
+
* @private
|
|
259
|
+
*/
|
|
260
|
+
async compressFile(filePath) {
|
|
261
|
+
const gzPath = await this.getUniqueCompressedFilePath(filePath);
|
|
262
|
+
const gzip = (0, node_zlib.createGzip)();
|
|
263
|
+
await (0, node_stream_promises.pipeline)((0, node_fs.createReadStream)(filePath), gzip, (0, node_fs.createWriteStream)(gzPath));
|
|
264
|
+
return gzPath;
|
|
265
|
+
}
|
|
266
|
+
/**
|
|
267
|
+
* Flushes queued log entries to disk asynchronously.
|
|
268
|
+
* This is used for normal batch processing operations.
|
|
269
|
+
* @private
|
|
270
|
+
*/
|
|
271
|
+
flush() {
|
|
272
|
+
if (!this.batchEnabled || this.batchQueue.length === 0) return;
|
|
273
|
+
if (this.batchTimer) {
|
|
274
|
+
clearTimeout(this.batchTimer);
|
|
275
|
+
this.batchTimer = null;
|
|
276
|
+
}
|
|
277
|
+
if (!this.stream) this.initStream(this.getRotatorOptions());
|
|
278
|
+
const batchContent = this.batchQueue.join("");
|
|
279
|
+
this.stream.write(batchContent);
|
|
280
|
+
this.batchQueue = [];
|
|
281
|
+
}
|
|
282
|
+
/**
|
|
283
|
+
* Synchronously flush logs to disk.
|
|
284
|
+
* This is used during process termination (SIGINT/SIGTERM) to ensure logs are written
|
|
285
|
+
* before the process exits. This method uses synchronous file I/O to guarantee that
|
|
286
|
+
* logs are written even during abrupt process termination.
|
|
287
|
+
* @private
|
|
288
|
+
*/
|
|
289
|
+
flushSync() {
|
|
290
|
+
if (!this.batchEnabled || this.batchQueue.length === 0) return;
|
|
291
|
+
if (this.batchTimer) {
|
|
292
|
+
clearTimeout(this.batchTimer);
|
|
293
|
+
this.batchTimer = null;
|
|
294
|
+
}
|
|
295
|
+
if (!this.stream) this.initStream(this.getRotatorOptions());
|
|
296
|
+
const batchContent = this.batchQueue.join("");
|
|
297
|
+
const rotator = this.stream;
|
|
298
|
+
if (rotator.currentFile) (0, node_fs.writeFileSync)(rotator.currentFile, batchContent, { flag: "a" });
|
|
299
|
+
this.batchQueue = [];
|
|
300
|
+
}
|
|
301
|
+
/**
|
|
302
|
+
* Schedules a batch flush operation.
|
|
303
|
+
* This creates a timer that will flush the batch after the configured timeout.
|
|
304
|
+
* The timer is unref'd to prevent keeping the process alive.
|
|
305
|
+
* @private
|
|
306
|
+
*/
|
|
307
|
+
scheduleBatchFlush() {
|
|
308
|
+
if (!this.batchTimer && !this.isDisposing) {
|
|
309
|
+
this.batchTimer = setTimeout(() => {
|
|
310
|
+
this.flush();
|
|
311
|
+
}, this.batchTimeout);
|
|
312
|
+
if (this.batchTimer.unref) this.batchTimer.unref();
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
/**
|
|
316
|
+
* Processes and writes a log entry.
|
|
317
|
+
* If batching is enabled, the entry is queued and written based on batch settings.
|
|
318
|
+
* If batching is disabled, the entry is written immediately.
|
|
319
|
+
* @param params - The log entry parameters
|
|
320
|
+
* @returns The original messages array
|
|
321
|
+
*/
|
|
322
|
+
shipToLogger({ logLevel, messages, data, hasData }) {
|
|
323
|
+
const logEntry = {
|
|
324
|
+
[this.fieldNames.level]: this.levelMap[logLevel] ?? logLevel,
|
|
325
|
+
[this.fieldNames.message]: messages.join(" ") || "",
|
|
326
|
+
[this.fieldNames.timestamp]: this.timestampFn(),
|
|
327
|
+
...this.staticData ? typeof this.staticData === "function" ? this.staticData() : this.staticData : {},
|
|
328
|
+
...hasData ? data : {}
|
|
329
|
+
};
|
|
330
|
+
const logString = `${JSON.stringify(logEntry)}${this.delimiter}`;
|
|
331
|
+
if (this.batchEnabled) {
|
|
332
|
+
this.batchQueue.push(logString);
|
|
333
|
+
if (this.batchQueue.length >= this.batchSize) this.flush();
|
|
334
|
+
else this.scheduleBatchFlush();
|
|
335
|
+
} else this.stream.write(logString);
|
|
336
|
+
return messages;
|
|
337
|
+
}
|
|
338
|
+
/**
|
|
339
|
+
* Disposes of the transport, cleaning up resources and flushing any remaining logs.
|
|
340
|
+
* This method:
|
|
341
|
+
* 1. Prevents new batch flushes from being scheduled
|
|
342
|
+
* 2. Cancels any pending batch flush
|
|
343
|
+
* 3. Flushes any remaining logs
|
|
344
|
+
* 4. Waits for any in-progress compression to complete
|
|
345
|
+
* 5. Closes the write stream
|
|
346
|
+
* 6. Removes the filename from the registry
|
|
347
|
+
*/
|
|
348
|
+
[Symbol.dispose]() {
|
|
349
|
+
if (this.stream || this.batchEnabled) {
|
|
350
|
+
this.isDisposing = true;
|
|
351
|
+
if (this.batchTimer) {
|
|
352
|
+
clearTimeout(this.batchTimer);
|
|
353
|
+
this.batchTimer = null;
|
|
354
|
+
}
|
|
355
|
+
if (this.batchEnabled) this.flush();
|
|
356
|
+
const checkAndEnd = () => {
|
|
357
|
+
if (!this.isCompressing) {
|
|
358
|
+
if (this.stream) this.stream.end();
|
|
359
|
+
LogFileRotationTransport.activeFilenames.delete(this.filename);
|
|
360
|
+
} else setTimeout(checkAndEnd, 100);
|
|
361
|
+
};
|
|
362
|
+
checkAndEnd();
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
};
|
|
366
|
+
|
|
367
|
+
//#endregion
|
|
373
368
|
exports.LogFileRotationTransport = LogFileRotationTransport;
|
|
374
369
|
//# sourceMappingURL=index.cjs.map
|