@rawnodes/logger 1.9.1 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,18 +1,18 @@
1
1
  'use strict';
2
2
 
3
- var winston = require('winston');
3
+ var pino = require('pino');
4
4
  var async_hooks = require('async_hooks');
5
- var DailyRotateFile = require('winston-daily-rotate-file');
6
- var util = require('util');
7
- var TransportStream = require('winston-transport');
5
+ var stream = require('stream');
6
+ var promises = require('fs/promises');
7
+ var rotatingFileStream = require('rotating-file-stream');
8
+ var events = require('events');
8
9
  var clientCloudwatchLogs = require('@aws-sdk/client-cloudwatch-logs');
9
10
  var zod = require('zod');
10
11
  var crypto = require('crypto');
11
12
 
12
13
  function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
13
14
 
14
- var DailyRotateFile__default = /*#__PURE__*/_interopDefault(DailyRotateFile);
15
- var TransportStream__default = /*#__PURE__*/_interopDefault(TransportStream);
15
+ var pino__default = /*#__PURE__*/_interopDefault(pino);
16
16
 
17
17
  // src/state.ts
18
18
  var LoggerStore = class {
@@ -45,238 +45,6 @@ function assertLogLevel(level) {
45
45
  }
46
46
  }
47
47
 
48
- // src/utils/mask-secrets.ts
49
- var DEFAULT_SECRET_PATTERNS = [
50
- "password",
51
- "secret",
52
- "token",
53
- "apikey",
54
- "api_key",
55
- "api-key",
56
- "auth",
57
- "credential",
58
- "private"
59
- ];
60
- var DEFAULT_MASK = "***";
61
- function isSecretKey(key, patterns) {
62
- const lowerKey = key.toLowerCase();
63
- return patterns.some((pattern) => lowerKey.includes(pattern.toLowerCase()));
64
- }
65
- function maskUrlCredentials(url, mask) {
66
- try {
67
- const parsed = new URL(url);
68
- if (parsed.password) {
69
- parsed.password = mask;
70
- }
71
- if (parsed.username && parsed.password) {
72
- parsed.username = mask;
73
- }
74
- return parsed.toString();
75
- } catch {
76
- return url;
77
- }
78
- }
79
- function maskSecrets(obj, options = {}) {
80
- const { patterns = DEFAULT_SECRET_PATTERNS, mask = DEFAULT_MASK, deep = true } = options;
81
- if (obj === null || obj === void 0) {
82
- return obj;
83
- }
84
- if (typeof obj === "string") {
85
- if (obj.startsWith("http://") || obj.startsWith("https://")) {
86
- return maskUrlCredentials(obj, mask);
87
- }
88
- return obj;
89
- }
90
- if (Array.isArray(obj)) {
91
- return deep ? obj.map((item) => maskSecrets(item, options)) : obj;
92
- }
93
- if (typeof obj === "object") {
94
- const result = {};
95
- for (const sym of Object.getOwnPropertySymbols(obj)) {
96
- result[sym] = obj[sym];
97
- }
98
- for (const [key, value] of Object.entries(obj)) {
99
- if (isSecretKey(key, patterns)) {
100
- result[key] = mask;
101
- } else if (deep && typeof value === "object" && value !== null) {
102
- result[key] = maskSecrets(value, options);
103
- } else if (typeof value === "string") {
104
- result[key] = maskSecrets(value, options);
105
- } else {
106
- result[key] = value;
107
- }
108
- }
109
- return result;
110
- }
111
- return obj;
112
- }
113
- function createMasker(options = {}) {
114
- return (obj) => maskSecrets(obj, options);
115
- }
116
-
117
- // src/formatters.ts
118
- var DEFAULT_CONTEXT = "APP";
119
- var LEVEL_COLORS = {
120
- error: "\x1B[31m",
121
- // red
122
- warn: "\x1B[33m",
123
- // yellow
124
- info: "\x1B[32m",
125
- // green
126
- http: "\x1B[35m",
127
- // magenta
128
- verbose: "\x1B[36m",
129
- // cyan
130
- debug: "\x1B[34m",
131
- // blue
132
- silly: "\x1B[90m"
133
- // grey
134
- };
135
- var RESET = "\x1B[0m";
136
- function colorizeLevel(level) {
137
- const color = LEVEL_COLORS[level] || "";
138
- return color ? `${color}${level}${RESET}` : level;
139
- }
140
- function formatMeta(meta, colors) {
141
- return Object.entries(meta).filter(([, value]) => value !== void 0 && value !== null).map(([key, value]) => {
142
- if (typeof value === "object") {
143
- const inspected = util.inspect(value, { depth: 4, colors, compact: false });
144
- return `
145
- ${key}: ${inspected.split("\n").join("\n ")}`;
146
- }
147
- return `
148
- ${key}: ${value}`;
149
- }).join("");
150
- }
151
- function flattenObject(obj, prefix = "") {
152
- const result = {};
153
- for (const [key, value] of Object.entries(obj)) {
154
- const newKey = prefix ? `${prefix}.${key}` : key;
155
- if (value !== null && typeof value === "object" && !Array.isArray(value) && !(value instanceof Error)) {
156
- Object.assign(result, flattenObject(value, newKey));
157
- } else {
158
- result[newKey] = value;
159
- }
160
- }
161
- return result;
162
- }
163
- function formatLogfmtValue(value) {
164
- if (value === null || value === void 0) {
165
- return "";
166
- }
167
- if (typeof value === "string") {
168
- if (value.includes(" ") || value.includes('"') || value.includes("=")) {
169
- return `"${value.replace(/"/g, '\\"')}"`;
170
- }
171
- return value;
172
- }
173
- if (typeof value === "number" || typeof value === "boolean") {
174
- return String(value);
175
- }
176
- if (value instanceof Error) {
177
- return `"${value.message.replace(/"/g, '\\"')}"`;
178
- }
179
- if (Array.isArray(value)) {
180
- return `"${JSON.stringify(value).replace(/"/g, '\\"')}"`;
181
- }
182
- return `"${JSON.stringify(value).replace(/"/g, '\\"')}"`;
183
- }
184
- function formatLogfmt(data) {
185
- const flattened = flattenObject(data);
186
- return Object.entries(flattened).filter(([, value]) => value !== void 0 && value !== null).map(([key, value]) => `${key}=${formatLogfmtValue(value)}`).join(" ");
187
- }
188
- function addStoreContext(store) {
189
- return winston.format((info) => {
190
- const storeContext = store.getStore();
191
- if (storeContext) {
192
- return { ...info, ...storeContext };
193
- }
194
- return info;
195
- })();
196
- }
197
- function maskSecretsFormat(options) {
198
- return winston.format((info) => {
199
- return maskSecrets(info, options);
200
- })();
201
- }
202
- function createFilterFormat(defaultLevel, rules, store) {
203
- if (!rules?.length && defaultLevel === void 0) {
204
- return winston.format((info) => info)();
205
- }
206
- return winston.format((info) => {
207
- const logLevel = info.level;
208
- const context = info.context;
209
- const storeContext = store.getStore();
210
- const matchingRule = rules?.find((rule) => matchesContext(storeContext, context, rule.match));
211
- const effectiveLevel = matchingRule?.level ?? defaultLevel ?? "silly";
212
- if (effectiveLevel === "off") return false;
213
- return LOG_LEVELS[logLevel] <= LOG_LEVELS[effectiveLevel] ? info : false;
214
- })();
215
- }
216
- function createPlainFormat(store) {
217
- return winston.format.combine(
218
- winston.format.errors({ stack: true }),
219
- winston.format.timestamp(),
220
- addStoreContext(store),
221
- maskSecretsFormat(),
222
- winston.format.printf(({ timestamp, level, context, message, ...meta }) => {
223
- const formattedMeta = formatMeta(meta, true);
224
- const coloredLevel = colorizeLevel(level);
225
- return `[${timestamp}] ${coloredLevel} [${context || DEFAULT_CONTEXT}] ${message}${formattedMeta}`;
226
- })
227
- );
228
- }
229
- function createJsonFormat(store) {
230
- return winston.format.combine(
231
- winston.format.errors({ stack: true }),
232
- winston.format.timestamp(),
233
- addStoreContext(store),
234
- maskSecretsFormat(),
235
- winston.format.json()
236
- );
237
- }
238
- function createLogfmtFormat(store) {
239
- return winston.format.combine(
240
- winston.format.errors({ stack: true }),
241
- winston.format.timestamp(),
242
- addStoreContext(store),
243
- maskSecretsFormat(),
244
- winston.format.printf(({ timestamp, level, context, message, ...meta }) => {
245
- const data = {
246
- level,
247
- msg: message,
248
- context: context || DEFAULT_CONTEXT,
249
- ts: timestamp,
250
- ...meta
251
- };
252
- return formatLogfmt(data);
253
- })
254
- );
255
- }
256
- function createSimpleFormat(store) {
257
- return winston.format.combine(
258
- winston.format.errors({ stack: true }),
259
- winston.format.timestamp(),
260
- addStoreContext(store),
261
- maskSecretsFormat(),
262
- winston.format.printf(({ timestamp, level, message }) => {
263
- return `[${timestamp}] ${level}: ${message}`;
264
- })
265
- );
266
- }
267
- function createFormat(logFormat, store) {
268
- switch (logFormat) {
269
- case "plain":
270
- return createPlainFormat(store);
271
- case "json":
272
- return createJsonFormat(store);
273
- case "logfmt":
274
- return createLogfmtFormat(store);
275
- case "simple":
276
- return createSimpleFormat(store);
277
- }
278
- }
279
-
280
48
  // src/transports/buffer.ts
