@rawnodes/logger 2.7.0 → 2.7.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +55 -1
- package/dist/index.d.mts +144 -8
- package/dist/index.d.ts +144 -8
- package/dist/index.js +261 -67
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +261 -67
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -6,7 +6,6 @@ var async_hooks = require('async_hooks');
|
|
|
6
6
|
var stream = require('stream');
|
|
7
7
|
var promises = require('fs/promises');
|
|
8
8
|
var rotatingFileStream = require('rotating-file-stream');
|
|
9
|
-
var events = require('events');
|
|
10
9
|
var clientCloudwatchLogs = require('@aws-sdk/client-cloudwatch-logs');
|
|
11
10
|
var crypto = require('crypto');
|
|
12
11
|
var zod = require('zod');
|
|
@@ -56,8 +55,24 @@ var MessageBuffer = class {
|
|
|
56
55
|
timer = null;
|
|
57
56
|
flushing = false;
|
|
58
57
|
closed = false;
|
|
58
|
+
droppedCount = 0;
|
|
59
59
|
add(message) {
|
|
60
60
|
if (this.closed) return;
|
|
61
|
+
const limit = this.options.maxQueueSize;
|
|
62
|
+
if (limit !== void 0 && this.queue.length >= limit) {
|
|
63
|
+
const policy = this.options.dropPolicy ?? "drop-oldest";
|
|
64
|
+
const dropped = policy === "drop-oldest" ? [this.queue.shift()] : [message];
|
|
65
|
+
this.droppedCount += 1;
|
|
66
|
+
if (this.options.onDrop) {
|
|
67
|
+
try {
|
|
68
|
+
this.options.onDrop(dropped);
|
|
69
|
+
} catch {
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
if (policy === "drop-newest") {
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
61
76
|
this.queue.push(message);
|
|
62
77
|
if (this.queue.length >= this.options.batchSize) {
|
|
63
78
|
void this.flush();
|
|
@@ -81,13 +96,34 @@ var MessageBuffer = class {
|
|
|
81
96
|
}
|
|
82
97
|
}
|
|
83
98
|
}
|
|
84
|
-
|
|
99
|
+
/**
|
|
100
|
+
* Stops accepting new messages and attempts to flush what's already buffered.
|
|
101
|
+
* Returns when the queue is drained OR when `timeoutMs` elapses. A timeout
|
|
102
|
+
* is essential during graceful shutdown — without it, a dead transport would
|
|
103
|
+
* block process exit indefinitely, and orchestrators like Kubernetes would
|
|
104
|
+
* escalate to SIGKILL after their grace period.
|
|
105
|
+
*
|
|
106
|
+
* Default timeout: 5 seconds. Pass `Infinity` to preserve legacy behaviour.
|
|
107
|
+
*/
|
|
108
|
+
async close(timeoutMs = 5e3) {
|
|
85
109
|
this.closed = true;
|
|
86
110
|
this.clearTimer();
|
|
111
|
+
const deadline = timeoutMs === Infinity ? Infinity : Date.now() + timeoutMs;
|
|
87
112
|
while (this.queue.length > 0) {
|
|
113
|
+
if (Date.now() >= deadline) {
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
88
116
|
await this.flush();
|
|
89
117
|
}
|
|
90
118
|
}
|
|
119
|
+
/** Current number of messages waiting in the queue. */
|
|
120
|
+
get size() {
|
|
121
|
+
return this.queue.length;
|
|
122
|
+
}
|
|
123
|
+
/** Total number of messages dropped due to queue overflow since creation. */
|
|
124
|
+
get droppedTotal() {
|
|
125
|
+
return this.droppedCount;
|
|
126
|
+
}
|
|
91
127
|
scheduleFlush() {
|
|
92
128
|
if (this.timer || this.closed) return;
|
|
93
129
|
this.timer = setTimeout(() => {
|
|
@@ -128,15 +164,19 @@ var DEFAULT_OPTIONS = {
|
|
|
128
164
|
maxRetries: 3,
|
|
129
165
|
retryDelay: 1e3
|
|
130
166
|
};
|
|
131
|
-
var BaseHttpTransport = class
|
|
167
|
+
var BaseHttpTransport = class {
|
|
132
168
|
buffer;
|
|
169
|
+
onErrorCallback;
|
|
133
170
|
constructor(opts = {}) {
|
|
134
|
-
|
|
171
|
+
this.onErrorCallback = opts.onError;
|
|
135
172
|
this.buffer = new MessageBuffer({
|
|
136
173
|
batchSize: opts.batchSize ?? DEFAULT_OPTIONS.batchSize,
|
|
137
174
|
flushInterval: opts.flushInterval ?? DEFAULT_OPTIONS.flushInterval,
|
|
138
175
|
maxRetries: opts.maxRetries ?? DEFAULT_OPTIONS.maxRetries,
|
|
139
176
|
retryDelay: opts.retryDelay ?? DEFAULT_OPTIONS.retryDelay,
|
|
177
|
+
maxQueueSize: opts.maxQueueSize,
|
|
178
|
+
dropPolicy: opts.dropPolicy,
|
|
179
|
+
onDrop: opts.onDrop,
|
|
140
180
|
onFlush: this.sendBatch.bind(this),
|
|
141
181
|
onError: this.handleError.bind(this)
|
|
142
182
|
});
|
|
@@ -146,8 +186,20 @@ var BaseHttpTransport = class extends events.EventEmitter {
|
|
|
146
186
|
this.buffer.add(message);
|
|
147
187
|
callback();
|
|
148
188
|
}
|
|
149
|
-
|
|
150
|
-
|
|
189
|
+
/**
|
|
190
|
+
* Stop accepting new messages and flush what's buffered. `timeoutMs` caps
|
|
191
|
+
* how long the flush may take; after that the remaining queue is discarded
|
|
192
|
+
* so shutdown can complete. Default 5000ms.
|
|
193
|
+
*/
|
|
194
|
+
close(timeoutMs) {
|
|
195
|
+
return this.buffer.close(timeoutMs);
|
|
196
|
+
}
|
|
197
|
+
/** Current buffered message count and total drops since creation. */
|
|
198
|
+
getMetrics() {
|
|
199
|
+
return {
|
|
200
|
+
queueSize: this.buffer.size,
|
|
201
|
+
droppedTotal: this.buffer.droppedTotal
|
|
202
|
+
};
|
|
151
203
|
}
|
|
152
204
|
transformMessage(info) {
|
|
153
205
|
const { level, message, timestamp, context, ...meta } = info;
|
|
@@ -160,11 +212,21 @@ var BaseHttpTransport = class extends events.EventEmitter {
|
|
|
160
212
|
};
|
|
161
213
|
}
|
|
162
214
|
handleError(error, messages) {
|
|
215
|
+
if (this.onErrorCallback) {
|
|
216
|
+
try {
|
|
217
|
+
this.onErrorCallback(error, messages);
|
|
218
|
+
return;
|
|
219
|
+
} catch (callbackError) {
|
|
220
|
+
console.error(
|
|
221
|
+
`[${this.constructor.name}] onError callback threw:`,
|
|
222
|
+
callbackError instanceof Error ? callbackError.message : callbackError
|
|
223
|
+
);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
163
226
|
console.error(
|
|
164
227
|
`[${this.constructor.name}] Failed to send ${messages.length} messages:`,
|
|
165
228
|
error.message
|
|
166
229
|
);
|
|
167
|
-
this.emit("error", error);
|
|
168
230
|
}
|
|
169
231
|
};
|
|
170
232
|
|
|
@@ -189,16 +251,23 @@ var LEVEL_EMOJI = {
|
|
|
189
251
|
debug: "\u26AA",
|
|
190
252
|
silly: "\u26AB"
|
|
191
253
|
};
|
|
254
|
+
var DEFAULT_REQUEST_TIMEOUT_MS = 1e4;
|
|
192
255
|
var DiscordTransport = class extends BaseHttpTransport {
|
|
193
256
|
config;
|
|
257
|
+
requestTimeout;
|
|
194
258
|
constructor(config) {
|
|
195
259
|
super({
|
|
196
260
|
batchSize: config.batchSize ?? 10,
|
|
197
261
|
flushInterval: config.flushInterval ?? 2e3,
|
|
198
262
|
maxRetries: config.maxRetries,
|
|
199
|
-
retryDelay: config.retryDelay
|
|
263
|
+
retryDelay: config.retryDelay,
|
|
264
|
+
maxQueueSize: config.maxQueueSize,
|
|
265
|
+
dropPolicy: config.dropPolicy,
|
|
266
|
+
onDrop: config.onDrop,
|
|
267
|
+
onError: config.onError
|
|
200
268
|
});
|
|
201
269
|
this.config = config;
|
|
270
|
+
this.requestTimeout = config.requestTimeout ?? DEFAULT_REQUEST_TIMEOUT_MS;
|
|
202
271
|
}
|
|
203
272
|
async sendBatch(messages) {
|
|
204
273
|
if (this.config.format === "markdown") {
|
|
@@ -302,7 +371,8 @@ ${msg.message}`;
|
|
|
302
371
|
const response = await fetch(this.config.webhookUrl, {
|
|
303
372
|
method: "POST",
|
|
304
373
|
headers: { "Content-Type": "application/json" },
|
|
305
|
-
body: JSON.stringify(payload)
|
|
374
|
+
body: JSON.stringify(payload),
|
|
375
|
+
signal: AbortSignal.timeout(this.requestTimeout)
|
|
306
376
|
});
|
|
307
377
|
if (!response.ok) {
|
|
308
378
|
const text = await response.text();
|
|
@@ -329,18 +399,25 @@ var LEVEL_EMOJI2 = {
|
|
|
329
399
|
debug: "\u26AA",
|
|
330
400
|
silly: "\u26AB"
|
|
331
401
|
};
|
|
402
|
+
var DEFAULT_REQUEST_TIMEOUT_MS2 = 1e4;
|
|
332
403
|
var TelegramTransport = class extends BaseHttpTransport {
|
|
333
404
|
config;
|
|
334
405
|
apiUrl;
|
|
406
|
+
requestTimeout;
|
|
335
407
|
constructor(config) {
|
|
336
408
|
super({
|
|
337
409
|
batchSize: config.batchSize ?? 20,
|
|
338
410
|
flushInterval: config.flushInterval ?? 1e3,
|
|
339
411
|
maxRetries: config.maxRetries,
|
|
340
|
-
retryDelay: config.retryDelay
|
|
412
|
+
retryDelay: config.retryDelay,
|
|
413
|
+
maxQueueSize: config.maxQueueSize,
|
|
414
|
+
dropPolicy: config.dropPolicy,
|
|
415
|
+
onDrop: config.onDrop,
|
|
416
|
+
onError: config.onError
|
|
341
417
|
});
|
|
342
418
|
this.config = config;
|
|
343
419
|
this.apiUrl = `https://api.telegram.org/bot${config.botToken}`;
|
|
420
|
+
this.requestTimeout = config.requestTimeout ?? DEFAULT_REQUEST_TIMEOUT_MS2;
|
|
344
421
|
}
|
|
345
422
|
async sendBatch(messages) {
|
|
346
423
|
const text = this.formatBatchMessage(messages);
|
|
@@ -401,7 +478,8 @@ var TelegramTransport = class extends BaseHttpTransport {
|
|
|
401
478
|
const response = await fetch(`${this.apiUrl}/sendMessage`, {
|
|
402
479
|
method: "POST",
|
|
403
480
|
headers: { "Content-Type": "application/json" },
|
|
404
|
-
body: JSON.stringify(body)
|
|
481
|
+
body: JSON.stringify(body),
|
|
482
|
+
signal: AbortSignal.timeout(this.requestTimeout)
|
|
405
483
|
});
|
|
406
484
|
if (!response.ok) {
|
|
407
485
|
const result = await response.json();
|
|
@@ -470,7 +548,11 @@ var CloudWatchTransport = class extends BaseHttpTransport {
|
|
|
470
548
|
batchSize: config.batchSize ?? 100,
|
|
471
549
|
flushInterval: config.flushInterval ?? 1e3,
|
|
472
550
|
maxRetries: config.maxRetries,
|
|
473
|
-
retryDelay: config.retryDelay
|
|
551
|
+
retryDelay: config.retryDelay,
|
|
552
|
+
maxQueueSize: config.maxQueueSize,
|
|
553
|
+
dropPolicy: config.dropPolicy,
|
|
554
|
+
onDrop: config.onDrop,
|
|
555
|
+
onError: config.onError
|
|
474
556
|
});
|
|
475
557
|
this.config = config;
|
|
476
558
|
this.resolvedLogStreamName = resolveLogStreamName(config.logStreamName, configHostname);
|
|
@@ -522,7 +604,10 @@ var CloudWatchTransport = class extends BaseHttpTransport {
|
|
|
522
604
|
async ensureInitialized() {
|
|
523
605
|
if (this.initialized) return;
|
|
524
606
|
if (!this.initPromise) {
|
|
525
|
-
this.initPromise = this.initialize()
|
|
607
|
+
this.initPromise = this.initialize().catch((err) => {
|
|
608
|
+
this.initPromise = null;
|
|
609
|
+
throw err;
|
|
610
|
+
});
|
|
526
611
|
}
|
|
527
612
|
await this.initPromise;
|
|
528
613
|
}
|
|
@@ -598,15 +683,34 @@ function getLevelName(levelNum) {
|
|
|
598
683
|
if (levelNum >= 20) return "debug";
|
|
599
684
|
return "silly";
|
|
600
685
|
}
|
|
601
|
-
function shouldPassTransport(log, level, rules,
|
|
686
|
+
function shouldPassTransport(log, level, rules, state) {
|
|
602
687
|
const logLevel = getLevelName(log.level);
|
|
688
|
+
const embeddedOverride = log.__or;
|
|
689
|
+
if (typeof embeddedOverride === "string") {
|
|
690
|
+
if (embeddedOverride === "off") return false;
|
|
691
|
+
const overrideLevel = embeddedOverride;
|
|
692
|
+
return LOG_LEVELS[logLevel] <= LOG_LEVELS[overrideLevel];
|
|
693
|
+
}
|
|
603
694
|
const context = log.context;
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
695
|
+
if (rules && rules.length > 0) {
|
|
696
|
+
const storeContext = state.store.getStore();
|
|
697
|
+
const logMeta = {};
|
|
698
|
+
for (const [key, value] of Object.entries(log)) {
|
|
699
|
+
if (!RESERVED_LOG_FIELDS.has(key)) {
|
|
700
|
+
logMeta[key] = value;
|
|
701
|
+
}
|
|
702
|
+
}
|
|
703
|
+
const matchingRule = rules.find((rule) => matchesContext(storeContext, context, rule.match, logMeta));
|
|
704
|
+
if (matchingRule) {
|
|
705
|
+
if (matchingRule.level === "off") return false;
|
|
706
|
+
return LOG_LEVELS[logLevel] <= LOG_LEVELS[matchingRule.level];
|
|
707
|
+
}
|
|
708
|
+
}
|
|
709
|
+
const effectiveLevel = level ?? "silly";
|
|
607
710
|
if (effectiveLevel === "off") return false;
|
|
608
711
|
return LOG_LEVELS[logLevel] <= LOG_LEVELS[effectiveLevel];
|
|
609
712
|
}
|
|
713
|
+
var RESERVED_LOG_FIELDS = /* @__PURE__ */ new Set(["level", "time", "msg", "context", "__or"]);
|
|
610
714
|
function formatLog(log, format, store) {
|
|
611
715
|
const levelName = getLevelName(log.level);
|
|
612
716
|
const timestamp = new Date(log.time).toISOString();
|
|
@@ -615,7 +719,7 @@ function formatLog(log, format, store) {
|
|
|
615
719
|
const storeContext = store.getStore();
|
|
616
720
|
const meta = {};
|
|
617
721
|
for (const [key, value] of Object.entries(log)) {
|
|
618
|
-
if (!
|
|
722
|
+
if (!RESERVED_LOG_FIELDS.has(key)) {
|
|
619
723
|
meta[key] = value;
|
|
620
724
|
}
|
|
621
725
|
}
|
|
@@ -664,7 +768,7 @@ function formatLog(log, format, store) {
|
|
|
664
768
|
return `[${timestamp}] ${levelName}: ${message}
|
|
665
769
|
`;
|
|
666
770
|
}
|
|
667
|
-
function createFormattedFilterStream(format, level, rules,
|
|
771
|
+
function createFormattedFilterStream(format, level, rules, state, destination) {
|
|
668
772
|
return new stream.Transform({
|
|
669
773
|
transform(chunk, _encoding, callback) {
|
|
670
774
|
const line = chunk.toString().trim();
|
|
@@ -677,24 +781,24 @@ function createFormattedFilterStream(format, level, rules, store, destination) {
|
|
|
677
781
|
callback();
|
|
678
782
|
return;
|
|
679
783
|
}
|
|
680
|
-
if (!shouldPassTransport(log, level, rules,
|
|
784
|
+
if (!shouldPassTransport(log, level, rules, state)) {
|
|
681
785
|
callback();
|
|
682
786
|
return;
|
|
683
787
|
}
|
|
684
|
-
const formatted = formatLog(log, format, store);
|
|
788
|
+
const formatted = formatLog(log, format, state.store);
|
|
685
789
|
destination.write(formatted);
|
|
686
790
|
callback();
|
|
687
791
|
}
|
|
688
792
|
});
|
|
689
793
|
}
|
|
690
|
-
function createStreams(config,
|
|
794
|
+
function createStreams(config, state) {
|
|
691
795
|
const streams = [];
|
|
692
796
|
const transports = [];
|
|
693
797
|
const consoleStream = createFormattedFilterStream(
|
|
694
798
|
config.console.format,
|
|
695
799
|
config.console.level,
|
|
696
800
|
config.console.rules,
|
|
697
|
-
|
|
801
|
+
state,
|
|
698
802
|
process.stdout
|
|
699
803
|
);
|
|
700
804
|
streams.push({
|
|
@@ -727,7 +831,7 @@ function createStreams(config, store) {
|
|
|
727
831
|
fileConfig.format,
|
|
728
832
|
fileConfig.level,
|
|
729
833
|
fileConfig.rules,
|
|
730
|
-
|
|
834
|
+
state,
|
|
731
835
|
rotatingStream
|
|
732
836
|
);
|
|
733
837
|
streams.push({
|
|
@@ -738,7 +842,7 @@ function createStreams(config, store) {
|
|
|
738
842
|
for (const discordConfig of toArray(config.discord)) {
|
|
739
843
|
const transport = new DiscordTransport(discordConfig);
|
|
740
844
|
transports.push(transport);
|
|
741
|
-
const discordStream = createHttpTransportStream(transport, discordConfig.level, discordConfig.rules,
|
|
845
|
+
const discordStream = createHttpTransportStream(transport, discordConfig.level, discordConfig.rules, state);
|
|
742
846
|
streams.push({
|
|
743
847
|
level: "trace",
|
|
744
848
|
stream: discordStream
|
|
@@ -747,7 +851,7 @@ function createStreams(config, store) {
|
|
|
747
851
|
for (const telegramConfig of toArray(config.telegram)) {
|
|
748
852
|
const transport = new TelegramTransport(telegramConfig);
|
|
749
853
|
transports.push(transport);
|
|
750
|
-
const telegramStream = createHttpTransportStream(transport, telegramConfig.level, telegramConfig.rules,
|
|
854
|
+
const telegramStream = createHttpTransportStream(transport, telegramConfig.level, telegramConfig.rules, state);
|
|
751
855
|
streams.push({
|
|
752
856
|
level: "trace",
|
|
753
857
|
stream: telegramStream
|
|
@@ -756,12 +860,22 @@ function createStreams(config, store) {
|
|
|
756
860
|
for (const cloudwatchConfig of toArray(config.cloudwatch)) {
|
|
757
861
|
const transport = new CloudWatchTransport(cloudwatchConfig, config.hostname);
|
|
758
862
|
transports.push(transport);
|
|
759
|
-
const cwStream = createHttpTransportStream(transport, cloudwatchConfig.level, cloudwatchConfig.rules,
|
|
863
|
+
const cwStream = createHttpTransportStream(transport, cloudwatchConfig.level, cloudwatchConfig.rules, state);
|
|
760
864
|
streams.push({
|
|
761
865
|
level: "trace",
|
|
762
866
|
stream: cwStream
|
|
763
867
|
});
|
|
764
868
|
}
|
|
869
|
+
if (config.relay) {
|
|
870
|
+
const relayStream = pino__default.default.transport({
|
|
871
|
+
target: "@rawnodes/logger/relay",
|
|
872
|
+
options: config.relay
|
|
873
|
+
});
|
|
874
|
+
streams.push({
|
|
875
|
+
level: "trace",
|
|
876
|
+
stream: relayStream
|
|
877
|
+
});
|
|
878
|
+
}
|
|
765
879
|
return {
|
|
766
880
|
destination: pino__default.default.multistream(streams),
|
|
767
881
|
transports
|
|
@@ -771,7 +885,7 @@ function toArray(value) {
|
|
|
771
885
|
if (!value) return [];
|
|
772
886
|
return Array.isArray(value) ? value : [value];
|
|
773
887
|
}
|
|
774
|
-
function createHttpTransportStream(transport, level, rules,
|
|
888
|
+
function createHttpTransportStream(transport, level, rules, state) {
|
|
775
889
|
return new stream.Writable({
|
|
776
890
|
write(chunk, _encoding, callback) {
|
|
777
891
|
const line = chunk.toString().trim();
|
|
@@ -784,15 +898,15 @@ function createHttpTransportStream(transport, level, rules, store) {
|
|
|
784
898
|
callback();
|
|
785
899
|
return;
|
|
786
900
|
}
|
|
787
|
-
if (!shouldPassTransport(log, level, rules,
|
|
901
|
+
if (!shouldPassTransport(log, level, rules, state)) {
|
|
788
902
|
callback();
|
|
789
903
|
return;
|
|
790
904
|
}
|
|
791
905
|
const levelName = getLevelName(log.level);
|
|
792
|
-
const storeContext = store.getStore();
|
|
906
|
+
const storeContext = state.store.getStore();
|
|
793
907
|
const meta = {};
|
|
794
908
|
for (const [key, value] of Object.entries(log)) {
|
|
795
|
-
if (!
|
|
909
|
+
if (!RESERVED_LOG_FIELDS.has(key)) {
|
|
796
910
|
meta[key] = value;
|
|
797
911
|
}
|
|
798
912
|
}
|
|
@@ -841,45 +955,53 @@ function buildIndexes(overrides) {
|
|
|
841
955
|
}
|
|
842
956
|
return { contextIndex, complexRules };
|
|
843
957
|
}
|
|
958
|
+
function canonicalMatchKey(match) {
|
|
959
|
+
const keys = Object.keys(match).sort();
|
|
960
|
+
const sorted = {};
|
|
961
|
+
for (const key of keys) {
|
|
962
|
+
sorted[key] = match[key];
|
|
963
|
+
}
|
|
964
|
+
return JSON.stringify(sorted);
|
|
965
|
+
}
|
|
844
966
|
function createState(config, store) {
|
|
845
967
|
const { defaultLevel, rules } = parseLevelConfig(config.level);
|
|
846
968
|
const loggerStore = store ?? new LoggerStore();
|
|
847
969
|
const levelOverrides = /* @__PURE__ */ new Map();
|
|
848
970
|
for (const rule of rules) {
|
|
849
|
-
const key =
|
|
971
|
+
const key = canonicalMatchKey(rule.match);
|
|
850
972
|
levelOverrides.set(key, rule);
|
|
851
973
|
}
|
|
852
974
|
const { contextIndex, complexRules } = buildIndexes(levelOverrides);
|
|
853
|
-
const
|
|
975
|
+
const state = {
|
|
976
|
+
pino: null,
|
|
977
|
+
store: loggerStore,
|
|
978
|
+
defaultLevel,
|
|
979
|
+
levelOverrides,
|
|
980
|
+
contextIndex,
|
|
981
|
+
complexRules,
|
|
982
|
+
callerConfig: void 0,
|
|
983
|
+
transports: []
|
|
984
|
+
};
|
|
985
|
+
const { destination, transports } = createStreams(config, state);
|
|
986
|
+
state.transports = transports;
|
|
854
987
|
const options = {
|
|
855
988
|
level: "trace",
|
|
856
989
|
// Accept all, we filter in shouldLog()
|
|
857
990
|
customLevels: CUSTOM_LEVELS,
|
|
858
991
|
base: { hostname: config.hostname ?? os.hostname() }
|
|
859
992
|
};
|
|
860
|
-
|
|
861
|
-
let callerConfig;
|
|
993
|
+
state.pino = pino__default.default(options, destination);
|
|
862
994
|
if (config.caller === true) {
|
|
863
|
-
callerConfig = {};
|
|
995
|
+
state.callerConfig = {};
|
|
864
996
|
} else if (config.caller && typeof config.caller === "object") {
|
|
865
|
-
callerConfig = config.caller;
|
|
997
|
+
state.callerConfig = config.caller;
|
|
866
998
|
}
|
|
867
|
-
return
|
|
868
|
-
pino: pinoLogger,
|
|
869
|
-
store: loggerStore,
|
|
870
|
-
defaultLevel,
|
|
871
|
-
levelOverrides,
|
|
872
|
-
contextIndex,
|
|
873
|
-
complexRules,
|
|
874
|
-
callerConfig,
|
|
875
|
-
transports
|
|
876
|
-
};
|
|
999
|
+
return state;
|
|
877
1000
|
}
|
|
878
1001
|
function rebuildIndexes(state) {
|
|
879
1002
|
const { contextIndex, complexRules } = buildIndexes(state.levelOverrides);
|
|
880
1003
|
state.contextIndex = contextIndex;
|
|
881
|
-
state.complexRules
|
|
882
|
-
state.complexRules.push(...complexRules);
|
|
1004
|
+
state.complexRules = complexRules;
|
|
883
1005
|
}
|
|
884
1006
|
function shouldLog(state, level, context) {
|
|
885
1007
|
const effectiveLevel = getEffectiveLevel(state, context);
|
|
@@ -899,10 +1021,24 @@ function getEffectiveLevel(state, loggerContext) {
|
|
|
899
1021
|
}
|
|
900
1022
|
return state.defaultLevel;
|
|
901
1023
|
}
|
|
902
|
-
function matchesContext(storeContext, loggerContext, match) {
|
|
903
|
-
const combined = { ...storeContext, context: loggerContext };
|
|
1024
|
+
function matchesContext(storeContext, loggerContext, match, logMeta) {
|
|
1025
|
+
const combined = { ...logMeta, ...storeContext, context: loggerContext };
|
|
904
1026
|
return Object.entries(match).every(([key, value]) => combined[key] === value);
|
|
905
1027
|
}
|
|
1028
|
+
function getGlobalOverrideLevel2(state, loggerContext, logMeta) {
|
|
1029
|
+
if (loggerContext) {
|
|
1030
|
+
const indexed = state.contextIndex.get(loggerContext);
|
|
1031
|
+
if (indexed && !indexed.readonly) return indexed.level;
|
|
1032
|
+
}
|
|
1033
|
+
const storeContext = state.store.getStore();
|
|
1034
|
+
for (const override of state.complexRules) {
|
|
1035
|
+
if (override.readonly) continue;
|
|
1036
|
+
if (matchesContext(storeContext, loggerContext, override.match, logMeta)) {
|
|
1037
|
+
return override.level;
|
|
1038
|
+
}
|
|
1039
|
+
}
|
|
1040
|
+
return void 0;
|
|
1041
|
+
}
|
|
906
1042
|
var LogLevelSchema = zod.z.enum([
|
|
907
1043
|
"off",
|
|
908
1044
|
"error",
|
|
@@ -941,7 +1077,12 @@ var HttpTransportBaseConfigSchema = zod.z.object({
|
|
|
941
1077
|
batchSize: zod.z.number().int().positive().optional(),
|
|
942
1078
|
flushInterval: zod.z.number().int().positive().optional(),
|
|
943
1079
|
maxRetries: zod.z.number().int().nonnegative().optional(),
|
|
944
|
-
retryDelay: zod.z.number().int().positive().optional()
|
|
1080
|
+
retryDelay: zod.z.number().int().positive().optional(),
|
|
1081
|
+
onError: zod.z.function().optional(),
|
|
1082
|
+
onDrop: zod.z.function().optional(),
|
|
1083
|
+
maxQueueSize: zod.z.number().int().positive().optional(),
|
|
1084
|
+
dropPolicy: zod.z.enum(["drop-oldest", "drop-newest"]).optional(),
|
|
1085
|
+
requestTimeout: zod.z.number().int().positive().optional()
|
|
945
1086
|
});
|
|
946
1087
|
var DiscordConfigSchema = zod.z.object({
|
|
947
1088
|
level: LogLevelSchema.optional(),
|
|
@@ -950,6 +1091,11 @@ var DiscordConfigSchema = zod.z.object({
|
|
|
950
1091
|
flushInterval: zod.z.number().int().positive().optional(),
|
|
951
1092
|
maxRetries: zod.z.number().int().nonnegative().optional(),
|
|
952
1093
|
retryDelay: zod.z.number().int().positive().optional(),
|
|
1094
|
+
onError: zod.z.function().optional(),
|
|
1095
|
+
onDrop: zod.z.function().optional(),
|
|
1096
|
+
maxQueueSize: zod.z.number().int().positive().optional(),
|
|
1097
|
+
dropPolicy: zod.z.enum(["drop-oldest", "drop-newest"]).optional(),
|
|
1098
|
+
requestTimeout: zod.z.number().int().positive().optional(),
|
|
953
1099
|
webhookUrl: zod.z.string().url("webhookUrl must be a valid URL"),
|
|
954
1100
|
format: zod.z.enum(["embed", "markdown"]).optional(),
|
|
955
1101
|
username: zod.z.string().optional(),
|
|
@@ -966,6 +1112,11 @@ var TelegramConfigSchema = zod.z.object({
|
|
|
966
1112
|
flushInterval: zod.z.number().int().positive().optional(),
|
|
967
1113
|
maxRetries: zod.z.number().int().nonnegative().optional(),
|
|
968
1114
|
retryDelay: zod.z.number().int().positive().optional(),
|
|
1115
|
+
onError: zod.z.function().optional(),
|
|
1116
|
+
onDrop: zod.z.function().optional(),
|
|
1117
|
+
maxQueueSize: zod.z.number().int().positive().optional(),
|
|
1118
|
+
dropPolicy: zod.z.enum(["drop-oldest", "drop-newest"]).optional(),
|
|
1119
|
+
requestTimeout: zod.z.number().int().positive().optional(),
|
|
969
1120
|
botToken: zod.z.string().min(1, "botToken is required"),
|
|
970
1121
|
chatId: zod.z.union([zod.z.string(), zod.z.number()]),
|
|
971
1122
|
parseMode: zod.z.enum(["Markdown", "MarkdownV2", "HTML"]).optional(),
|
|
@@ -998,6 +1149,11 @@ var CloudWatchConfigSchema = zod.z.object({
|
|
|
998
1149
|
flushInterval: zod.z.number().int().positive().optional(),
|
|
999
1150
|
maxRetries: zod.z.number().int().nonnegative().optional(),
|
|
1000
1151
|
retryDelay: zod.z.number().int().positive().optional(),
|
|
1152
|
+
onError: zod.z.function().optional(),
|
|
1153
|
+
onDrop: zod.z.function().optional(),
|
|
1154
|
+
maxQueueSize: zod.z.number().int().positive().optional(),
|
|
1155
|
+
dropPolicy: zod.z.enum(["drop-oldest", "drop-newest"]).optional(),
|
|
1156
|
+
requestTimeout: zod.z.number().int().positive().optional(),
|
|
1001
1157
|
logGroupName: zod.z.string().min(1, "logGroupName is required"),
|
|
1002
1158
|
logStreamName: LogStreamNameSchema.optional(),
|
|
1003
1159
|
region: zod.z.string().min(1, "region is required"),
|
|
@@ -1006,6 +1162,14 @@ var CloudWatchConfigSchema = zod.z.object({
|
|
|
1006
1162
|
createLogGroup: zod.z.boolean().optional(),
|
|
1007
1163
|
createLogStream: zod.z.boolean().optional()
|
|
1008
1164
|
});
|
|
1165
|
+
var RelayConfigSchema = zod.z.object({
|
|
1166
|
+
apiUrl: zod.z.string().url("apiUrl must be a valid URL"),
|
|
1167
|
+
token: zod.z.string().min(1, "token is required"),
|
|
1168
|
+
pollInterval: zod.z.number().int().positive().optional(),
|
|
1169
|
+
bufferSize: zod.z.number().int().positive().optional(),
|
|
1170
|
+
reconnectDelay: zod.z.number().int().positive().optional(),
|
|
1171
|
+
maxReconnectDelay: zod.z.number().int().positive().optional()
|
|
1172
|
+
});
|
|
1009
1173
|
var LevelConfigObjectSchema = zod.z.object({
|
|
1010
1174
|
default: LogLevelSchema,
|
|
1011
1175
|
rules: zod.z.array(LevelRuleSchema).optional()
|
|
@@ -1029,6 +1193,7 @@ var LoggerConfigSchema = zod.z.object({
|
|
|
1029
1193
|
discord: zod.z.union([DiscordConfigSchema, zod.z.array(DiscordConfigSchema)]).optional(),
|
|
1030
1194
|
telegram: zod.z.union([TelegramConfigSchema, zod.z.array(TelegramConfigSchema)]).optional(),
|
|
1031
1195
|
cloudwatch: zod.z.union([CloudWatchConfigSchema, zod.z.array(CloudWatchConfigSchema)]).optional(),
|
|
1196
|
+
relay: RelayConfigSchema.optional(),
|
|
1032
1197
|
caller: zod.z.union([zod.z.boolean(), CallerConfigSchema]).optional(),
|
|
1033
1198
|
hostname: zod.z.string().optional(),
|
|
1034
1199
|
autoShutdown: zod.z.union([zod.z.boolean(), AutoShutdownConfigSchema]).optional()
|
|
@@ -1276,7 +1441,10 @@ var Logger = class _Logger {
|
|
|
1276
1441
|
this.state = state;
|
|
1277
1442
|
this.context = context;
|
|
1278
1443
|
}
|
|
1279
|
-
|
|
1444
|
+
// Lazy: `.for()` creates many child loggers (hot path), most never call
|
|
1445
|
+
// `profile()`. Allocating a fresh Map per child wastes ~200 bytes each and
|
|
1446
|
+
// caused OOM in benchmarks creating millions of children.
|
|
1447
|
+
profileTimers;
|
|
1280
1448
|
static create(config, store) {
|
|
1281
1449
|
const validatedConfig = validateConfig(config);
|
|
1282
1450
|
const state = createState(validatedConfig, store);
|
|
@@ -1296,12 +1464,12 @@ var Logger = class _Logger {
|
|
|
1296
1464
|
}
|
|
1297
1465
|
setLevelOverride(match, level) {
|
|
1298
1466
|
assertLogLevel(level);
|
|
1299
|
-
const key =
|
|
1467
|
+
const key = canonicalMatchKey(match);
|
|
1300
1468
|
this.state.levelOverrides.set(key, { match, level });
|
|
1301
1469
|
rebuildIndexes(this.state);
|
|
1302
1470
|
}
|
|
1303
1471
|
removeLevelOverride(match) {
|
|
1304
|
-
const key =
|
|
1472
|
+
const key = canonicalMatchKey(match);
|
|
1305
1473
|
const override = this.state.levelOverrides.get(key);
|
|
1306
1474
|
if (override?.readonly) {
|
|
1307
1475
|
return false;
|
|
@@ -1331,33 +1499,55 @@ var Logger = class _Logger {
|
|
|
1331
1499
|
/**
|
|
1332
1500
|
* Gracefully shutdown the logger, flushing all pending messages.
|
|
1333
1501
|
* Should be called before process exit to ensure no logs are lost.
|
|
1502
|
+
*
|
|
1503
|
+
* `timeoutMs` is forwarded to each transport's `close()` as a per-transport
|
|
1504
|
+
* cap; it prevents a dead transport from blocking the whole shutdown.
|
|
1505
|
+
* Default: 5000ms. Pass `Infinity` for legacy unbounded behaviour.
|
|
1334
1506
|
*/
|
|
1335
|
-
async shutdown() {
|
|
1336
|
-
const closePromises = this.state.transports.map((transport) => transport.close());
|
|
1507
|
+
async shutdown(timeoutMs = 5e3) {
|
|
1508
|
+
const closePromises = this.state.transports.map((transport) => transport.close(timeoutMs));
|
|
1337
1509
|
await Promise.all(closePromises);
|
|
1338
1510
|
}
|
|
1339
1511
|
// Profiling
|
|
1340
1512
|
profile(id, meta) {
|
|
1341
|
-
const
|
|
1513
|
+
const timers = this.profileTimers ??= /* @__PURE__ */ new Map();
|
|
1514
|
+
const existing = timers.get(id);
|
|
1342
1515
|
if (existing) {
|
|
1343
1516
|
const duration = Date.now() - existing;
|
|
1344
|
-
|
|
1517
|
+
timers.delete(id);
|
|
1345
1518
|
this.info(`${id} completed`, { ...meta, durationMs: duration });
|
|
1346
1519
|
} else {
|
|
1347
|
-
|
|
1520
|
+
timers.set(id, Date.now());
|
|
1348
1521
|
}
|
|
1349
1522
|
}
|
|
1350
1523
|
// Logging methods
|
|
1524
|
+
/**
|
|
1525
|
+
* Log an error. Supported shapes:
|
|
1526
|
+
* error(message: string)
|
|
1527
|
+
* error(message: string, meta)
|
|
1528
|
+
* error(error: Error | unknown)
|
|
1529
|
+
* error(error: Error | unknown, message: string)
|
|
1530
|
+
* error(error: Error | unknown, meta)
|
|
1531
|
+
* error(error: Error | unknown, message: string, meta)
|
|
1532
|
+
*
|
|
1533
|
+
* The first argument is treated as an error value whenever it is not a string.
|
|
1534
|
+
* Non-Error values (plain objects, numbers, etc. — e.g. anything caught by
|
|
1535
|
+
* TypeScript's `catch (err)` clause, which is typed as `unknown`) are passed
|
|
1536
|
+
* through `serializeError` so they produce a sensible `errorMessage` and, when
|
|
1537
|
+
* possible, a `stack` / HTTP diagnostic payload.
|
|
1538
|
+
*/
|
|
1351
1539
|
error(errorOrMessage, messageOrMeta, meta) {
|
|
1352
1540
|
if (!shouldLog(this.state, "error", this.context)) return;
|
|
1353
|
-
if (errorOrMessage
|
|
1354
|
-
if (typeof messageOrMeta === "string") {
|
|
1355
|
-
this.log("error", messageOrMeta, meta, errorOrMessage);
|
|
1356
|
-
} else {
|
|
1357
|
-
this.log("error", errorOrMessage.message, messageOrMeta, errorOrMessage);
|
|
1358
|
-
}
|
|
1359
|
-
} else {
|
|
1541
|
+
if (typeof errorOrMessage === "string") {
|
|
1360
1542
|
this.log("error", errorOrMessage, messageOrMeta);
|
|
1543
|
+
return;
|
|
1544
|
+
}
|
|
1545
|
+
const errorValue = errorOrMessage;
|
|
1546
|
+
if (typeof messageOrMeta === "string") {
|
|
1547
|
+
this.log("error", messageOrMeta, meta, errorValue);
|
|
1548
|
+
} else {
|
|
1549
|
+
const fallbackMessage = errorValue instanceof Error ? errorValue.message : serializeError(errorValue).errorMessage;
|
|
1550
|
+
this.log("error", fallbackMessage, messageOrMeta, errorValue);
|
|
1361
1551
|
}
|
|
1362
1552
|
}
|
|
1363
1553
|
warn(message, meta) {
|
|
@@ -1392,6 +1582,10 @@ var Logger = class _Logger {
|
|
|
1392
1582
|
if (storeContext) {
|
|
1393
1583
|
Object.assign(logMeta, storeContext);
|
|
1394
1584
|
}
|
|
1585
|
+
const overrideLevel = getGlobalOverrideLevel2(this.state, this.context, logMeta);
|
|
1586
|
+
if (overrideLevel !== void 0) {
|
|
1587
|
+
logMeta.__or = overrideLevel;
|
|
1588
|
+
}
|
|
1395
1589
|
if (this.state.callerConfig) {
|
|
1396
1590
|
const callerInfo = getCallerInfo(this.state.callerConfig, callerOffset);
|
|
1397
1591
|
if (callerInfo) {
|