plslog 1.0.0 → 1.1.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/dist/index.d.mts +147 -2
- package/dist/index.d.ts +147 -2
- package/dist/index.js +509 -8
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +509 -8
- package/dist/index.mjs.map +1 -1
- package/package.json +16 -15
package/dist/index.mjs
CHANGED
|
@@ -6,6 +6,181 @@ function isBase64(link) {
|
|
|
6
6
|
return link.startsWith("data:image/") && link.includes(";base64");
|
|
7
7
|
}
|
|
8
8
|
var Formatter = class {
|
|
9
|
+
/**
|
|
10
|
+
* Check if a field should be filtered based on configured rules
|
|
11
|
+
*/
|
|
12
|
+
static shouldFilterField(key, value, path, filterFields) {
|
|
13
|
+
if (!filterFields || filterFields.length === 0) {
|
|
14
|
+
return false;
|
|
15
|
+
}
|
|
16
|
+
for (const filter of filterFields) {
|
|
17
|
+
if (typeof filter === "string" && key === filter) {
|
|
18
|
+
return true;
|
|
19
|
+
}
|
|
20
|
+
if (filter instanceof RegExp && filter.test(key)) {
|
|
21
|
+
return true;
|
|
22
|
+
}
|
|
23
|
+
if (typeof filter === "function") {
|
|
24
|
+
const shouldShow = filter(key, value, path);
|
|
25
|
+
if (shouldShow === false) {
|
|
26
|
+
return true;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
return false;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Check if a field should be included based on pick/omit rules
|
|
34
|
+
*/
|
|
35
|
+
static shouldIncludeField(key, value, path, options) {
|
|
36
|
+
if (options?.pick && options.pick.length > 0) {
|
|
37
|
+
const pathWithoutIndices = path.filter((p) => !p.startsWith("["));
|
|
38
|
+
const fullPath = [...pathWithoutIndices, key].join(".");
|
|
39
|
+
const isInPickList = options.pick.some((pickPath) => {
|
|
40
|
+
if (pickPath.endsWith(".*")) {
|
|
41
|
+
const prefix = pickPath.slice(0, -2);
|
|
42
|
+
return fullPath.startsWith(prefix + ".") || fullPath === prefix;
|
|
43
|
+
}
|
|
44
|
+
if (!pickPath.includes(".") && key === pickPath) {
|
|
45
|
+
return true;
|
|
46
|
+
}
|
|
47
|
+
if (fullPath === pickPath) {
|
|
48
|
+
return true;
|
|
49
|
+
}
|
|
50
|
+
if (pickPath.startsWith(fullPath + ".")) {
|
|
51
|
+
return true;
|
|
52
|
+
}
|
|
53
|
+
if (fullPath.startsWith(pickPath + ".")) {
|
|
54
|
+
return true;
|
|
55
|
+
}
|
|
56
|
+
return false;
|
|
57
|
+
});
|
|
58
|
+
if (!isInPickList) {
|
|
59
|
+
return { include: false, filtered: false };
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
if (options?.omit && options.omit.length > 0) {
|
|
63
|
+
const shouldOmit = this.shouldFilterField(key, value, path, options.omit);
|
|
64
|
+
if (shouldOmit) {
|
|
65
|
+
return { include: true, filtered: true };
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
return { include: true, filtered: false };
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Get the replacement value for a filtered field
|
|
72
|
+
*/
|
|
73
|
+
static getFilteredValue(value, mode = "redact", replacement = "***REDACTED***") {
|
|
74
|
+
switch (mode) {
|
|
75
|
+
case "redact":
|
|
76
|
+
return replacement;
|
|
77
|
+
case "type":
|
|
78
|
+
if (value === null) return "[null]";
|
|
79
|
+
if (value === void 0) return "[undefined]";
|
|
80
|
+
if (Array.isArray(value)) return `[Array(${value.length})]`;
|
|
81
|
+
return `[${typeof value}]`;
|
|
82
|
+
case "length":
|
|
83
|
+
if (typeof value === "string") return `[${value.length} chars]`;
|
|
84
|
+
if (Array.isArray(value)) return `[${value.length} items]`;
|
|
85
|
+
if (typeof value === "object" && value !== null) {
|
|
86
|
+
return `[${Object.keys(value).length} keys]`;
|
|
87
|
+
}
|
|
88
|
+
return `[${typeof value}]`;
|
|
89
|
+
case "hide":
|
|
90
|
+
return void 0;
|
|
91
|
+
// Will be removed by caller
|
|
92
|
+
default:
|
|
93
|
+
return replacement;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Sanitize sensitive data from stack traces
|
|
98
|
+
*/
|
|
99
|
+
static sanitizeStackTrace(stack, customPatterns) {
|
|
100
|
+
const defaultPatterns = [
|
|
101
|
+
/([?&])(token|key|secret|password|auth|api[_-]?key|access[_-]?token|client[_-]?secret)=[^&\s)#]*/gi,
|
|
102
|
+
/\/\/[^/]*:[^@]*@/g,
|
|
103
|
+
// Basic auth in URLs (user:pass@domain)
|
|
104
|
+
/(bearer\s+)[a-zA-Z0-9\-._~+/]+=*/gi
|
|
105
|
+
// Bearer tokens
|
|
106
|
+
];
|
|
107
|
+
const allPatterns = [...defaultPatterns, ...customPatterns || []];
|
|
108
|
+
let sanitized = stack;
|
|
109
|
+
allPatterns.forEach((pattern) => {
|
|
110
|
+
sanitized = sanitized.replace(pattern, (match, p1) => {
|
|
111
|
+
if (p1) {
|
|
112
|
+
return `${p1}***REDACTED***`;
|
|
113
|
+
}
|
|
114
|
+
return "***REDACTED***";
|
|
115
|
+
});
|
|
116
|
+
});
|
|
117
|
+
return sanitized;
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Serialize Error objects with all relevant properties (name, message, stack, cause)
|
|
121
|
+
*/
|
|
122
|
+
static serializeError(error, config, currentDepth = 0) {
|
|
123
|
+
const maxCauseDepth = config?.maxCauseDepth ?? 10;
|
|
124
|
+
if (currentDepth > maxCauseDepth) {
|
|
125
|
+
return "[Max Cause Depth Reached]";
|
|
126
|
+
}
|
|
127
|
+
const includeStack = config?.includeStack ?? true;
|
|
128
|
+
const includeCause = config?.includeCause ?? true;
|
|
129
|
+
const stackFrameLimit = config?.stackFrameLimit;
|
|
130
|
+
const sanitizeStack = config?.sanitizeStack ?? false;
|
|
131
|
+
const serialized = {
|
|
132
|
+
name: error.name,
|
|
133
|
+
message: error.message
|
|
134
|
+
};
|
|
135
|
+
if ("code" in error) {
|
|
136
|
+
const errorWithCode = error;
|
|
137
|
+
if (typeof errorWithCode.code === "number") {
|
|
138
|
+
serialized.code = errorWithCode.code;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
if (includeStack && error.stack) {
|
|
142
|
+
let stack = error.stack;
|
|
143
|
+
if (sanitizeStack) {
|
|
144
|
+
stack = this.sanitizeStackTrace(stack, config?.sanitizePatterns);
|
|
145
|
+
}
|
|
146
|
+
if (stackFrameLimit !== void 0 && stackFrameLimit > 0) {
|
|
147
|
+
const lines = stack.split("\n");
|
|
148
|
+
if (lines.length > stackFrameLimit) {
|
|
149
|
+
stack = lines.slice(0, stackFrameLimit).join("\n") + "\n... (truncated)";
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
serialized.stack = stack;
|
|
153
|
+
}
|
|
154
|
+
if (includeCause && error.cause !== void 0) {
|
|
155
|
+
if (error.cause instanceof Error) {
|
|
156
|
+
const serializedCause = this.serializeError(error.cause, config, currentDepth + 1);
|
|
157
|
+
serialized.cause = typeof serializedCause === "string" ? serializedCause : serializedCause;
|
|
158
|
+
} else {
|
|
159
|
+
serialized.cause = error.cause;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
for (const key in error) {
|
|
163
|
+
if (Object.prototype.hasOwnProperty.call(error, key)) {
|
|
164
|
+
if (!["name", "message", "stack", "cause"].includes(key)) {
|
|
165
|
+
const errorWithCustomProps = error;
|
|
166
|
+
serialized[key] = errorWithCustomProps[key];
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
if (error.name === "AggregateError" && "errors" in error) {
|
|
171
|
+
const aggregateError = error;
|
|
172
|
+
if (Array.isArray(aggregateError.errors)) {
|
|
173
|
+
serialized.errors = aggregateError.errors.map((e) => {
|
|
174
|
+
if (e instanceof Error) {
|
|
175
|
+
const serializedErr = this.serializeError(e, config, currentDepth + 1);
|
|
176
|
+
return typeof serializedErr === "string" ? { name: "Error", message: serializedErr } : serializedErr;
|
|
177
|
+
}
|
|
178
|
+
return e;
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
return serialized;
|
|
183
|
+
}
|
|
9
184
|
/**
|
|
10
185
|
* Format base64 string for logging
|
|
11
186
|
*/
|
|
@@ -31,8 +206,11 @@ var Formatter = class {
|
|
|
31
206
|
/**
|
|
32
207
|
* Helper to serialize Blob and File objects for logging
|
|
33
208
|
*/
|
|
34
|
-
static serializeSpecialObjects(obj, maxDepth = 10, currentDepth = 0) {
|
|
209
|
+
static serializeSpecialObjects(obj, maxDepth = 10, currentDepth = 0, filterOptions, currentPath = []) {
|
|
35
210
|
if (currentDepth > maxDepth) return "[Max Depth Reached]";
|
|
211
|
+
if (obj instanceof Error) {
|
|
212
|
+
return this.serializeError(obj, filterOptions?.errorConfig, 0);
|
|
213
|
+
}
|
|
36
214
|
if (typeof obj === "string" && isBase64(obj)) {
|
|
37
215
|
return this.formatBase64String(obj);
|
|
38
216
|
}
|
|
@@ -51,13 +229,62 @@ var Formatter = class {
|
|
|
51
229
|
};
|
|
52
230
|
}
|
|
53
231
|
if (Array.isArray(obj)) {
|
|
54
|
-
return obj.map(
|
|
232
|
+
return obj.map(
|
|
233
|
+
(item, index) => this.serializeSpecialObjects(
|
|
234
|
+
item,
|
|
235
|
+
maxDepth,
|
|
236
|
+
currentDepth + 1,
|
|
237
|
+
filterOptions,
|
|
238
|
+
[...currentPath, `[${index}]`]
|
|
239
|
+
)
|
|
240
|
+
);
|
|
55
241
|
}
|
|
56
242
|
if (obj !== null && typeof obj === "object") {
|
|
57
243
|
const serialized = {};
|
|
58
244
|
for (const key in obj) {
|
|
59
245
|
if (Object.prototype.hasOwnProperty.call(obj, key)) {
|
|
60
|
-
|
|
246
|
+
const value = obj[key];
|
|
247
|
+
const newPath = [...currentPath, key];
|
|
248
|
+
const { include, filtered } = this.shouldIncludeField(
|
|
249
|
+
key,
|
|
250
|
+
value,
|
|
251
|
+
currentPath,
|
|
252
|
+
filterOptions
|
|
253
|
+
);
|
|
254
|
+
if (!include) {
|
|
255
|
+
continue;
|
|
256
|
+
}
|
|
257
|
+
let isGloballyFiltered = false;
|
|
258
|
+
if (filterOptions?.filterFields && filterOptions.filterFields.length > 0) {
|
|
259
|
+
isGloballyFiltered = this.shouldFilterField(key, value, currentPath, filterOptions.filterFields);
|
|
260
|
+
}
|
|
261
|
+
if (isGloballyFiltered) {
|
|
262
|
+
const filteredValue = this.getFilteredValue(
|
|
263
|
+
value,
|
|
264
|
+
filterOptions?.globalFilterMode,
|
|
265
|
+
filterOptions?.globalFilterReplacement
|
|
266
|
+
);
|
|
267
|
+
if (filteredValue !== void 0) {
|
|
268
|
+
serialized[key] = filteredValue;
|
|
269
|
+
}
|
|
270
|
+
} else if (filtered) {
|
|
271
|
+
const filteredValue = this.getFilteredValue(
|
|
272
|
+
value,
|
|
273
|
+
filterOptions?.filterMode,
|
|
274
|
+
filterOptions?.filterReplacement
|
|
275
|
+
);
|
|
276
|
+
if (filteredValue !== void 0) {
|
|
277
|
+
serialized[key] = filteredValue;
|
|
278
|
+
}
|
|
279
|
+
} else {
|
|
280
|
+
serialized[key] = this.serializeSpecialObjects(
|
|
281
|
+
value,
|
|
282
|
+
maxDepth,
|
|
283
|
+
currentDepth + 1,
|
|
284
|
+
filterOptions,
|
|
285
|
+
newPath
|
|
286
|
+
);
|
|
287
|
+
}
|
|
61
288
|
}
|
|
62
289
|
}
|
|
63
290
|
return serialized;
|
|
@@ -76,10 +303,23 @@ var Formatter = class {
|
|
|
76
303
|
const isArray = Array.isArray(arg);
|
|
77
304
|
if (isObject || isArray) {
|
|
78
305
|
const maxDepth = options?.maxDepth ?? 5;
|
|
79
|
-
const serialized = this.serializeSpecialObjects(
|
|
306
|
+
const serialized = this.serializeSpecialObjects(
|
|
307
|
+
arg,
|
|
308
|
+
maxDepth,
|
|
309
|
+
0,
|
|
310
|
+
{
|
|
311
|
+
pick: options?.pick,
|
|
312
|
+
omit: options?.omit,
|
|
313
|
+
filterFields: options?.filterFields,
|
|
314
|
+
filterMode: options?.filterMode,
|
|
315
|
+
filterReplacement: options?.filterReplacement,
|
|
316
|
+
globalFilterMode: options?.globalFilterMode,
|
|
317
|
+
globalFilterReplacement: options?.globalFilterReplacement,
|
|
318
|
+
errorConfig: options?.errorConfig
|
|
319
|
+
}
|
|
320
|
+
);
|
|
80
321
|
const formatted = format(serialized, {
|
|
81
|
-
|
|
82
|
-
indent: options?.indent ?? 2,
|
|
322
|
+
indent: 2,
|
|
83
323
|
maxDepth,
|
|
84
324
|
// Remove Object and Array labels
|
|
85
325
|
printFunctionName: false,
|
|
@@ -108,6 +348,10 @@ var LOG_COLORS = {
|
|
|
108
348
|
none: "#fff"
|
|
109
349
|
};
|
|
110
350
|
var DEFAULT_NAMESPACE = "app";
|
|
351
|
+
var DEFAULT_DEDUP_CONFIG = {
|
|
352
|
+
enabled: false,
|
|
353
|
+
flushInterval: 100
|
|
354
|
+
};
|
|
111
355
|
|
|
112
356
|
// src/styler.ts
|
|
113
357
|
function getColorForLevel(level) {
|
|
@@ -214,6 +458,41 @@ var NamespaceMatcher = class {
|
|
|
214
458
|
var namespaceMatcher = new NamespaceMatcher();
|
|
215
459
|
var namespace_matcher_default = namespaceMatcher;
|
|
216
460
|
|
|
461
|
+
// src/dedup-hasher.ts
|
|
462
|
+
var DedupHasher = class {
|
|
463
|
+
/**
|
|
464
|
+
* Default hash function - simple string concatenation
|
|
465
|
+
* For better performance, we use a simple approach rather than crypto hashing
|
|
466
|
+
*/
|
|
467
|
+
static generateKey(level, message, args) {
|
|
468
|
+
const argsString = this.serializeArgs(args);
|
|
469
|
+
return `${level}:${message}:${argsString}`;
|
|
470
|
+
}
|
|
471
|
+
/**
|
|
472
|
+
* Serialize arguments to a consistent string representation
|
|
473
|
+
*/
|
|
474
|
+
static serializeArgs(args) {
|
|
475
|
+
if (args.length === 0) return "";
|
|
476
|
+
try {
|
|
477
|
+
return JSON.stringify(args, (key, value) => {
|
|
478
|
+
if (typeof value === "function") {
|
|
479
|
+
return "[Function]";
|
|
480
|
+
}
|
|
481
|
+
if (typeof value === "symbol") {
|
|
482
|
+
return value.toString();
|
|
483
|
+
}
|
|
484
|
+
if (value === void 0) {
|
|
485
|
+
return "[undefined]";
|
|
486
|
+
}
|
|
487
|
+
return value;
|
|
488
|
+
});
|
|
489
|
+
} catch (error) {
|
|
490
|
+
return String(args);
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
};
|
|
494
|
+
var dedup_hasher_default = DedupHasher;
|
|
495
|
+
|
|
217
496
|
// src/logger.ts
|
|
218
497
|
var globalConfig = {};
|
|
219
498
|
function configure(options) {
|
|
@@ -222,9 +501,22 @@ function configure(options) {
|
|
|
222
501
|
var Logger = class {
|
|
223
502
|
constructor(options = {}) {
|
|
224
503
|
this._level = "debug";
|
|
504
|
+
this._dedupBuffer = /* @__PURE__ */ new Map();
|
|
505
|
+
// Temporary filter options that reset after each log
|
|
506
|
+
this._tempFilterOptions = {};
|
|
507
|
+
// Custom context for conditional logging
|
|
508
|
+
this._context = {};
|
|
509
|
+
// Temporary conditions that reset after each log
|
|
510
|
+
this._tempConditions = [];
|
|
225
511
|
this._namespace = options.namespace ?? DEFAULT_NAMESPACE;
|
|
226
512
|
this._level = options.level ?? globalConfig.level ?? "debug";
|
|
227
513
|
this._maxDepth = options.maxDepth ?? globalConfig.maxDepth ?? 10;
|
|
514
|
+
this._context = options.context ?? {};
|
|
515
|
+
this._dedupConfig = {
|
|
516
|
+
...DEFAULT_DEDUP_CONFIG,
|
|
517
|
+
...globalConfig.dedup || {},
|
|
518
|
+
...options.dedup || {}
|
|
519
|
+
};
|
|
228
520
|
}
|
|
229
521
|
debug(message, ...args) {
|
|
230
522
|
this.log("debug", message, ...args);
|
|
@@ -242,13 +534,134 @@ var Logger = class {
|
|
|
242
534
|
this.log("error", message, ...args);
|
|
243
535
|
return this;
|
|
244
536
|
}
|
|
537
|
+
/**
|
|
538
|
+
* Assert that a condition is truthy. Logs an error if the condition is falsy.
|
|
539
|
+
* Similar to console.assert() but integrates with plslog's features.
|
|
540
|
+
* Uses JavaScript truthiness - falsy values: false, 0, '', null, undefined, NaN
|
|
541
|
+
*
|
|
542
|
+
* @param condition - The condition to assert (uses JavaScript truthiness)
|
|
543
|
+
* @param message - Error message to log if assertion fails
|
|
544
|
+
* @param args - Additional arguments to log
|
|
545
|
+
* @returns this for chaining
|
|
546
|
+
*
|
|
547
|
+
* @example
|
|
548
|
+
* logger.assert(user !== null, 'User should not be null', user);
|
|
549
|
+
* logger.assert(count > 0, 'Count must be positive', { count });
|
|
550
|
+
* logger.assert(data, 'Data is required'); // Checks if data is truthy
|
|
551
|
+
*/
|
|
552
|
+
assert(condition, message, ...args) {
|
|
553
|
+
if (!condition) {
|
|
554
|
+
this.log("error", message, ...args);
|
|
555
|
+
}
|
|
556
|
+
return this;
|
|
557
|
+
}
|
|
245
558
|
setLevel(level) {
|
|
246
559
|
this._level = level;
|
|
247
560
|
}
|
|
561
|
+
/**
|
|
562
|
+
* Set the namespace for this logger instance
|
|
563
|
+
* Returns this for method chaining
|
|
564
|
+
*
|
|
565
|
+
* @param namespace - The namespace string (e.g., 'service:api', 'component')
|
|
566
|
+
* @returns this for chaining
|
|
567
|
+
*
|
|
568
|
+
* @example
|
|
569
|
+
* const log = logger().namespace('service:api');
|
|
570
|
+
* log.debug('Message'); // Logs with namespace 'service:api'
|
|
571
|
+
*
|
|
572
|
+
* // Can also be called after instantiation
|
|
573
|
+
* const log2 = logger();
|
|
574
|
+
* log2.namespace('component').debug('Message');
|
|
575
|
+
*/
|
|
576
|
+
namespace(namespace) {
|
|
577
|
+
this._namespace = namespace;
|
|
578
|
+
return this;
|
|
579
|
+
}
|
|
248
580
|
maxDepth(maxDepth) {
|
|
249
581
|
this._maxDepth = maxDepth;
|
|
250
582
|
return this;
|
|
251
583
|
}
|
|
584
|
+
/**
|
|
585
|
+
* Pick only specific fields to log (whitelist approach)
|
|
586
|
+
* Supports dot notation for nested fields: 'user.name', 'user.email'
|
|
587
|
+
* Supports wildcards: 'user.*' matches all fields under user
|
|
588
|
+
*/
|
|
589
|
+
pick(fields) {
|
|
590
|
+
this._tempFilterOptions.pick = fields;
|
|
591
|
+
return this;
|
|
592
|
+
}
|
|
593
|
+
/**
|
|
594
|
+
* Omit specific fields from logging (blacklist approach)
|
|
595
|
+
* Supports strings, regex patterns, and predicate functions
|
|
596
|
+
*/
|
|
597
|
+
omit(fields) {
|
|
598
|
+
this._tempFilterOptions.omit = fields;
|
|
599
|
+
return this;
|
|
600
|
+
}
|
|
601
|
+
/**
|
|
602
|
+
* Set filter mode for per-log filtering
|
|
603
|
+
* - 'redact': Replace with custom string (default: '***REDACTED***')
|
|
604
|
+
* - 'hide': Remove field completely
|
|
605
|
+
* - 'type': Show type info like '[string]', '[Array(3)]'
|
|
606
|
+
* - 'length': Show length info like '[8 chars]', '[3 items]'
|
|
607
|
+
*/
|
|
608
|
+
filterMode(mode, replacement) {
|
|
609
|
+
this._tempFilterOptions.filterMode = mode;
|
|
610
|
+
if (replacement !== void 0) {
|
|
611
|
+
this._tempFilterOptions.filterReplacement = replacement;
|
|
612
|
+
}
|
|
613
|
+
return this;
|
|
614
|
+
}
|
|
615
|
+
once(enabled = true) {
|
|
616
|
+
this._dedupConfig.enabled = enabled;
|
|
617
|
+
return this;
|
|
618
|
+
}
|
|
619
|
+
flushDedup() {
|
|
620
|
+
const keys = Array.from(this._dedupBuffer.keys());
|
|
621
|
+
keys.forEach((key) => this.flushDedupEntry(key));
|
|
622
|
+
return this;
|
|
623
|
+
}
|
|
624
|
+
/**
|
|
625
|
+
* Execute log only if condition is true
|
|
626
|
+
* Supports boolean values or predicate functions
|
|
627
|
+
* Multiple when() calls are AND-ed together
|
|
628
|
+
*
|
|
629
|
+
* @example
|
|
630
|
+
* // Boolean condition
|
|
631
|
+
* logger.when(isDev).debug('Dev only');
|
|
632
|
+
*
|
|
633
|
+
* // Predicate with context
|
|
634
|
+
* logger.when(ctx => ctx.namespace === 'auth').debug('Auth log');
|
|
635
|
+
*
|
|
636
|
+
* // Chaining (both must be true)
|
|
637
|
+
* logger
|
|
638
|
+
* .when(ctx => ctx.level === 'debug')
|
|
639
|
+
* .when(isDevMode)
|
|
640
|
+
* .debug('Conditional');
|
|
641
|
+
*/
|
|
642
|
+
when(condition) {
|
|
643
|
+
this._tempConditions.push(condition);
|
|
644
|
+
return this;
|
|
645
|
+
}
|
|
646
|
+
/**
|
|
647
|
+
* Execute log only if condition is false (inverse of when)
|
|
648
|
+
* Supports boolean values or predicate functions
|
|
649
|
+
*
|
|
650
|
+
* @example
|
|
651
|
+
* // Boolean condition
|
|
652
|
+
* logger.unless(isProd).debug('Not in production');
|
|
653
|
+
*
|
|
654
|
+
* // Predicate with context
|
|
655
|
+
* logger.unless(ctx => ctx.level === 'error').info('Non-error log');
|
|
656
|
+
*/
|
|
657
|
+
unless(condition) {
|
|
658
|
+
if (typeof condition === "function") {
|
|
659
|
+
this._tempConditions.push((ctx) => !condition(ctx));
|
|
660
|
+
} else {
|
|
661
|
+
this._tempConditions.push(!condition);
|
|
662
|
+
}
|
|
663
|
+
return this;
|
|
664
|
+
}
|
|
252
665
|
getLevelPriority(level) {
|
|
253
666
|
const priorities = {
|
|
254
667
|
debug: 0,
|
|
@@ -260,6 +673,21 @@ var Logger = class {
|
|
|
260
673
|
return priorities[level];
|
|
261
674
|
}
|
|
262
675
|
log(level, message, ...args) {
|
|
676
|
+
if (this._tempConditions.length > 0) {
|
|
677
|
+
const ctx = {
|
|
678
|
+
namespace: this._namespace,
|
|
679
|
+
level,
|
|
680
|
+
...this._context
|
|
681
|
+
};
|
|
682
|
+
const conditionsMet = this._tempConditions.every(
|
|
683
|
+
(condition) => typeof condition === "function" ? condition(ctx) : condition
|
|
684
|
+
);
|
|
685
|
+
this._tempConditions = [];
|
|
686
|
+
if (!conditionsMet) {
|
|
687
|
+
this._tempFilterOptions = {};
|
|
688
|
+
return;
|
|
689
|
+
}
|
|
690
|
+
}
|
|
263
691
|
if (!namespace_matcher_default.matches(this._namespace, globalConfig.namespaces)) {
|
|
264
692
|
return;
|
|
265
693
|
}
|
|
@@ -270,8 +698,80 @@ var Logger = class {
|
|
|
270
698
|
} else if (this.getLevelPriority(this._level) > this.getLevelPriority(level)) {
|
|
271
699
|
return;
|
|
272
700
|
}
|
|
701
|
+
const filterOptions = { ...this._tempFilterOptions };
|
|
702
|
+
this._tempFilterOptions = {};
|
|
703
|
+
if (this._dedupConfig.enabled) {
|
|
704
|
+
this.logWithDedup(level, message, filterOptions, ...args);
|
|
705
|
+
} else {
|
|
706
|
+
this.logImmediate(level, message, filterOptions, ...args);
|
|
707
|
+
}
|
|
708
|
+
}
|
|
709
|
+
logWithDedup(level, message, filterOptions, ...args) {
|
|
710
|
+
const key = dedup_hasher_default.generateKey(level, message, args);
|
|
711
|
+
const existing = this._dedupBuffer.get(key);
|
|
712
|
+
const now = Date.now();
|
|
713
|
+
if (existing) {
|
|
714
|
+
existing.count++;
|
|
715
|
+
existing.lastTimestamp = now;
|
|
716
|
+
existing.args = args;
|
|
717
|
+
existing.filterOptions = filterOptions;
|
|
718
|
+
if (existing.flushTimeoutId !== void 0) {
|
|
719
|
+
clearTimeout(existing.flushTimeoutId);
|
|
720
|
+
}
|
|
721
|
+
existing.flushTimeoutId = setTimeout(() => {
|
|
722
|
+
this.flushDedupEntry(key);
|
|
723
|
+
}, this._dedupConfig.flushInterval);
|
|
724
|
+
} else {
|
|
725
|
+
const entry = {
|
|
726
|
+
key,
|
|
727
|
+
level,
|
|
728
|
+
message,
|
|
729
|
+
args,
|
|
730
|
+
count: 1,
|
|
731
|
+
firstTimestamp: now,
|
|
732
|
+
lastTimestamp: now,
|
|
733
|
+
filterOptions
|
|
734
|
+
};
|
|
735
|
+
this._dedupBuffer.set(key, entry);
|
|
736
|
+
entry.flushTimeoutId = setTimeout(() => {
|
|
737
|
+
this.flushDedupEntry(key);
|
|
738
|
+
}, this._dedupConfig.flushInterval);
|
|
739
|
+
}
|
|
740
|
+
}
|
|
741
|
+
flushDedupEntry(key) {
|
|
742
|
+
const entry = this._dedupBuffer.get(key);
|
|
743
|
+
if (!entry) return;
|
|
744
|
+
let displayMessage = entry.message;
|
|
745
|
+
if (entry.count > 1) {
|
|
746
|
+
const firstTime = this.formatTimestamp(entry.firstTimestamp);
|
|
747
|
+
const lastTime = this.formatTimestamp(entry.lastTimestamp);
|
|
748
|
+
displayMessage = `${entry.message} (x${entry.count}, ${firstTime} \u2192 ${lastTime})`;
|
|
749
|
+
}
|
|
750
|
+
this.logImmediate(entry.level, displayMessage, entry.filterOptions || {}, ...entry.args);
|
|
751
|
+
this._dedupBuffer.delete(key);
|
|
752
|
+
}
|
|
753
|
+
formatTimestamp(timestamp) {
|
|
754
|
+
const date = new Date(timestamp);
|
|
755
|
+
const hours = String(date.getHours()).padStart(2, "0");
|
|
756
|
+
const minutes = String(date.getMinutes()).padStart(2, "0");
|
|
757
|
+
const seconds = String(date.getSeconds()).padStart(2, "0");
|
|
758
|
+
const milliseconds = String(date.getMilliseconds()).padStart(3, "0");
|
|
759
|
+
return `${hours}:${minutes}:${seconds}.${milliseconds}`;
|
|
760
|
+
}
|
|
761
|
+
logImmediate(level, message, filterOptions, ...args) {
|
|
273
762
|
const formattedObject = Formatter.formatObj(args, {
|
|
274
|
-
maxDepth: this._maxDepth
|
|
763
|
+
maxDepth: this._maxDepth,
|
|
764
|
+
// Global filter fields (always applied for security)
|
|
765
|
+
filterFields: globalConfig.filterFields,
|
|
766
|
+
globalFilterMode: globalConfig.filterMode,
|
|
767
|
+
globalFilterReplacement: globalConfig.filterReplacement,
|
|
768
|
+
// Per-log pick/omit (contextual filtering)
|
|
769
|
+
pick: filterOptions.pick,
|
|
770
|
+
omit: filterOptions.omit,
|
|
771
|
+
filterMode: filterOptions.filterMode,
|
|
772
|
+
filterReplacement: filterOptions.filterReplacement,
|
|
773
|
+
// Error serialization config
|
|
774
|
+
errorConfig: globalConfig.errorSerialization
|
|
275
775
|
});
|
|
276
776
|
const formattedMessage = formattedObject ? `${message} ${formattedObject}` : message;
|
|
277
777
|
const { logMessage, logStyles } = styler_default.style({
|
|
@@ -299,7 +799,8 @@ var Logger = class {
|
|
|
299
799
|
}
|
|
300
800
|
};
|
|
301
801
|
var plslog = ((options = {}) => {
|
|
302
|
-
|
|
802
|
+
const normalizedOptions = typeof options === "string" ? { namespace: options } : options;
|
|
803
|
+
return new Logger(normalizedOptions);
|
|
303
804
|
});
|
|
304
805
|
plslog.configure = configure;
|
|
305
806
|
var logger_default = plslog;
|