@rawnodes/logger 1.10.0 → 2.0.1

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,230 @@ 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 formatValue = (key, value) => {
608
+ if (typeof value === "string" && value.includes("\n")) {
609
+ return "\n " + value.split("\n").join("\n ");
610
+ }
611
+ return JSON.stringify(value);
612
+ };
613
+ const metaStr = Object.keys(meta).length > 0 ? "\n " + Object.entries(meta).map(([k, v]) => `${k}: ${formatValue(k, v)}`).join("\n ") : "";
614
+ return `[${timestamp}] ${coloredLevel} [${context}] ${message}${metaStr}
615
+ `;
616
+ }
617
+ if (format === "logfmt") {
618
+ const parts = [`level=${levelName}`, `msg="${message}"`, `context=${context}`, `ts=${timestamp}`];
619
+ for (const [k, v] of Object.entries(meta)) {
620
+ parts.push(`${k}=${JSON.stringify(v)}`);
621
+ }
622
+ return parts.join(" ") + "\n";
623
+ }
624
+ return `[${timestamp}] ${levelName}: ${message}
625
+ `;
718
626
  }
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
- ];
627
+ function createFormattedFilterStream(format, level, rules, store, destination) {
628
+ return new stream.Transform({
629
+ transform(chunk, _encoding, callback) {
630
+ const line = chunk.toString().trim();
631
+ if (!line) {
632
+ callback();
633
+ return;
634
+ }
635
+ const log = parseLogLine(line);
636
+ if (!log) {
637
+ callback();
638
+ return;
639
+ }
640
+ if (!shouldPassTransport(log, level, rules, store)) {
641
+ callback();
642
+ return;
643
+ }
644
+ const formatted = formatLog(log, format, store);
645
+ destination.write(formatted);
646
+ callback();
647
+ }
648
+ });
649
+ }
650
+ function createStreams(config, store) {
651
+ const streams = [];
652
+ const consoleStream = createFormattedFilterStream(
653
+ config.console.format,
654
+ config.console.level,
655
+ config.console.rules,
656
+ store,
657
+ process.stdout
658
+ );
659
+ streams.push({
660
+ level: "trace",
661
+ stream: consoleStream
662
+ });
728
663
  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
- })
664
+ const fileConfig = config.file;
665
+ promises.mkdir(fileConfig.dirname, { recursive: true }).catch(() => {
666
+ });
667
+ const rotatingStream = rotatingFileStream.createStream(
668
+ (time, index) => {
669
+ const date = time instanceof Date ? time : /* @__PURE__ */ new Date();
670
+ const dateStr = date.toISOString().split("T")[0];
671
+ const base = fileConfig.filename.replace(".log", "");
672
+ return `${base}-${dateStr}${index ? `.${index}` : ""}.log`;
673
+ },
674
+ {
675
+ path: fileConfig.dirname,
676
+ size: fileConfig.maxSize || "20M",
677
+ interval: fileConfig.datePattern === "YYYY-MM-DD-HH" ? "1h" : "1d",
678
+ compress: fileConfig.zippedArchive ? "gzip" : false,
679
+ maxFiles: parseInt(fileConfig.maxFiles?.replace("d", "") || "14")
680
+ }
681
+ );
682
+ const fileStream = createFormattedFilterStream(
683
+ fileConfig.format,
684
+ fileConfig.level,
685
+ fileConfig.rules,
686
+ store,
687
+ rotatingStream
742
688
  );
689
+ streams.push({
690
+ level: "trace",
691
+ stream: fileStream
692
+ });
743
693
  }
744
694
  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);
695
+ const transport = new DiscordTransport(discordConfig);
696
+ const discordStream = createHttpTransportStream(transport, discordConfig.level, discordConfig.rules, store);
697
+ streams.push({
698
+ level: "trace",
699
+ stream: discordStream
700
+ });
751
701
  }
752
702
  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);
