@controlium/utils 1.0.2-alpha.2 → 1.0.2-alpha.4

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.
@@ -1,865 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.Logger = void 0;
4
- const fs_1 = require("fs");
5
- const date_fns_1 = require("date-fns");
6
- // ----------------------------
7
- // Module-level defaults
8
- // ----------------------------
9
- const DEFAULT_VIDEO_CODEC = "webm";
10
- const DEFAULT_VIDEO_WIDTH = 320;
11
- const DEFAULT_VIDEO_HEIGHT = 180;
12
- const DEFAULT_WRITELINE_MAX_LINES = 55;
13
- const DEFAULT_WRITELINE_SUPPRESS_ELAPSED = false;
14
- const DEFAULT_WRITELINE_SUPPRESS_TIME = false;
15
- const DEFAULT_WRITELINE_SUPPRESS_ALL = false;
16
- const DEFAULT_WRITELINE_SUPPRESS_MULTI = false;
17
- const DEFAULT_WRITELINE_FORMAT_TIME = "HH:mm:ss";
18
- const DEFAULT_WRITELINE_FORMAT_ELAPSED = "mm:ss.SSS";
19
- const DEFAULT_LOG_LEVEL = 6; // Framework debug
20
- const DEFAULT_LOG_TO_CONSOLE = false;
21
- const DEFAULT_THROW_ERROR_LOG_FAIL = false;
22
- const DEFAULT_PANIC_MODE = false;
23
- const DEFAULT_PANIC_CODE = "P";
24
- const DEFAULT_PANIC_DESCRIPTOR = "P: ";
25
- // ----------------------------
26
- // Internal constants
27
- // ----------------------------
28
- /** Maximum length of a data/mediaType string shown in error messages before truncation. */
29
- const ERROR_DISPLAY_STRING_MAX_LENGTH = 30;
30
- /** Number of characters kept from the start of a truncated error-display string. */
31
- const ERROR_DISPLAY_STRING_HEAD_LENGTH = 25;
32
- /** Number of characters kept from the end of a truncated error-display string. */
33
- const ERROR_DISPLAY_STRING_TAIL_LENGTH = 3;
34
- /** Minimum character-width used when zero-padding the write-type label. */
35
- const WRITE_TYPE_PAD_WIDTH = 5;
36
- /** Stack-frame substring used to identify internal Logger frames and skip them. */
37
- const LOGGER_STACK_FRAME_MARKER = "logger.ts";
38
- class Logger {
39
- /**
40
- * Resets the logger to its default configuration.
41
- *
42
- * Clears the output callback and restores all options to their default values.
43
- * Optionally resets the elapsed-time start point to the current moment.
44
- *
45
- * @param resetStartTime - When `true`, the start time used for elapsed-time
46
- * calculation is reset to now. When `false` (the default), the existing
47
- * start time is preserved so elapsed time continues from the original baseline.
48
- *
49
- * @example
50
- * Logger.reset(); // Reset options only, keep elapsed-time baseline
51
- * Logger.reset(true); // Reset options and restart the elapsed timer
52
- */
53
- static reset(resetStartTime = false) {
54
- if (resetStartTime) {
55
- this.startTime = Date.now();
56
- }
57
- this.clearOutputCallback();
58
- this.options = Logger.buildDefaultOptions();
59
- }
60
- // ----------------------------
61
- // Public Getters/Setters
62
- // ----------------------------
63
- /**
64
- * Controls whether log output is written to the console.
65
- *
66
- * When `true`, every log line is written to `console.log()` in addition
67
- * to any configured {@link logOutputCallback}.
68
- * When `false`, output is sent only to the callback (if defined).
69
- *
70
- * @example
71
- * Logger.logToConsole = true;
72
- */
73
- static set logToConsole(value) {
74
- this.options.logToConsole = value;
75
- }
76
- /**
77
- * Returns whether log output is currently being written to the console.
78
- *
79
- * @returns `true` if console logging is enabled; otherwise `false`.
80
- */
81
- static get logToConsole() {
82
- return this.options.logToConsole;
83
- }
84
- /**
85
- * Enables or disables Panic Mode.
86
- *
87
- * When `true`, the current log level is ignored and all log calls produce
88
- * output — equivalent to setting the log level to `Verbose`.
89
- * When `false`, normal log-level filtering applies.
90
- *
91
- * @example
92
- * Logger.panicMode = true; // Force all output regardless of log level
93
- */
94
- static set panicMode(value) {
95
- this.options.panicMode = value;
96
- }
97
- /**
98
- * Returns whether Panic Mode is currently active.
99
- *
100
- * When `true`, all log output is written regardless of the configured log level.
101
- *
102
- * @returns `true` if Panic Mode is enabled; otherwise `false`.
103
- */
104
- static get panicMode() {
105
- return this.options.panicMode;
106
- }
107
- /**
108
- * Controls whether an exception is thrown when a log-output operation fails.
109
- *
110
- * When `true`, any error raised while executing the configured
111
- * {@link logOutputCallback} is re-thrown to the caller.
112
- * When `false`, such errors are suppressed and logged internally instead.
113
- *
114
- * @example
115
- * Logger.throwErrorIfLogOutputFails = true;
116
- */
117
- static set throwErrorIfLogOutputFails(value) {
118
- this.options.throwErrorIfLogOutputFails = value;
119
- }
120
- /**
121
- * Returns whether log-output failures are re-thrown as exceptions.
122
- *
123
- * @returns `true` if logging errors are propagated to the caller;
124
- * `false` if they are suppressed and logged internally.
125
- */
126
- static get throwErrorIfLogOutputFails() {
127
- return this.options.throwErrorIfLogOutputFails;
128
- }
129
- /**
130
- * Returns the current global logging level.
131
- *
132
- * Only messages with a level less than or equal to this value
133
- * (or within the configured {@link loggingFilter} range) will be output.
134
- *
135
- * @returns The current numeric log level.
136
- *
137
- * @see {@link Levels} for named level constants.
138
- */
139
- static get loggingLevel() {
140
- return this.options.loggingCurrentLevel;
141
- }
142
- /**
143
- * Sets the global logging level.
144
- *
145
- * Accepts a named level string (case-insensitive, spaces ignored),
146
- * or a non-negative integer. Unknown strings and invalid numbers
147
- * fall back to `FrameworkDebug` and emit a warning.
148
- *
149
- * @param requiredLevel - The desired level as a `Levels` constant, number, or string.
150
- *
151
- * @example
152
- * Logger.loggingLevel = Logger.Levels.Warning;
153
- * Logger.loggingLevel = 3;
154
- * Logger.loggingLevel = "TestInformation";
155
- */
156
- static set loggingLevel(requiredLevel) {
157
- if (typeof requiredLevel === "string") {
158
- this.options.loggingCurrentLevel = this.levelFromText(requiredLevel);
159
- }
160
- else {
161
- if (Number.isInteger(requiredLevel) && requiredLevel >= 0) {
162
- this.options.loggingCurrentLevel = requiredLevel;
163
- }
164
- else {
165
- this.options.loggingCurrentLevel = this.Levels.FrameworkDebug;
166
- Logger.writeLine(this.Levels.Warning, `Invalid Log Level [${requiredLevel}] (Must be integer greater than zero). Defaulting to Framework Debug!`);
167
- }
168
- }
169
- }
170
- /**
171
- * Returns the current logging level as a human-readable string.
172
- *
173
- * @returns A descriptive string for the current log level,
174
- * e.g. `"Test information (TSINF)"`.
175
- */
176
- static get loggingLevelText() {
177
- return this.levelToText(this.loggingLevel);
178
- }
179
- /**
180
- * Builds a human-readable description of the active logging configuration,
181
- * combining the current level with any active filter range.
182
- *
183
- * Includes a Panic Mode preamble prefix when {@link panicMode} is active.
184
- *
185
- * @param level - The current logging level.
186
- * @param minLevel - The minimum level of the active filter range (`NoOutput` if no filter).
187
- * @param maxLevel - The maximum level of the active filter range (`NoOutput` if no filter).
188
- * @returns A descriptive string. Returns the plain level name when no valid
189
- * filter range is active.
190
- *
191
- * @example
192
- * Logger.loggingLevelDescription(Logger.Levels.FrameworkDebug, Logger.Levels.NoOutput, Logger.Levels.NoOutput);
193
- * // => "Framework debug (FKDBG)"
194
- */
195
- static loggingLevelDescription(level, minLevel, maxLevel) {
196
- const currentLevelText = this.levelToText(level);
197
- const preamble = this.options.panicMode
198
- ? this.options.panicDescriptorPreamble
199
- : "";
200
- // If a valid min/max range is set (neither is NoOutput AND min <= max)
201
- if (minLevel !== this.Levels.NoOutput && maxLevel !== this.Levels.NoOutput) {
202
- if (minLevel === maxLevel) {
203
- return (preamble +
204
- `Levels [${this.levelToText(minLevel)}] and [${currentLevelText}]`);
205
- }
206
- else if (minLevel <= maxLevel) {
207
- return (preamble +
208
- `Between levels [${this.levelToText(minLevel)} and ${this.levelToText(maxLevel)}] and level ${currentLevelText}`);
209
- }
210
- }
211
- // Fallback: no filter range active, or inverted range — return plain level name
212
- return preamble + currentLevelText;
213
- }
214
- /**
215
- * Returns the current log-level filter range.
216
- *
217
- * When both `min` and `max` are `NoOutput` (i.e. `0`), no filter is active
218
- * and only {@link loggingLevel} controls output.
219
- * When a valid range is set, messages whose level falls within
220
- * `[min, max]` are also output regardless of the current logging level.
221
- *
222
- * @returns An object with `min` and `max` numeric level values.
223
- */
224
- static get loggingFilter() {
225
- return {
226
- min: this.options.filterMinCurrentLevel,
227
- max: this.options.filterMaxCurrentLevel
228
- };
229
- }
230
- /**
231
- * Sets a log-level filter range for additional output inclusion.
232
- *
233
- * Messages whose level falls within `[min, max]` will be output even if
234
- * they fall outside the current {@link loggingLevel}. Pass `NoOutput` (or
235
- * omit) for either bound to disable that side of the filter.
236
- *
237
- * Both bounds accept a named level string or a non-negative integer.
238
- * Invalid values fall back to `NoOutput` and emit a warning.
239
- *
240
- * @param min - Lower bound of the filter range (inclusive).
241
- * @param max - Upper bound of the filter range (inclusive).
242
- *
243
- * @example
244
- * Logger.loggingFilter = { min: Logger.Levels.Error, max: Logger.Levels.Warning };
245
- */
246
- static set loggingFilter({ min, max }) {
247
- if (typeof min === "string") {
248
- this.options.filterMinCurrentLevel = this.levelFromText(min);
249
- }
250
- else {
251
- if (Number.isInteger(min) && Number(min) >= 0) {
252
- this.options.filterMinCurrentLevel = min;
253
- }
254
- else {
255
- this.options.filterMinCurrentLevel = this.Levels.NoOutput;
256
- Logger.writeLine(this.Levels.Warning, `Invalid Log Level [${min}] (Must be integer greater than zero). Defaulting to NoOutput!`);
257
- }
258
- }
259
- if (typeof max === "string") {
260
- this.options.filterMaxCurrentLevel = this.levelFromText(max);
261
- }
262
- else {
263
- if (Number.isInteger(max) && Number(max) >= 0) {
264
- this.options.filterMaxCurrentLevel = max;
265
- }
266
- else {
267
- this.options.filterMaxCurrentLevel = this.Levels.NoOutput;
268
- Logger.writeLine(this.Levels.Warning, `Invalid Log Level [${max}] (Must be integer greater than zero). Defaulting to NoOutput!`);
269
- }
270
- }
271
- }
272
- /**
273
- * Returns the current `writeLine` formatting options.
274
- *
275
- * These control preamble suppression, max lines, timestamp format,
276
- * and elapsed time format for log lines written via {@link writeLine}.
277
- *
278
- * @returns The active {@link WriteLineOptions} configuration object.
279
- */
280
- static get writeLineOptions() {
281
- return this.options.writeLine;
282
- }
283
- /**
284
- * Returns the current video attachment options.
285
- *
286
- * These defaults are used by {@link attachVideo} and {@link attachVideoFile}
287
- * when no explicit options are provided.
288
- *
289
- * @returns The active {@link VideoOptions} configuration object.
290
- */
291
- static get videoOptions() {
292
- return this.options.video;
293
- }
294
- /**
295
- * Sets the video attachment options used when attaching video to log output.
296
- *
297
- * Provided values are merged with the current options. If `height` or `width`
298
- * fall outside the valid resolution limits, both dimensions are ignored and
299
- * the existing values are retained.
300
- *
301
- * @param options - Partial or full {@link VideoOptions} to apply.
302
- *
303
- * @example
304
- * Logger.videoOptions = { videoCodec: "mp4", width: 1280, height: 720 };
305
- */
306
- static set videoOptions(options) {
307
- this.options.video = {
308
- videoCodec: options.videoCodec ?? this.options.video.videoCodec,
309
- ...this.checkAndGetVideoResolution(options)
310
- };
311
- }
312
- /**
313
- * Clears the {@link logOutputCallback}, disabling callback-based log output.
314
- *
315
- * After calling this, log output will only go to the console
316
- * (if {@link logToConsole} is `true`), or be silently dropped.
317
- *
318
- * @example
319
- * Logger.clearOutputCallback();
320
- */
321
- static clearOutputCallback() {
322
- this.logOutputCallback = undefined;
323
- }
324
- // ----------------------------
325
- // Public Attach Methods
326
- // ----------------------------
327
- /**
328
- * Attaches a screenshot to the log output at the specified log level.
329
- *
330
- * The screenshot is base64-encoded and passed to {@link logOutputCallback}
331
- * with a media type of `"base64:image/png"`. Accepts either a raw `Buffer`
332
- * or an existing base64 string.
333
- *
334
- * Has no effect if the given `logLevel` does not pass the current level filter.
335
- *
336
- * @param logLevel - The log level at which to attach the screenshot.
337
- * @param screenshot - A `Buffer` containing raw PNG data, or a base64-encoded string.
338
- *
339
- * @example
340
- * Logger.attachScreenshot(Logger.Levels.TestDebug, screenshotBuffer);
341
- */
342
- static attachScreenshot(logLevel, screenshot) {
343
- if (this.logLevelOk(logLevel)) {
344
- if (typeof screenshot === "string") {
345
- screenshot = Buffer.from(screenshot).toString("base64");
346
- }
347
- else {
348
- screenshot = screenshot.toString("base64");
349
- }
350
- this.attach(logLevel, screenshot, "base64:image/png");
351
- }
352
- }
353
- /**
354
- * Attaches an HTML string to the log output at the specified log level.
355
- *
356
- * The HTML is passed to {@link logOutputCallback} with a media type of `"text/html"`.
357
- * Useful for attaching rich content such as tables or styled reports.
358
- *
359
- * Has no effect if the given `logLevel` does not pass the current level filter.
360
- *
361
- * @param logLevel - The log level at which to attach the HTML.
362
- * @param htmlString - A valid HTML string to attach.
363
- *
364
- * @example
365
- * Logger.attachHTML(Logger.Levels.TestInformation, "<b>Test passed</b>");
366
- */
367
- static attachHTML(logLevel, htmlString) {
368
- this.attach(logLevel, htmlString, "text/html");
369
- }
370
- /**
371
- * Reads a video file from disk and attaches it to the log output.
372
- *
373
- * The file at `videoFilePath` is read as a `Buffer` and passed to
374
- * {@link attachVideo}. If the file cannot be read, an error is processed
375
- * via {@link throwErrorIfLogOutputFails}.
376
- *
377
- * Has no effect if the given `logLevel` does not pass the current level filter.
378
- *
379
- * @param logLevel - The log level at which to attach the video.
380
- * @param videoFilePath - Absolute or relative path to the video file.
381
- * @param options - Optional {@link VideoOptions} overriding the current defaults.
382
- *
383
- * @example
384
- * Logger.attachVideoFile(Logger.Levels.TestDebug, "./recordings/run.webm");
385
- */
386
- static attachVideoFile(logLevel, videoFilePath, options = this.videoOptions) {
387
- if (this.logLevelOk(logLevel)) {
388
- let videoBuffer;
389
- try {
390
- videoBuffer = (0, fs_1.readFileSync)(videoFilePath);
391
- }
392
- catch (err) {
393
- const errText = `Error thrown reading video data from given file path:-\n${err.message}`;
394
- this.processError(errText);
395
- return;
396
- }
397
- this.attachVideo(logLevel, videoBuffer, options);
398
- }
399
- }
400
- /**
401
- * Attaches a video buffer to the log output at the specified log level.
402
- *
403
- * The video is base64-encoded and wrapped in an HTML `<video>` element,
404
- * then passed to {@link logOutputCallback} with a media type of `"text/html"`.
405
- * When {@link panicMode} is active, the video element is given a
406
- * `title="PANIC_MODE"` attribute.
407
- *
408
- * Has no effect if the given `logLevel` does not pass the current level filter.
409
- *
410
- * @param logLevel - The log level at which to attach the video.
411
- * @param video - A `Buffer` containing the raw video data.
412
- * @param options - Optional {@link VideoOptions} overriding the current defaults.
413
- *
414
- * @example
415
- * Logger.attachVideo(Logger.Levels.TestDebug, videoBuffer, { width: 640, height: 360 });
416
- */
417
- static attachVideo(logLevel, video, options) {
418
- const actualOptions = options == null
419
- ? this.options.video
420
- : {
421
- videoCodec: options.videoCodec ?? this.options.video.videoCodec,
422
- ...this.checkAndGetVideoResolution(options)
423
- };
424
- const videoSourceString = `data:video/${actualOptions.videoCodec};base64,` +
425
- video.toString("base64");
426
- const videoStringNoData = `<video controls width="${actualOptions.width}" height="${actualOptions.height}"${this.panicMode ? ` title="PANIC_MODE"` : ""}><source src="<Video Data>" type="video/${actualOptions.videoCodec}">Video (Codec ${actualOptions.videoCodec}) not supported by browser</video>`;
427
- const videoString = videoStringNoData.replace("<Video Data>", videoSourceString);
428
- this.attach(logLevel, videoString, "text/html");
429
- }
430
- /**
431
- * Passes arbitrary data to the {@link logOutputCallback} at the specified log level.
432
- *
433
- * If the callback is not set, an error is logged instead. If the callback throws,
434
- * the error is processed via {@link throwErrorIfLogOutputFails}.
435
- *
436
- * Has no effect if the given `logLevel` does not pass the current level filter.
437
- *
438
- * @param logLevel - The log level at which to attach the data.
439
- * @param dataString - The data payload to pass to the callback.
440
- * @param mediaType - A MIME-type-style string describing the data
441
- * (e.g. `"text/html"`, `"base64:image/png"`).
442
- *
443
- * @example
444
- * Logger.attach(Logger.Levels.TestDebug, myPayload, "text/plain");
445
- */
446
- static attach(logLevel, dataString, mediaType) {
447
- if (this.logLevelOk(logLevel)) {
448
- if (typeof this.logOutputCallback === "function") {
449
- try {
450
- this.logOutputCallback(dataString, mediaType);
451
- }
452
- catch (err) {
453
- const errText = `Error thrown from Log Output Callback:-\n${err.message}\nwhen called with data string:-\n${this.truncateForDisplay(dataString)}\nand mediaType:-\n${this.truncateForDisplay(mediaType)}`;
454
- this.processError(errText);
455
- }
456
- }
457
- else {
458
- Logger.writeLine(this.Levels.Error, `Log Output callback is type [${typeof this.logOutputCallback}]. Must be type [function]. No attach performed.`);
459
- }
460
- }
461
- }
462
- // ----------------------------
463
- // Public writeLine
464
- // ----------------------------
465
- /**
466
- * Writes a log line (or multiple lines) at the specified log level.
467
- *
468
- * Multi-line strings are split and each line is written individually.
469
- * If the total number of lines exceeds `maxLines`, intermediate lines
470
- * are replaced with a single truncation notice.
471
- *
472
- * Each line is prefixed with a preamble (timestamp, elapsed time, calling method,
473
- * and level label) unless suppressed via `options` or the global
474
- * {@link writeLineOptions} configuration.
475
- *
476
- * Has no effect if the given `logLevel` does not pass the current level filter
477
- * (unless {@link panicMode} is active).
478
- *
479
- * @param logLevel - The log level at which to write the message.
480
- * @param textString - The message to log. May contain newlines.
481
- * @param options - Optional per-call overrides for {@link WriteLineOptions}.
482
- *
483
- * @example
484
- * Logger.writeLine(Logger.Levels.TestInformation, "Test started");
485
- * Logger.writeLine(Logger.Levels.Warning, "Something looks off", { suppressAllPreamble: true });
486
- */
487
- static writeLine(logLevel, textString, options) {
488
- const stackObj = {};
489
- Error.captureStackTrace(stackObj, this.writeLine);
490
- const stack = stackObj?.stack ?? "[Unknown]";
491
- const callingMethodDetails = this.callingMethodDetails(stack, options?.stackOffset ?? 0);
492
- const maxLines = options?.maxLines ?? this.options.writeLine.maxLines;
493
- const suppressAllPreamble = options?.suppressAllPreamble ??
494
- this.options.writeLine.suppressAllPreamble;
495
- const suppressMultilinePreamble = options?.suppressMultilinePreamble ??
496
- this.options.writeLine.suppressMultilinePreamble;
497
- const suppressTimeStamp = options?.suppressTimeStamp ?? this.options.writeLine.suppressTimeStamp;
498
- const suppressElapsed = options?.suppressElapsed ?? this.options.writeLine.suppressElapsed;
499
- const timeFormat = suppressTimeStamp
500
- ? undefined
501
- : (options?.timeFormat ?? this.options.writeLine.timeFormat);
502
- const elapsedFormat = suppressElapsed
503
- ? undefined
504
- : (options?.elapsedFormat ?? this.options.writeLine.elapsedFormat);
505
- if (!stack.includes(".doWriteLine")) {
506
- const normalizedMaxLines = maxLines < 1 ? 1 : maxLines;
507
- const textArray = textString.split(/\r?\n/);
508
- let isFirstLine = true;
509
- if (normalizedMaxLines < 3) {
510
- const errorMessage = `maxLines must be 3 or greater! Number given was <${maxLines}>`;
511
- Logger.writeLine(this.Levels.Error, errorMessage);
512
- throw new Error(errorMessage);
513
- }
514
- textArray.forEach((line, index) => {
515
- if (textArray.length <= normalizedMaxLines ||
516
- index < normalizedMaxLines - 2 ||
517
- index === textArray.length - 1) {
518
- this.doWriteLine(!(suppressAllPreamble || (suppressMultilinePreamble && !isFirstLine)), callingMethodDetails, logLevel, line, { time: timeFormat, elapsed: elapsedFormat });
519
- }
520
- else if (index === normalizedMaxLines - 2) {
521
- this.doWriteLine(!(suppressAllPreamble || (suppressMultilinePreamble && !isFirstLine)), callingMethodDetails, logLevel, `... (Skipping some lines as total length (${textArray.length}) > ${normalizedMaxLines}!!)`, { time: timeFormat, elapsed: elapsedFormat });
522
- }
523
- isFirstLine = false;
524
- });
525
- }
526
- }
527
- // ----------------------------
528
- // Private Utilities
529
- // ----------------------------
530
- /** Builds a fresh default options object. Extracted so both the field initialiser and reset() share one source of truth. */
531
- static buildDefaultOptions() {
532
- return {
533
- loggingCurrentLevel: DEFAULT_LOG_LEVEL,
534
- filterMinCurrentLevel: Logger.Levels.NoOutput,
535
- filterMaxCurrentLevel: Logger.Levels.NoOutput,
536
- logToConsole: DEFAULT_LOG_TO_CONSOLE,
537
- throwErrorIfLogOutputFails: DEFAULT_THROW_ERROR_LOG_FAIL,
538
- panicMode: DEFAULT_PANIC_MODE,
539
- panicCodePreamble: DEFAULT_PANIC_CODE,
540
- panicDescriptorPreamble: DEFAULT_PANIC_DESCRIPTOR,
541
- writeLine: {
542
- maxLines: DEFAULT_WRITELINE_MAX_LINES,
543
- suppressTimeStamp: DEFAULT_WRITELINE_SUPPRESS_TIME,
544
- suppressElapsed: DEFAULT_WRITELINE_SUPPRESS_ELAPSED,
545
- suppressAllPreamble: DEFAULT_WRITELINE_SUPPRESS_ALL,
546
- suppressMultilinePreamble: DEFAULT_WRITELINE_SUPPRESS_MULTI,
547
- timeFormat: DEFAULT_WRITELINE_FORMAT_TIME,
548
- elapsedFormat: DEFAULT_WRITELINE_FORMAT_ELAPSED
549
- },
550
- video: {
551
- videoCodec: DEFAULT_VIDEO_CODEC,
552
- width: DEFAULT_VIDEO_WIDTH,
553
- height: DEFAULT_VIDEO_HEIGHT
554
- }
555
- };
556
- }
557
- static levelToText(level) {
558
- switch (level) {
559
- case this.Levels.FrameworkDebug:
560
- return "Framework debug (FKDBG)";
561
- case this.Levels.FrameworkInformation:
562
- return "Framework information (FKINF)";
563
- case this.Levels.TestDebug:
564
- return "Test debug (TSDBG)";
565
- case this.Levels.TestInformation:
566
- return "Test information (TSINF)";
567
- case this.Levels.Warning:
568
- return "Errors (ERROR) & Warnings (WARNING) only";
569
- case this.Levels.Error:
570
- return "Errors only (ERROR)";
571
- case this.Levels.NoOutput:
572
- return "No output from Log (NOOUT)";
573
- default:
574
- return level < 0
575
- ? "Unknown!"
576
- : `Special Level - (${this.options.loggingCurrentLevel})`;
577
- }
578
- }
579
- static levelFromText(text) {
580
- // Strip all whitespace so e.g. "Framework Debug", "frameworkdebug", "framework debug" all match
581
- switch (text.toLowerCase().replace(/\s+/g, "")) {
582
- case "special":
583
- case "verbose":
584
- case "maximum":
585
- case "max":
586
- return Number.MAX_SAFE_INTEGER;
587
- case "frameworkdebug":
588
- case "fkdbg":
589
- return this.Levels.FrameworkDebug;
590
- case "frameworkinformation":
591
- case "fkinf":
592
- return this.Levels.FrameworkInformation;
593
- case "testdebug":
594
- case "tsdbg":
595
- return this.Levels.TestDebug;
596
- case "testinformation":
597
- case "tsinf":
598
- return this.Levels.TestInformation;
599
- case "warn":
600
- case "warng":
601
- case "warning":
602
- return this.Levels.Warning;
603
- case "error":
604
- return this.Levels.Error;
605
- case "nooutput":
606
- case "noout":
607
- return this.Levels.NoOutput;
608
- default: {
609
- const actualLevel = this.options.loggingCurrentLevel;
610
- this.options.loggingCurrentLevel = this.Levels.FrameworkDebug;
611
- Logger.writeLine(this.Levels.Warning, `Unknown Log Level [${text}]. Defaulting to Framework Debug!`);
612
- this.options.loggingCurrentLevel = actualLevel;
613
- return this.Levels.FrameworkDebug;
614
- }
615
- }
616
- }
617
- static checkAndGetVideoResolution(resolution) {
618
- const heightValid = resolution.height == null ||
619
- this.isValidVideoResolutionNumber(resolution.height, this.videoResolutionLimits.minHeight, this.videoResolutionLimits.maxHeight, `Invalid video window height [${resolution.height}]: must be number equal or between ${this.videoResolutionLimits.minHeight} and ${this.videoResolutionLimits.maxHeight}. Height (and width if set) ignored`);
620
- const widthValid = resolution.width == null ||
621
- this.isValidVideoResolutionNumber(resolution.width, this.videoResolutionLimits.minWidth, this.videoResolutionLimits.maxWidth, `Invalid video window width [${resolution.width}]: must be number equal or between ${this.videoResolutionLimits.minWidth} and ${this.videoResolutionLimits.maxWidth}. Width (and height if set) ignored`);
622
- const height = (resolution.height == null
623
- ? this.options.video.height
624
- : heightValid && widthValid
625
- ? resolution.height
626
- : this.options.video.height);
627
- const width = (resolution.width == null
628
- ? this.options.video.width
629
- : heightValid && widthValid
630
- ? resolution.width
631
- : this.options.video.width);
632
- return { height, width };
633
- }
634
- static doWriteLine(doPreamble, callingMethodDetails, logLevel, textString, formatOptions) {
635
- if (this.logLevelOk(logLevel)) {
636
- const callBackGood = typeof this.logOutputCallback === "function";
637
- const preAmble = doPreamble
638
- ? this.getPreAmble(callingMethodDetails, logLevel, formatOptions)
639
- : "";
640
- const textToWrite = preAmble + textString;
641
- let doneConsoleWrite = false;
642
- let doneCallbackWrite = false;
643
- if (this.options.logToConsole) {
644
- console.log(textToWrite);
645
- doneConsoleWrite = true;
646
- }
647
- if (callBackGood) {
648
- try {
649
- this.logOutputCallback(textToWrite);
650
- doneCallbackWrite = true;
651
- }
652
- catch (err) {
653
- const errText = `Error thrown from Log Output Callback during writeLine:-\n${err.message}`;
654
- // Avoid infinite recursion: log directly to console rather than calling processError -> writeLine
655
- console.error(errText);
656
- if (this.options.throwErrorIfLogOutputFails) {
657
- throw new Error(errText);
658
- }
659
- }
660
- }
661
- if (!doneConsoleWrite && !doneCallbackWrite) {
662
- console.log(textToWrite);
663
- }
664
- }
665
- }
666
- static getPreAmble(methodBase, typeOfWrite, timeFormat) {
667
- const writeType = (this.options.panicMode ? this.options.panicCodePreamble : "") +
668
- this.getWriteTypeString(typeOfWrite);
669
- // timeFormat.time is undefined when suppressTimeStamp is active
670
- const timeStamp = timeFormat.time === undefined
671
- ? ""
672
- : `[${(0, date_fns_1.format)(Date.now(), timeFormat.time)}]`;
673
- // timeFormat.elapsed is undefined when suppressElapsed is active
674
- const diff = Date.now() - this.startTime;
675
- const utcDate = new Date(diff);
676
- utcDate.setMinutes(utcDate.getMinutes() + utcDate.getTimezoneOffset());
677
- const elapsedTime = timeFormat.elapsed === undefined
678
- ? ""
679
- : `[${(0, date_fns_1.format)(utcDate, timeFormat.elapsed)}]`;
680
- return `${writeType} - ${timeStamp}${elapsedTime} [${methodBase}]: `;
681
- }
682
- /**
683
- * Left-pads a number (or numeric string) with zeroes to reach the required minimum length.
684
- * Kept private and self-contained so `logger.ts` has no external utility dependencies.
685
- */
686
- static pad(num, requiredMinimumLength) {
687
- let numString = num.toString();
688
- while (numString.length < requiredMinimumLength) {
689
- numString = "0" + numString;
690
- }
691
- return numString;
692
- }
693
- static getWriteTypeString(levelOfWrite) {
694
- switch (levelOfWrite) {
695
- case this.Levels.Error:
696
- return "ERROR";
697
- case this.Levels.Warning:
698
- return "WARNG";
699
- case this.Levels.FrameworkDebug:
700
- return "FKDBG";
701
- case this.Levels.FrameworkInformation:
702
- return "FKINF";
703
- case this.Levels.TestDebug:
704
- return "TSDBG";
705
- case this.Levels.TestInformation:
706
- return "TSINF";
707
- default:
708
- return this.pad(levelOfWrite, WRITE_TYPE_PAD_WIDTH);
709
- }
710
- }
711
- static callingMethodDetails(methodBase, stackOffset = 0) {
712
- let methodName = "<Unknown>";
713
- let typeName = "";
714
- if (methodBase) {
715
- const methodBaseLines = methodBase.split("\n");
716
- if (methodBaseLines.length > 1) {
717
- // Skip past internal Logger stack frames to find the real caller
718
- let indexOfFirstNonLogLine = methodBaseLines
719
- .slice(1)
720
- .findIndex((item) => !item.includes(LOGGER_STACK_FRAME_MARKER));
721
- indexOfFirstNonLogLine =
722
- indexOfFirstNonLogLine === -1 ? 1 : indexOfFirstNonLogLine + 1;
723
- const safeOffset = Math.max(0, stackOffset);
724
- const targetIndex = Math.min(indexOfFirstNonLogLine + safeOffset, methodBaseLines.length - 1);
725
- methodName = methodBaseLines[targetIndex]
726
- .replace(/\s\s+/g, " ")
727
- .trim();
728
- if (methodName.startsWith("at ")) {
729
- const tempA = methodName.split(" ");
730
- methodName = tempA.slice(0, 1).concat([tempA.slice(1).join(" ")])[1];
731
- if (methodName.includes("/") &&
732
- methodName.includes(":") &&
733
- methodName.includes(")")) {
734
- typeName = methodName
735
- .split("/")[methodName.split("/").length - 1].split(")")[0];
736
- }
737
- methodName = methodName.split(" ")[0];
738
- }
739
- }
740
- }
741
- return `${methodName}${typeName === "" ? "" : `(${typeName})`}`;
742
- }
743
- static isValidVideoResolutionNumber(val, min, max, errorMessage) {
744
- if (typeof val === "number") {
745
- if (Number.isInteger(val) && val >= min && val <= max)
746
- return true;
747
- Logger.writeLine(this.Levels.Warning, errorMessage);
748
- return false;
749
- }
750
- else {
751
- Logger.writeLine(this.Levels.Error, errorMessage);
752
- throw new Error(`Resolution number given was a <${typeof val}>! Must only be a number!`);
753
- }
754
- }
755
- static logLevelOk(passedInLogLevel) {
756
- if (this.panicMode === true)
757
- return true;
758
- if (passedInLogLevel === this.Levels.NoOutput)
759
- return false;
760
- const withinCurrentLevel = passedInLogLevel <= this.options.loggingCurrentLevel;
761
- const withinFilterRange = passedInLogLevel >= this.options.filterMinCurrentLevel &&
762
- passedInLogLevel <= this.options.filterMaxCurrentLevel;
763
- return withinCurrentLevel || withinFilterRange;
764
- }
765
- static processError(errorText) {
766
- if (this.options.throwErrorIfLogOutputFails) {
767
- throw new Error(errorText);
768
- }
769
- else {
770
- Logger.writeLine(this.Levels.Error, errorText, {
771
- suppressMultilinePreamble: true
772
- });
773
- }
774
- }
775
- /**
776
- * Truncates a string for safe display in error messages.
777
- * Shows the first {@link ERROR_DISPLAY_STRING_HEAD_LENGTH} characters, an ellipsis,
778
- * and the last {@link ERROR_DISPLAY_STRING_TAIL_LENGTH} characters when the string
779
- * exceeds {@link ERROR_DISPLAY_STRING_MAX_LENGTH}.
780
- */
781
- static truncateForDisplay(value) {
782
- if (typeof value !== "string") {
783
- return `<Not a string! Is type ${typeof value}>`;
784
- }
785
- if (value.length > ERROR_DISPLAY_STRING_MAX_LENGTH) {
786
- return (value.slice(0, ERROR_DISPLAY_STRING_HEAD_LENGTH) +
787
- "..." +
788
- value.slice(-ERROR_DISPLAY_STRING_TAIL_LENGTH));
789
- }
790
- return value;
791
- }
792
- }
793
- exports.Logger = Logger;
794
- /**
795
- * Numeric log level constants used to control and filter log output.
796
- *
797
- * Levels are ordered from highest verbosity to lowest:
798
- * - `Maximum` / `Verbose` — log everything
799
- * - `FrameworkDebug` — internal framework debug messages
800
- * - `FrameworkInformation` — internal framework info messages
801
- * - `TestDebug` — test-level debug messages
802
- * - `TestInformation` — test-level info messages
803
- * - `Warning` — warnings and errors
804
- * - `Error` — errors only
805
- * - `NoOutput` — suppress all output
806
- *
807
- * @example
808
- * Logger.loggingLevel = Logger.Levels.TestInformation;
809
- */
810
- Logger.Levels = {
811
- /** Maximum verbosity. Same numeric value as `Verbose`. */
812
- Maximum: Number.MAX_SAFE_INTEGER,
813
- /** Verbose logging. Same numeric value as `Maximum`. */
814
- Verbose: Number.MAX_SAFE_INTEGER,
815
- /** Framework-level debug logs. */
816
- FrameworkDebug: 6,
817
- /** Framework-level informational logs. */
818
- FrameworkInformation: 5,
819
- /** Test-level debug logs. */
820
- TestDebug: 4,
821
- /** Test-level informational logs. */
822
- TestInformation: 3,
823
- /** Warnings (and errors). */
824
- Warning: 2,
825
- /** Errors only. */
826
- Error: 1,
827
- /** No log output is allowed at this level. */
828
- NoOutput: 0
829
- };
830
- // ----------------------------
831
- // Private Static Properties
832
- // ----------------------------
833
- Logger.videoResolutionLimits = {
834
- minHeight: 180,
835
- maxHeight: 4320,
836
- minWidth: 320,
837
- maxWidth: 7680
838
- };
839
- Logger.options = Logger.buildDefaultOptions();
840
- Logger.startTime = Date.now();
841
- // ----------------------------
842
- // Public Static Properties
843
- // ----------------------------
844
- /**
845
- * Callback invoked for every log output (after formatting and preamble generation).
846
- *
847
- * If defined, the logger calls this function with:
848
- * - `message`: The final formatted log line or attached payload.
849
- * - `mediaType`: Optional MIME-type-style string.
850
- * Examples:
851
- * - `"text/plain"` for normal log lines
852
- * - `"text/html"` for HTML attachments
853
- * - `"base64:image/png"` for screenshots
854
- *
855
- * If the callback throws, the logger catches the error and processes it,
856
- * reporting to the test suite if required.
857
- *
858
- * Set this to `undefined` (or call `Logger.clearOutputCallback()`) to disable callback output.
859
- *
860
- * @example
861
- * Logger.logOutputCallback = (message, mediaType) => {
862
- * myReporter.attach(message, mediaType ?? "text/plain");
863
- * };
864
- */
865
- Logger.logOutputCallback = undefined;