@rawnodes/logger 2.7.1 → 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 +143 -7
- package/dist/index.d.ts +143 -7
- package/dist/index.js +257 -69
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +257 -69
- 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,21 +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
|
-
|
|
607
|
-
|
|
608
|
-
|
|
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];
|
|
609
707
|
}
|
|
610
708
|
}
|
|
611
|
-
const
|
|
612
|
-
const effectiveLevel = matchingRule?.level ?? level ?? "silly";
|
|
709
|
+
const effectiveLevel = level ?? "silly";
|
|
613
710
|
if (effectiveLevel === "off") return false;
|
|
614
711
|
return LOG_LEVELS[logLevel] <= LOG_LEVELS[effectiveLevel];
|
|
615
712
|
}
|
|
713
|
+
var RESERVED_LOG_FIELDS = /* @__PURE__ */ new Set(["level", "time", "msg", "context", "__or"]);
|
|
616
714
|
function formatLog(log, format, store) {
|
|
617
715
|
const levelName = getLevelName(log.level);
|
|
618
716
|
const timestamp = new Date(log.time).toISOString();
|
|
@@ -621,7 +719,7 @@ function formatLog(log, format, store) {
|
|
|
621
719
|
const storeContext = store.getStore();
|
|
622
720
|
const meta = {};
|
|
623
721
|
for (const [key, value] of Object.entries(log)) {
|
|
624
|
-
if (!
|
|
722
|
+
if (!RESERVED_LOG_FIELDS.has(key)) {
|
|
625
723
|
meta[key] = value;
|
|
626
724
|
}
|
|
627
725
|
}
|
|
@@ -670,7 +768,7 @@ function formatLog(log, format, store) {
|
|
|
670
768
|
return `[${timestamp}] ${levelName}: ${message}
|
|
671
769
|
`;
|
|
672
770
|
}
|
|
673
|
-
function createFormattedFilterStream(format, level, rules,
|
|
771
|
+
function createFormattedFilterStream(format, level, rules, state, destination) {
|
|
674
772
|
return new stream.Transform({
|
|
675
773
|
transform(chunk, _encoding, callback) {
|
|
676
774
|
const line = chunk.toString().trim();
|
|
@@ -683,24 +781,24 @@ function createFormattedFilterStream(format, level, rules, store, destination) {
|
|
|
683
781
|
callback();
|
|
684
782
|
return;
|
|
685
783
|
}
|
|
686
|
-
if (!shouldPassTransport(log, level, rules,
|
|
784
|
+
if (!shouldPassTransport(log, level, rules, state)) {
|
|
687
785
|
callback();
|
|
688
786
|
return;
|
|
689
787
|
}
|
|
690
|
-
const formatted = formatLog(log, format, store);
|
|
788
|
+
const formatted = formatLog(log, format, state.store);
|
|
691
789
|
destination.write(formatted);
|
|
692
790
|
callback();
|
|
693
791
|
}
|
|
694
792
|
});
|
|
695
793
|
}
|
|
696
|
-
function createStreams(config,
|
|
794
|
+
function createStreams(config, state) {
|
|
697
795
|
const streams = [];
|
|
698
796
|
const transports = [];
|
|
699
797
|
const consoleStream = createFormattedFilterStream(
|
|
700
798
|
config.console.format,
|
|
701
799
|
config.console.level,
|
|
702
800
|
config.console.rules,
|
|
703
|
-
|
|
801
|
+
state,
|
|
704
802
|
process.stdout
|
|
705
803
|
);
|
|
706
804
|
streams.push({
|
|
@@ -733,7 +831,7 @@ function createStreams(config, store) {
|
|
|
733
831
|
fileConfig.format,
|
|
734
832
|
fileConfig.level,
|
|
735
833
|
fileConfig.rules,
|
|
736
|
-
|
|
834
|
+
state,
|
|
737
835
|
rotatingStream
|
|
738
836
|
);
|
|
739
837
|
streams.push({
|
|
@@ -744,7 +842,7 @@ function createStreams(config, store) {
|
|
|
744
842
|
for (const discordConfig of toArray(config.discord)) {
|
|
745
843
|
const transport = new DiscordTransport(discordConfig);
|
|
746
844
|
transports.push(transport);
|
|
747
|
-
const discordStream = createHttpTransportStream(transport, discordConfig.level, discordConfig.rules,
|
|
845
|
+
const discordStream = createHttpTransportStream(transport, discordConfig.level, discordConfig.rules, state);
|
|
748
846
|
streams.push({
|
|
749
847
|
level: "trace",
|
|
750
848
|
stream: discordStream
|
|
@@ -753,7 +851,7 @@ function createStreams(config, store) {
|
|
|
753
851
|
for (const telegramConfig of toArray(config.telegram)) {
|
|
754
852
|
const transport = new TelegramTransport(telegramConfig);
|
|
755
853
|
transports.push(transport);
|
|
756
|
-
const telegramStream = createHttpTransportStream(transport, telegramConfig.level, telegramConfig.rules,
|
|
854
|
+
const telegramStream = createHttpTransportStream(transport, telegramConfig.level, telegramConfig.rules, state);
|
|
757
855
|
streams.push({
|
|
758
856
|
level: "trace",
|
|
759
857
|
stream: telegramStream
|
|
@@ -762,12 +860,22 @@ function createStreams(config, store) {
|
|
|
762
860
|
for (const cloudwatchConfig of toArray(config.cloudwatch)) {
|
|
763
861
|
const transport = new CloudWatchTransport(cloudwatchConfig, config.hostname);
|
|
764
862
|
transports.push(transport);
|
|
765
|
-
const cwStream = createHttpTransportStream(transport, cloudwatchConfig.level, cloudwatchConfig.rules,
|
|
863
|
+
const cwStream = createHttpTransportStream(transport, cloudwatchConfig.level, cloudwatchConfig.rules, state);
|
|
766
864
|
streams.push({
|
|
767
865
|
level: "trace",
|
|
768
866
|
stream: cwStream
|
|
769
867
|
});
|
|
770
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
|
+
}
|
|
771
879
|
return {
|
|
772
880
|
destination: pino__default.default.multistream(streams),
|
|
773
881
|
transports
|
|
@@ -777,7 +885,7 @@ function toArray(value) {
|
|
|
777
885
|
if (!value) return [];
|
|
778
886
|
return Array.isArray(value) ? value : [value];
|
|
779
887
|
}
|
|
780
|
-
function createHttpTransportStream(transport, level, rules,
|
|
888
|
+
function createHttpTransportStream(transport, level, rules, state) {
|
|
781
889
|
return new stream.Writable({
|
|
782
890
|
write(chunk, _encoding, callback) {
|
|
783
891
|
const line = chunk.toString().trim();
|
|
@@ -790,15 +898,15 @@ function createHttpTransportStream(transport, level, rules, store) {
|
|
|
790
898
|
callback();
|
|
791
899
|
return;
|
|
792
900
|
}
|
|
793
|
-
if (!shouldPassTransport(log, level, rules,
|
|
901
|
+
if (!shouldPassTransport(log, level, rules, state)) {
|
|
794
902
|
callback();
|
|
795
903
|
return;
|
|
796
904
|
}
|
|
797
905
|
const levelName = getLevelName(log.level);
|
|
798
|
-
const storeContext = store.getStore();
|
|
906
|
+
const storeContext = state.store.getStore();
|
|
799
907
|
const meta = {};
|
|
800
908
|
for (const [key, value] of Object.entries(log)) {
|
|
801
|
-
if (!
|
|
909
|
+
if (!RESERVED_LOG_FIELDS.has(key)) {
|
|
802
910
|
meta[key] = value;
|
|
803
911
|
}
|
|
804
912
|
}
|
|
@@ -847,45 +955,53 @@ function buildIndexes(overrides) {
|
|
|
847
955
|
}
|
|
848
956
|
return { contextIndex, complexRules };
|
|
849
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
|
+
}
|
|
850
966
|
function createState(config, store) {
|
|
851
967
|
const { defaultLevel, rules } = parseLevelConfig(config.level);
|
|
852
968
|
const loggerStore = store ?? new LoggerStore();
|
|
853
969
|
const levelOverrides = /* @__PURE__ */ new Map();
|
|
854
970
|
for (const rule of rules) {
|
|
855
|
-
const key =
|
|
971
|
+
const key = canonicalMatchKey(rule.match);
|
|
856
972
|
levelOverrides.set(key, rule);
|
|
857
973
|
}
|
|
858
974
|
const { contextIndex, complexRules } = buildIndexes(levelOverrides);
|
|
859
|
-
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;
|
|
860
987
|
const options = {
|
|
861
988
|
level: "trace",
|
|
862
989
|
// Accept all, we filter in shouldLog()
|
|
863
990
|
customLevels: CUSTOM_LEVELS,
|
|
864
991
|
base: { hostname: config.hostname ?? os.hostname() }
|
|
865
992
|
};
|
|
866
|
-
|
|
867
|
-
let callerConfig;
|
|
993
|
+
state.pino = pino__default.default(options, destination);
|
|
868
994
|
if (config.caller === true) {
|
|
869
|
-
callerConfig = {};
|
|
995
|
+
state.callerConfig = {};
|
|
870
996
|
} else if (config.caller && typeof config.caller === "object") {
|
|
871
|
-
callerConfig = config.caller;
|
|
997
|
+
state.callerConfig = config.caller;
|
|
872
998
|
}
|
|
873
|
-
return
|
|
874
|
-
pino: pinoLogger,
|
|
875
|
-
store: loggerStore,
|
|
876
|
-
defaultLevel,
|
|
877
|
-
levelOverrides,
|
|
878
|
-
contextIndex,
|
|
879
|
-
complexRules,
|
|
880
|
-
callerConfig,
|
|
881
|
-
transports
|
|
882
|
-
};
|
|
999
|
+
return state;
|
|
883
1000
|
}
|
|
884
1001
|
function rebuildIndexes(state) {
|
|
885
1002
|
const { contextIndex, complexRules } = buildIndexes(state.levelOverrides);
|
|
886
1003
|
state.contextIndex = contextIndex;
|
|
887
|
-
state.complexRules
|
|
888
|
-
state.complexRules.push(...complexRules);
|
|
1004
|
+
state.complexRules = complexRules;
|
|
889
1005
|
}
|
|
890
1006
|
function shouldLog(state, level, context) {
|
|
891
1007
|
const effectiveLevel = getEffectiveLevel(state, context);
|
|
@@ -909,6 +1025,20 @@ function matchesContext(storeContext, loggerContext, match, logMeta) {
|
|
|
909
1025
|
const combined = { ...logMeta, ...storeContext, context: loggerContext };
|
|
910
1026
|
return Object.entries(match).every(([key, value]) => combined[key] === value);
|
|
911
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
|
+
}
|
|
912
1042
|
var LogLevelSchema = zod.z.enum([
|
|
913
1043
|
"off",
|
|
914
1044
|
"error",
|
|
@@ -947,7 +1077,12 @@ var HttpTransportBaseConfigSchema = zod.z.object({
|
|
|
947
1077
|
batchSize: zod.z.number().int().positive().optional(),
|
|
948
1078
|
flushInterval: zod.z.number().int().positive().optional(),
|
|
949
1079
|
maxRetries: zod.z.number().int().nonnegative().optional(),
|
|
950
|
-
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()
|
|
951
1086
|
});
|
|
952
1087
|
var DiscordConfigSchema = zod.z.object({
|
|
953
1088
|
level: LogLevelSchema.optional(),
|
|
@@ -956,6 +1091,11 @@ var DiscordConfigSchema = zod.z.object({
|
|
|
956
1091
|
flushInterval: zod.z.number().int().positive().optional(),
|
|
957
1092
|
maxRetries: zod.z.number().int().nonnegative().optional(),
|
|
958
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(),
|
|
959
1099
|
webhookUrl: zod.z.string().url("webhookUrl must be a valid URL"),
|
|
960
1100
|
format: zod.z.enum(["embed", "markdown"]).optional(),
|
|
961
1101
|
username: zod.z.string().optional(),
|
|
@@ -972,6 +1112,11 @@ var TelegramConfigSchema = zod.z.object({
|
|
|
972
1112
|
flushInterval: zod.z.number().int().positive().optional(),
|
|
973
1113
|
maxRetries: zod.z.number().int().nonnegative().optional(),
|
|
974
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(),
|
|
975
1120
|
botToken: zod.z.string().min(1, "botToken is required"),
|
|
976
1121
|
chatId: zod.z.union([zod.z.string(), zod.z.number()]),
|
|
977
1122
|
parseMode: zod.z.enum(["Markdown", "MarkdownV2", "HTML"]).optional(),
|
|
@@ -1004,6 +1149,11 @@ var CloudWatchConfigSchema = zod.z.object({
|
|
|
1004
1149
|
flushInterval: zod.z.number().int().positive().optional(),
|
|
1005
1150
|
maxRetries: zod.z.number().int().nonnegative().optional(),
|
|
1006
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(),
|
|
1007
1157
|
logGroupName: zod.z.string().min(1, "logGroupName is required"),
|
|
1008
1158
|
logStreamName: LogStreamNameSchema.optional(),
|
|
1009
1159
|
region: zod.z.string().min(1, "region is required"),
|
|
@@ -1012,6 +1162,14 @@ var CloudWatchConfigSchema = zod.z.object({
|
|
|
1012
1162
|
createLogGroup: zod.z.boolean().optional(),
|
|
1013
1163
|
createLogStream: zod.z.boolean().optional()
|
|
1014
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
|
+
});
|
|
1015
1173
|
var LevelConfigObjectSchema = zod.z.object({
|
|
1016
1174
|
default: LogLevelSchema,
|
|
1017
1175
|
rules: zod.z.array(LevelRuleSchema).optional()
|
|
@@ -1035,6 +1193,7 @@ var LoggerConfigSchema = zod.z.object({
|
|
|
1035
1193
|
discord: zod.z.union([DiscordConfigSchema, zod.z.array(DiscordConfigSchema)]).optional(),
|
|
1036
1194
|
telegram: zod.z.union([TelegramConfigSchema, zod.z.array(TelegramConfigSchema)]).optional(),
|
|
1037
1195
|
cloudwatch: zod.z.union([CloudWatchConfigSchema, zod.z.array(CloudWatchConfigSchema)]).optional(),
|
|
1196
|
+
relay: RelayConfigSchema.optional(),
|
|
1038
1197
|
caller: zod.z.union([zod.z.boolean(), CallerConfigSchema]).optional(),
|
|
1039
1198
|
hostname: zod.z.string().optional(),
|
|
1040
1199
|
autoShutdown: zod.z.union([zod.z.boolean(), AutoShutdownConfigSchema]).optional()
|
|
@@ -1282,7 +1441,10 @@ var Logger = class _Logger {
|
|
|
1282
1441
|
this.state = state;
|
|
1283
1442
|
this.context = context;
|
|
1284
1443
|
}
|
|
1285
|
-
|
|
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;
|
|
1286
1448
|
static create(config, store) {
|
|
1287
1449
|
const validatedConfig = validateConfig(config);
|
|
1288
1450
|
const state = createState(validatedConfig, store);
|
|
@@ -1302,12 +1464,12 @@ var Logger = class _Logger {
|
|
|
1302
1464
|
}
|
|
1303
1465
|
setLevelOverride(match, level) {
|
|
1304
1466
|
assertLogLevel(level);
|
|
1305
|
-
const key =
|
|
1467
|
+
const key = canonicalMatchKey(match);
|
|
1306
1468
|
this.state.levelOverrides.set(key, { match, level });
|
|
1307
1469
|
rebuildIndexes(this.state);
|
|
1308
1470
|
}
|
|
1309
1471
|
removeLevelOverride(match) {
|
|
1310
|
-
const key =
|
|
1472
|
+
const key = canonicalMatchKey(match);
|
|
1311
1473
|
const override = this.state.levelOverrides.get(key);
|
|
1312
1474
|
if (override?.readonly) {
|
|
1313
1475
|
return false;
|
|
@@ -1337,33 +1499,55 @@ var Logger = class _Logger {
|
|
|
1337
1499
|
/**
|
|
1338
1500
|
* Gracefully shutdown the logger, flushing all pending messages.
|
|
1339
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.
|
|
1340
1506
|
*/
|
|
1341
|
-
async shutdown() {
|
|
1342
|
-
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));
|
|
1343
1509
|
await Promise.all(closePromises);
|
|
1344
1510
|
}
|
|
1345
1511
|
// Profiling
|
|
1346
1512
|
profile(id, meta) {
|
|
1347
|
-
const
|
|
1513
|
+
const timers = this.profileTimers ??= /* @__PURE__ */ new Map();
|
|
1514
|
+
const existing = timers.get(id);
|
|
1348
1515
|
if (existing) {
|
|
1349
1516
|
const duration = Date.now() - existing;
|
|
1350
|
-
|
|
1517
|
+
timers.delete(id);
|
|
1351
1518
|
this.info(`${id} completed`, { ...meta, durationMs: duration });
|
|
1352
1519
|
} else {
|
|
1353
|
-
|
|
1520
|
+
timers.set(id, Date.now());
|
|
1354
1521
|
}
|
|
1355
1522
|
}
|
|
1356
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
|
+
*/
|
|
1357
1539
|
error(errorOrMessage, messageOrMeta, meta) {
|
|
1358
1540
|
if (!shouldLog(this.state, "error", this.context)) return;
|
|
1359
|
-
if (errorOrMessage
|
|
1360
|
-
if (typeof messageOrMeta === "string") {
|
|
1361
|
-
this.log("error", messageOrMeta, meta, errorOrMessage);
|
|
1362
|
-
} else {
|
|
1363
|
-
this.log("error", errorOrMessage.message, messageOrMeta, errorOrMessage);
|
|
1364
|
-
}
|
|
1365
|
-
} else {
|
|
1541
|
+
if (typeof errorOrMessage === "string") {
|
|
1366
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);
|
|
1367
1551
|
}
|
|
1368
1552
|
}
|
|
1369
1553
|
warn(message, meta) {
|
|
@@ -1398,6 +1582,10 @@ var Logger = class _Logger {
|
|
|
1398
1582
|
if (storeContext) {
|
|
1399
1583
|
Object.assign(logMeta, storeContext);
|
|
1400
1584
|
}
|
|
1585
|
+
const overrideLevel = getGlobalOverrideLevel2(this.state, this.context, logMeta);
|
|
1586
|
+
if (overrideLevel !== void 0) {
|
|
1587
|
+
logMeta.__or = overrideLevel;
|
|
1588
|
+
}
|
|
1401
1589
|
if (this.state.callerConfig) {
|
|
1402
1590
|
const callerInfo = getCallerInfo(this.state.callerConfig, callerOffset);
|
|
1403
1591
|
if (callerInfo) {
|