@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.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,21 +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
|
-
|
|
601
|
-
|
|
602
|
-
|
|
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];
|
|
603
701
|
}
|
|
604
702
|
}
|
|
605
|
-
const
|
|
606
|
-
const effectiveLevel = matchingRule?.level ?? level ?? "silly";
|
|
703
|
+
const effectiveLevel = level ?? "silly";
|
|
607
704
|
if (effectiveLevel === "off") return false;
|
|
608
705
|
return LOG_LEVELS[logLevel] <= LOG_LEVELS[effectiveLevel];
|
|
609
706
|
}
|
|
707
|
+
var RESERVED_LOG_FIELDS = /* @__PURE__ */ new Set(["level", "time", "msg", "context", "__or"]);
|
|
610
708
|
function formatLog(log, format, store) {
|
|
611
709
|
const levelName = getLevelName(log.level);
|
|
612
710
|
const timestamp = new Date(log.time).toISOString();
|
|
@@ -615,7 +713,7 @@ function formatLog(log, format, store) {
|
|
|
615
713
|
const storeContext = store.getStore();
|
|
616
714
|
const meta = {};
|
|
617
715
|
for (const [key, value] of Object.entries(log)) {
|
|
618
|
-
if (!
|
|
716
|
+
if (!RESERVED_LOG_FIELDS.has(key)) {
|
|
619
717
|
meta[key] = value;
|
|
620
718
|
}
|
|
621
719
|
}
|
|
@@ -664,7 +762,7 @@ function formatLog(log, format, store) {
|
|
|
664
762
|
return `[${timestamp}] ${levelName}: ${message}
|
|
665
763
|
`;
|
|
666
764
|
}
|
|
667
|
-
function createFormattedFilterStream(format, level, rules,
|
|
765
|
+
function createFormattedFilterStream(format, level, rules, state, destination) {
|
|
668
766
|
return new Transform({
|
|
669
767
|
transform(chunk, _encoding, callback) {
|
|
670
768
|
const line = chunk.toString().trim();
|
|
@@ -677,24 +775,24 @@ function createFormattedFilterStream(format, level, rules, store, destination) {
|
|
|
677
775
|
callback();
|
|
678
776
|
return;
|
|
679
777
|
}
|
|
680
|
-
if (!shouldPassTransport(log, level, rules,
|
|
778
|
+
if (!shouldPassTransport(log, level, rules, state)) {
|
|
681
779
|
callback();
|
|
682
780
|
return;
|
|
683
781
|
}
|
|
684
|
-
const formatted = formatLog(log, format, store);
|
|
782
|
+
const formatted = formatLog(log, format, state.store);
|
|
685
783
|
destination.write(formatted);
|
|
686
784
|
callback();
|
|
687
785
|
}
|
|
688
786
|
});
|
|
689
787
|
}
|
|
690
|
-
function createStreams(config,
|
|
788
|
+
function createStreams(config, state) {
|
|
691
789
|
const streams = [];
|
|
692
790
|
const transports = [];
|
|
693
791
|
const consoleStream = createFormattedFilterStream(
|
|
694
792
|
config.console.format,
|
|
695
793
|
config.console.level,
|
|
696
794
|
config.console.rules,
|
|
697
|
-
|
|
795
|
+
state,
|
|
698
796
|
process.stdout
|
|
699
797
|
);
|
|
700
798
|
streams.push({
|
|
@@ -727,7 +825,7 @@ function createStreams(config, store) {
|
|
|
727
825
|
fileConfig.format,
|
|
728
826
|
fileConfig.level,
|
|
729
827
|
fileConfig.rules,
|
|
730
|
-
|
|
828
|
+
state,
|
|
731
829
|
rotatingStream
|
|
732
830
|
);
|
|
733
831
|
streams.push({
|
|
@@ -738,7 +836,7 @@ function createStreams(config, store) {
|
|
|
738
836
|
for (const discordConfig of toArray(config.discord)) {
|
|
739
837
|
const transport = new DiscordTransport(discordConfig);
|
|
740
838
|
transports.push(transport);
|
|
741
|
-
const discordStream = createHttpTransportStream(transport, discordConfig.level, discordConfig.rules,
|
|
839
|
+
const discordStream = createHttpTransportStream(transport, discordConfig.level, discordConfig.rules, state);
|
|
742
840
|
streams.push({
|
|
743
841
|
level: "trace",
|
|
744
842
|
stream: discordStream
|
|
@@ -747,7 +845,7 @@ function createStreams(config, store) {
|
|
|
747
845
|
for (const telegramConfig of toArray(config.telegram)) {
|
|
748
846
|
const transport = new TelegramTransport(telegramConfig);
|
|
749
847
|
transports.push(transport);
|
|
750
|
-
const telegramStream = createHttpTransportStream(transport, telegramConfig.level, telegramConfig.rules,
|
|
848
|
+
const telegramStream = createHttpTransportStream(transport, telegramConfig.level, telegramConfig.rules, state);
|
|
751
849
|
streams.push({
|
|
752
850
|
level: "trace",
|
|
753
851
|
stream: telegramStream
|
|
@@ -756,12 +854,22 @@ function createStreams(config, store) {
|
|
|
756
854
|
for (const cloudwatchConfig of toArray(config.cloudwatch)) {
|
|
757
855
|
const transport = new CloudWatchTransport(cloudwatchConfig, config.hostname);
|
|
758
856
|
transports.push(transport);
|
|
759
|
-
const cwStream = createHttpTransportStream(transport, cloudwatchConfig.level, cloudwatchConfig.rules,
|
|
857
|
+
const cwStream = createHttpTransportStream(transport, cloudwatchConfig.level, cloudwatchConfig.rules, state);
|
|
760
858
|
streams.push({
|
|
761
859
|
level: "trace",
|
|
762
860
|
stream: cwStream
|
|
763
861
|
});
|
|
764
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
|
+
}
|
|
765
873
|
return {
|
|
766
874
|
destination: pino.multistream(streams),
|
|
767
875
|
transports
|
|
@@ -771,7 +879,7 @@ function toArray(value) {
|
|
|
771
879
|
if (!value) return [];
|
|
772
880
|
return Array.isArray(value) ? value : [value];
|
|
773
881
|
}
|
|
774
|
-
function createHttpTransportStream(transport, level, rules,
|
|
882
|
+
function createHttpTransportStream(transport, level, rules, state) {
|
|
775
883
|
return new Writable({
|
|
776
884
|
write(chunk, _encoding, callback) {
|
|
777
885
|
const line = chunk.toString().trim();
|
|
@@ -784,15 +892,15 @@ function createHttpTransportStream(transport, level, rules, store) {
|
|
|
784
892
|
callback();
|
|
785
893
|
return;
|
|
786
894
|
}
|
|
787
|
-
if (!shouldPassTransport(log, level, rules,
|
|
895
|
+
if (!shouldPassTransport(log, level, rules, state)) {
|
|
788
896
|
callback();
|
|
789
897
|
return;
|
|
790
898
|
}
|
|
791
899
|
const levelName = getLevelName(log.level);
|
|
792
|
-
const storeContext = store.getStore();
|
|
900
|
+
const storeContext = state.store.getStore();
|
|
793
901
|
const meta = {};
|
|
794
902
|
for (const [key, value] of Object.entries(log)) {
|
|
795
|
-
if (!
|
|
903
|
+
if (!RESERVED_LOG_FIELDS.has(key)) {
|
|
796
904
|
meta[key] = value;
|
|
797
905
|
}
|
|
798
906
|
}
|
|
@@ -841,45 +949,53 @@ function buildIndexes(overrides) {
|
|
|
841
949
|
}
|
|
842
950
|
return { contextIndex, complexRules };
|
|
843
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
|
+
}
|
|
844
960
|
function createState(config, store) {
|
|
845
961
|
const { defaultLevel, rules } = parseLevelConfig(config.level);
|
|
846
962
|
const loggerStore = store ?? new LoggerStore();
|
|
847
963
|
const levelOverrides = /* @__PURE__ */ new Map();
|
|
848
964
|
for (const rule of rules) {
|
|
849
|
-
const key =
|
|
965
|
+
const key = canonicalMatchKey(rule.match);
|
|
850
966
|
levelOverrides.set(key, rule);
|
|
851
967
|
}
|
|
852
968
|
const { contextIndex, complexRules } = buildIndexes(levelOverrides);
|
|
853
|
-
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;
|
|
854
981
|
const options = {
|
|
855
982
|
level: "trace",
|
|
856
983
|
// Accept all, we filter in shouldLog()
|
|
857
984
|
customLevels: CUSTOM_LEVELS,
|
|
858
985
|
base: { hostname: config.hostname ?? hostname() }
|
|
859
986
|
};
|
|
860
|
-
|
|
861
|
-
let callerConfig;
|
|
987
|
+
state.pino = pino(options, destination);
|
|
862
988
|
if (config.caller === true) {
|
|
863
|
-
callerConfig = {};
|
|
989
|
+
state.callerConfig = {};
|
|
864
990
|
} else if (config.caller && typeof config.caller === "object") {
|
|
865
|
-
callerConfig = config.caller;
|
|
991
|
+
state.callerConfig = config.caller;
|
|
866
992
|
}
|
|
867
|
-
return
|
|
868
|
-
pino: pinoLogger,
|
|
869
|
-
store: loggerStore,
|
|
870
|
-
defaultLevel,
|
|
871
|
-
levelOverrides,
|
|
872
|
-
contextIndex,
|
|
873
|
-
complexRules,
|
|
874
|
-
callerConfig,
|
|
875
|
-
transports
|
|
876
|
-
};
|
|
993
|
+
return state;
|
|
877
994
|
}
|
|
878
995
|
function rebuildIndexes(state) {
|
|
879
996
|
const { contextIndex, complexRules } = buildIndexes(state.levelOverrides);
|
|
880
997
|
state.contextIndex = contextIndex;
|
|
881
|
-
state.complexRules
|
|
882
|
-
state.complexRules.push(...complexRules);
|
|
998
|
+
state.complexRules = complexRules;
|
|
883
999
|
}
|
|
884
1000
|
function shouldLog(state, level, context) {
|
|
885
1001
|
const effectiveLevel = getEffectiveLevel(state, context);
|
|
@@ -903,6 +1019,20 @@ function matchesContext(storeContext, loggerContext, match, logMeta) {
|
|
|
903
1019
|
const combined = { ...logMeta, ...storeContext, context: loggerContext };
|
|
904
1020
|
return Object.entries(match).every(([key, value]) => combined[key] === value);
|
|
905
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
|
+
}
|
|
906
1036
|
var LogLevelSchema = z.enum([
|
|
907
1037
|
"off",
|
|
908
1038
|
"error",
|
|
@@ -941,7 +1071,12 @@ var HttpTransportBaseConfigSchema = z.object({
|
|
|
941
1071
|
batchSize: z.number().int().positive().optional(),
|
|
942
1072
|
flushInterval: z.number().int().positive().optional(),
|
|
943
1073
|
maxRetries: z.number().int().nonnegative().optional(),
|
|
944
|
-
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()
|
|
945
1080
|
});
|
|
946
1081
|
var DiscordConfigSchema = z.object({
|
|
947
1082
|
level: LogLevelSchema.optional(),
|
|
@@ -950,6 +1085,11 @@ var DiscordConfigSchema = z.object({
|
|
|
950
1085
|
flushInterval: z.number().int().positive().optional(),
|
|
951
1086
|
maxRetries: z.number().int().nonnegative().optional(),
|
|
952
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(),
|
|
953
1093
|
webhookUrl: z.string().url("webhookUrl must be a valid URL"),
|
|
954
1094
|
format: z.enum(["embed", "markdown"]).optional(),
|
|
955
1095
|
username: z.string().optional(),
|
|
@@ -966,6 +1106,11 @@ var TelegramConfigSchema = z.object({
|
|
|
966
1106
|
flushInterval: z.number().int().positive().optional(),
|
|
967
1107
|
maxRetries: z.number().int().nonnegative().optional(),
|
|
968
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(),
|
|
969
1114
|
botToken: z.string().min(1, "botToken is required"),
|
|
970
1115
|
chatId: z.union([z.string(), z.number()]),
|
|
971
1116
|
parseMode: z.enum(["Markdown", "MarkdownV2", "HTML"]).optional(),
|
|
@@ -998,6 +1143,11 @@ var CloudWatchConfigSchema = z.object({
|
|
|
998
1143
|
flushInterval: z.number().int().positive().optional(),
|
|
999
1144
|
maxRetries: z.number().int().nonnegative().optional(),
|
|
1000
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(),
|
|
1001
1151
|
logGroupName: z.string().min(1, "logGroupName is required"),
|
|
1002
1152
|
logStreamName: LogStreamNameSchema.optional(),
|
|
1003
1153
|
region: z.string().min(1, "region is required"),
|
|
@@ -1006,6 +1156,14 @@ var CloudWatchConfigSchema = z.object({
|
|
|
1006
1156
|
createLogGroup: z.boolean().optional(),
|
|
1007
1157
|
createLogStream: z.boolean().optional()
|
|
1008
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
|
+
});
|
|
1009
1167
|
var LevelConfigObjectSchema = z.object({
|
|
1010
1168
|
default: LogLevelSchema,
|
|
1011
1169
|
rules: z.array(LevelRuleSchema).optional()
|
|
@@ -1029,6 +1187,7 @@ var LoggerConfigSchema = z.object({
|
|
|
1029
1187
|
discord: z.union([DiscordConfigSchema, z.array(DiscordConfigSchema)]).optional(),
|
|
1030
1188
|
telegram: z.union([TelegramConfigSchema, z.array(TelegramConfigSchema)]).optional(),
|
|
1031
1189
|
cloudwatch: z.union([CloudWatchConfigSchema, z.array(CloudWatchConfigSchema)]).optional(),
|
|
1190
|
+
relay: RelayConfigSchema.optional(),
|
|
1032
1191
|
caller: z.union([z.boolean(), CallerConfigSchema]).optional(),
|
|
1033
1192
|
hostname: z.string().optional(),
|
|
1034
1193
|
autoShutdown: z.union([z.boolean(), AutoShutdownConfigSchema]).optional()
|
|
@@ -1276,7 +1435,10 @@ var Logger = class _Logger {
|
|
|
1276
1435
|
this.state = state;
|
|
1277
1436
|
this.context = context;
|
|
1278
1437
|
}
|
|
1279
|
-
|
|
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;
|
|
1280
1442
|
static create(config, store) {
|
|
1281
1443
|
const validatedConfig = validateConfig(config);
|
|
1282
1444
|
const state = createState(validatedConfig, store);
|
|
@@ -1296,12 +1458,12 @@ var Logger = class _Logger {
|
|
|
1296
1458
|
}
|
|
1297
1459
|
setLevelOverride(match, level) {
|
|
1298
1460
|
assertLogLevel(level);
|
|
1299
|
-
const key =
|
|
1461
|
+
const key = canonicalMatchKey(match);
|
|
1300
1462
|
this.state.levelOverrides.set(key, { match, level });
|
|
1301
1463
|
rebuildIndexes(this.state);
|
|
1302
1464
|
}
|
|
1303
1465
|
removeLevelOverride(match) {
|
|
1304
|
-
const key =
|
|
1466
|
+
const key = canonicalMatchKey(match);
|
|
1305
1467
|
const override = this.state.levelOverrides.get(key);
|
|
1306
1468
|
if (override?.readonly) {
|
|
1307
1469
|
return false;
|
|
@@ -1331,33 +1493,55 @@ var Logger = class _Logger {
|
|
|
1331
1493
|
/**
|
|
1332
1494
|
* Gracefully shutdown the logger, flushing all pending messages.
|
|
1333
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.
|
|
1334
1500
|
*/
|
|
1335
|
-
async shutdown() {
|
|
1336
|
-
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));
|
|
1337
1503
|
await Promise.all(closePromises);
|
|
1338
1504
|
}
|
|
1339
1505
|
// Profiling
|
|
1340
1506
|
profile(id, meta) {
|
|
1341
|
-
const
|
|
1507
|
+
const timers = this.profileTimers ??= /* @__PURE__ */ new Map();
|
|
1508
|
+
const existing = timers.get(id);
|
|
1342
1509
|
if (existing) {
|
|
1343
1510
|
const duration = Date.now() - existing;
|
|
1344
|
-
|
|
1511
|
+
timers.delete(id);
|
|
1345
1512
|
this.info(`${id} completed`, { ...meta, durationMs: duration });
|
|
1346
1513
|
} else {
|
|
1347
|
-
|
|
1514
|
+
timers.set(id, Date.now());
|
|
1348
1515
|
}
|
|
1349
1516
|
}
|
|
1350
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
|
+
*/
|
|
1351
1533
|
error(errorOrMessage, messageOrMeta, meta) {
|
|
1352
1534
|
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 {
|
|
1535
|
+
if (typeof errorOrMessage === "string") {
|
|
1360
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);
|
|
1361
1545
|
}
|
|
1362
1546
|
}
|
|
1363
1547
|
warn(message, meta) {
|
|
@@ -1392,6 +1576,10 @@ var Logger = class _Logger {
|
|
|
1392
1576
|
if (storeContext) {
|
|
1393
1577
|
Object.assign(logMeta, storeContext);
|
|
1394
1578
|
}
|
|
1579
|
+
const overrideLevel = getGlobalOverrideLevel2(this.state, this.context, logMeta);
|
|
1580
|
+
if (overrideLevel !== void 0) {
|
|
1581
|
+
logMeta.__or = overrideLevel;
|
|
1582
|
+
}
|
|
1395
1583
|
if (this.state.callerConfig) {
|
|
1396
1584
|
const callerInfo = getCallerInfo(this.state.callerConfig, callerOffset);
|
|
1397
1585
|
if (callerInfo) {
|