281
49
  var MessageBuffer = class {
282
50
  constructor(options) {
@@ -358,7 +126,7 @@ var DEFAULT_OPTIONS = {
358
126
  maxRetries: 3,
359
127
  retryDelay: 1e3
360
128
  };
361
- var BaseHttpTransport = class extends TransportStream__default.default {
129
+ var BaseHttpTransport = class extends events.EventEmitter {
362
130
  buffer;
363
131
  constructor(opts = {}) {
364
132
  super();
@@ -384,7 +152,7 @@ var BaseHttpTransport = class extends TransportStream__default.default {
384
152
  return {
385
153
  level,
386
154
  message: String(message),
387
- timestamp: timestamp ? new Date(String(timestamp)) : /* @__PURE__ */ new Date(),
155
+ timestamp: timestamp instanceof Date ? timestamp : timestamp ? new Date(String(timestamp)) : /* @__PURE__ */ new Date(),
388
156
  context,
389
157
  meta: Object.keys(meta).length > 0 ? meta : void 0
390
158
  };
@@ -409,6 +177,16 @@ var DEFAULT_EMBED_COLORS = {
409
177
  debug: 3447003,
410
178
  silly: 9807270
411
179
  };
180
+ var LEVEL_EMOJI = {
181
+ off: "\u26AB",
182
+ error: "\u{1F534}",
183
+ warn: "\u{1F7E1}",
184
+ info: "\u{1F7E2}",
185
+ http: "\u{1F535}",
186
+ verbose: "\u{1F7E3}",
187
+ debug: "\u26AA",
188
+ silly: "\u26AB"
189
+ };
412
190
  var DiscordTransport = class extends BaseHttpTransport {
413
191
  config;
414
192
  constructor(config) {
@@ -421,6 +199,13 @@ var DiscordTransport = class extends BaseHttpTransport {
421
199
  this.config = config;
422
200
  }
423
201
  async sendBatch(messages) {
202
+ if (this.config.format === "markdown") {
203
+ await this.sendMarkdownBatch(messages);
204
+ } else {
205
+ await this.sendEmbedBatch(messages);
206
+ }
207
+ }
208
+ async sendEmbedBatch(messages) {
424
209
  const chunks = this.chunkArray(messages, 10);
425
210
  for (const chunk of chunks) {
426
211
  const payload = {
@@ -431,6 +216,51 @@ var DiscordTransport = class extends BaseHttpTransport {
431
216
  await this.sendWebhook(payload);
432
217
  }
433
218
  }
219
+ async sendMarkdownBatch(messages) {
220
+ const content = messages.map((msg) => this.formatMarkdown(msg)).join("\n\n---\n\n");
221
+ const chunks = this.splitContent(content, 1900);
222
+ for (const chunk of chunks) {
223
+ const payload = {
224
+ username: this.config.username,
225
+ avatar_url: this.config.avatarUrl,
226
+ content: chunk
227
+ };
228
+ await this.sendWebhook(payload);
229
+ }
230
+ }
231
+ formatMarkdown(msg) {
232
+ const emoji = LEVEL_EMOJI[msg.level];
233
+ const level = msg.level.toUpperCase();
234
+ const context = msg.context || "APP";
235
+ const timestamp = this.config.includeTimestamp !== false ? `\`${msg.timestamp.toISOString()}\`` : "";
236
+ let text = `${emoji} **${level}** [${context}] ${timestamp}
237
+ ${msg.message}`;
238
+ if (this.config.includeMeta !== false && msg.meta && Object.keys(msg.meta).length > 0) {
239
+ text += "\n```json\n" + JSON.stringify(msg.meta, null, 2) + "\n```";
240
+ }
241
+ return text;
242
+ }
243
+ splitContent(content, maxLength) {
244
+ if (content.length <= maxLength) return [content];
245
+ const chunks = [];
246
+ let current = content;
247
+ while (current.length > 0) {
248
+ if (current.length <= maxLength) {
249
+ chunks.push(current);
250
+ break;
251
+ }
252
+ let splitAt = current.lastIndexOf("\n", maxLength);
253
+ if (splitAt === -1 || splitAt < maxLength / 2) {
254
+ splitAt = current.lastIndexOf(" ", maxLength);
255
+ }
256
+ if (splitAt === -1 || splitAt < maxLength / 2) {
257
+ splitAt = maxLength;
258
+ }
259
+ chunks.push(current.slice(0, splitAt));
260
+ current = current.slice(splitAt).trimStart();
261
+ }
262
+ return chunks;
263
+ }
434
264
  createEmbed(msg) {
435
265
  const color = this.config.embedColors?.[msg.level] ?? DEFAULT_EMBED_COLORS[msg.level];
436
266
  const embed = {
@@ -487,7 +317,7 @@ var DiscordTransport = class extends BaseHttpTransport {
487
317
  };
488
318
 
489
319
  // src/transports/telegram.ts
490
- var LEVEL_EMOJI = {
320
+ var LEVEL_EMOJI2 = {
491
321
  off: "",
492
322
  error: "\u{1F534}",
493
323
  warn: "\u{1F7E1}",
@@ -515,7 +345,7 @@ var TelegramTransport = class extends BaseHttpTransport {
515
345
  await this.sendMessage(text, messages);
516
346
  }
517
347
  formatBatchMessage(messages) {
518
- const parseMode = this.config.parseMode ?? "Markdown";
348
+ const parseMode = this.config.parseMode ?? "HTML";
519
349
  return messages.map((msg) => {
520
350
  if (parseMode === "HTML") {
521
351
  return this.formatHtml(msg);
@@ -524,9 +354,10 @@ var TelegramTransport = class extends BaseHttpTransport {
524
354
  }).join("\n\n---\n\n");
525
355
  }
526
356
  formatMarkdown(msg, v2) {
527
- const emoji = LEVEL_EMOJI[msg.level];
357
+ const emoji = LEVEL_EMOJI2[msg.level];
528
358
  const escape = v2 ? this.escapeMarkdownV2.bind(this) : (s) => s;
529
- let text = `${emoji} *${msg.level.toUpperCase()}* \\[${escape(msg.context || "APP")}\\]
359
+ const bracket = v2 ? ["\\[", "\\]"] : ["[", "]"];
360
+ let text = `${emoji} *${msg.level.toUpperCase()}* ${bracket[0]}${escape(msg.context || "APP")}${bracket[1]}
530
361
  `;
531
362
  text += escape(msg.message);
532
363
  if (msg.meta && Object.keys(msg.meta).length > 0) {
@@ -536,7 +367,7 @@ var TelegramTransport = class extends BaseHttpTransport {
536
367
  return text;
537
368
  }
538
369
  formatHtml(msg) {
539
- const emoji = LEVEL_EMOJI[msg.level];
370
+ const emoji = LEVEL_EMOJI2[msg.level];
540
371
  let text = `${emoji} <b>${msg.level.toUpperCase()}</b> [${this.escapeHtml(msg.context || "APP")}]
541
372
  `;
542
373
  text += this.escapeHtml(msg.message);
@@ -556,7 +387,7 @@ var TelegramTransport = class extends BaseHttpTransport {
556
387
  const body = {
557
388
  chat_id: this.config.chatId,
558
389
  text,
559
- parse_mode: this.config.parseMode ?? "Markdown",
390
+ parse_mode: this.config.parseMode ?? "HTML",
560
391
  disable_notification: this.shouldMute(messages)
561
392
  };
562
393
  if (this.config.threadId) {
@@ -711,85 +542,224 @@ var CloudWatchTransport = class extends BaseHttpTransport {
711
542
  }
712
543
  };
713
544
 
714
- // src/transports.ts
715
- function toArray(value) {
716
- if (!value) return [];
717
- return Array.isArray(value) ? value : [value];
545
+ // src/streams.ts
546
+ function parseLogLine(line) {
547
+ try {
548
+ return JSON.parse(line);
549
+ } catch {
550
+ return null;
551
+ }
552
+ }
553
+ function getLevelName(levelNum) {
554
+ if (levelNum >= 50) return "error";
555
+ if (levelNum >= 40) return "warn";
556
+ if (levelNum >= 30) return "info";
557
+ if (levelNum >= 25) return "http";
558
+ if (levelNum >= 20) return "debug";
559
+ return "silly";
560
+ }
561
+ function shouldPassTransport(log, level, rules, store) {
562
+ const logLevel = getLevelName(log.level);
563
+ const context = log.context;
564
+ const storeContext = store.getStore();
565
+ const matchingRule = rules?.find((rule) => matchesContext(storeContext, context, rule.match));
566
+ const effectiveLevel = matchingRule?.level ?? level ?? "silly";
567
+ if (effectiveLevel === "off") return false;
568
+ return LOG_LEVELS[logLevel] <= LOG_LEVELS[effectiveLevel];
569
+ }
570
+ function formatLog(log, format, store) {
571
+ const levelName = getLevelName(log.level);
572
+ const timestamp = new Date(log.time).toISOString();
573
+ const context = log.context || "APP";
574
+ const message = log.msg || "";
575
+ const storeContext = store.getStore();
576
+ const meta = {};
577
+ for (const [key, value] of Object.entries(log)) {
578
+ if (!["level", "time", "msg", "context"].includes(key)) {
579
+ meta[key] = value;
580
+ }
581
+ }
582
+ if (storeContext) {
583
+ Object.assign(meta, storeContext);
584
+ }
585
+ if (format === "json") {
586
+ return JSON.stringify({
587
+ level: levelName,
588
+ timestamp,
589
+ context,
590
+ message,
591
+ ...meta
592
+ }) + "\n";
593
+ }
594
+ if (format === "plain") {
595
+ const LEVEL_COLORS = {
596
+ error: "\x1B[31m",
597
+ warn: "\x1B[33m",
598
+ info: "\x1B[32m",
599
+ http: "\x1B[35m",
600
+ verbose: "\x1B[36m",
601
+ debug: "\x1B[34m",
602
+ silly: "\x1B[90m"
603
+ };
604
+ const RESET = "\x1B[0m";
605
+ const color = LEVEL_COLORS[levelName] || "";
606
+ const coloredLevel = color ? `${color}${levelName}${RESET}` : levelName;
607
+ const metaStr = Object.keys(meta).length > 0 ? "\n " + Object.entries(meta).map(([k, v]) => `${k}: ${JSON.stringify(v)}`).join("\n ") : "";
608
+ return `[${timestamp}] ${coloredLevel} [${context}] ${message}${metaStr}
609
+ `;
610
+ }
611
+ if (format === "logfmt") {
612
+ const parts = [`level=${levelName}`, `msg="${message}"`, `context=${context}`, `ts=${timestamp}`];
613
+ for (const [k, v] of Object.entries(meta)) {
614
+ parts.push(`${k}=${JSON.stringify(v)}`);
615
+ }
616
+ return parts.join(" ") + "\n";
617
+ }
618
+ return `[${timestamp}] ${levelName}: ${message}
619
+ `;
718
620
  }
719
- function createTransports(config, store) {
720
- const result = [
721
- new winston.transports.Console({
722
- format: winston.format.combine(
723
- createFilterFormat(config.console.level, config.console.rules, store),
724
- createFormat(config.console.format, store)
725
- )
726
- })
727
- ];
621
+ function createFormattedFilterStream(format, level, rules, store, destination) {
622
+ return new stream.Transform({
623
+ transform(chunk, _encoding, callback) {
624
+ const line = chunk.toString().trim();
625
+ if (!line) {
626
+ callback();
627
+ return;
628
+ }
629
+ const log = parseLogLine(line);
630
+ if (!log) {
631
+ callback();
632
+ return;
633
+ }
634
+ if (!shouldPassTransport(log, level, rules, store)) {
635
+ callback();
636
+ return;
637
+ }
638
+ const formatted = formatLog(log, format, store);
639
+ destination.write(formatted);
640
+ callback();
641
+ }
642
+ });
643
+ }
644
+ function createStreams(config, store) {
645
+ const streams = [];
646
+ const consoleStream = createFormattedFilterStream(
647
+ config.console.format,
648
+ config.console.level,
649
+ config.console.rules,
650
+ store,
651
+ process.stdout
652
+ );
653
+ streams.push({
654
+ level: "trace",
655
+ stream: consoleStream
656
+ });
728
657
  if (config.file) {
729
- result.push(
730
- new DailyRotateFile__default.default({
731
- format: winston.format.combine(
732
- createFilterFormat(config.file.level, config.file.rules, store),
733
- createFormat(config.file.format, store)
734
- ),
735
- dirname: config.file.dirname,
736
- filename: config.file.filename,
737
- datePattern: config.file.datePattern ?? "YYYY-MM-DD",
738
- zippedArchive: config.file.zippedArchive ?? false,
739
- maxSize: config.file.maxSize ?? "20m",
740
- maxFiles: config.file.maxFiles ?? "14d"
741
- })
658
+ const fileConfig = config.file;
659
+ promises.mkdir(fileConfig.dirname, { recursive: true }).catch(() => {
660
+ });
661
+ const rotatingStream = rotatingFileStream.createStream(
662
+ (time, index) => {
663
+ const date = time instanceof Date ? time : /* @__PURE__ */ new Date();
664
+ const dateStr = date.toISOString().split("T")[0];
665
+ const base = fileConfig.filename.replace(".log", "");
666
+ return `${base}-${dateStr}${index ? `.${index}` : ""}.log`;
667
+ },
668
+ {
669
+ path: fileConfig.dirname,
670
+ size: fileConfig.maxSize || "20M",
671
+ interval: fileConfig.datePattern === "YYYY-MM-DD-HH" ? "1h" : "1d",
672
+ compress: fileConfig.zippedArchive ? "gzip" : false,
673
+ maxFiles: parseInt(fileConfig.maxFiles?.replace("d", "") || "14")
674
+ }
742
675
  );
676
+ const fileStream = createFormattedFilterStream(
677
+ fileConfig.format,
678
+ fileConfig.level,
679
+ fileConfig.rules,
680
+ store,
681
+ rotatingStream
682
+ );
683
+ streams.push({
684
+ level: "trace",
685
+ stream: fileStream
686
+ });
743
687
  }
744
688
  for (const discordConfig of toArray(config.discord)) {
745
- const discord = new DiscordTransport(discordConfig);
746
- discord.format = winston.format.combine(
747
- createFilterFormat(discordConfig.level, discordConfig.rules, store),
748
- winston.format.timestamp()
749
- );
750
- result.push(discord);
689
+ const transport = new DiscordTransport(discordConfig);
690
+ const discordStream = createHttpTransportStream(transport, discordConfig.level, discordConfig.rules, store);
691
+ streams.push({
692
+ level: "trace",
693
+ stream: discordStream
694
+ });
751
695
  }
752
696
  for (const telegramConfig of toArray(config.telegram)) {
753
- const telegram = new TelegramTransport(telegramConfig);
754
- telegram.format = winston.format.combine(
755
- createFilterFormat(telegramConfig.level, telegramConfig.rules, store),
756
- winston.format.timestamp()
757
- );
758
- result.push(telegram);
697
+ const transport = new TelegramTransport(telegramConfig);
698
+ const telegramStream = createHttpTransportStream(transport, telegramConfig.level, telegramConfig.rules, store);
699
+ streams.push({
700
+ level: "trace",
701
+ stream: telegramStream
702
+ });
759
703
  }
760
704
  for (const cloudwatchConfig of toArray(config.cloudwatch)) {
761
- const cloudwatch = new CloudWatchTransport(cloudwatchConfig);
762
- cloudwatch.format = winston.format.combine(
763
- createFilterFormat(cloudwatchConfig.level, cloudwatchConfig.rules, store),
764
- winston.format.timestamp()
765
- );
766
- result.push(cloudwatch);
705
+ const transport = new CloudWatchTransport(cloudwatchConfig);
706
+ const cwStream = createHttpTransportStream(transport, cloudwatchConfig.level, cloudwatchConfig.rules, store);
707
+ streams.push({
708
+ level: "trace",
709
+ stream: cwStream
710
+ });
767
711
  }
768
- return result;
712
+ return pino__default.default.multistream(streams);
769
713
  }
770
- function createExceptionHandlers(config, store) {
771
- const result = [
772
- new winston.transports.Console({
773
- format: createFormat(config.console.format, store)
774
- })
775
- ];
776
- if (config.file) {
777
- result.push(
778
- new DailyRotateFile__default.default({
779
- format: createFormat(config.file.format, store),
780
- dirname: config.file.dirname,
781
- filename: `exceptions-${config.file.filename}`,
782
- datePattern: config.file.datePattern ?? "YYYY-MM-DD",
783
- zippedArchive: config.file.zippedArchive ?? false,
784
- maxSize: config.file.maxSize ?? "20m",
785
- maxFiles: config.file.maxFiles ?? "14d"
786
- })
787
- );
788
- }
789
- return result;
714
+ function toArray(value) {
715
+ if (!value) return [];
716
+ return Array.isArray(value) ? value : [value];
717
+ }
718
+ function createHttpTransportStream(transport, level, rules, store) {
719
+ return new stream.Writable({
720
+ write(chunk, _encoding, callback) {
721
+ const line = chunk.toString().trim();
722
+ if (!line) {
723
+ callback();
724
+ return;
725
+ }
726
+ const log = parseLogLine(line);
727
+ if (!log) {
728
+ callback();
729
+ return;
730
+ }
731
+ if (!shouldPassTransport(log, level, rules, store)) {
732
+ callback();
733
+ return;
734
+ }
735
+ const levelName = getLevelName(log.level);
736
+ const storeContext = store.getStore();
737
+ const meta = {};
738
+ for (const [key, value] of Object.entries(log)) {
739
+ if (!["level", "time", "msg", "context"].includes(key)) {
740
+ meta[key] = value;
741
+ }
742
+ }
743
+ if (storeContext) {
744
+ Object.assign(meta, storeContext);
745
+ }
746
+ transport.log({
747
+ level: levelName,
748
+ message: log.msg || "",
749
+ context: log.context,
750
+ timestamp: new Date(log.time),
751
+ ...meta
752
+ }, callback);
753
+ }
754
+ });
790
755
  }
791
756
 
792
757
  // src/state.ts
758
+ var CUSTOM_LEVELS = {
759
+ http: 25,
760
+ verbose: 25,
761
+ silly: 10
762
+ };
793
763
  function parseLevelConfig(level) {
794
764
  if (typeof level === "string") {
795
765
  assertLogLevel(level);
@@ -802,37 +772,64 @@ function parseLevelConfig(level) {
802
772
  });
803
773
  return { defaultLevel: level.default, rules };
804
774
  }
775
+ function buildIndexes(overrides) {
776
+ const contextIndex = /* @__PURE__ */ new Map();
777
+ const complexRules = [];
778
+ for (const override of overrides.values()) {
779
+ const keys = Object.keys(override.match);
780
+ if (keys.length === 1 && keys[0] === "context" && typeof override.match.context === "string") {
781
+ contextIndex.set(override.match.context, override);
782
+ } else {
783
+ complexRules.push(override);
784
+ }
785
+ }
786
+ return { contextIndex, complexRules };
787
+ }
805
788
  function createState(config, store) {
806
789
  const { defaultLevel, rules } = parseLevelConfig(config.level);
807
790
  const loggerStore = store ?? new LoggerStore();
808
- const exceptionHandlers = createExceptionHandlers(config, loggerStore);
809
- const winston$1 = winston.createLogger({
810
- level: "silly",
811
- // Accept all, we filter in shouldLog()
812
- transports: createTransports(config, loggerStore),
813
- exceptionHandlers,
814
- rejectionHandlers: exceptionHandlers,
815
- exitOnError: false
816
- });
817
791
  const levelOverrides = /* @__PURE__ */ new Map();
818
792
  for (const rule of rules) {
819
793
  const key = JSON.stringify(rule.match);
820
794
  levelOverrides.set(key, rule);
821
795
  }
796
+ const { contextIndex, complexRules } = buildIndexes(levelOverrides);
797
+ const streams = createStreams(config, loggerStore);
798
+ const options = {
799
+ level: "trace",
800
+ // Accept all, we filter in shouldLog()
801
+ customLevels: CUSTOM_LEVELS,
802
+ base: void 0
803
+ // Disable pid and hostname
804
+ };
805
+ const pinoLogger = pino__default.default(options, streams);
822
806
  return {
823
- winston: winston$1,
807
+ pino: pinoLogger,
824
808
  store: loggerStore,
825
809
  defaultLevel,
826
- levelOverrides
810
+ levelOverrides,
811
+ contextIndex,
812
+ complexRules
827
813
  };
828
814
  }
815
+ function rebuildIndexes(state) {
816
+ const { contextIndex, complexRules } = buildIndexes(state.levelOverrides);
817
+ state.contextIndex = contextIndex;
818
+ state.complexRules.length = 0;
819
+ state.complexRules.push(...complexRules);
820
+ }
829
821
  function shouldLog(state, level, context) {
830
822
  const effectiveLevel = getEffectiveLevel(state, context);
823
+ if (effectiveLevel === "off") return false;
831
824
  return LOG_LEVELS[level] <= LOG_LEVELS[effectiveLevel];
832
825
  }
833
826
  function getEffectiveLevel(state, loggerContext) {
827
+ if (loggerContext) {
828
+ const indexed = state.contextIndex.get(loggerContext);
829
+ if (indexed) return indexed.level;
830
+ }
834
831
  const storeContext = state.store.getStore();
835
- for (const { match, level } of state.levelOverrides.values()) {
832
+ for (const { match, level } of state.complexRules) {
836
833
  if (matchesContext(storeContext, loggerContext, match)) {
837
834
  return level;
838
835
  }
@@ -890,6 +887,7 @@ var DiscordConfigSchema = zod.z.object({
890
887
  maxRetries: zod.z.number().int().nonnegative().optional(),
891
888
  retryDelay: zod.z.number().int().positive().optional(),
892
889
  webhookUrl: zod.z.string().url("webhookUrl must be a valid URL"),
890
+ format: zod.z.enum(["embed", "markdown"]).optional(),
893
891
  username: zod.z.string().optional(),
894
892
  avatarUrl: zod.z.string().url().optional(),
895
893
  embedColors: zod.z.record(zod.z.string(), zod.z.number().int()).optional(),
@@ -955,6 +953,7 @@ var Logger = class _Logger {
955
953
  this.state = state;
956
954
  this.context = context;
957
955
  }
956
+ profileTimers = /* @__PURE__ */ new Map();
958
957
  static create(config, store) {
959
958
  const validatedConfig = validateConfig(config);
960
959
  const state = createState(validatedConfig, store);
@@ -971,6 +970,7 @@ var Logger = class _Logger {
971
970
  assertLogLevel(level);
972
971
  const key = JSON.stringify(match);
973
972
  this.state.levelOverrides.set(key, { match, level });
973
+ rebuildIndexes(this.state);
974
974
  }
975
975
  removeLevelOverride(match) {
976
976
  const key = JSON.stringify(match);
@@ -978,26 +978,50 @@ var Logger = class _Logger {
978
978
  if (override?.readonly) {
979
979
  return false;
980
980
  }
981
- return this.state.levelOverrides.delete(key);
981
+ const deleted = this.state.levelOverrides.delete(key);
982
+ if (deleted) {
983
+ rebuildIndexes(this.state);
984
+ }
985
+ return deleted;
982
986
  }
983
987
  clearLevelOverrides() {
988
+ let changed = false;
984
989
  for (const [key, override] of this.state.levelOverrides) {
985
990
  if (!override.readonly) {
986
991
  this.state.levelOverrides.delete(key);
992
+ changed = true;
987
993
  }
988
994
  }
995
+ if (changed) {
996
+ rebuildIndexes(this.state);
997
+ }
989
998
  }
990
999
  getLevelOverrides() {
991
1000
  return Array.from(this.state.levelOverrides.values());
992
1001
  }
993
1002
  // Profiling
994
1003
  profile(id, meta) {
995
- this.state.winston.profile(id, meta);
1004
+ const existing = this.profileTimers.get(id);
1005
+ if (existing) {
1006
+ const duration = Date.now() - existing;
1007
+ this.profileTimers.delete(id);
1008
+ this.info(`${id} completed`, { ...meta, durationMs: duration });
1009
+ } else {
1010
+ this.profileTimers.set(id, Date.now());
1011
+ }
996
1012
  }
997
1013
  // Logging methods
998
- error(message, error, meta) {
1014
+ error(errorOrMessage, messageOrMeta, meta) {
999
1015
  if (!shouldLog(this.state, "error", this.context)) return;
1000
- this.log("error", message, meta, error);
1016
+ if (errorOrMessage instanceof Error) {
1017
+ if (typeof messageOrMeta === "string") {
1018
+ this.log("error", messageOrMeta, meta, errorOrMessage);
1019
+ } else {
1020
+ this.log("error", errorOrMessage.message, messageOrMeta, errorOrMessage);
1021
+ }
1022
+ } else {
1023
+ this.log("error", errorOrMessage, messageOrMeta);
1024
+ }
1001
1025
  }
1002
1026
  warn(message, meta) {
1003
1027
  if (!shouldLog(this.state, "warn", this.context)) return;
@@ -1027,13 +1051,41 @@ var Logger = class _Logger {
1027
1051
  log(level, message, meta, error) {
1028
1052
  const resolved = typeof meta === "function" ? meta() : meta;
1029
1053
  const logMeta = { context: this.context, ...resolved };
1054
+ const storeContext = this.state.store.getStore();
1055
+ if (storeContext) {
1056
+ Object.assign(logMeta, storeContext);
1057
+ }
1030
1058
  if (error instanceof Error) {
1031
1059
  logMeta.errorMessage = error.message;
1032
1060
  logMeta.stack = error.stack;
1033
1061
  } else if (error !== void 0) {
1034
1062
  logMeta.error = error;
1035
1063
  }
1036
- this.state.winston.log(level, message, logMeta);
1064
+ const pinoMethod = this.getPinoMethod(level);
1065
+ pinoMethod.call(this.state.pino, logMeta, message);
1066
+ }
1067
+ getPinoMethod(level) {
1068
+ switch (level) {
1069
+ case "error":
1070
+ return this.state.pino.error.bind(this.state.pino);
1071
+ case "warn":
1072
+ return this.state.pino.warn.bind(this.state.pino);
1073
+ case "info":
1074
+ return this.state.pino.info.bind(this.state.pino);
1075
+ case "http":
1076
+ case "verbose":
1077
+ return (obj, msg) => {
1078
+ this.state.pino[level](obj, msg);
1079
+ };
1080
+ case "debug":
1081
+ return this.state.pino.debug.bind(this.state.pino);
1082
+ case "silly":
1083
+ return (obj, msg) => {
1084
+ this.state.pino.silly(obj, msg);
1085
+ };
1086
+ default:
1087
+ return this.state.pino.info.bind(this.state.pino);
1088
+ }
1037
1089
  }
1038
1090
  };
1039
1091
 
@@ -1137,6 +1189,114 @@ function getOrGenerateRequestId(headers, options = {}) {
1137
1189
  return extractRequestId(headers) ?? generateRequestId(options);
1138
1190
  }
1139
1191
 
1192
+ // src/utils/mask-secrets.ts
1193
+ var DEFAULT_SECRET_PATTERNS = [
1194
+ "password",
1195
+ "secret",
1196
+ "token",
1197
+ "apikey",
1198
+ "api_key",
1199
+ "api-key",
1200
+ "auth",
1201
+ "credential",
1202
+ "private"
1203
+ ];
1204
+ var DEFAULT_MASK = "***";
1205
+ function isSecretKey(key, patterns) {
1206
+ const lowerKey = key.toLowerCase();
1207
+ return patterns.some((pattern) => lowerKey.includes(pattern.toLowerCase()));
1208
+ }
1209
+ function maskUrlCredentials(url, mask) {
1210
+ try {
1211
+ const parsed = new URL(url);
1212
+ if (parsed.password) {
1213
+ parsed.password = mask;
1214
+ }
1215
+ if (parsed.username && parsed.password) {
1216
+ parsed.username = mask;
1217
+ }
1218
+ return parsed.toString();
1219
+ } catch {
1220
+ return url;
1221
+ }
1222
+ }
1223
+ function maskSecrets(obj, options = {}) {
1224
+ const { patterns = DEFAULT_SECRET_PATTERNS, mask = DEFAULT_MASK, deep = true } = options;
1225
+ if (obj === null || obj === void 0) {
1226
+ return obj;
1227
+ }
1228
+ if (typeof obj === "string") {
1229
+ if (obj.startsWith("http://") || obj.startsWith("https://")) {
1230
+ return maskUrlCredentials(obj, mask);
1231
+ }
1232
+ return obj;
1233
+ }
1234
+ if (Array.isArray(obj)) {
1235
+ return deep ? obj.map((item) => maskSecrets(item, options)) : obj;
1236
+ }
1237
+ if (typeof obj === "object") {
1238
+ const result = {};
1239
+ for (const sym of Object.getOwnPropertySymbols(obj)) {
1240
+ result[sym] = obj[sym];
1241
+ }
1242
+ for (const [key, value] of Object.entries(obj)) {
1243
+ if (isSecretKey(key, patterns)) {
1244
+ result[key] = mask;
1245
+ } else if (deep && typeof value === "object" && value !== null) {
1246
+ result[key] = maskSecrets(value, options);
1247
+ } else if (typeof value === "string") {
1248
+ result[key] = maskSecrets(value, options);
1249
+ } else {
1250
+ result[key] = value;
1251
+ }
1252
+ }
1253
+ return result;
1254
+ }
1255
+ return obj;
1256
+ }
1257
+ function createMasker(options = {}) {
1258
+ return (obj) => maskSecrets(obj, options);
1259
+ }
1260
+
1261
+ // src/formatters.ts
1262
+ function flattenObject(obj, prefix = "") {
1263
+ const result = {};
1264
+ for (const [key, value] of Object.entries(obj)) {
1265
+ const newKey = prefix ? `${prefix}.${key}` : key;
1266
+ if (value !== null && typeof value === "object" && !Array.isArray(value) && !(value instanceof Error)) {
1267
+ Object.assign(result, flattenObject(value, newKey));
1268
+ } else {
1269
+ result[newKey] = value;
1270
+ }
1271
+ }
1272
+ return result;
1273
+ }
1274
+ function formatLogfmtValue(value) {
1275
+ if (value === null || value === void 0) {
1276
+ return "";
1277
+ }
1278
+ if (typeof value === "string") {
1279
+ if (value.includes(" ") || value.includes('"') || value.includes("=")) {
1280
+ return `"${value.replace(/"/g, '\\"')}"`;
1281
+ }
1282
+ return value;
1283
+ }
1284
+ if (typeof value === "number" || typeof value === "boolean") {
1285
+ return String(value);
1286
+ }
1287
+ if (value instanceof Error) {
1288
+ return `"${value.message.replace(/"/g, '\\"')}"`;
1289
+ }
1290
+ if (Array.isArray(value)) {
1291
+ return `"${JSON.stringify(value).replace(/"/g, '\\"')}"`;
1292
+ }
1293
+ return `"${JSON.stringify(value).replace(/"/g, '\\"')}"`;
1294
+ }
1295
+ function formatLogfmt(data) {
1296
+ const flattened = flattenObject(data);
1297
+ return Object.entries(flattened).filter(([, value]) => value !== void 0 && value !== null).map(([key, value]) => `${key}=${formatLogfmtValue(value)}`).join(" ");
1298
+ }
1299
+
1140
1300
  exports.BaseHttpTransport = BaseHttpTransport;
1141
1301
  exports.CloudWatchConfigSchema = CloudWatchConfigSchema;
1142
1302
  exports.CloudWatchTransport = CloudWatchTransport;