@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.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,230 @@ 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 formatValue = (key, value) => {
602
+ if (typeof value === "string" && value.includes("\n")) {
603
+ return "\n " + value.split("\n").join("\n ");
604
+ }
605
+ return JSON.stringify(value);
606
+ };
607
+ const metaStr = Object.keys(meta).length > 0 ? "\n " + Object.entries(meta).map(([k, v]) => `${k}: ${formatValue(k, 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
+ `;
711
620
  }
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
- ];
621
+ function createFormattedFilterStream(format, level, rules, store, destination) {
622
+ return new 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
+ });
721
657
  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
- })
658
+ const fileConfig = config.file;
659
+ mkdir(fileConfig.dirname, { recursive: true }).catch(() => {
660
+ });
661
+ const rotatingStream = 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
+ }
675
+ );
676
+ const fileStream = createFormattedFilterStream(
677
+ fileConfig.format,
678
+ fileConfig.level,
679
+ fileConfig.rules,
680
+ store,
681
+ rotatingStream
735
682
  );
683
+ streams.push({
684
+ level: "trace",
685
+ stream: fileStream
686
+ });
736
687
  }
737
688
  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);
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
+ });
744
695
  }
745
696
  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);
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
+ });
752
703
  }
753
704
  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);
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
+ });
760
711
  }
761
- return result;
712
+ return pino.multistream(streams);
762
713
  }
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;
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 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
+ });
783
755
  }
784
756
 
785
757
  // src/state.ts
758
+ var CUSTOM_LEVELS = {
759
+ http: 25,
760
+ verbose: 25,
761
+ silly: 10
762
+ };
786
763
  function parseLevelConfig(level) {
787
764
  if (typeof level === "string") {
788
765
  assertLogLevel(level);
@@ -795,37 +772,64 @@ function parseLevelConfig(level) {
795
772
  });
796
773
  return { defaultLevel: level.default, rules };
797
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
+ }
798
788
  function createState(config, store) {
799
789
  const { defaultLevel, rules } = parseLevelConfig(config.level);
800
790
  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
791
  const levelOverrides = /* @__PURE__ */ new Map();
811
792
  for (const rule of rules) {
812
793
  const key = JSON.stringify(rule.match);
813
794
  levelOverrides.set(key, rule);
814
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(options, streams);
815
806
  return {
816
- winston,
807
+ pino: pinoLogger,
817
808
  store: loggerStore,
818
809
  defaultLevel,
819
- levelOverrides
810
+ levelOverrides,
811
+ contextIndex,
812
+ complexRules
820
813
  };
821
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
+ }
822
821
  function shouldLog(state, level, context) {
823
822
  const effectiveLevel = getEffectiveLevel(state, context);
823
+ if (effectiveLevel === "off") return false;
824
824
  return LOG_LEVELS[level] <= LOG_LEVELS[effectiveLevel];
825
825
  }
826
826
  function getEffectiveLevel(state, loggerContext) {
827
+ if (loggerContext) {
828
+ const indexed = state.contextIndex.get(loggerContext);
829
+ if (indexed) return indexed.level;
830
+ }
827
831
  const storeContext = state.store.getStore();
828
- for (const { match, level } of state.levelOverrides.values()) {
832
+ for (const { match, level } of state.complexRules) {
829
833
  if (matchesContext(storeContext, loggerContext, match)) {
830
834
  return level;
831
835
  }
@@ -883,6 +887,7 @@ var DiscordConfigSchema = z.object({
883
887
  maxRetries: z.number().int().nonnegative().optional(),
884
888
  retryDelay: z.number().int().positive().optional(),
885
889
  webhookUrl: z.string().url("webhookUrl must be a valid URL"),
890
+ format: z.enum(["embed", "markdown"]).optional(),
886
891
  username: z.string().optional(),
887
892
  avatarUrl: z.string().url().optional(),
888
893
  embedColors: z.record(z.string(), z.number().int()).optional(),
@@ -948,6 +953,7 @@ var Logger = class _Logger {
948
953
  this.state = state;
949
954
  this.context = context;
950
955
  }
956
+ profileTimers = /* @__PURE__ */ new Map();
951
957
  static create(config, store) {
952
958
  const validatedConfig = validateConfig(config);
953
959
  const state = createState(validatedConfig, store);
@@ -964,6 +970,7 @@ var Logger = class _Logger {
964
970
  assertLogLevel(level);
965
971
  const key = JSON.stringify(match);
966
972
  this.state.levelOverrides.set(key, { match, level });
973
+ rebuildIndexes(this.state);
967
974
  }
968
975
  removeLevelOverride(match) {
969
976
  const key = JSON.stringify(match);
@@ -971,26 +978,50 @@ var Logger = class _Logger {
971
978
  if (override?.readonly) {
972
979
  return false;
973
980
  }
974
- 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;
975
986
  }
976
987
  clearLevelOverrides() {
988
+ let changed = false;
977
989
  for (const [key, override] of this.state.levelOverrides) {
978
990
  if (!override.readonly) {
979
991
  this.state.levelOverrides.delete(key);
992
+ changed = true;
980
993
  }
981
994
  }
995
+ if (changed) {
996
+ rebuildIndexes(this.state);
997
+ }
982
998
  }
983
999
  getLevelOverrides() {
984
1000
  return Array.from(this.state.levelOverrides.values());
985
1001
  }
986
1002
  // Profiling
987
1003
  profile(id, meta) {
988
- 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
+ }
989
1012
  }
990
1013
  // Logging methods
991
- error(message, error, meta) {
1014
+ error(errorOrMessage, messageOrMeta, meta) {
992
1015
  if (!shouldLog(this.state, "error", this.context)) return;
993
- 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
+ }
994
1025
  }
995
1026
  warn(message, meta) {
996
1027
  if (!shouldLog(this.state, "warn", this.context)) return;
@@ -1020,13 +1051,41 @@ var Logger = class _Logger {
1020
1051
  log(level, message, meta, error) {
1021
1052
  const resolved = typeof meta === "function" ? meta() : meta;
1022
1053
  const logMeta = { context: this.context, ...resolved };
1054
+ const storeContext = this.state.store.getStore();
1055
+ if (storeContext) {
1056
+ Object.assign(logMeta, storeContext);
1057
+ }
1023
1058
  if (error instanceof Error) {
1024
1059
  logMeta.errorMessage = error.message;
1025
1060
  logMeta.stack = error.stack;
1026
1061
  } else if (error !== void 0) {
1027
1062
  logMeta.error = error;
1028
1063
  }
1029
- 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
+ }
1030
1089
  }
1031
1090
  };
1032
1091
 
@@ -1130,6 +1189,114 @@ function getOrGenerateRequestId(headers, options = {}) {
1130
1189
  return extractRequestId(headers) ?? generateRequestId(options);
1131
1190
  }
1132
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
+
1133
1300
  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
1301
  //# sourceMappingURL=index.mjs.map
1135
1302
  //# sourceMappingURL=index.mjs.map