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