analogger 1.26.1 → 1.28.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -263,18 +263,23 @@ Display the browser native message box if run from it; otherwise, it displays th
263
263
  ### setOptions()
264
264
 
265
265
 
266
- | **Options** | **default** | **Expect** | **Description** |
267
- |------------------|-------------|-----------------------------------|------------------------------------------------------------------------------------|
268
- | silent | false | boolean | _Hide logs from console (not errors)_ |
269
- | hideLog | false | boolean | _Same as above (silent has precedence over hideLog)_ |
270
- | hideError | false | boolean | _Hide errors from console_ |
271
- | hideHookMessage | false | boolean | _Hide the automatic message shown when some native console methods are overridden_ |
272
- | hidePassingTests | false | boolean | _Hide Live test results_ |
273
- | logToDom | false | string (DOM Selector) | _display log in a DOM container_ |
274
- | logToFile | false | string (File path) | _write log to a file if running from Node_ |
275
- | logToRemote | undefined | string (URL) | _Send log to a remote (more info in the next version)_ |
276
- | requiredLogLevel | "LOG" | "LOG" / "INFO" / "WARN" / "ERROR" | _Define the log level from which the system can show a log entry_ |
277
- | enableDate | false | boolean | _Show date + time (instead of time only)_ |
266
+ | **Options** | **default** | **Expect** | **Description** |
267
+ |---------------------|-------------|-----------------------------------|------------------------------------------------------------------------------------|
268
+ | silent | false | boolean | _Hide logs from console (not errors)_ |
269
+ | hideLog | false | boolean | _Same as above (silent has precedence over hideLog)_ |
270
+ | hideError | false | boolean | _Hide errors from console_ |
271
+ | hideHookMessage | false | boolean | _Hide the automatic message shown when some native console methods are overridden_ |
272
+ | hidePassingTests | false | boolean | _Hide Live test results_ |
273
+ | logToDom | false | string (DOM Selector) | _display log in a DOM container_ |
274
+ | logToFile | false | string (File path) | _write log to a file if running from Node_ |
275
+ | logToRemote | undefined | string (URL) | _Send log to a remote (more info in the next version)_ |
276
+ | logMaxSize | 0 | number | _Set maximum size for the log file_ |
277
+ | compressArchives | false | boolean | _Whether to archive and compress the logs after deleting an archive_ |
278
+ | compressionLevel | 1 | number | _Archive compression level (0 to 9 with 0 = no compression)_ |
279
+ | addArchiveTimestamp | true | boolean | _Whether to add a timestamp to the generated rotated logs_ |
280
+ | logMaxArchives | 3 | number | _Maximum number of log files to use_ |
281
+ | requiredLogLevel | "LOG" | "LOG" / "INFO" / "WARN" / "ERROR" | _Define the log level from which the system can show a log entry_ |
282
+ | enableDate | false | boolean | _Show date + time (instead of time only)_ |
278
283
 
279
284
  <br/>
280
285
 
