@ncoderz/log-m8 1.0.1

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 ADDED
@@ -0,0 +1,1412 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
7
+ var __export = (target, all) => {
8
+ for (var name in all)
9
+ __defProp(target, name, { get: all[name], enumerable: true });
10
+ };
11
+ var __copyProps = (to, from, except, desc) => {
12
+ if (from && typeof from === "object" || typeof from === "function") {
13
+ for (let key of __getOwnPropNames(from))
14
+ if (!__hasOwnProp.call(to, key) && key !== except)
15
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
16
+ }
17
+ return to;
18
+ };
19
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
20
+ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
21
+
22
+ // src/index.ts
23
+ var index_exports = {};
24
+ __export(index_exports, {
25
+ LogLevel: () => LogLevel,
26
+ LogM8Utils: () => LogM8Utils,
27
+ Logging: () => Logging,
28
+ PluginKind: () => PluginKind
29
+ });
30
+ module.exports = __toCommonJS(index_exports);
31
+
32
+ // src/LogLevel.ts
33
+ var LogLevel = {
34
+ off: "off",
35
+ // No logging
36
+ fatal: "fatal",
37
+ error: "error",
38
+ warn: "warn",
39
+ info: "info",
40
+ debug: "debug",
41
+ track: "track",
42
+ // Special log level for analytics
43
+ trace: "trace"
44
+ };
45
+
46
+ // src/PluginKind.ts
47
+ var PluginKind = {
48
+ appender: "appender",
49
+ // Log output destinations (console, file, network, etc.)
50
+ filter: "filter",
51
+ // Event filtering logic (level, content, rate limiting, etc.)
52
+ formatter: "formatter"
53
+ // Event formatting (text, JSON, custom templates, etc.)
54
+ };
55
+
56
+ // src/appenders/ConsoleAppender.ts
57
+ var NAME = "console";
58
+ var VERSION = "1.0.0";
59
+ var KIND = PluginKind.appender;
60
+ var SUPPORTED_LEVELS = /* @__PURE__ */ new Set([
61
+ LogLevel.fatal,
62
+ LogLevel.error,
63
+ LogLevel.warn,
64
+ LogLevel.info,
65
+ LogLevel.debug,
66
+ LogLevel.track,
67
+ LogLevel.trace
68
+ ]);
69
+ var ConsoleAppender = class {
70
+ constructor() {
71
+ __publicField(this, "name", NAME);
72
+ __publicField(this, "version", VERSION);
73
+ __publicField(this, "kind", KIND);
74
+ __publicField(this, "supportedLevels", SUPPORTED_LEVELS);
75
+ __publicField(this, "enabled", true);
76
+ __publicField(this, "priority");
77
+ __publicField(this, "_config");
78
+ __publicField(this, "_formatter");
79
+ __publicField(this, "_filters", []);
80
+ __publicField(this, "_available", true);
81
+ // Console method mapping with fallbacks for missing methods
82
+ __publicField(this, "off", () => {
83
+ });
84
+ __publicField(this, "fatal", console.error ? console.error.bind(console) : console.log.bind(console));
85
+ __publicField(this, "error", console.error ? console.error.bind(console) : console.log.bind(console));
86
+ __publicField(this, "warn", console.warn ? console.warn.bind(console) : console.log.bind(console));
87
+ __publicField(this, "info", console.info ? console.info.bind(console) : console.log.bind(console));
88
+ __publicField(this, "debug", console.debug ? console.debug.bind(console) : console.log.bind(console));
89
+ // Avoid console.trace as it captures stack traces and is significantly slower; prefer debug/log
90
+ __publicField(this, "trace", console.debug ? console.debug.bind(console) : console.log.bind(console));
91
+ __publicField(this, "track", console.log.bind(console));
92
+ }
93
+ init(config, formatter, filters) {
94
+ this._config = config;
95
+ this._formatter = formatter;
96
+ this._filters = filters || [];
97
+ this._available = typeof console !== "undefined" && !!console.log;
98
+ this.enabled = this._config?.enabled !== false;
99
+ this.priority = this._config?.priority;
100
+ }
101
+ dispose() {
102
+ }
103
+ write(event) {
104
+ if (!this._available) return;
105
+ for (const filter of this._filters) {
106
+ if (filter.enabled && !filter.filter(event)) {
107
+ return;
108
+ }
109
+ }
110
+ const data = this._formatter ? this._formatter.format(event) : [event];
111
+ this[event.level](...data);
112
+ }
113
+ flush() {
114
+ }
115
+ enableFilter(name) {
116
+ const filter = this._getFilter(name);
117
+ if (!filter) return;
118
+ filter.enabled = true;
119
+ }
120
+ disableFilter(name) {
121
+ const filter = this._getFilter(name);
122
+ if (!filter) return;
123
+ filter.enabled = false;
124
+ }
125
+ _getFilter(name) {
126
+ return this._filters.find((f) => f.name === name);
127
+ }
128
+ };
129
+ var ConsoleAppenderFactory = class {
130
+ constructor() {
131
+ __publicField(this, "name", NAME);
132
+ __publicField(this, "version", VERSION);
133
+ __publicField(this, "kind", KIND);
134
+ }
135
+ create(config) {
136
+ const appender = new ConsoleAppender();
137
+ appender.init(config);
138
+ return appender;
139
+ }
140
+ };
141
+
142
+ // src/appenders/FileAppender.ts
143
+ var import_fs = require("fs");
144
+
145
+ // src/LogM8Utils.ts
146
+ var TIMESTAMP_TOKEN_REGEX = /(yyyy|SSS|hh|mm|ss|SS|zz|z|yy|MM|dd|A|a|h|S)/g;
147
+ var EXCLUDED_ERROR_KEYS = /* @__PURE__ */ new Set(["name", "message", "stack", "cause"]);
148
+ var COMMON_NON_ENUMERABLE_PROPS = ["code", "errno", "syscall", "path"];
149
+ var LogM8Utils = class _LogM8Utils {
150
+ /**
151
+ * Detects browser environment for feature compatibility.
152
+ *
153
+ * @returns True when both window and document global objects are available
154
+ */
155
+ static isBrowser() {
156
+ return typeof window !== "undefined" && typeof window.document !== "undefined";
157
+ }
158
+ /**
159
+ * Check if an object is a string.
160
+ *
161
+ * @param obj - The object to check.
162
+ * @returns true if the object is a string, otherwise false.
163
+ */
164
+ static isString(obj) {
165
+ return typeof obj === "string" || obj instanceof String;
166
+ }
167
+ /**
168
+ * Traverses nested object properties using dot-separated path notation.
169
+ *
170
+ * Supports both object property access and array indexing with numeric keys.
171
+ * Also supports bracket notation for array indices which is normalized internally
172
+ * (e.g., `data[0].items[2]` becomes `data.0.items.2`).
173
+ * Safe navigation that returns undefined for invalid paths rather than throwing.
174
+ *
175
+ * @param obj - Source object to traverse
176
+ * @param path - Dot-separated property path (e.g., 'user.profile.name', 'items.0.id')
177
+ * or a path with bracket indices (e.g., 'items[0].id')
178
+ * @returns Property value at the specified path, or undefined if not found
179
+ *
180
+ * @example
181
+ * ```typescript
182
+ * const data = { user: { profile: { name: 'John' } }, items: [{ id: 1 }, { id: 2 }] };
183
+ * getPropertyByPath(data, 'user.profile.name'); // 'John'
184
+ * getPropertyByPath(data, 'items.0.id'); // 1
185
+ * getPropertyByPath(data, 'items[1].id'); // 2 (bracket notation)
186
+ * getPropertyByPath(data, 'missing.path'); // undefined
187
+ * ```
188
+ */
189
+ static getPropertyByPath(obj, path) {
190
+ let value = obj;
191
+ const normalized = path.replace(/\[(\d+)\]/g, ".$1");
192
+ const segments = normalized.split(".");
193
+ for (const key of segments) {
194
+ if (typeof value === "object" && value !== null) {
195
+ if (Array.isArray(value)) {
196
+ const idx = Number(key);
197
+ if (Number.isInteger(idx) && idx >= 0) {
198
+ value = value[idx];
199
+ continue;
200
+ }
201
+ }
202
+ value = value[key];
203
+ } else {
204
+ return void 0;
205
+ }
206
+ }
207
+ return value;
208
+ }
209
+ /**
210
+ * Formats Date objects using preset formats or custom token patterns.
211
+ *
212
+ * Supports common presets ('iso', 'locale') and flexible token-based formatting
213
+ * for complete control over timestamp appearance. Tokens are replaced with
214
+ * corresponding date/time components, while non-token text is preserved literally.
215
+ *
216
+ * Supported format tokens:
217
+ * - yyyy: 4-digit year (2025)
218
+ * - yy: 2-digit year (25)
219
+ * - MM: month with leading zero (01-12)
220
+ * - dd: day with leading zero (01-31)
221
+ * - hh: 24-hour format hour with leading zero (00-23)
222
+ * - h: 12-hour format hour (1-12)
223
+ * - mm: minutes with leading zero (00-59)
224
+ * - ss: seconds with leading zero (00-59)
225
+ * - SSS: milliseconds with leading zeros (000-999)
226
+ * - SS: centiseconds with leading zero (00-99)
227
+ * - S: deciseconds (0-9)
228
+ * - A: uppercase AM/PM
229
+ * - a: lowercase am/pm
230
+ * - z: timezone offset with colon (±HH:MM)
231
+ * - zz: timezone offset without colon (±HHMM)
232
+ *
233
+ * @param date - Date instance to format
234
+ * @param fmt - Format preset ('iso'|'locale') or custom token pattern
235
+ * @returns Formatted timestamp string
236
+ *
237
+ * @example
238
+ * ```typescript
239
+ * const date = new Date('2025-08-04T14:23:45.123Z');
240
+ *
241
+ * // Presets
242
+ * formatTimestamp(date, 'iso'); // '2025-08-04T14:23:45.123Z'
243
+ * formatTimestamp(date, 'locale'); // '8/4/2025, 2:23:45 PM' (locale-dependent)
244
+ *
245
+ * // Custom patterns
246
+ * formatTimestamp(date, 'yyyy-MM-dd hh:mm:ss'); // '2025-08-04 14:23:45'
247
+ * formatTimestamp(date, 'MM/dd/yyyy h:mm A'); // '08/04/2025 2:23 PM'
248
+ * formatTimestamp(date, 'hh:mm:ss.SSS'); // '14:23:45.123'
249
+ * formatTimestamp(date, 'yyyy-MM-dd hh:mm:ss z'); // '2025-08-04 14:23:45 +00:00'
250
+ * ```
251
+ */
252
+ static formatTimestamp(date, fmt) {
253
+ const fmtLower = fmt?.toLowerCase();
254
+ if (!fmt || fmtLower === "iso" || fmtLower === "toisostring") {
255
+ return date.toISOString();
256
+ }
257
+ if (fmtLower === "locale" || fmtLower === "tolocalestring") {
258
+ return date.toLocaleString();
259
+ }
260
+ const pad = (n, z = 2) => String(n).padStart(z, "0");
261
+ const hours24 = date.getHours();
262
+ const hours12 = hours24 % 12 === 0 ? 12 : hours24 % 12;
263
+ return fmt.replace(TIMESTAMP_TOKEN_REGEX, (m) => {
264
+ switch (m) {
265
+ case "yyyy":
266
+ return pad(date.getFullYear(), 4);
267
+ case "yy":
268
+ return pad(date.getFullYear() % 100);
269
+ case "MM":
270
+ return pad(date.getMonth() + 1);
271
+ case "dd":
272
+ return pad(date.getDate());
273
+ case "hh":
274
+ return pad(hours24);
275
+ case "h":
276
+ return pad(hours12);
277
+ case "mm":
278
+ return pad(date.getMinutes());
279
+ case "ss":
280
+ return pad(date.getSeconds());
281
+ case "SSS":
282
+ return pad(date.getMilliseconds(), 3);
283
+ case "SS":
284
+ return pad(Math.floor(date.getMilliseconds() / 10), 2);
285
+ case "S":
286
+ return pad(Math.floor(date.getMilliseconds() / 100), 1);
287
+ case "A":
288
+ return hours24 < 12 ? "AM" : "PM";
289
+ case "a":
290
+ return hours24 < 12 ? "am" : "pm";
291
+ case "z":
292
+ case "zz": {
293
+ const tzOffset = -date.getTimezoneOffset();
294
+ const tzSign = tzOffset >= 0 ? "+" : "-";
295
+ const tzHours = Math.floor(Math.abs(tzOffset) / 60);
296
+ const tzMinutes = Math.abs(tzOffset) % 60;
297
+ if (m === "z") {
298
+ return `${tzSign}${pad(tzHours)}:${pad(tzMinutes)}`;
299
+ }
300
+ return `${tzSign}${pad(tzHours)}${pad(tzMinutes)}`;
301
+ }
302
+ default:
303
+ return m;
304
+ }
305
+ });
306
+ }
307
+ /**
308
+ * Converts arbitrary values into JSON strings optimized for logging systems.
309
+ *
310
+ * This utility ensures that any JavaScript value can be safely logged without causing
311
+ * serialization errors or producing excessively large output. It's designed specifically
312
+ * for logging contexts where reliability and readability are more important than
313
+ * perfect fidelity.
314
+ *
315
+ * ## Key Features
316
+ *
317
+ * **Depth Protection**: Prevents stack overflows and excessive output by limiting
318
+ * object traversal depth. Objects/arrays beyond the limit are replaced with
319
+ * "[Object]" or "[Array]" placeholders.
320
+ *
321
+ * **Array Length Limiting**: Automatically truncates arrays that exceed the maximum
322
+ * length threshold. Truncated arrays include a message indicating how many additional
323
+ * items were omitted.
324
+ *
325
+ * **String Truncation**: Automatically truncates long strings to prevent log flooding.
326
+ * Truncated strings end with an ellipsis (…) character.
327
+ *
328
+ * **Type Safety**: Handles problematic JavaScript types that would normally cause
329
+ * JSON.stringify to throw:
330
+ * - BigInt values are converted to strings
331
+ * - Date objects are normalized to ISO 8601 format
332
+ * - Error instances are serialized to structured objects via {@link LogM8Utils.serializeError}
333
+ *
334
+ * ## Implementation Details
335
+ *
336
+ * - Uses a WeakMap to track traversal depth per object, preventing revisits to the
337
+ * same instance at different depths
338
+ * - Respects existing `toJSON()` methods on objects
339
+ * - Not designed for full cycle detection - cyclic references are handled by the
340
+ * depth limit rather than explicit cycle breaking
341
+ * - The replacer function executes in the context of the parent object, enabling
342
+ * depth tracking through the traversal
343
+ *
344
+ * @param value - Any JavaScript value to stringify for logging (events, contexts, errors, etc.)
345
+ * @param options - Configuration for controlling output size and complexity
346
+ * @param options.maxDepth - Maximum nesting depth for objects/arrays (default: 3).
347
+ * Level 0 = primitive values only,
348
+ * Level 1 = top-level properties,
349
+ * Level 2 = nested properties, etc.
350
+ * @param options.maxArrayLength - Maximum number of array elements to include before truncation (default: 100).
351
+ * Arrays exceeding this limit will be truncated with a message
352
+ * indicating the number of omitted items.
353
+ * @param options.maxStringLength - Maximum character length for strings before truncation (default: 200)
354
+ * @param space - Indentation for pretty-printing. Can be a number (spaces) or string (e.g., '\t').
355
+ * Pass undefined for compact output (recommended for production logs).
356
+ *
357
+ * @returns A JSON string that is guaranteed to be safe for logging systems
358
+ *
359
+ * @example
360
+ * // Basic usage with default options
361
+ * const json = LogM8Utils.stringifyLog({ message: 'User logged in', userId: 12345 });
362
+ * // => '{"message":"User logged in","userId":12345}'
363
+ *
364
+ * @example
365
+ * // Handling problematic types
366
+ * const data = {
367
+ * bigNumber: 123456789012345678901234567890n,
368
+ * timestamp: new Date('2024-01-15T10:30:00Z'),
369
+ * error: new Error('Connection failed'),
370
+ * longText: 'x'.repeat(500)
371
+ * };
372
+ * const json = LogM8Utils.stringifyLog(data);
373
+ * // => '{"bigNumber":"123456789012345678901234567890","timestamp":"2024-01-15T10:30:00.000Z","error":{...},"longText":"xxx...xxx…"}'
374
+ *
375
+ * @example
376
+ * // Array length limiting for large arrays
377
+ * const largeData = {
378
+ * items: new Array(1000).fill({ id: 1, name: 'item' }),
379
+ * values: Array.from({ length: 500 }, (_, i) => i)
380
+ * };
381
+ * const json = LogM8Utils.stringifyLog(largeData, { maxArrayLength: 50 });
382
+ * // => '{"items":[{...},{...},...,"... 950 more items"],"values":[0,1,2,...,"... 450 more items"]}'
383
+ *
384
+ * @example
385
+ * // Depth limiting for deeply nested objects
386
+ * const deepObj = {
387
+ * level1: {
388
+ * level2: {
389
+ * level3: {
390
+ * level4: {
391
+ * level5: 'too deep'
392
+ * }
393
+ * }
394
+ * }
395
+ * }
396
+ * };
397
+ * const json = LogM8Utils.stringifyLog(deepObj, { maxDepth: 3 });
398
+ * // => '{"level1":{"level2":{"level3":{"level4":"[Object]"}}}}'
399
+ *
400
+ * @example
401
+ * // Custom options for verbose debugging
402
+ * const debugJson = LogM8Utils.stringifyLog(
403
+ * complexObject,
404
+ * { maxDepth: 5, maxArrayLength: 500, maxStringLength: 1000 },
405
+ * 2 // Pretty print with 2 spaces
406
+ * );
407
+ *
408
+ * @example
409
+ * // Production logging with minimal output
410
+ * const prodJson = LogM8Utils.stringifyLog(
411
+ * userEvent,
412
+ * { maxDepth: 2, maxArrayLength: 20, maxStringLength: 100 } // Aggressive truncation for high-volume logs
413
+ * );
414
+ *
415
+ * @example
416
+ * // Handling arrays with mixed content
417
+ * const mixedArray = {
418
+ * results: [
419
+ * { id: 1, data: 'first' },
420
+ * { id: 2, data: 'second' },
421
+ * ...Array(200).fill({ id: 999, data: 'bulk' })
422
+ * ]
423
+ * };
424
+ * const json = LogM8Utils.stringifyLog(mixedArray, { maxArrayLength: 10 });
425
+ * // First 10 items preserved, then truncation message
426
+ *
427
+ * @see {@link LogM8Utils.serializeError} - For Error serialization details
428
+ */
429
+ static stringifyLog(value, { maxDepth = 3, maxStringLength = 200, maxArrayLength = 100 } = {}, space) {
430
+ const levels = /* @__PURE__ */ new WeakMap();
431
+ function replacer(key, v) {
432
+ if (v && typeof v === "object") {
433
+ const parentLevel = levels.get(this) ?? 0;
434
+ if (parentLevel >= maxDepth) {
435
+ return Array.isArray(v) ? "[Array]" : "[Object]";
436
+ }
437
+ levels.set(v, parentLevel + 1);
438
+ if (Array.isArray(v) && v.length > maxArrayLength) {
439
+ const truncated = v.slice(0, maxArrayLength);
440
+ truncated.push(`... ${v.length - maxArrayLength} more items`);
441
+ return truncated;
442
+ }
443
+ }
444
+ if (_LogM8Utils.isString(v) && v.length > maxStringLength) {
445
+ return v.slice(0, maxStringLength) + "\u2026";
446
+ }
447
+ if (typeof v === "bigint") {
448
+ return v.toString();
449
+ }
450
+ if (v instanceof Date) {
451
+ return v.toISOString();
452
+ }
453
+ if (v instanceof Error) {
454
+ return _LogM8Utils.serializeError(v);
455
+ }
456
+ return v;
457
+ }
458
+ return JSON.stringify(value, replacer, space);
459
+ }
460
+ /**
461
+ * Serializes an Error (or Error-like) into a plain, JSON-safe object.
462
+ *
463
+ * Behavior:
464
+ * - Returns null for falsy inputs.
465
+ * - If the object provides a custom toJSON(), that result is used verbatim to
466
+ * honor caller-defined serialization.
467
+ * - Otherwise includes standard fields: name, message, stack.
468
+ * - Recursively serializes the optional error.cause chain using the same rules.
469
+ * - Handles circular references in the cause chain safely.
470
+ * - Copies other own enumerable properties (excluding name, message, stack, cause),
471
+ * skipping properties that throw on access or are not JSON-serializable.
472
+ * - Optionally includes common non-enumerable properties like 'code' if present.
473
+ *
474
+ * Important:
475
+ * - This function does not attempt to preserve all non-enumerable properties.
476
+ * - Circular references in the cause chain are detected and handled gracefully.
477
+ *
478
+ * @param error - Unknown error input (Error instance or compatible object).
479
+ * @returns Structured error data suitable for logging, or null when input is falsy.
480
+ *
481
+ * @example
482
+ * try {
483
+ * throw new Error('Boom');
484
+ * } catch (e) {
485
+ * const payload = LogM8Utils.serializeError(e);
486
+ * // { name: 'Error', message: 'Boom', stack: '...', ... }
487
+ * }
488
+ */
489
+ static serializeError(error) {
490
+ const serializeErrorInternal = (error2, seen) => {
491
+ if (!error2) return null;
492
+ const errorObj = error2;
493
+ if (typeof errorObj === "object" && errorObj !== null) {
494
+ if (seen.has(errorObj)) {
495
+ return {
496
+ name: "CircularReference",
497
+ message: "Circular reference detected in error cause chain"
498
+ };
499
+ }
500
+ seen.add(errorObj);
501
+ }
502
+ if (typeof errorObj.toJSON === "function") {
503
+ try {
504
+ const result = errorObj.toJSON();
505
+ JSON.stringify(result);
506
+ return result;
507
+ } catch (_e) {
508
+ }
509
+ }
510
+ const serialized = {
511
+ name: errorObj.name || "Error",
512
+ message: errorObj.message || "",
513
+ stack: errorObj.stack
514
+ };
515
+ if ("cause" in errorObj && errorObj.cause !== void 0) {
516
+ serialized.cause = serializeErrorInternal(errorObj.cause, seen);
517
+ }
518
+ for (const key in errorObj) {
519
+ if (Object.prototype.hasOwnProperty.call(errorObj, key) && !EXCLUDED_ERROR_KEYS.has(key)) {
520
+ try {
521
+ const value = errorObj[key];
522
+ JSON.stringify(value);
523
+ serialized[key] = value;
524
+ } catch (_e) {
525
+ }
526
+ }
527
+ }
528
+ for (const prop of COMMON_NON_ENUMERABLE_PROPS) {
529
+ try {
530
+ const descriptor = Object.getOwnPropertyDescriptor(errorObj, prop);
531
+ if (descriptor && descriptor.value !== void 0) {
532
+ JSON.stringify(descriptor.value);
533
+ serialized[prop] = descriptor.value;
534
+ }
535
+ } catch (_e) {
536
+ }
537
+ }
538
+ return serialized;
539
+ };
540
+ return serializeErrorInternal(error, /* @__PURE__ */ new WeakSet());
541
+ }
542
+ };
543
+
544
+ // src/appenders/FileAppender.ts
545
+ var NAME2 = "file";
546
+ var VERSION2 = "1.0.0";
547
+ var KIND2 = PluginKind.appender;
548
+ var SUPPORTED_LEVELS2 = /* @__PURE__ */ new Set([
549
+ LogLevel.fatal,
550
+ LogLevel.error,
551
+ LogLevel.warn,
552
+ LogLevel.info,
553
+ LogLevel.debug,
554
+ LogLevel.track,
555
+ LogLevel.trace
556
+ ]);
557
+ var FileAppender = class {
558
+ constructor() {
559
+ __publicField(this, "name", NAME2);
560
+ __publicField(this, "version", VERSION2);
561
+ __publicField(this, "kind", KIND2);
562
+ __publicField(this, "supportedLevels", SUPPORTED_LEVELS2);
563
+ __publicField(this, "enabled", true);
564
+ __publicField(this, "priority");
565
+ __publicField(this, "_config");
566
+ __publicField(this, "_formatter");
567
+ __publicField(this, "_filters", []);
568
+ __publicField(this, "_stream");
569
+ }
570
+ init(config, formatter, filters) {
571
+ this._config = config;
572
+ this._formatter = formatter;
573
+ this._filters = filters || [];
574
+ const flags = this._config.append ? "a" : "w";
575
+ this._stream = (0, import_fs.createWriteStream)(this._config.filename, { flags });
576
+ this.enabled = this._config?.enabled !== false;
577
+ this.priority = this._config?.priority;
578
+ }
579
+ dispose() {
580
+ this._stream?.end();
581
+ }
582
+ write(event) {
583
+ if (!this._stream) return;
584
+ for (const filter of this._filters) {
585
+ if (filter.enabled && !filter.filter(event)) {
586
+ return;
587
+ }
588
+ }
589
+ const data = this._formatter ? this._formatter.format(event) : [event];
590
+ const message = data.map((d) => {
591
+ if (LogM8Utils.isString(d)) return d;
592
+ return String(d);
593
+ }).join(" ");
594
+ this._stream.write(message + "\n");
595
+ }
596
+ flush() {
597
+ }
598
+ enableFilter(name) {
599
+ const filter = this._getFilter(name);
600
+ if (!filter) return;
601
+ filter.enabled = true;
602
+ }
603
+ disableFilter(name) {
604
+ const filter = this._getFilter(name);
605
+ if (!filter) return;
606
+ filter.enabled = false;
607
+ }
608
+ _getFilter(name) {
609
+ return this._filters.find((f) => f.name === name);
610
+ }
611
+ };
612
+ var FileAppenderFactory = class {
613
+ constructor() {
614
+ __publicField(this, "name", NAME2);
615
+ __publicField(this, "version", VERSION2);
616
+ __publicField(this, "kind", KIND2);
617
+ }
618
+ create(config) {
619
+ const appender = new FileAppender();
620
+ appender.init(config);
621
+ return appender;
622
+ }
623
+ };
624
+
625
+ // src/filters/MatchFilter.ts
626
+ var MatchFilter = class {
627
+ constructor() {
628
+ __publicField(this, "name", "match-filter");
629
+ __publicField(this, "version", "1.0.0");
630
+ __publicField(this, "kind", PluginKind.filter);
631
+ __publicField(this, "enabled", true);
632
+ __publicField(this, "_allow");
633
+ __publicField(this, "_deny");
634
+ }
635
+ /**
636
+ * Initializes allow/deny rule maps. Values are compared using deep equality for
637
+ * arrays/objects and strict equality for primitives. Missing maps are treated as empty.
638
+ * @param config - Filter configuration with optional allow/deny maps
639
+ */
640
+ init(config) {
641
+ const cfg = config ?? {};
642
+ this._allow = cfg.allow ?? void 0;
643
+ this._deny = cfg.deny ?? void 0;
644
+ this.enabled = cfg.enabled !== false;
645
+ }
646
+ dispose() {
647
+ }
648
+ /**
649
+ * Evaluates the given event against configured rules.
650
+ * - allow: if provided and non-empty, ALL rules must match (AND)
651
+ * - deny: if provided, ANY match denies (OR); deny takes precedence over allow
652
+ * Returns false on unexpected errors to fail-safe.
653
+ * @param logEvent - Event to evaluate
654
+ * @returns true when the event should be logged; false to drop
655
+ */
656
+ filter(logEvent) {
657
+ try {
658
+ if (this._allow && Object.keys(this._allow).length > 0) {
659
+ for (const [path, expected] of Object.entries(this._allow)) {
660
+ const actual = LogM8Utils.getPropertyByPath(logEvent, path);
661
+ if (!this._isEqual(actual, expected)) return false;
662
+ }
663
+ }
664
+ if (this._deny && Object.keys(this._deny).length > 0) {
665
+ for (const [path, expected] of Object.entries(this._deny)) {
666
+ const actual = LogM8Utils.getPropertyByPath(logEvent, path);
667
+ if (this._isEqual(actual, expected)) return false;
668
+ }
669
+ }
670
+ return true;
671
+ } catch (_err) {
672
+ return false;
673
+ }
674
+ }
675
+ // Simple deep equality for primitives, arrays, plain objects, dates
676
+ _isEqual(a, b) {
677
+ if (a === b) return true;
678
+ if (typeof a === "number" && typeof b === "number") {
679
+ return Number.isNaN(a) && Number.isNaN(b);
680
+ }
681
+ if (a instanceof Date && b instanceof Date) return a.getTime() === b.getTime();
682
+ if (Array.isArray(a) && Array.isArray(b)) {
683
+ if (a.length !== b.length) return false;
684
+ for (let i = 0; i < a.length; i++) {
685
+ if (!this._isEqual(a[i], b[i])) return false;
686
+ }
687
+ return true;
688
+ }
689
+ if (this._isPlainObject(a) && this._isPlainObject(b)) {
690
+ const aKeys = Object.keys(a);
691
+ const bKeys = Object.keys(b);
692
+ if (aKeys.length !== bKeys.length) return false;
693
+ for (const key of aKeys) {
694
+ if (!Object.prototype.hasOwnProperty.call(b, key)) return false;
695
+ if (!this._isEqual(a[key], b[key]))
696
+ return false;
697
+ }
698
+ return true;
699
+ }
700
+ return false;
701
+ }
702
+ _isPlainObject(val) {
703
+ return typeof val === "object" && val !== null && !Array.isArray(val) && Object.getPrototypeOf(val) === Object.prototype;
704
+ }
705
+ };
706
+ var MatchFilterFactory = class {
707
+ constructor() {
708
+ __publicField(this, "name", "match-filter");
709
+ __publicField(this, "version", "1.0.0");
710
+ __publicField(this, "kind", PluginKind.filter);
711
+ }
712
+ create(config) {
713
+ const filter = new MatchFilter();
714
+ filter.init(config);
715
+ return filter;
716
+ }
717
+ };
718
+
719
+ // src/formatters/DefaultFormatter.ts
720
+ var NAME3 = "default-formatter";
721
+ var VERSION3 = "1.0.0";
722
+ var KIND3 = PluginKind.formatter;
723
+ var DEFAULT_FORMAT = ["{timestamp} {LEVEL} [{logger}]", "{message}", "{data}"];
724
+ var DEFAULT_TIMESTAMP_FORMAT = "hh:mm:ss.SSS";
725
+ var DefaultFormatter = class {
726
+ constructor() {
727
+ __publicField(this, "name", NAME3);
728
+ __publicField(this, "version", VERSION3);
729
+ __publicField(this, "kind", KIND3);
730
+ __publicField(this, "_config");
731
+ __publicField(this, "_format");
732
+ __publicField(this, "_timestampFormat", DEFAULT_TIMESTAMP_FORMAT);
733
+ __publicField(this, "_levelMap");
734
+ __publicField(this, "_colorEnabled", false);
735
+ // ANSI color codes for Node.js terminal output
736
+ __publicField(this, "_levelColorMap", {
737
+ trace: "\x1B[37m",
738
+ // White
739
+ track: "\x1B[38;5;208m",
740
+ // Orange
741
+ debug: "\x1B[90m",
742
+ // Grey
743
+ info: "\x1B[34m",
744
+ // Blue
745
+ warn: "\x1B[33m",
746
+ // Yellow
747
+ error: "\x1B[31m",
748
+ // Red
749
+ fatal: "\x1B[41m"
750
+ // Red background
751
+ });
752
+ // CSS color styles for browser console output
753
+ __publicField(this, "_levelCssColorMap", {
754
+ trace: "color: #bbb;",
755
+ // Light gray
756
+ track: "color: orange;",
757
+ debug: "color: grey;",
758
+ info: "color: blue;",
759
+ warn: "color: gold;",
760
+ error: "color: red;",
761
+ fatal: "background: red; color: white;"
762
+ });
763
+ }
764
+ init(config) {
765
+ const isBrowser = LogM8Utils.isBrowser();
766
+ this._config = Object.assign({}, config);
767
+ this._colorEnabled = !!this._config.color;
768
+ this._format = [];
769
+ let formatConfig = this._config.format ?? DEFAULT_FORMAT;
770
+ if (typeof this._config.format === "string") formatConfig = [this._config.format];
771
+ if (formatConfig) {
772
+ for (const f of formatConfig) {
773
+ const regex = /(\{[^}]+\})|([^{}]+)/g;
774
+ const parts = [];
775
+ let match;
776
+ while ((match = regex.exec(f)) !== null) {
777
+ if (match[1]) {
778
+ parts.push(match[1]);
779
+ } else if (match[2] !== void 0) {
780
+ parts.push(match[2]);
781
+ }
782
+ }
783
+ this._format.push(parts);
784
+ }
785
+ }
786
+ this._timestampFormat = this._config.timestampFormat ?? DEFAULT_TIMESTAMP_FORMAT;
787
+ const levelValues = Object.values(LogLevel);
788
+ const maxLevelLength = Math.max(...levelValues.map((l) => l.length));
789
+ this._levelMap = levelValues.reduce(
790
+ (acc, level) => {
791
+ let levelStr = level.toUpperCase().padEnd(maxLevelLength, " ");
792
+ if (this._colorEnabled) {
793
+ if (isBrowser) {
794
+ const css = this._levelCssColorMap[level] || "";
795
+ acc[level] = [`%c${levelStr}`, css];
796
+ return acc;
797
+ } else {
798
+ const color = this._levelColorMap[level] || "";
799
+ const reset = "\x1B[0m";
800
+ levelStr = color + levelStr + reset;
801
+ }
802
+ }
803
+ acc[level] = levelStr;
804
+ return acc;
805
+ },
806
+ {}
807
+ );
808
+ }
809
+ dispose() {
810
+ }
811
+ format(logEvent) {
812
+ let output;
813
+ const formatArr = this._format;
814
+ if (formatArr.length > 0) {
815
+ output = formatArr.map((item) => {
816
+ if (item.length === 1) {
817
+ return this.resolveToken(item[0], logEvent);
818
+ }
819
+ return item.reduce((str, part) => {
820
+ const val = this.resolveToken(part, logEvent);
821
+ return str + String(val);
822
+ }, "");
823
+ });
824
+ const dataIndex = output.findIndex((v) => Array.isArray(v));
825
+ if (dataIndex >= 0) {
826
+ const data = output[dataIndex];
827
+ if (data.length > 0) output.splice(dataIndex, 1, ...data);
828
+ else output.splice(dataIndex, 1);
829
+ }
830
+ } else {
831
+ output = [
832
+ LogM8Utils.formatTimestamp(logEvent.timestamp, this._timestampFormat),
833
+ logEvent.level,
834
+ logEvent.logger,
835
+ logEvent.message,
836
+ ...logEvent.data,
837
+ logEvent.context
838
+ ];
839
+ }
840
+ return output;
841
+ }
842
+ resolveToken(part, logEvent) {
843
+ if (part.startsWith("{") && part.endsWith("}")) {
844
+ const key = part.slice(1, -1);
845
+ if (key === "message" && typeof logEvent.message !== "string") {
846
+ return logEvent.message;
847
+ } else if (key === "LEVEL") {
848
+ return this._levelMap[logEvent.level] ?? logEvent.level;
849
+ } else if (key === "timestamp") {
850
+ const raw = LogM8Utils.getPropertyByPath(logEvent, key);
851
+ return LogM8Utils.formatTimestamp(raw, this._timestampFormat);
852
+ }
853
+ return LogM8Utils.getPropertyByPath(logEvent, key);
854
+ }
855
+ return part;
856
+ }
857
+ };
858
+ var DefaultFormatterFactory = class {
859
+ constructor() {
860
+ __publicField(this, "name", NAME3);
861
+ __publicField(this, "version", VERSION3);
862
+ __publicField(this, "kind", KIND3);
863
+ }
864
+ create(config) {
865
+ const appender = new DefaultFormatter();
866
+ appender.init(config);
867
+ return appender;
868
+ }
869
+ };
870
+
871
+ // src/formatters/JsonFormatter.ts
872
+ var NAME4 = "json-formatter";
873
+ var VERSION4 = "1.0.0";
874
+ var KIND4 = PluginKind.formatter;
875
+ var DEFAULT_FORMAT2 = ["timestamp", "level", "logger", "message", "data"];
876
+ var DEFAULT_TIMESTAMP_FORMAT2 = "iso";
877
+ var DEFAULT_PRETTY = 2;
878
+ var DEFAULT_MAX_DEPTH = 3;
879
+ var DEFAULT_MAX_STRING_LEN = 1e3;
880
+ var DEFAULT_MAX_ARRAY_LEN = 100;
881
+ var JsonFormatter = class {
882
+ constructor() {
883
+ __publicField(this, "name", NAME4);
884
+ __publicField(this, "version", VERSION4);
885
+ __publicField(this, "kind", KIND4);
886
+ __publicField(this, "_config");
887
+ __publicField(this, "_format");
888
+ __publicField(this, "_pretty");
889
+ __publicField(this, "_maxDepth", DEFAULT_MAX_DEPTH);
890
+ __publicField(this, "_maxStringLen", DEFAULT_MAX_STRING_LEN);
891
+ __publicField(this, "_maxArrayLen", DEFAULT_MAX_ARRAY_LEN);
892
+ __publicField(this, "_timestampFormat", DEFAULT_TIMESTAMP_FORMAT2);
893
+ }
894
+ init(config) {
895
+ this._config = Object.assign({}, config);
896
+ this._pretty = this._config.pretty === true ? DEFAULT_PRETTY : this._config.pretty ? this._config.pretty : void 0;
897
+ this._maxDepth = this._config.maxDepth ?? DEFAULT_MAX_DEPTH;
898
+ this._maxStringLen = this._config.maxStringLen ?? DEFAULT_MAX_STRING_LEN;
899
+ this._maxArrayLen = this._config.maxArrayLen ?? DEFAULT_MAX_ARRAY_LEN;
900
+ let formatConfig = this._config.format ?? DEFAULT_FORMAT2;
901
+ if (typeof this._config.format === "string") formatConfig = [this._config.format];
902
+ this._format = formatConfig;
903
+ this._timestampFormat = this._config.timestampFormat ?? DEFAULT_TIMESTAMP_FORMAT2;
904
+ }
905
+ dispose() {
906
+ }
907
+ format(logEvent) {
908
+ let outputObj = {};
909
+ const formatArr = this._format;
910
+ if (formatArr.length > 0) {
911
+ formatArr.forEach((item) => {
912
+ const t = this.resolveToken(item, logEvent);
913
+ if (t != void 0) {
914
+ outputObj[t.key] = t.value;
915
+ }
916
+ });
917
+ } else {
918
+ outputObj = logEvent;
919
+ }
920
+ return [
921
+ LogM8Utils.stringifyLog(
922
+ outputObj,
923
+ {
924
+ maxDepth: this._maxDepth,
925
+ maxStringLength: this._maxStringLen,
926
+ maxArrayLength: this._maxArrayLen
927
+ },
928
+ this._pretty
929
+ )
930
+ ];
931
+ }
932
+ resolveToken(part, logEvent) {
933
+ const key = part;
934
+ if (key === "LEVEL") {
935
+ return { key, value: logEvent.level };
936
+ } else if (key === "timestamp") {
937
+ const raw = LogM8Utils.getPropertyByPath(logEvent, key);
938
+ return { key, value: LogM8Utils.formatTimestamp(raw, this._timestampFormat) };
939
+ }
940
+ return { key, value: LogM8Utils.getPropertyByPath(logEvent, key) };
941
+ }
942
+ };
943
+ var JsonFormatterFactory = class {
944
+ constructor() {
945
+ __publicField(this, "name", NAME4);
946
+ __publicField(this, "version", VERSION4);
947
+ __publicField(this, "kind", KIND4);
948
+ }
949
+ create(config) {
950
+ const appender = new JsonFormatter();
951
+ appender.init(config);
952
+ return appender;
953
+ }
954
+ };
955
+
956
+ // src/PluginManager.ts
957
+ var PluginManager = class {
958
+ constructor() {
959
+ __publicField(this, "_pluginFactories", /* @__PURE__ */ new Map());
960
+ __publicField(this, "_plugins", []);
961
+ }
962
+ /**
963
+ * Registers a plugin factory.
964
+ * @param pluginFactory - Factory to register for creating plugins.
965
+ * @throws Error if a factory with the same name is already registered.
966
+ */
967
+ registerPluginFactory(pluginFactory) {
968
+ if (this._pluginFactories.has(pluginFactory.name)) {
969
+ throw new Error(`LogM8: Plugin with name ${pluginFactory.name} is already registered.`);
970
+ }
971
+ this._pluginFactories.set(pluginFactory.name, pluginFactory);
972
+ }
973
+ /**
974
+ * Creates and registers a plugin instance using the matching factory.
975
+ * @param kind - The kind of plugin to create (appender, filter, formatter).
976
+ * @param nameOrConfig - Plugin name string or configuration object with plugin name.
977
+ * @returns The created plugin instance.
978
+ * @throws Error if no matching factory is found.
979
+ */
980
+ createPlugin(kind, nameOrConfig) {
981
+ const name = typeof nameOrConfig === "string" ? nameOrConfig : nameOrConfig.name;
982
+ const config = typeof nameOrConfig === "string" ? { name } : nameOrConfig;
983
+ const pluginFactory = this.getPluginFactory(name, kind);
984
+ if (!pluginFactory) {
985
+ throw new Error(`LogM8: Plugin factory kind '${kind}' with name '${name}' not found.`);
986
+ }
987
+ const plugin = pluginFactory.create(config);
988
+ this._plugins.push(plugin);
989
+ return plugin;
990
+ }
991
+ /**
992
+ * Retrieves a registered plugin factory by name and kind.
993
+ * @param name - The factory name.
994
+ * @param kind - The expected plugin kind.
995
+ * @returns The factory if found and matching kind, otherwise undefined.
996
+ */
997
+ getPluginFactory(name, kind) {
998
+ const pluginFactory = this._pluginFactories.get(name);
999
+ if (!pluginFactory || kind !== pluginFactory.kind) return;
1000
+ return pluginFactory;
1001
+ }
1002
+ /**
1003
+ * Disposes all created plugin instances by invoking their dispose methods.
1004
+ * Clears the internal plugin list.
1005
+ */
1006
+ disposePlugins() {
1007
+ this._plugins.forEach((plugin) => {
1008
+ plugin.dispose();
1009
+ });
1010
+ this._plugins = [];
1011
+ }
1012
+ /**
1013
+ * Clears all registered plugin factories without disposing instances.
1014
+ */
1015
+ clearFactories() {
1016
+ this._pluginFactories.clear();
1017
+ }
1018
+ };
1019
+
1020
+ // src/LogM8.ts
1021
+ var MAX_LOG_BUFFER_SIZE = 100;
1022
+ var DEFAULT_APPENDERS = [
1023
+ {
1024
+ name: "console",
1025
+ formatter: "default-formatter"
1026
+ }
1027
+ ];
1028
+ var LogM8 = class {
1029
+ constructor() {
1030
+ __publicField(this, "_initialized", false);
1031
+ __publicField(this, "_pluginManager", new PluginManager());
1032
+ __publicField(this, "_loggers", /* @__PURE__ */ new Map());
1033
+ __publicField(this, "_appenders", []);
1034
+ __publicField(this, "_filters", []);
1035
+ __publicField(this, "_defaultLevel", LogLevel.info);
1036
+ __publicField(this, "_logLevelValues", Object.values(LogLevel));
1037
+ __publicField(this, "_logLevelSet", new Set(this._logLevelValues));
1038
+ // Buffer for log events before the system is initialized
1039
+ __publicField(this, "_logBuffer", []);
1040
+ this._pluginManager.registerPluginFactory(new ConsoleAppenderFactory());
1041
+ this._pluginManager.registerPluginFactory(new FileAppenderFactory());
1042
+ this._pluginManager.registerPluginFactory(new DefaultFormatterFactory());
1043
+ this._pluginManager.registerPluginFactory(new JsonFormatterFactory());
1044
+ this._pluginManager.registerPluginFactory(new MatchFilterFactory());
1045
+ }
1046
+ /**
1047
+ * Initializes the logging system with configuration and flushes any buffered events.
1048
+ *
1049
+ * Sets up default and per-logger levels, creates configured appenders with their
1050
+ * formatters and filters, and processes any events buffered before initialization.
1051
+ * Appenders are sorted by priority (descending) for deterministic execution order.
1052
+ *
1053
+ * @param config - Logging configuration object
1054
+ * @param config.level - Default log level for all loggers ('info' if not specified)
1055
+ * @param config.loggers - Per-logger level overrides by name
1056
+ * @param config.appenders - Appender configurations (defaults to console if not specified)
1057
+ *
1058
+ * @throws {Error} When referenced plugin factories are not registered
1059
+ *
1060
+ * @example
1061
+ * ```typescript
1062
+ * Logging.init({
1063
+ * level: 'warn',
1064
+ * loggers: { 'app.database': 'debug' },
1065
+ * appenders: [{
1066
+ * name: 'console',
1067
+ * formatter: 'default-formatter',
1068
+ * filters: ['sensitive-data']
1069
+ * }]
1070
+ * });
1071
+ * ```
1072
+ */
1073
+ init(config) {
1074
+ config = Object.assign({}, config);
1075
+ this._reset();
1076
+ this._defaultLevel = this._logLevelSet.has(config.level) ? config.level : LogLevel.info;
1077
+ for (const [name, level] of Object.entries(config.loggers ?? {})) {
1078
+ const logger = this.getLogger(name);
1079
+ const l = this._logLevelSet.has(level) ? level : this._defaultLevel;
1080
+ logger.setLevel(l);
1081
+ }
1082
+ const appenderConfigs = config.appenders ?? DEFAULT_APPENDERS;
1083
+ for (const appenderConfig of appenderConfigs) {
1084
+ const appender = this._pluginManager.createPlugin(
1085
+ PluginKind.appender,
1086
+ appenderConfig
1087
+ );
1088
+ const formatter = appenderConfig.formatter ? this._pluginManager.createPlugin(
1089
+ PluginKind.formatter,
1090
+ appenderConfig.formatter
1091
+ ) : void 0;
1092
+ const filters = [];
1093
+ const ac = appenderConfig;
1094
+ for (const filterConfig of ac.filters ?? []) {
1095
+ const filter = this._pluginManager.createPlugin(PluginKind.filter, filterConfig);
1096
+ if (filter) {
1097
+ filters.push(filter);
1098
+ } else {
1099
+ if (console && console.log) {
1100
+ console.log(
1101
+ `LogM8: Filter '${filterConfig}' not found for appender ${appenderConfig.name}.`
1102
+ );
1103
+ }
1104
+ }
1105
+ }
1106
+ appender.init(appenderConfig, formatter, filters);
1107
+ this._appenders.push(appender);
1108
+ }
1109
+ this._sortAppenders();
1110
+ for (const filterConfig of config.filters ?? []) {
1111
+ const filter = this._pluginManager.createPlugin(PluginKind.filter, filterConfig);
1112
+ if (filter) {
1113
+ this._filters.push(filter);
1114
+ } else {
1115
+ if (console && console.log) {
1116
+ console.log(`LogM8: Filter '${filterConfig}' not found (global).`);
1117
+ }
1118
+ }
1119
+ }
1120
+ this._initialized = true;
1121
+ }
1122
+ /**
1123
+ * Shuts down the logging system and releases all resources.
1124
+ *
1125
+ * Flushes all appenders, disposes plugin instances, clears logger registry,
1126
+ * discards buffered events, and deregisters plugin factories. The system
1127
+ * can be reinitialized after disposal.
1128
+ *
1129
+ * @example
1130
+ * ```typescript
1131
+ * // Graceful shutdown
1132
+ * await new Promise(resolve => {
1133
+ * Logging.flushAppenders();
1134
+ * setTimeout(() => {
1135
+ * Logging.dispose();
1136
+ * resolve();
1137
+ * }, 100);
1138
+ * });
1139
+ * ```
1140
+ */
1141
+ dispose() {
1142
+ this._reset();
1143
+ this._logBuffer = [];
1144
+ this._pluginManager.clearFactories();
1145
+ this._initialized = false;
1146
+ }
1147
+ /**
1148
+ * Retrieves or creates a logger instance with hierarchical naming.
1149
+ *
1150
+ * Logger instances are cached and reused for the same name. Names can be
1151
+ * provided as strings with dot-separation or as array segments that get
1152
+ * joined. Each logger maintains independent level and context settings.
1153
+ *
1154
+ * @param name - Logger name as string ('app.service') or segments (['app', 'service'])
1155
+ * @returns Logger instance for the specified name
1156
+ *
1157
+ * @example
1158
+ * ```typescript
1159
+ * const logger1 = Logging.getLogger('app.database');
1160
+ * const logger2 = Logging.getLogger(['app', 'database']);
1161
+ * // logger1 === logger2 (same instance)
1162
+ *
1163
+ * logger1.setLevel('debug');
1164
+ * logger1.setContext({ service: 'postgres' });
1165
+ * ```
1166
+ */
1167
+ getLogger(name) {
1168
+ let nameStr = name;
1169
+ if (Array.isArray(name)) {
1170
+ nameStr = name.join(".");
1171
+ }
1172
+ const existingLogger = this._loggers.get(nameStr);
1173
+ if (existingLogger) return existingLogger;
1174
+ const logger = {
1175
+ name: nameStr,
1176
+ level: this._defaultLevel,
1177
+ context: {}
1178
+ };
1179
+ logger.fatal = this._log.bind(this, logger, LogLevel.fatal);
1180
+ logger.error = this._log.bind(this, logger, LogLevel.error);
1181
+ logger.warn = this._log.bind(this, logger, LogLevel.warn);
1182
+ logger.info = this._log.bind(this, logger, LogLevel.info);
1183
+ logger.debug = this._log.bind(this, logger, LogLevel.debug);
1184
+ logger.trace = this._log.bind(this, logger, LogLevel.trace);
1185
+ logger.track = this._log.bind(this, logger, LogLevel.track);
1186
+ logger.setLevel = this._setLevel.bind(this, logger);
1187
+ logger.setContext = this._setContext.bind(this, logger);
1188
+ logger.getLogger = (name2) => this.getLogger([logger.name, name2]);
1189
+ this._setLevel(logger, this._defaultLevel);
1190
+ this._loggers.set(logger.name, logger);
1191
+ return logger;
1192
+ }
1193
+ /**
1194
+ * Enables an appender to resume processing log events.
1195
+ *
1196
+ * @param name - Name of the appender to enable
1197
+ */
1198
+ enableAppender(name) {
1199
+ const appender = this._getAppender(name);
1200
+ if (!appender) return;
1201
+ appender.enabled = true;
1202
+ }
1203
+ /**
1204
+ * Disables an appender to stop processing log events.
1205
+ *
1206
+ * @param name - Name of the appender to disable
1207
+ */
1208
+ disableAppender(name) {
1209
+ const appender = this._getAppender(name);
1210
+ if (!appender) return;
1211
+ appender.enabled = false;
1212
+ }
1213
+ /**
1214
+ * Forces an appender to flush any buffered output.
1215
+ *
1216
+ * Catches and logs flush errors to console without interrupting operation.
1217
+ * Useful for ensuring data persistence before shutdown or at intervals.
1218
+ *
1219
+ * @param name - Name of the appender to flush
1220
+ */
1221
+ flushAppender(name) {
1222
+ const appender = this._getAppender(name);
1223
+ if (!appender) return;
1224
+ try {
1225
+ appender.flush();
1226
+ } catch (err) {
1227
+ if (console && console.error) {
1228
+ console.error(`LogM8: Failed to flush appender: ${appender.name}:`, err);
1229
+ }
1230
+ }
1231
+ }
1232
+ /**
1233
+ * Flushes all configured appenders.
1234
+ *
1235
+ * Iterates through all appenders calling flush on each, with individual
1236
+ * error handling per appender.
1237
+ */
1238
+ flushAppenders() {
1239
+ for (const appender of this._appenders) {
1240
+ this.flushAppender(appender.name);
1241
+ }
1242
+ }
1243
+ /**
1244
+ * Enables a filter to resume processing log events.
1245
+ *
1246
+ * When an appender name is provided, enables the filter only for that specific
1247
+ * appender. When no appender is specified, enables the filter globally.
1248
+ * Silently ignores requests for non-existent filters or appenders.
1249
+ *
1250
+ * @param name - Name of the filter to enable
1251
+ * @param appenderName - Optional appender name to enable filter for specific appender only
1252
+ *
1253
+ * @example
1254
+ * ```typescript
1255
+ * // Enable filter globally
1256
+ * Logging.enableFilter('sensitive-data');
1257
+ *
1258
+ * // Enable filter only for console appender
1259
+ * Logging.enableFilter('debug-filter', 'console');
1260
+ * ```
1261
+ */
1262
+ enableFilter(name, appenderName) {
1263
+ if (appenderName) {
1264
+ this._getAppender(appenderName)?.enableFilter(name);
1265
+ return;
1266
+ }
1267
+ const filter = this._getFilter(name);
1268
+ if (!filter) return;
1269
+ filter.enabled = true;
1270
+ }
1271
+ /**
1272
+ * Disables a filter to stop processing log events.
1273
+ *
1274
+ * When an appender name is provided, disables the filter only for that specific
1275
+ * appender. When no appender is specified, disables the filter globally.
1276
+ * Silently ignores requests for non-existent filters or appenders.
1277
+ *
1278
+ * @param name - Name of the filter to disable
1279
+ * @param appenderName - Optional appender name to disable filter for specific appender only
1280
+ *
1281
+ * @example
1282
+ * ```typescript
1283
+ * // Disable filter globally
1284
+ * Logging.disableFilter('sensitive-data');
1285
+ *
1286
+ * // Disable filter only for file appender
1287
+ * Logging.disableFilter('debug-filter', 'file');
1288
+ * ```
1289
+ */
1290
+ disableFilter(name, appenderName) {
1291
+ if (appenderName) {
1292
+ this._getAppender(appenderName)?.disableFilter(name);
1293
+ return;
1294
+ }
1295
+ const filter = this._getFilter(name);
1296
+ if (!filter) return;
1297
+ filter.enabled = false;
1298
+ }
1299
+ /**
1300
+ * Registers a custom plugin factory for appenders, formatters, or filters.
1301
+ *
1302
+ * Allows extending the logging system with custom implementations.
1303
+ * Must be called before init() to be available during configuration.
1304
+ *
1305
+ * @param pluginFactory - Factory instance implementing the PluginFactory interface
1306
+ *
1307
+ * @example
1308
+ * ```typescript
1309
+ * class SlackAppenderFactory implements PluginFactory {
1310
+ * name = 'slack';
1311
+ * kind = PluginKind.appender;
1312
+ * create(config) { return new SlackAppender(config); }
1313
+ * }
1314
+ *
1315
+ * Logging.registerPluginFactory(new SlackAppenderFactory());
1316
+ * ```
1317
+ */
1318
+ registerPluginFactory(pluginFactory) {
1319
+ this._pluginManager.registerPluginFactory(pluginFactory);
1320
+ }
1321
+ _log(logger, level, message, ...data) {
1322
+ const levelNumber = this._logLevelValues.indexOf(level);
1323
+ if (levelNumber > logger._levelNumber) return;
1324
+ const logEvent = {
1325
+ logger: logger.name,
1326
+ level,
1327
+ message,
1328
+ data,
1329
+ context: logger.context,
1330
+ timestamp: /* @__PURE__ */ new Date()
1331
+ };
1332
+ if (this._initialized) {
1333
+ if (this._logBuffer.length > 0) {
1334
+ for (const bufferedEvent of this._logBuffer) {
1335
+ this._processLogEvent(bufferedEvent);
1336
+ }
1337
+ this._logBuffer = [];
1338
+ }
1339
+ this._processLogEvent(logEvent);
1340
+ } else {
1341
+ if (this._logBuffer.length >= MAX_LOG_BUFFER_SIZE) {
1342
+ this._logBuffer.shift();
1343
+ }
1344
+ this._logBuffer.push(logEvent);
1345
+ }
1346
+ }
1347
+ _setLevel(logger, level) {
1348
+ logger.level = level;
1349
+ logger._levelNumber = this._logLevelValues.indexOf(level);
1350
+ logger.isEnabled = level !== LogLevel.off;
1351
+ const levelNumber = logger._levelNumber;
1352
+ logger.isFatal = this._logLevelValues.indexOf(LogLevel.fatal) <= levelNumber;
1353
+ logger.isError = this._logLevelValues.indexOf(LogLevel.error) <= levelNumber;
1354
+ logger.isWarn = this._logLevelValues.indexOf(LogLevel.warn) <= levelNumber;
1355
+ logger.isInfo = this._logLevelValues.indexOf(LogLevel.info) <= levelNumber;
1356
+ logger.isDebug = this._logLevelValues.indexOf(LogLevel.debug) <= levelNumber;
1357
+ logger.isTrack = this._logLevelValues.indexOf(LogLevel.track) <= levelNumber;
1358
+ logger.isTrace = this._logLevelValues.indexOf(LogLevel.trace) <= levelNumber;
1359
+ }
1360
+ _setContext(logger, context) {
1361
+ logger.context = context ?? {};
1362
+ }
1363
+ _processLogEvent(event) {
1364
+ for (const filter of this._filters) {
1365
+ if (filter.enabled && !filter.filter(event)) {
1366
+ return;
1367
+ }
1368
+ }
1369
+ for (const appender of this._appenders) {
1370
+ try {
1371
+ if (!appender.enabled) continue;
1372
+ if (!appender.supportedLevels.has(event.level)) continue;
1373
+ appender.write(event);
1374
+ } catch (err) {
1375
+ if (console && console.log) {
1376
+ console.log(`LogM8: Failed to append log with '${appender.name}':`, err);
1377
+ }
1378
+ }
1379
+ }
1380
+ }
1381
+ _getAppender(name) {
1382
+ return this._appenders.find((a) => a.name === name);
1383
+ }
1384
+ _sortAppenders() {
1385
+ this._appenders.sort((a, b) => {
1386
+ const aPriority = a?.priority ?? 0;
1387
+ const bPriority = b?.priority ?? 0;
1388
+ return bPriority - aPriority;
1389
+ });
1390
+ }
1391
+ _getFilter(name) {
1392
+ return this._filters.find((f) => f.name === name);
1393
+ }
1394
+ _reset() {
1395
+ this.flushAppenders();
1396
+ this._appenders = [];
1397
+ this._loggers.clear();
1398
+ this._defaultLevel = LogLevel.info;
1399
+ this._pluginManager.disposePlugins();
1400
+ }
1401
+ };
1402
+
1403
+ // src/index.ts
1404
+ var Logging = new LogM8();
1405
+ // Annotate the CommonJS export names for ESM import in node:
1406
+ 0 && (module.exports = {
1407
+ LogLevel,
1408
+ LogM8Utils,
1409
+ Logging,
1410
+ PluginKind
1411
+ });
1412
+ //# sourceMappingURL=index.cjs.map