703
+ const transport = new TelegramTransport(telegramConfig);
704
+ const telegramStream = createHttpTransportStream(transport, telegramConfig.level, telegramConfig.rules, store);
705
+ streams.push({
706
+ level: "trace",
707
+ stream: telegramStream
708
+ });
759
709
  }
760
710
  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);
711
+ const transport = new CloudWatchTransport(cloudwatchConfig);
712
+ const cwStream = createHttpTransportStream(transport, cloudwatchConfig.level, cloudwatchConfig.rules, store);
713
+ streams.push({
714
+ level: "trace",
715
+ stream: cwStream
716
+ });
767
717
  }
768
- return result;
718
+ return pino__default.default.multistream(streams);
769
719
  }
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;
720
+ function toArray(value) {
721
+ if (!value) return [];
722
+ return Array.isArray(value) ? value : [value];
723
+ }
724
+ function createHttpTransportStream(transport, level, rules, store) {
725
+ return new stream.Writable({
726
+ write(chunk, _encoding, callback) {
727
+ const line = chunk.toString().trim();
728
+ if (!line) {
729
+ callback();
730
+ return;
731
+ }
732
+ const log = parseLogLine(line);
733
+ if (!log) {
734
+ callback();
735
+ return;
736
+ }
737
+ if (!shouldPassTransport(log, level, rules, store)) {
738
+ callback();
739
+ return;
740
+ }
741
+ const levelName = getLevelName(log.level);
742
+ const storeContext = store.getStore();
743
+ const meta = {};
744
+ for (const [key, value] of Object.entries(log)) {
745
+ if (!["level", "time", "msg", "context"].includes(key)) {
746
+ meta[key] = value;
747
+ }
748
+ }
749
+ if (storeContext) {
750
+ Object.assign(meta, storeContext);
751
+ }
752
+ transport.log({
753
+ level: levelName,
754
+ message: log.msg || "",
755
+ context: log.context,
756
+ timestamp: new Date(log.time),
757
+ ...meta
758
+ }, callback);
759
+ }
760
+ });
790
761
  }
791
762
 
792
763
  // src/state.ts
764
+ var CUSTOM_LEVELS = {
765
+ http: 25,
766
+ verbose: 25,
767
+ silly: 10
768
+ };
793
769
  function parseLevelConfig(level) {
794
770
  if (typeof level === "string") {
795
771
  assertLogLevel(level);
@@ -802,37 +778,64 @@ function parseLevelConfig(level) {
802
778
  });
803
779
  return { defaultLevel: level.default, rules };
804
780
  }
781
+ function buildIndexes(overrides) {
782
+ const contextIndex = /* @__PURE__ */ new Map();
783
+ const complexRules = [];
784
+ for (const override of overrides.values()) {
785
+ const keys = Object.keys(override.match);
786
+ if (keys.length === 1 && keys[0] === "context" && typeof override.match.context === "string") {
787
+ contextIndex.set(override.match.context, override);
788
+ } else {
789
+ complexRules.push(override);
790
+ }
791
+ }
792
+ return { contextIndex, complexRules };
793
+ }
805
794
  function createState(config, store) {
806
795
  const { defaultLevel, rules } = parseLevelConfig(config.level);
807
796
  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
797
  const levelOverrides = /* @__PURE__ */ new Map();
818
798
  for (const rule of rules) {
819
799
  const key = JSON.stringify(rule.match);
820
800
  levelOverrides.set(key, rule);
821
801
  }
802
+ const { contextIndex, complexRules } = buildIndexes(levelOverrides);
803
+ const streams = createStreams(config, loggerStore);
804
+ const options = {
805
+ level: "trace",
806
+ // Accept all, we filter in shouldLog()
807
+ customLevels: CUSTOM_LEVELS,
808
+ base: void 0
809
+ // Disable pid and hostname
810
+ };
811
+ const pinoLogger = pino__default.default(options, streams);
822
812
  return {
823
- winston: winston$1,
813
+ pino: pinoLogger,
824
814
  store: loggerStore,
825
815
  defaultLevel,
826
- levelOverrides
816
+ levelOverrides,
817
+ contextIndex,
818
+ complexRules
827
819
  };
828
820
  }