package/ana-logger.d.cts CHANGED
@@ -110,7 +110,7 @@ declare class ____AnaLogger {
110
110
  isBrowser(): boolean;
111
111
  resetLogger(): void;
112
112
  resetOptions(): void;
113
- setOptions({ contextLenMax, idLenMax, lidLenMax, symbolLenMax, messageLenMax, hideLog, hideError, hideHookMessage, hidePassingTests, logToDom, logToFile, logToRemote, logToRemoteUrl, logToRemoteBinaryUrl, loopback, requiredLogLevel, oneConsolePerContext, silent, enableDate, protocol, host, port, pathname, binarypathname }?: any): void;
113
+ setOptions({ contextLenMax, idLenMax, lidLenMax, symbolLenMax, messageLenMax, hideLog, hideError, hideHookMessage, hidePassingTests, logToDom, logToFile, logMaxSize, logMaxArchives, logIndexArchive, addArchiveTimestamp, addArchiveIndex, compressArchives, compressionLevel, logToRemote, logToRemoteUrl, logToRemoteBinaryUrl, loopback, requiredLogLevel, oneConsolePerContext, silent, enableDate, protocol, host, port, pathname, binarypathname }?: any): void;
114
114
  EOL: string;
115
115
  getOptions(): {
116
116
  hideHookMessage: boolean;
@@ -15,7 +15,6 @@ import {CONSOLE_HEADER_CLASSNAME, CONSOLE_FOOTER_CLASSNAME} from "./constants.m
15
15
  // to-ansi is also used by the browser
16
16
 
17
17
 
18
-
19
18
  const DEFAULT = {
20
19
  moduleName: "analogger",
21
20
  // Default values for remote-logging
@@ -171,6 +170,166 @@ const symbolNames = {
171
170
  // --------------------------------------------------
172
171
  // Helpers
173
172
  // --------------------------------------------------
173
+ function getFilePathProperties(filePath) {
174
+ try {
175
+ const ext = path.extname(filePath);
176
+ const basename = path.basename(filePath, ext);
177
+ const dirname = path.dirname(filePath);
178
+ const fPath = filePath.slice(0, filePath.length - ext.length);
179
+ return {
180
+ extension: ext, filePath: fPath, basename, dirname
181
+ };
182
+ } catch (e) {
183
+ console.error("FILEPATH_EXT_FAILURE: ", e.message);
184
+ }
185
+ return {
186
+ extension: ".log", filePath
187
+ };
188
+ }
189
+
190
+ function getConsistentTimestamp() {
191
+ const now = new Date();
192
+
193
+ // ISO 8601 format with milliseconds and timezone offset
194
+ const isoString = now.toISOString();
195
+
196
+ return isoString.replace(/:/g, "-").replace(/\./g, "-");
197
+ }
198
+
199
+ /**
200
+ * Deletes all files in the given directory that match the specified filename prefix, index, and extension.
201
+ *
202
+ * @param {string} directory - The directory containing the files.
203
+ * @param {string} filenamePrefix - The prefix of the filename (e.g., "demo").
204
+ * @param {string} index - The index to match (e.g., "01", "02", "03").
205
+ * @param {string} extension - The file extension (e.g., "log").
206
+ * @param archiveName
207
+ * @param compressionLevel
208
+ * @param {function} deletionCallback - A callback function to handle the result.
209
+ */
210
+ function deleteFilesWithIndex(directory, filenamePrefix, index, extension, archiveName, compressionLevel, deletionCallback) {
211
+ fs.readdir(directory, (err, files) => {
212
+ if (err) {
213
+ deletionCallback(err, null);
214
+ return;
215
+ }
216
+
217
+ const deletedFiles = [];
218
+ let filesProcessed = 0;
219
+
220
+ const removeFile = (filePath, callback) => {
221
+ if (!fs.existsSync(filePath)) {
222
+ callback(null, null);
223
+ return;
224
+ }
225
+ fs.unlink(filePath, (unlinkErr) => {
226
+ if (unlinkErr) {
227
+ console.error(`DELETION_FAILURE: Error deleting file ${filePath}: ${unlinkErr}`);
228
+ } else {
229
+ deletedFiles.push(filePath);
230
+ }
231
+ filesProcessed++;
232
+ if (filesProcessed === files.length) {
233
+ callback(null, deletedFiles);
234
+ }
235
+ });
236
+ };
237
+
238
+ const processFile = (file) => {
239
+ if (file.startsWith(filenamePrefix + ".") && file.endsWith(index + extension)) {
240
+ const filePath = path.join(directory, file);
241
+
242
+ if (archiveName)
243
+ {
244
+ createTarGzArchiveSync(filePath, archiveName, compressionLevel);
245
+ removeFile(filePath, deletionCallback);
246
+ }
247
+ else
248
+ {
249
+ removeFile(filePath, deletionCallback);
250
+ }
251
+
252
+ } else {
253
+ filesProcessed++;
254
+ if (filesProcessed === files.length) {
255
+ deletionCallback(null, deletedFiles);
256
+ }
257
+ }
258
+ };
259
+
260
+ if (files.length === 0) {
261
+ deletionCallback(null, deletedFiles); // Handle empty directory
262
+ } else {
263
+ files.forEach(processFile, compressionLevel);
264
+ }
265
+ });
266
+ }
267
+
268
+ /**
269
+ * Adds a log file to a tar.gz archive.
270
+ *
271
+ * @param {string} inputFile - The path to the log file to be added to the archive.
272
+ * @param {string} archivePath - The path to the tar.gz archive.
273
+ * @param compressionLevel
274
+ */
275
+ function createTarGzArchiveSync(inputFile, archivePath, compressionLevel = 1) {
276
+ try {
277
+ return ;
278
+
279
+ // Check if the input file exists
280
+ if (!fs.existsSync(inputFile)) {
281
+ return;
282
+ }
283
+
284
+ // Create the archive if it doesn't exist, otherwise append
285
+ if (!fs.existsSync(archivePath)) {
286
+ // Create a new archive with the single file at root
287
+ const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'tar-gz-init-'));
288
+ try {
289
+ const destFilePath = path.join(tempDir, path.basename(inputFile));
290
+ fs.copyFileSync(inputFile, destFilePath);
291
+ tar.c({
292
+ sync: true,
293
+ gzip: { level: compressionLevel },
294
+ file: archivePath,
295
+ cwd: tempDir,
296
+ portable: true,
297
+ }, [path.basename(inputFile)]);
298
+ } finally {
299
+ fs.rmSync(tempDir, { recursive: true, force: true });
300
+ }
301
+ return;
302
+ }
303
+
304
+ // If archive exists, append
305
+ const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'tar-gz-append-'));
306
+
307
+ try {
308
+ tar.x({
309
+ file: archivePath,
310
+ cwd: tempDir,
311
+ sync: true,
312
+ });
313
+
314
+ const destFilePath = path.join(tempDir, path.basename(inputFile));
315
+ fs.copyFileSync(inputFile, destFilePath);
316
+
317
+ tar.c({
318
+ gzip: true,
319
+ file: archivePath,
320
+ cwd: tempDir,
321
+ sync: true,
322
+ portable: true,
323
+ }, fs.readdirSync(tempDir));
324
+
325
+ } finally {
326
+ fs.rmSync(tempDir, { recursive: true, force: true });
327
+ }
328
+ } catch (err) {
329
+ console.error(`ARCHIVE_FAILURE: ${e.message}`);
330
+ }
331
+ }
332
+
174
333
  /**
175
334
  * https://stackoverflow.com/questions/17575790/environment-detection-node-js-or-browser
176
335
  * @returns {string}
@@ -449,10 +608,16 @@ class ____AnaLogger
449
608
  this.options.oneConsolePerContext = true;
450
609
  this.options.logToDom = undefined;
451
610
  this.options.logToFile = undefined;
611
+ this.options.logMaxSize = 0;
612
+ this.options.logMaxArchives = 3;
613
+ this.options.logIndexArchive = 0;
452
614
  this.options.logToRemote = undefined;
615
+ this.options.addArchiveTimestamp = true;
616
+ this.options.addArchiveIndex = true;
453
617
  this.options.logToRemoteUrl = undefined;
454
618
  this.options.logToRemoteBinaryUrl = undefined;
455
- this.options.logToDomlogToFile = undefined;
619
+ this.options.compressArchives = false;
620
+ this.options.compressionLevel = 1;
456
621
  this.options.protocol = undefined;
457
622
  this.options.host = undefined;
458
623
  this.options.port = undefined;
@@ -478,6 +643,13 @@ class ____AnaLogger
478
643
  hidePassingTests = undefined,
479
644
  logToDom = undefined,
480
645
  logToFile = undefined,
646
+ logMaxSize = 0,
647
+ logMaxArchives = 3,
648
+ logIndexArchive = 0,
649
+ addArchiveTimestamp = true,
650
+ addArchiveIndex = true,
651
+ compressArchives = false,
652
+ compressionLevel = 1,
481
653
  logToRemote = undefined,
482
654
  logToRemoteUrl = undefined,
483
655
  logToRemoteBinaryUrl = undefined,
@@ -500,6 +672,14 @@ class ____AnaLogger
500
672
  this.options.messageLenMax = messageLenMax;
501
673
  this.options.symbolLenMax = symbolLenMax;
502
674
 
675
+ this.options.logMaxSize = logMaxSize;
676
+ this.options.logMaxArchives = logMaxArchives;
677
+ this.options.logIndexArchive = logIndexArchive;
678
+ this.options.addArchiveTimestamp = addArchiveTimestamp;
679
+ this.options.addArchiveIndex = addArchiveIndex;
680
+ this.options.compressArchives = compressArchives;
681
+ this.options.compressionLevel = compressionLevel;
682
+
503
683
  this.options.requiredLogLevel = requiredLogLevel;
504
684
 
505
685
  // TODO: Make one of silent or hideToLog options obsolete
@@ -1357,10 +1537,56 @@ class ____AnaLogger
1357
1537
  {
1358
1538
  try
1359
1539
  {
1540
+ if (!fs.existsSync(this.options.logToFilePath))
1541
+ {
1542
+ const dir = path.dirname(this.options.logToFilePath);
1543
+ if (!fs.existsSync(dir))
1544
+ {
1545
+ fs.mkdirSync(dir, { recursive: true });
1546
+ }
1547
+ fs.writeFileSync(this.options.logToFilePath, "");
1548
+ }
1549
+
1550
+ // Check filesize doesn't exceed the limit set by the user
1551
+ if (this.options.logMaxSize) {
1552
+ const stats = fs.statSync(this.options.logToFilePath);
1553
+ const fileSizeInBytes = stats.size;
1554
+ if (fileSizeInBytes > this.options.logMaxSize) {
1555
+
1556
+ if (this.options.logIndexArchive < this.options.logMaxArchives) {
1557
+ ++this.options.logIndexArchive;
1558
+ } else {
1559
+ this.options.logIndexArchive = 1;
1560
+ }
1561
+
1562
+ // Extract the archive name without the extension
1563
+ const padding = this.options.logMaxArchives.toString().length + 1;
1564
+ const {filePath, extension, basename, dirname} = getFilePathProperties(this.options.logToFilePath);
1565
+
1566
+ // Find index of the next archive
1567
+ let indexStr, timeStamp;
1568
+ indexStr = this.options.addArchiveIndex ? "." + this.options.logIndexArchive.toString().padStart(padding, "0") : "";
1569
+ timeStamp = this.options.addArchiveTimestamp ? "." + getConsistentTimestamp() : "";
1570
+
1571
+ // Deduce old log file name
1572
+ const oldFileName = `${filePath}${timeStamp}${indexStr}${extension}`;
1573
+
1574
+ // Deduce archive name
1575
+ const archiveFileName = this.options.compressArchives ? `${filePath}.tar.gz` : "";
1576
+
1577
+ // Delete old archives
1578
+ deleteFilesWithIndex(dirname, basename, indexStr, extension, archiveFileName, this.options.compressionLevel, (error/*, deletedFiles*/) => {
1579
+ if (error) {
1580
+ console.error(`DELETION_FAILURE: Failed to delete some files`);
1581
+ }
1582
+ });
1583
+
1584
+ fs.renameSync(this.options.logToFilePath, oldFileName);
1585
+ fs.writeFileSync(this.options.logToFilePath, "");
1586
+ }
1587
+ }
1360
1588
  fs.appendFileSync(this.options.logToFilePath, text + this.EOL);
1361
- }
1362
- catch (e)
1363
- {
1589
+ } catch (e) {
1364
1590
  /* istanbul ignore next */
1365
1591
  console.rawError("LOG_TO_FILE_FAILURE: ", e.message);
1366
1592
  }