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