@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.mjs
CHANGED
|
@@ -4,7 +4,6 @@ import { AsyncLocalStorage } from 'async_hooks';
|
|
|
4
4
|
import { Transform, Writable } from 'stream';
|
|
5
5
|
import { mkdir } from 'fs/promises';
|
|
6
6
|
import { createStream } from 'rotating-file-stream';
|
|
7
|
-
import { EventEmitter } from 'events';
|
|
8
7
|
import { CloudWatchLogsClient, PutLogEventsCommand, CreateLogGroupCommand, CreateLogStreamCommand, DescribeLogStreamsCommand } from '@aws-sdk/client-cloudwatch-logs';
|
|
9
8
|
import { randomUUID } from 'crypto';
|
|
10
9
|
import { z } from 'zod';
|
|
@@ -50,8 +49,24 @@ var MessageBuffer = class {
|
|
|
50
49
|
timer = null;
|
|
51
50
|
flushing = false;
|
|
52
51
|
closed = false;
|
|
52
|
+
droppedCount = 0;
|
|
53
53
|
add(message) {
|
|
54
54
|
if (this.closed) return;
|
|
55
|
+
const limit = this.options.maxQueueSize;
|
|
56
|
+
if (limit !== void 0 && this.queue.length >= limit) {
|
|
57
|
+
const policy = this.options.dropPolicy ?? "drop-oldest";
|
|
58
|
+
const dropped = policy === "drop-oldest" ? [this.queue.shift()] : [message];
|
|
59
|
+
this.droppedCount += 1;
|
|
60
|
+
if (this.options.onDrop) {
|
|
61
|
+
try {
|
|
62
|
+
this.options.onDrop(dropped);
|
|
63
|
+
} catch {
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
if (policy === "drop-newest") {
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
55
70
|
this.queue.push(message);
|
|
56
71
|
if (this.queue.length >= this.options.batchSize) {
|
|
57
72
|
void this.flush();
|
|
@@ -75,13 +90,34 @@ var MessageBuffer = class {
|
|
|
75
90
|
}
|
|
76
91
|
}
|
|
77
92
|
}
|
|
78
|
-
|
|
93
|
+
/**
|
|
94
|
+
* Stops accepting new messages and attempts to flush what's already buffered.
|
|
95
|
+
* Returns when the queue is drained OR when `timeoutMs` elapses. A timeout
|
|
96
|
+
* is essential during graceful shutdown — without it, a dead transport would
|
|
97
|
+
* block process exit indefinitely, and orchestrators like Kubernetes would
|
|
98
|
+
* escalate to SIGKILL after their grace period.
|
|
99
|
+
*
|
|
100
|
+
* Default timeout: 5 seconds. Pass `Infinity` to preserve legacy behaviour.
|
|
101
|
+
*/
|
|
102
|
+
async close(timeoutMs = 5e3) {
|
|
79
103
|
this.closed = true;
|
|
80
104
|
this.clearTimer();
|
|
105
|
+
const deadline = timeoutMs === Infinity ? Infinity : Date.now() + timeoutMs;
|
|
81
106
|
while (this.queue.length > 0) {
|
|
107
|
+
if (Date.now() >= deadline) {
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
82
110
|
await this.flush();
|
|
83
111
|
}
|
|
84
112
|
}
|
|
113
|
+
/** Current number of messages waiting in the queue. */
|
|
114
|
+
get size() {
|
|
115
|
+
return this.queue.length;
|
|
116
|
+
}
|
|
117
|
+
/** Total number of messages dropped due to queue overflow since creation. */
|
|
118
|
+
get droppedTotal() {
|
|
119
|
+
return this.droppedCount;
|
|
120
|
+
}
|
|
85
121
|
scheduleFlush() {
|
|
86
122
|
if (this.timer || this.closed) return;
|
|
87
123
|
this.timer = setTimeout(() => {
|
|
@@ -122,15 +158,19 @@ var DEFAULT_OPTIONS = {
|
|
|
122
158
|
maxRetries: 3,
|
|
123
159
|
retryDelay: 1e3
|
|
124
160
|
};
|
|
125
|
-
var BaseHttpTransport = class
|
|
161
|
+
var BaseHttpTransport = class {
|
|
126
162
|
buffer;
|
|
163
|
+
onErrorCallback;
|
|
127
164
|
constructor(opts = {}) {
|
|
128
|
-
|
|
165
|
+
this.onErrorCallback = opts.onError;
|
|
129
166
|
this.buffer = new MessageBuffer({
|
|
130
167
|
batchSize: opts.batchSize ?? DEFAULT_OPTIONS.batchSize,
|
|
131
168
|
flushInterval: opts.flushInterval ?? DEFAULT_OPTIONS.flushInterval,
|
|
132
169
|
maxRetries: opts.maxRetries ?? DEFAULT_OPTIONS.maxRetries,
|
|
133
170
|
retryDelay: opts.retryDelay ?? DEFAULT_OPTIONS.retryDelay,
|
|
171
|
+
maxQueueSize: opts.maxQueueSize,
|
|
172
|
+
dropPolicy: opts.dropPolicy,
|
|
173
|
+
onDrop: opts.onDrop,
|
|
134
174
|
onFlush: this.sendBatch.bind(this),
|
|
135
175
|
onError: this.handleError.bind(this)
|
|
136
176
|
});
|
|
@@ -140,8 +180,20 @@ var BaseHttpTransport = class extends EventEmitter {
|
|
|
140
180
|
this.buffer.add(message);
|
|
141
181
|
callback();
|
|
142
182
|
}
|
|
143
|
-
|
|
144
|
-
|
|
183
|
+
/**
|
|
184
|
+
* Stop accepting new messages and flush what's buffered. `timeoutMs` caps
|
|
185
|
+
* how long the flush may take; after that the remaining queue is discarded
|
|
186
|
+
* so shutdown can complete. Default 5000ms.
|
|
187
|
+
*/
|
|
188
|
+
close(timeoutMs) {
|
|
189
|
+
return this.buffer.close(timeoutMs);
|
|
190
|
+
}
|
|
191
|
+
/** Current buffered message count and total drops since creation. */
|
|
192
|
+
getMetrics() {
|
|
193
|
+
return {
|
|
194
|
+
queueSize: this.buffer.size,
|
|
195
|
+
droppedTotal: this.buffer.droppedTotal
|
|
196
|
+
};
|
|
145
197
|
}
|
|
146
198
|
transformMessage(info) {
|
|
147
199
|
const { level, message, timestamp, context, ...meta } = info;
|
|
@@ -154,11 +206,21 @@ var BaseHttpTransport = class extends EventEmitter {
|
|
|
154
206
|
};
|
|
155
207
|
}
|
|
156
208
|
handleError(error, messages) {
|
|
209
|
+
if (this.onErrorCallback) {
|
|
210
|
+
try {
|
|
211
|
+
this.onErrorCallback(error, messages);
|
|
212
|
+
return;
|
|
213
|
+
} catch (callbackError) {
|
|
214
|
+
console.error(
|
|
215
|
+
`[${this.constructor.name}] onError callback threw:`,
|
|
216
|
+
callbackError instanceof Error ? callbackError.message : callbackError
|
|
217
|
+
);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
157
220
|
console.error(
|
|
158
221
|
`[${this.constructor.name}] Failed to send ${messages.length} messages:`,
|
|
159
222
|
error.message
|
|
160
223
|
);
|
|
161
|
-
this.emit("error", error);
|
|
162
224
|
}
|
|
163
225
|
};
|
|
164
226
|
|
|
@@ -183,16 +245,23 @@ var LEVEL_EMOJI = {
|
|
|
183
245
|
debug: "\u26AA",
|
|
184
246
|
silly: "\u26AB"
|
|
185
247
|
};
|
|
248
|
+
var DEFAULT_REQUEST_TIMEOUT_MS = 1e4;
|
|
186
249
|
var DiscordTransport = class extends BaseHttpTransport {
|
|
187
250
|
config;
|
|
251
|
+
requestTimeout;
|
|
188
252
|
constructor(config) {
|
|
189
253
|
super({
|
|
190
254
|
batchSize: config.batchSize ?? 10,
|
|
191
255
|
flushInterval: config.flushInterval ?? 2e3,
|
|
192
256
|
maxRetries: config.maxRetries,
|
|
193
|
-
retryDelay: config.retryDelay
|
|
257
|
+
retryDelay: config.retryDelay,
|
|
258
|
+
maxQueueSize: config.maxQueueSize,
|
|
259
|
+
dropPolicy: config.dropPolicy,
|
|
260
|
+
onDrop: config.onDrop,
|
|
261
|
+
onError: config.onError
|
|
194
262
|
});
|
|
195
263
|
this.config = config;
|
|
264
|
+
this.requestTimeout = config.requestTimeout ?? DEFAULT_REQUEST_TIMEOUT_MS;
|
|
196
265
|
}
|
|
197
266
|
async sendBatch(messages) {
|
|
198
267
|
if (this.config.format === "markdown") {
|
|
@@ -296,7 +365,8 @@ ${msg.message}`;
|
|
|
296
365
|
const response = await fetch(this.config.webhookUrl, {
|
|
297
366
|
method: "POST",
|
|
298
367
|
headers: { "Content-Type": "application/json" },
|
|
299
|
-
body: JSON.stringify(payload)
|
|
368
|
+
body: JSON.stringify(payload),
|
|
369
|
+
signal: AbortSignal.timeout(this.requestTimeout)
|
|
300
370
|
});
|
|
301
371
|
if (!response.ok) {
|
|
302
372
|
const text = await response.text();
|
|
@@ -323,18 +393,25 @@ var LEVEL_EMOJI2 = {
|
|
|
323
393
|
debug: "\u26AA",
|
|
324
394
|
silly: "\u26AB"
|
|
325
395
|
};
|
|
396
|
+
var DEFAULT_REQUEST_TIMEOUT_MS2 = 1e4;
|
|
326
397
|
var TelegramTransport = class extends BaseHttpTransport {
|
|
327
398
|
config;
|
|
328
399
|
apiUrl;
|
|
400
|
+
requestTimeout;
|
|
329
401
|
constructor(config) {
|
|
330
402
|
super({
|
|
331
403
|
batchSize: config.batchSize ?? 20,
|
|
332
404
|
flushInterval: config.flushInterval ?? 1e3,
|
|
333
405
|
maxRetries: config.maxRetries,
|
|
334
|
-
retryDelay: config.retryDelay
|
|
406
|
+
retryDelay: config.retryDelay,
|
|
407
|
+
maxQueueSize: config.maxQueueSize,
|
|
408
|
+
dropPolicy: config.dropPolicy,
|
|
409
|
+
onDrop: config.onDrop,
|
|
410
|
+
onError: config.onError
|
|
335
411
|
});
|
|
336
412
|
this.config = config;
|
|
337
413
|
this.apiUrl = `https://api.telegram.org/bot${config.botToken}`;
|
|
414
|
+
this.requestTimeout = config.requestTimeout ?? DEFAULT_REQUEST_TIMEOUT_MS2;
|
|
338
415
|
}
|
|
339
416
|
async sendBatch(messages) {
|
|
340
417
|
const text = this.formatBatchMessage(messages);
|
|
@@ -395,7 +472,8 @@ var TelegramTransport = class extends BaseHttpTransport {
|
|
|
395
472
|
const response = await fetch(`${this.apiUrl}/sendMessage`, {
|
|
396
473
|
method: "POST",
|
|
397
474
|
headers: { "Content-Type": "application/json" },
|
|
398
|
-
body: JSON.stringify(body)
|
|
475
|
+
body: JSON.stringify(body),
|
|
476
|
+
signal: AbortSignal.timeout(this.requestTimeout)
|
|
399
477
|
});
|
|
400
478
|
if (!response.ok) {
|
|
401
479
|
const result = await response.json();
|
|
@@ -464,7 +542,11 @@ var CloudWatchTransport = class extends BaseHttpTransport {
|
|
|
464
542
|
batchSize: config.batchSize ?? 100,
|
|
465
543
|
flushInterval: config.flushInterval ?? 1e3,
|
|
466
544
|
maxRetries: config.maxRetries,
|
|
467
|
-
retryDelay: config.retryDelay
|
|
545
|
+
retryDelay: config.retryDelay,
|
|
546
|
+
maxQueueSize: config.maxQueueSize,
|
|
547
|
+
dropPolicy: config.dropPolicy,
|
|
548
|
+
onDrop: config.onDrop,
|
|
549
|
+
onError: config.onError
|
|
468
550
|
});
|
|
469
551
|
this.config = config;
|
|
470
552
|
this.resolvedLogStreamName = resolveLogStreamName(config.logStreamName, configHostname);
|
|
@@ -516,7 +598,10 @@ var CloudWatchTransport = class extends BaseHttpTransport {
|
|
|
516
598
|
async ensureInitialized() {
|
|
517
599
|
if (this.initialized) return;
|
|
518
600
|
if (!this.initPromise) {
|
|
519
|
-
this.initPromise = this.initialize()
|
|
601
|
+
this.initPromise = this.initialize().catch((err) => {
|
|
602
|
+
this.initPromise = null;
|
|
603
|
+
throw err;
|
|
604
|
+
});
|
|
520
605
|
}
|
|
521
606
|
await this.initPromise;
|
|
522
607
|
}
|
|
@@ -592,15 +677,34 @@ function getLevelName(levelNum) {
|
|
|
592
677
|
if (levelNum >= 20) return "debug";
|
|
593
678
|
return "silly";
|
|
594
679
|
}
|
|
595
|
-
function shouldPassTransport(log, level, rules,
|
|
680
|
+
function shouldPassTransport(log, level, rules, state) {
|
|
596
681
|
const logLevel = getLevelName(log.level);
|
|
682
|
+
const embeddedOverride = log.__or;
|
|
683
|
+
if (typeof embeddedOverride === "string") {
|
|
684
|
+
if (embeddedOverride === "off") return false;
|
|
685
|
+
const overrideLevel = embeddedOverride;
|
|
686
|
+
return LOG_LEVELS[logLevel] <= LOG_LEVELS[overrideLevel];
|
|
687
|
+
}
|
|
597
688
|
const context = log.context;
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
689
|
+
if (rules && rules.length > 0) {
|
|
690
|
+
const storeContext = state.store.getStore();
|
|
691
|
+
const logMeta = {};
|
|
692
|
+
for (const [key, value] of Object.entries(log)) {
|
|
693
|
+
if (!RESERVED_LOG_FIELDS.has(key)) {
|
|
694
|
+
logMeta[key] = value;
|
|
695
|
+
}
|
|
696
|
+
}
|
|
697
|
+
const matchingRule = rules.find((rule) => matchesContext(storeContext, context, rule.match, logMeta));
|
|
698
|
+
if (matchingRule) {
|
|
699
|
+
if (matchingRule.level === "off") return false;
|
|
700
|
+
return LOG_LEVELS[logLevel] <= LOG_LEVELS[matchingRule.level];
|
|
701
|
+
}
|
|
702
|
+
}
|
|
703
|
+
const effectiveLevel = level ?? "silly";
|
|
601
704
|
if (effectiveLevel === "off") return false;
|
|
602
705
|
return LOG_LEVELS[logLevel] <= LOG_LEVELS[effectiveLevel];
|
|
603
706
|
}
|
|
707
|
+
var RESERVED_LOG_FIELDS = /* @__PURE__ */ new Set(["level", "time", "msg", "context", "__or"]);
|
|
604
708
|
function formatLog(log, format, store) {
|
|
605
709
|
const levelName = getLevelName(log.level);
|
|
606
710
|
const timestamp = new Date(log.time).toISOString();
|
|
@@ -609,7 +713,7 @@ function formatLog(log, format, store) {
|
|
|
609
713
|
const storeContext = store.getStore();
|
|
610
714
|
const meta = {};
|
|
611
715
|
for (const [key, value] of Object.entries(log)) {
|
|
612
|
-
if (!
|
|
716
|
+
if (!RESERVED_LOG_FIELDS.has(key)) {
|
|
613
717
|
meta[key] = value;
|
|
614
718
|
}
|
|
615
719
|
}
|
|
@@ -658,7 +762,7 @@ function formatLog(log, format, store) {
|
|
|
658
762
|
return `[${timestamp}] ${levelName}: ${message}
|
|
659
763
|
`;
|
|
660
764
|
}
|
|
661
|
-
function createFormattedFilterStream(format, level, rules,
|
|
765
|
+
function createFormattedFilterStream(format, level, rules, state, destination) {
|
|
662
766
|
return new Transform({
|
|
663
767
|
transform(chunk, _encoding, callback) {
|
|
664
768
|
const line = chunk.toString().trim();
|
|
@@ -671,24 +775,24 @@ function createFormattedFilterStream(format, level, rules, store, destination) {
|
|
|
671
775
|
callback();
|
|
672
776
|
return;
|
|
673
777
|
}
|
|
674
|
-
if (!shouldPassTransport(log, level, rules,
|
|
778
|
+
if (!shouldPassTransport(log, level, rules, state)) {
|
|
675
779
|
callback();
|
|
676
780
|
return;
|
|
677
781
|
}
|
|
678
|
-
const formatted = formatLog(log, format, store);
|
|
782
|
+
const formatted = formatLog(log, format, state.store);
|
|
679
783
|
destination.write(formatted);
|
|
680
784
|
callback();
|
|
681
785
|
}
|
|
682
786
|
});
|
|
683
787
|
}
|
|
684
|
-
function createStreams(config,
|
|
788
|
+
function createStreams(config, state) {
|
|
685
789
|
const streams = [];
|
|
686
790
|
const transports = [];
|
|
687
791
|
const consoleStream = createFormattedFilterStream(
|
|
688
792
|
config.console.format,
|
|
689
793
|
config.console.level,
|
|
690
794
|
config.console.rules,
|
|
691
|
-
|
|
795
|
+
state,
|
|
692
796
|
process.stdout
|
|
693
797
|
);
|
|
694
798
|
streams.push({
|
|
@@ -721,7 +825,7 @@ function createStreams(config, store) {
|
|
|
721
825
|
fileConfig.format,
|
|
722
826
|
fileConfig.level,
|
|
723
827
|
fileConfig.rules,
|
|
724
|
-
|
|
828
|
+
state,
|
|
725
829
|
rotatingStream
|
|
726
830
|
);
|
|
727
831
|
streams.push({
|
|
@@ -732,7 +836,7 @@ function createStreams(config, store) {
|
|
|
732
836
|
for (const discordConfig of toArray(config.discord)) {
|
|
733
837
|
const transport = new DiscordTransport(discordConfig);
|
|
734
838
|
transports.push(transport);
|
|
735
|
-
const discordStream = createHttpTransportStream(transport, discordConfig.level, discordConfig.rules,
|
|
839
|
+
const discordStream = createHttpTransportStream(transport, discordConfig.level, discordConfig.rules, state);
|
|
736
840
|
streams.push({
|
|
737
841
|
level: "trace",
|
|
738
842
|
stream: discordStream
|
|
@@ -741,7 +845,7 @@ function createStreams(config, store) {
|
|
|
741
845
|
for (const telegramConfig of toArray(config.telegram)) {
|
|
742
846
|
const transport = new TelegramTransport(telegramConfig);
|
|
743
847
|
transports.push(transport);
|
|
744
|
-
const telegramStream = createHttpTransportStream(transport, telegramConfig.level, telegramConfig.rules,
|
|
848
|
+
const telegramStream = createHttpTransportStream(transport, telegramConfig.level, telegramConfig.rules, state);
|
|
745
849
|
streams.push({
|
|
746
850
|
level: "trace",
|
|
747
851
|
stream: telegramStream
|
|
@@ -750,12 +854,22 @@ function createStreams(config, store) {
|
|
|
750
854
|
for (const cloudwatchConfig of toArray(config.cloudwatch)) {
|
|
751
855
|
const transport = new CloudWatchTransport(cloudwatchConfig, config.hostname);
|
|
752
856
|
transports.push(transport);
|
|
753
|
-
const cwStream = createHttpTransportStream(transport, cloudwatchConfig.level, cloudwatchConfig.rules,
|
|
857
|
+
const cwStream = createHttpTransportStream(transport, cloudwatchConfig.level, cloudwatchConfig.rules, state);
|
|
754
858
|
streams.push({
|
|
755
859
|
level: "trace",
|
|
756
860
|
stream: cwStream
|
|
757
861
|
});
|
|
758
862
|
}
|
|
863
|
+
if (config.relay) {
|
|
864
|
+
const relayStream = pino.transport({
|
|
865
|
+
target: "@rawnodes/logger/relay",
|
|
866
|
+
options: config.relay
|
|
867
|
+
});
|
|
868
|
+
streams.push({
|
|
869
|
+
level: "trace",
|
|
870
|
+
stream: relayStream
|
|
871
|
+
});
|
|
872
|
+
}
|
|
759
873
|
return {
|
|
760
874
|
destination: pino.multistream(streams),
|
|
761
875
|
transports
|
|
@@ -765,7 +879,7 @@ function toArray(value) {
|
|
|
765
879
|
if (!value) return [];
|
|
766
880
|
return Array.isArray(value) ? value : [value];
|
|
767
881
|
}
|
|
768
|
-
function createHttpTransportStream(transport, level, rules,
|
|
882
|
+
function createHttpTransportStream(transport, level, rules, state) {
|
|
769
883
|
return new Writable({
|
|
770
884
|
write(chunk, _encoding, callback) {
|
|
771
885
|
const line = chunk.toString().trim();
|
|
@@ -778,15 +892,15 @@ function createHttpTransportStream(transport, level, rules, store) {
|
|
|
778
892
|
callback();
|
|
779
893
|
return;
|
|
780
894
|
}
|
|
781
|
-
if (!shouldPassTransport(log, level, rules,
|
|
895
|
+
if (!shouldPassTransport(log, level, rules, state)) {
|
|
782
896
|
callback();
|
|
783
897
|
return;
|
|
784
898
|
}
|
|
785
899
|
const levelName = getLevelName(log.level);
|
|
786
|
-
const storeContext = store.getStore();
|
|
900
|
+
const storeContext = state.store.getStore();
|
|
787
901
|
const meta = {};
|
|
788
902
|
for (const [key, value] of Object.entries(log)) {
|
|
789
|
-
if (!
|
|
903
|
+
if (!RESERVED_LOG_FIELDS.has(key)) {
|
|
790
904
|
meta[key] = value;
|
|
791
905
|
}
|
|
792
906
|
}
|
|
@@ -835,45 +949,53 @@ function buildIndexes(overrides) {
|
|
|
835
949
|
}
|
|
836
950
|
return { contextIndex, complexRules };
|
|
837
951
|
}
|
|
952
|
+
function canonicalMatchKey(match) {
|
|
953
|
+
const keys = Object.keys(match).sort();
|
|
954
|
+
const sorted = {};
|
|
955
|
+
for (const key of keys) {
|
|
956
|
+
sorted[key] = match[key];
|
|
957
|
+
}
|
|
958
|
+
return JSON.stringify(sorted);
|
|
959
|
+
}
|
|
838
960
|
function createState(config, store) {
|
|
839
961
|
const { defaultLevel, rules } = parseLevelConfig(config.level);
|
|
840
962
|
const loggerStore = store ?? new LoggerStore();
|
|
841
963
|
const levelOverrides = /* @__PURE__ */ new Map();
|
|
842
964
|
for (const rule of rules) {
|
|
843
|
-
const key =
|
|
965
|
+
const key = canonicalMatchKey(rule.match);
|
|
844
966
|
levelOverrides.set(key, rule);
|
|
845
967
|
}
|
|
846
968
|
const { contextIndex, complexRules } = buildIndexes(levelOverrides);
|
|
847
|
-
const
|
|
969
|
+
const state = {
|
|
970
|
+
pino: null,
|
|
971
|
+
store: loggerStore,
|
|
972
|
+
defaultLevel,
|
|
973
|
+
levelOverrides,
|
|
974
|
+
contextIndex,
|
|
975
|
+
complexRules,
|
|
976
|
+
callerConfig: void 0,
|
|
977
|
+
transports: []
|
|
978
|
+
};
|
|
979
|
+
const { destination, transports } = createStreams(config, state);
|
|
980
|
+
state.transports = transports;
|
|
848
981
|
const options = {
|
|
849
982
|
level: "trace",
|
|
850
983
|
// Accept all, we filter in shouldLog()
|
|
851
984
|
customLevels: CUSTOM_LEVELS,
|
|
852
985
|
base: { hostname: config.hostname ?? hostname() }
|
|
853
986
|
};
|
|
854
|
-
|
|
855
|
-
let callerConfig;
|
|
987
|
+
state.pino = pino(options, destination);
|
|
856
988
|
if (config.caller === true) {
|
|
857
|
-
callerConfig = {};
|
|
989
|
+
state.callerConfig = {};
|
|
858
990
|
} else if (config.caller && typeof config.caller === "object") {
|
|
859
|
-
callerConfig = config.caller;
|
|
991
|
+
state.callerConfig = config.caller;
|
|
860
992
|
}
|
|
861
|
-
return
|
|
862
|
-
pino: pinoLogger,
|
|
863
|
-
store: loggerStore,
|
|
864
|
-
defaultLevel,
|
|
865
|
-
levelOverrides,
|
|
866
|
-
contextIndex,
|
|
867
|
-
complexRules,
|
|
868
|
-
callerConfig,
|
|
869
|
-
transports
|
|
870
|
-
};
|
|
993
|
+
return state;
|
|
871
994
|
}
|
|
872
995
|
function rebuildIndexes(state) {
|
|
873
996
|
const { contextIndex, complexRules } = buildIndexes(state.levelOverrides);
|
|
874
997
|
state.contextIndex = contextIndex;
|
|
875
|
-
state.complexRules
|
|
876
|
-
state.complexRules.push(...complexRules);
|
|
998
|
+
state.complexRules = complexRules;
|
|
877
999
|
}
|
|
878
1000
|
function shouldLog(state, level, context) {
|
|
879
1001
|
const effectiveLevel = getEffectiveLevel(state, context);
|
|
@@ -893,10 +1015,24 @@ function getEffectiveLevel(state, loggerContext) {
|
|
|
893
1015
|
}
|
|
894
1016
|
return state.defaultLevel;
|
|
895
1017
|
}
|
|
896
|
-
function matchesContext(storeContext, loggerContext, match) {
|
|
897
|
-
const combined = { ...storeContext, context: loggerContext };
|
|
1018
|
+
function matchesContext(storeContext, loggerContext, match, logMeta) {
|
|
1019
|
+
const combined = { ...logMeta, ...storeContext, context: loggerContext };
|
|
898
1020
|
return Object.entries(match).every(([key, value]) => combined[key] === value);
|
|
899
1021
|
}
|
|
1022
|
+
function getGlobalOverrideLevel2(state, loggerContext, logMeta) {
|
|
1023
|
+
if (loggerContext) {
|
|
1024
|
+
const indexed = state.contextIndex.get(loggerContext);
|
|
1025
|
+
if (indexed && !indexed.readonly) return indexed.level;
|
|
1026
|
+
}
|
|
1027
|
+
const storeContext = state.store.getStore();
|
|
1028
|
+
for (const override of state.complexRules) {
|
|
1029
|
+
if (override.readonly) continue;
|
|
1030
|
+
if (matchesContext(storeContext, loggerContext, override.match, logMeta)) {
|
|
1031
|
+
return override.level;
|
|
1032
|
+
}
|
|
1033
|
+
}
|
|
1034
|
+
return void 0;
|
|
1035
|
+
}
|
|
900
1036
|
var LogLevelSchema = z.enum([
|
|
901
1037
|
"off",
|
|
902
1038
|
"error",
|
|
@@ -935,7 +1071,12 @@ var HttpTransportBaseConfigSchema = z.object({
|
|
|
935
1071
|
batchSize: z.number().int().positive().optional(),
|
|
936
1072
|
flushInterval: z.number().int().positive().optional(),
|
|
937
1073
|
maxRetries: z.number().int().nonnegative().optional(),
|
|
938
|
-
retryDelay: z.number().int().positive().optional()
|
|
1074
|
+
retryDelay: z.number().int().positive().optional(),
|
|
1075
|
+
onError: z.function().optional(),
|
|
1076
|
+
onDrop: z.function().optional(),
|
|
1077
|
+
maxQueueSize: z.number().int().positive().optional(),
|
|
1078
|
+
dropPolicy: z.enum(["drop-oldest", "drop-newest"]).optional(),
|
|
1079
|
+
requestTimeout: z.number().int().positive().optional()
|
|
939
1080
|
});
|
|
940
1081
|
var DiscordConfigSchema = z.object({
|
|
941
1082
|
level: LogLevelSchema.optional(),
|
|
@@ -944,6 +1085,11 @@ var DiscordConfigSchema = z.object({
|
|
|
944
1085
|
flushInterval: z.number().int().positive().optional(),
|
|
945
1086
|
maxRetries: z.number().int().nonnegative().optional(),
|
|
946
1087
|
retryDelay: z.number().int().positive().optional(),
|
|
1088
|
+
onError: z.function().optional(),
|
|
1089
|
+
onDrop: z.function().optional(),
|
|
1090
|
+
maxQueueSize: z.number().int().positive().optional(),
|
|
1091
|
+
dropPolicy: z.enum(["drop-oldest", "drop-newest"]).optional(),
|
|
1092
|
+
requestTimeout: z.number().int().positive().optional(),
|
|
947
1093
|
webhookUrl: z.string().url("webhookUrl must be a valid URL"),
|
|
948
1094
|
format: z.enum(["embed", "markdown"]).optional(),
|
|
949
1095
|
username: z.string().optional(),
|
|
@@ -960,6 +1106,11 @@ var TelegramConfigSchema = z.object({
|
|
|
960
1106
|
flushInterval: z.number().int().positive().optional(),
|
|
961
1107
|
maxRetries: z.number().int().nonnegative().optional(),
|
|
962
1108
|
retryDelay: z.number().int().positive().optional(),
|
|
1109
|
+
onError: z.function().optional(),
|
|
1110
|
+
onDrop: z.function().optional(),
|
|
1111
|
+
maxQueueSize: z.number().int().positive().optional(),
|
|
1112
|
+
dropPolicy: z.enum(["drop-oldest", "drop-newest"]).optional(),
|
|
1113
|
+
requestTimeout: z.number().int().positive().optional(),
|
|
963
1114
|
botToken: z.string().min(1, "botToken is required"),
|
|
964
1115
|
chatId: z.union([z.string(), z.number()]),
|
|
965
1116
|
parseMode: z.enum(["Markdown", "MarkdownV2", "HTML"]).optional(),
|
|
@@ -992,6 +1143,11 @@ var CloudWatchConfigSchema = z.object({
|
|
|
992
1143
|
flushInterval: z.number().int().positive().optional(),
|
|
993
1144
|
maxRetries: z.number().int().nonnegative().optional(),
|
|
994
1145
|
retryDelay: z.number().int().positive().optional(),
|
|
1146
|
+
onError: z.function().optional(),
|
|
1147
|
+
onDrop: z.function().optional(),
|
|
1148
|
+
maxQueueSize: z.number().int().positive().optional(),
|
|
1149
|
+
dropPolicy: z.enum(["drop-oldest", "drop-newest"]).optional(),
|
|
1150
|
+
requestTimeout: z.number().int().positive().optional(),
|
|
995
1151
|
logGroupName: z.string().min(1, "logGroupName is required"),
|
|
996
1152
|
logStreamName: LogStreamNameSchema.optional(),
|
|
997
1153
|
region: z.string().min(1, "region is required"),
|
|
@@ -1000,6 +1156,14 @@ var CloudWatchConfigSchema = z.object({
|
|
|
1000
1156
|
createLogGroup: z.boolean().optional(),
|
|
1001
1157
|
createLogStream: z.boolean().optional()
|
|
1002
1158
|
});
|
|
1159
|
+
var RelayConfigSchema = z.object({
|
|
1160
|
+
apiUrl: z.string().url("apiUrl must be a valid URL"),
|
|
1161
|
+
token: z.string().min(1, "token is required"),
|
|
1162
|
+
pollInterval: z.number().int().positive().optional(),
|
|
1163
|
+
bufferSize: z.number().int().positive().optional(),
|
|
1164
|
+
reconnectDelay: z.number().int().positive().optional(),
|
|
1165
|
+
maxReconnectDelay: z.number().int().positive().optional()
|
|
1166
|
+
});
|
|
1003
1167
|
var LevelConfigObjectSchema = z.object({
|
|
1004
1168
|
default: LogLevelSchema,
|
|
1005
1169
|
rules: z.array(LevelRuleSchema).optional()
|
|
@@ -1023,6 +1187,7 @@ var LoggerConfigSchema = z.object({
|
|
|
1023
1187
|
discord: z.union([DiscordConfigSchema, z.array(DiscordConfigSchema)]).optional(),
|
|
1024
1188
|
telegram: z.union([TelegramConfigSchema, z.array(TelegramConfigSchema)]).optional(),
|
|
1025
1189
|
cloudwatch: z.union([CloudWatchConfigSchema, z.array(CloudWatchConfigSchema)]).optional(),
|
|
1190
|
+
relay: RelayConfigSchema.optional(),
|
|
1026
1191
|
caller: z.union([z.boolean(), CallerConfigSchema]).optional(),
|
|
1027
1192
|
hostname: z.string().optional(),
|
|
1028
1193
|
autoShutdown: z.union([z.boolean(), AutoShutdownConfigSchema]).optional()
|
|
@@ -1270,7 +1435,10 @@ var Logger = class _Logger {
|
|
|
1270
1435
|
this.state = state;
|
|
1271
1436
|
this.context = context;
|
|
1272
1437
|
}
|
|
1273
|
-
|
|
1438
|
+
// Lazy: `.for()` creates many child loggers (hot path), most never call
|
|
1439
|
+
// `profile()`. Allocating a fresh Map per child wastes ~200 bytes each and
|
|
1440
|
+
// caused OOM in benchmarks creating millions of children.
|
|
1441
|
+
profileTimers;
|
|
1274
1442
|
static create(config, store) {
|
|
1275
1443
|
const validatedConfig = validateConfig(config);
|
|
1276
1444
|
const state = createState(validatedConfig, store);
|
|
@@ -1290,12 +1458,12 @@ var Logger = class _Logger {
|
|
|
1290
1458
|
}
|
|
1291
1459
|
setLevelOverride(match, level) {
|
|
1292
1460
|
assertLogLevel(level);
|
|
1293
|
-
const key =
|
|
1461
|
+
const key = canonicalMatchKey(match);
|
|
1294
1462
|
this.state.levelOverrides.set(key, { match, level });
|
|
1295
1463
|
rebuildIndexes(this.state);
|
|
1296
1464
|
}
|
|
1297
1465
|
removeLevelOverride(match) {
|
|
1298
|
-
const key =
|
|
1466
|
+
const key = canonicalMatchKey(match);
|
|
1299
1467
|
const override = this.state.levelOverrides.get(key);
|
|
1300
1468
|
if (override?.readonly) {
|
|
1301
1469
|
return false;
|
|
@@ -1325,33 +1493,55 @@ var Logger = class _Logger {
|
|
|
1325
1493
|
/**
|
|
1326
1494
|
* Gracefully shutdown the logger, flushing all pending messages.
|
|
1327
1495
|
* Should be called before process exit to ensure no logs are lost.
|
|
1496
|
+
*
|
|
1497
|
+
* `timeoutMs` is forwarded to each transport's `close()` as a per-transport
|
|
1498
|
+
* cap; it prevents a dead transport from blocking the whole shutdown.
|
|
1499
|
+
* Default: 5000ms. Pass `Infinity` for legacy unbounded behaviour.
|
|
1328
1500
|
*/
|
|
1329
|
-
async shutdown() {
|
|
1330
|
-
const closePromises = this.state.transports.map((transport) => transport.close());
|
|
1501
|
+
async shutdown(timeoutMs = 5e3) {
|
|
1502
|
+
const closePromises = this.state.transports.map((transport) => transport.close(timeoutMs));
|
|
1331
1503
|
await Promise.all(closePromises);
|
|
1332
1504
|
}
|
|
1333
1505
|
// Profiling
|
|
1334
1506
|
profile(id, meta) {
|
|
1335
|
-
const
|
|
1507
|
+
const timers = this.profileTimers ??= /* @__PURE__ */ new Map();
|
|
1508
|
+
const existing = timers.get(id);
|
|
1336
1509
|
if (existing) {
|
|
1337
1510
|
const duration = Date.now() - existing;
|
|
1338
|
-
|
|
1511
|
+
timers.delete(id);
|
|
1339
1512
|
this.info(`${id} completed`, { ...meta, durationMs: duration });
|
|
1340
1513
|
} else {
|
|
1341
|
-
|
|
1514
|
+
timers.set(id, Date.now());
|
|
1342
1515
|
}
|
|
1343
1516
|
}
|
|
1344
1517
|
// Logging methods
|
|
1518
|
+
/**
|
|
1519
|
+
* Log an error. Supported shapes:
|
|
1520
|
+
* error(message: string)
|
|
1521
|
+
* error(message: string, meta)
|
|
1522
|
+
* error(error: Error | unknown)
|
|
1523
|
+
* error(error: Error | unknown, message: string)
|
|
1524
|
+
* error(error: Error | unknown, meta)
|
|
1525
|
+
* error(error: Error | unknown, message: string, meta)
|
|
1526
|
+
*
|
|
1527
|
+
* The first argument is treated as an error value whenever it is not a string.
|
|
1528
|
+
* Non-Error values (plain objects, numbers, etc. — e.g. anything caught by
|
|
1529
|
+
* TypeScript's `catch (err)` clause, which is typed as `unknown`) are passed
|
|
1530
|
+
* through `serializeError` so they produce a sensible `errorMessage` and, when
|
|
1531
|
+
* possible, a `stack` / HTTP diagnostic payload.
|
|
1532
|
+
*/
|
|
1345
1533
|
error(errorOrMessage, messageOrMeta, meta) {
|
|
1346
1534
|
if (!shouldLog(this.state, "error", this.context)) return;
|
|
1347
|
-
if (errorOrMessage
|
|
1348
|
-
if (typeof messageOrMeta === "string") {
|
|
1349
|
-
this.log("error", messageOrMeta, meta, errorOrMessage);
|
|
1350
|
-
} else {
|
|
1351
|
-
this.log("error", errorOrMessage.message, messageOrMeta, errorOrMessage);
|
|
1352
|
-
}
|
|
1353
|
-
} else {
|
|
1535
|
+
if (typeof errorOrMessage === "string") {
|
|
1354
1536
|
this.log("error", errorOrMessage, messageOrMeta);
|
|
1537
|
+
return;
|
|
1538
|
+
}
|
|
1539
|
+
const errorValue = errorOrMessage;
|
|
1540
|
+
if (typeof messageOrMeta === "string") {
|
|
1541
|
+
this.log("error", messageOrMeta, meta, errorValue);
|
|
1542
|
+
} else {
|
|
1543
|
+
const fallbackMessage = errorValue instanceof Error ? errorValue.message : serializeError(errorValue).errorMessage;
|
|
1544
|
+
this.log("error", fallbackMessage, messageOrMeta, errorValue);
|
|
1355
1545
|
}
|
|
1356
1546
|
}
|
|
1357
1547
|
warn(message, meta) {
|
|
@@ -1386,6 +1576,10 @@ var Logger = class _Logger {
|
|
|
1386
1576
|
if (storeContext) {
|
|
1387
1577
|
Object.assign(logMeta, storeContext);
|
|
1388
1578
|
}
|
|
1579
|
+
const overrideLevel = getGlobalOverrideLevel2(this.state, this.context, logMeta);
|
|
1580
|
+
if (overrideLevel !== void 0) {
|
|
1581
|
+
logMeta.__or = overrideLevel;
|
|
1582
|
+
}
|
|
1389
1583
|
if (this.state.callerConfig) {
|
|
1390
1584
|
const callerInfo = getCallerInfo(this.state.callerConfig, callerOffset);
|
|
1391
1585
|
if (callerInfo) {
|