821
+ function rebuildIndexes(state) {
822
+ const { contextIndex, complexRules } = buildIndexes(state.levelOverrides);
823
+ state.contextIndex = contextIndex;
824
+ state.complexRules.length = 0;
825
+ state.complexRules.push(...complexRules);
826
+ }
829
827
  function shouldLog(state, level, context) {
830
828
  const effectiveLevel = getEffectiveLevel(state, context);
829
+ if (effectiveLevel === "off") return false;
831
830
  return LOG_LEVELS[level] <= LOG_LEVELS[effectiveLevel];
832
831
  }
833
832
  function getEffectiveLevel(state, loggerContext) {
833
+ if (loggerContext) {
834
+ const indexed = state.contextIndex.get(loggerContext);
835
+ if (indexed) return indexed.level;
836
+ }
834
837
  const storeContext = state.store.getStore();
835
- for (const { match, level } of state.levelOverrides.values()) {
838
+ for (const { match, level } of state.complexRules) {
836
839
  if (matchesContext(storeContext, loggerContext, match)) {
837
840
  return level;
838
841
  }
@@ -890,6 +893,7 @@ var DiscordConfigSchema = zod.z.object({
890
893
  maxRetries: zod.z.number().int().nonnegative().optional(),
891
894
  retryDelay: zod.z.number().int().positive().optional(),
892
895
  webhookUrl: zod.z.string().url("webhookUrl must be a valid URL"),
896
+ format: zod.z.enum(["embed", "markdown"]).optional(),
893
897
  username: zod.z.string().optional(),
894
898
  avatarUrl: zod.z.string().url().optional(),
895
899
  embedColors: zod.z.record(zod.z.string(), zod.z.number().int()).optional(),
@@ -955,6 +959,7 @@ var Logger = class _Logger {
955
959
  this.state = state;
956
960
  this.context = context;
957
961
  }
962
+ profileTimers = /* @__PURE__ */ new Map();
958
963
  static create(config, store) {
959
964
  const validatedConfig = validateConfig(config);
960
965
  const state = createState(validatedConfig, store);
@@ -971,6 +976,7 @@ var Logger = class _Logger {
971
976
  assertLogLevel(level);
972
977
  const key = JSON.stringify(match);
973
978
  this.state.levelOverrides.set(key, { match, level });
979
+ rebuildIndexes(this.state);
974
980
  }
975
981
  removeLevelOverride(match) {
976
982
  const key = JSON.stringify(match);
@@ -978,26 +984,50 @@ var Logger = class _Logger {
978
984
  if (override?.readonly) {
979
985
  return false;
980
986
  }
981
- return this.state.levelOverrides.delete(key);
987
+ const deleted = this.state.levelOverrides.delete(key);
988
+ if (deleted) {
989
+ rebuildIndexes(this.state);
990
+ }
991
+ return deleted;
982
992
  }
983
993
  clearLevelOverrides() {
994
+ let changed = false;
984
995
  for (const [key, override] of this.state.levelOverrides) {
985
996
  if (!override.readonly) {
986
997
  this.state.levelOverrides.delete(key);
998
+ changed = true;
987
999
  }
988
1000
  }
1001
+ if (changed) {
1002
+ rebuildIndexes(this.state);
1003
+ }
989
1004
  }
990
1005
  getLevelOverrides() {
991
1006
  return Array.from(this.state.levelOverrides.values());
992
1007
  }
993
1008
  // Profiling
994
1009
  profile(id, meta) {
995
- this.state.winston.profile(id, meta);
1010
+ const existing = this.profileTimers.get(id);
1011
+ if (existing) {
1012
+ const duration = Date.now() - existing;
1013
+ this.profileTimers.delete(id);
1014
+ this.info(`${id} completed`, { ...meta, durationMs: duration });
1015
+ } else {
1016
+ this.profileTimers.set(id, Date.now());
1017
+ }
996
1018
  }
997
1019
  // Logging methods
998
- error(message, error, meta) {
1020
+ error(errorOrMessage, messageOrMeta, meta) {
999
1021
  if (!shouldLog(this.state, "error", this.context)) return;
1000
- this.log("error", message, meta, error);
1022
+ if (errorOrMessage instanceof Error) {
1023
+ if (typeof messageOrMeta === "string") {
1024
+ this.log("error", messageOrMeta, meta, errorOrMessage);
1025
+ } else {
1026
+ this.log("error", errorOrMessage.message, messageOrMeta, errorOrMessage);
1027
+ }
1028
+ } else {
1029
+ this.log("error", errorOrMessage, messageOrMeta);
1030
+ }
1001
1031
  }
1002
1032
  warn(message, meta) {
1003
1033
  if (!shouldLog(this.state, "warn", this.context)) return;
@@ -1027,13 +1057,41 @@ var Logger = class _Logger {
1027
1057
  log(level, message, meta, error) {
1028
1058
  const resolved = typeof meta === "function" ? meta() : meta;
1029
1059
  const logMeta = { context: this.context, ...resolved };
1060
+ const storeContext = this.state.store.getStore();
1061
+ if (storeContext) {
1062
+ Object.assign(logMeta, storeContext);
1063
+ }
1030
1064
  if (error instanceof Error) {
1031
1065
  logMeta.errorMessage = error.message;
1032
1066
  logMeta.stack = error.stack;
1033
1067
  } else if (error !== void 0) {
1034
1068
  logMeta.error = error;
1035
1069
  }
1036
- this.state.winston.log(level, message, logMeta);
1070
+ const pinoMethod = this.getPinoMethod(level);
1071
+ pinoMethod.call(this.state.pino, logMeta, message);
1072
+ }
1073
+ getPinoMethod(level) {
1074
+ switch (level) {
1075
+ case "error":
1076
+ return this.state.pino.error.bind(this.state.pino);
1077
+ case "warn":
1078
+ return this.state.pino.warn.bind(this.state.pino);
1079
+ case "info":
1080
+ return this.state.pino.info.bind(this.state.pino);
1081
+ case "http":
1082
+ case "verbose":
1083
+ return (obj, msg) => {
1084
+ this.state.pino[level](obj, msg);
1085
+ };
1086
+ case "debug":
1087
+ return this.state.pino.debug.bind(this.state.pino);
1088
+ case "silly":
1089
+ return (obj, msg) => {
1090
+ this.state.pino.silly(obj, msg);
1091
+ };
1092
+ default:
1093
+ return this.state.pino.info.bind(this.state.pino);
1094
+ }
1037
1095
  }
1038
1096
  };
1039
1097
 
@@ -1137,6 +1195,114 @@ function getOrGenerateRequestId(headers, options = {}) {
1137
1195
  return extractRequestId(headers) ?? generateRequestId(options);
1138
1196
  }
1139
1197
 
1198
+ // src/utils/mask-secrets.ts
1199
+ var DEFAULT_SECRET_PATTERNS = [
1200
+ "password",
1201
+ "secret",
1202
+ "token",
1203
+ "apikey",
1204
+ "api_key",
1205
+ "api-key",
1206
+ "auth",
1207
+ "credential",
1208
+ "private"
1209
+ ];
1210
+ var DEFAULT_MASK = "***";
1211
+ function isSecretKey(key, patterns) {
1212
+ const lowerKey = key.toLowerCase();
1213
+ return patterns.some((pattern) => lowerKey.includes(pattern.toLowerCase()));
1214
+ }
1215
+ function maskUrlCredentials(url, mask) {
1216
+ try {
1217
+ const parsed = new URL(url);
1218
+ if (parsed.password) {
1219
+ parsed.password = mask;
1220
+ }
1221
+ if (parsed.username && parsed.password) {
1222
+ parsed.username = mask;
1223
+ }
1224
+ return parsed.toString();
1225
+ } catch {
1226
+ return url;
1227
+ }
1228
+ }
1229
+ function maskSecrets(obj, options = {}) {
1230
+ const { patterns = DEFAULT_SECRET_PATTERNS, mask = DEFAULT_MASK, deep = true } = options;
1231
+ if (obj === null || obj === void 0) {
1232
+ return obj;
1233
+ }
1234
+ if (typeof obj === "string") {
1235
+ if (obj.startsWith("http://") || obj.startsWith("https://")) {
1236
+ return maskUrlCredentials(obj, mask);
1237
+ }
1238
+ return obj;
1239
+ }
1240
+ if (Array.isArray(obj)) {
1241
+ return deep ? obj.map((item) => maskSecrets(item, options)) : obj;
1242
+ }
1243
+ if (typeof obj === "object") {
1244
+ const result = {};
1245
+ for (const sym of Object.getOwnPropertySymbols(obj)) {
1246
+ result[sym] = obj[sym];
1247
+ }
1248
+ for (const [key, value] of Object.entries(obj)) {
1249
+ if (isSecretKey(key, patterns)) {
1250
+ result[key] = mask;
1251
+ } else if (deep && typeof value === "object" && value !== null) {
1252
+ result[key] = maskSecrets(value, options);
1253
+ } else if (typeof value === "string") {
1254
+ result[key] = maskSecrets(value, options);
1255
+ } else {
1256
+ result[key] = value;
1257
+ }
1258
+ }
1259
+ return result;
1260
+ }
1261
+ return obj;
1262
+ }
1263
+ function createMasker(options = {}) {
1264
+ return (obj) => maskSecrets(obj, options);
1265
+ }
1266
+
1267
+ // src/formatters.ts
1268
+ function flattenObject(obj, prefix = "") {
1269
+ const result = {};
1270
+ for (const [key, value] of Object.entries(obj)) {
1271
+ const newKey = prefix ? `${prefix}.${key}` : key;
1272
+ if (value !== null && typeof value === "object" && !Array.isArray(value) && !(value instanceof Error)) {
1273
+ Object.assign(result, flattenObject(value, newKey));
1274
+ } else {
1275
+ result[newKey] = value;
1276
+ }
1277
+ }
1278
+ return result;
1279
+ }
1280
+ function formatLogfmtValue(value) {
1281
+ if (value === null || value === void 0) {
1282
+ return "";
1283
+ }
1284
+ if (typeof value === "string") {
1285
+ if (value.includes(" ") || value.includes('"') || value.includes("=")) {
1286
+ return `"${value.replace(/"/g, '\\"')}"`;
1287
+ }
1288
+ return value;
1289
+ }
1290
+ if (typeof value === "number" || typeof value === "boolean") {
1291
+ return String(value);
1292
+ }
1293
+ if (value instanceof Error) {
1294
+ return `"${value.message.replace(/"/g, '\\"')}"`;
1295
+ }
1296
+ if (Array.isArray(value)) {
1297
+ return `"${JSON.stringify(value).replace(/"/g, '\\"')}"`;
1298
+ }
1299
+ return `"${JSON.stringify(value).replace(/"/g, '\\"')}"`;
1300
+ }
1301
+ function formatLogfmt(data) {
1302
+ const flattened = flattenObject(data);
1303
+ return Object.entries(flattened).filter(([, value]) => value !== void 0 && value !== null).map(([key, value]) => `${key}=${formatLogfmtValue(value)}`).join(" ");
1304
+ }
1305
+
1140
1306
  exports.BaseHttpTransport = BaseHttpTransport;
1141
1307
  exports.CloudWatchConfigSchema = CloudWatchConfigSchema;
1142
1308
  exports.CloudWatchTransport = CloudWatchTransport;