@rawnodes/logger 2.11.0 → 2.12.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/README.md CHANGED
@@ -316,6 +316,8 @@ telegram: {
316
316
  }
317
317
  ```
318
318
 
319
+ File open, write, and rotation errors disable only the affected file destination. Console and remote transports continue logging. By default, a single diagnostic with the error code is written to stderr. Set `file.onError` to handle the error yourself; the callback receives the original `Error`.
320
+
319
321
  ### CloudWatch
320
322
 
321
323
  Send logs to AWS CloudWatch Logs:
@@ -871,6 +873,7 @@ interface LoggerConfig {
871
873
  maxFiles?: string;
872
874
  maxSize?: string;
873
875
  zippedArchive?: boolean;
876
+ onError?: (error: Error) => void;
874
877
  };
875
878
  }
876
879
 
package/dist/index.d.mts CHANGED
@@ -1,5 +1,5 @@
1
- import { L as LoggerContext, a as LoggerConfig, b as LevelOverrideMatch, c as LogLevel, d as LevelOverride, M as Meta, D as DiscordConfig, T as TelegramConfig, C as CloudWatchConfig, e as MaskReplacer, Z as ZohoCliqConfig, f as CallerConfig, g as CallerInfo, h as LogFormat, i as LevelRule, j as ConsoleConfig, F as FileConfig, H as HttpTransportBaseConfig, k as LevelConfigObject, l as LevelConfig, R as RelayConfig, A as AutoShutdownConfig, m as LogStreamPattern, n as LogStreamPatternConfig, o as LogStreamTemplateConfig, p as LogStreamName } from './types-lfJLhC8J.mjs';
2
- export { q as LOG_LEVELS, x as MaskSecretsOptions, t as ZohoCliqBotConfig, s as assertLogLevel, v as createMasker, r as isValidLogLevel, w as maskReplacer, u as maskSecrets } from './types-lfJLhC8J.mjs';
1
+ import { L as LoggerContext, a as LoggerConfig, b as LevelOverrideMatch, c as LogLevel, d as LevelOverride, M as Meta, D as DiscordConfig, T as TelegramConfig, C as CloudWatchConfig, e as MaskReplacer, Z as ZohoCliqConfig, f as CallerConfig, g as CallerInfo, h as LogFormat, i as LevelRule, j as ConsoleConfig, F as FileConfig, H as HttpTransportBaseConfig, k as LevelConfigObject, l as LevelConfig, R as RelayConfig, A as AutoShutdownConfig, m as LogStreamPattern, n as LogStreamPatternConfig, o as LogStreamTemplateConfig, p as LogStreamName } from './types-CD5hHXR4.mjs';
2
+ export { q as LOG_LEVELS, x as MaskSecretsOptions, t as ZohoCliqBotConfig, s as assertLogLevel, v as createMasker, r as isValidLogLevel, w as maskReplacer, u as maskSecrets } from './types-CD5hHXR4.mjs';
3
3
  import { z } from 'zod';
4
4
 
5
5
  declare class LoggerStore<TContext extends LoggerContext = LoggerContext> {
@@ -218,20 +218,24 @@ declare class TelegramTransport extends BaseHttpTransport {
218
218
  declare class CloudWatchTransport extends BaseHttpTransport {
219
219
  private config;
220
220
  private client;
221
- private sequenceToken;
222
221
  private initialized;
223
222
  private initPromise;
223
+ private initFailures;
224
+ private nextInitAttempt;
224
225
  private resolvedLogStreamName;
225
226
  private maskReplacer;
227
+ private static readonly INIT_BACKOFF_BASE_MS;
228
+ private static readonly INIT_BACKOFF_MAX_MS;
226
229
  constructor(config: CloudWatchConfig, configHostname?: string, maskReplacerFn?: MaskReplacer);
227
230
  protected sendBatch(messages: BufferedMessage[]): Promise<void>;
231
+ private putLogEvents;
228
232
  private ensureInitialized;
229
233
  private initialize;
230
234
  private createLogGroupIfNotExists;
231
235
  private createLogStreamIfNotExists;
232
- private fetchSequenceToken;
233
236
  private isResourceAlreadyExistsError;
234
- private isInvalidSequenceTokenError;
237
+ private isResourceNotFoundError;
238
+ private isAwsErrorNamed;
235
239
  }
236
240
 
237
241
  declare class ZohoCliqTransport extends BaseHttpTransport {
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { L as LoggerContext, a as LoggerConfig, b as LevelOverrideMatch, c as LogLevel, d as LevelOverride, M as Meta, D as DiscordConfig, T as TelegramConfig, C as CloudWatchConfig, e as MaskReplacer, Z as ZohoCliqConfig, f as CallerConfig, g as CallerInfo, h as LogFormat, i as LevelRule, j as ConsoleConfig, F as FileConfig, H as HttpTransportBaseConfig, k as LevelConfigObject, l as LevelConfig, R as RelayConfig, A as AutoShutdownConfig, m as LogStreamPattern, n as LogStreamPatternConfig, o as LogStreamTemplateConfig, p as LogStreamName } from './types-lfJLhC8J.js';
2
- export { q as LOG_LEVELS, x as MaskSecretsOptions, t as ZohoCliqBotConfig, s as assertLogLevel, v as createMasker, r as isValidLogLevel, w as maskReplacer, u as maskSecrets } from './types-lfJLhC8J.js';
1
+ import { L as LoggerContext, a as LoggerConfig, b as LevelOverrideMatch, c as LogLevel, d as LevelOverride, M as Meta, D as DiscordConfig, T as TelegramConfig, C as CloudWatchConfig, e as MaskReplacer, Z as ZohoCliqConfig, f as CallerConfig, g as CallerInfo, h as LogFormat, i as LevelRule, j as ConsoleConfig, F as FileConfig, H as HttpTransportBaseConfig, k as LevelConfigObject, l as LevelConfig, R as RelayConfig, A as AutoShutdownConfig, m as LogStreamPattern, n as LogStreamPatternConfig, o as LogStreamTemplateConfig, p as LogStreamName } from './types-CD5hHXR4.js';
2
+ export { q as LOG_LEVELS, x as MaskSecretsOptions, t as ZohoCliqBotConfig, s as assertLogLevel, v as createMasker, r as isValidLogLevel, w as maskReplacer, u as maskSecrets } from './types-CD5hHXR4.js';
3
3
  import { z } from 'zod';
4
4
 
5
5
  declare class LoggerStore<TContext extends LoggerContext = LoggerContext> {
@@ -218,20 +218,24 @@ declare class TelegramTransport extends BaseHttpTransport {
218
218
  declare class CloudWatchTransport extends BaseHttpTransport {
219
219
  private config;
220
220
  private client;
221
- private sequenceToken;
222
221
  private initialized;
223
222
  private initPromise;
223
+ private initFailures;
224
+ private nextInitAttempt;
224
225
  private resolvedLogStreamName;
225
226
  private maskReplacer;
227
+ private static readonly INIT_BACKOFF_BASE_MS;
228
+ private static readonly INIT_BACKOFF_MAX_MS;
226
229
  constructor(config: CloudWatchConfig, configHostname?: string, maskReplacerFn?: MaskReplacer);
227
230
  protected sendBatch(messages: BufferedMessage[]): Promise<void>;
231
+ private putLogEvents;
228
232
  private ensureInitialized;
229
233
  private initialize;
230
234
  private createLogGroupIfNotExists;
231
235
  private createLogStreamIfNotExists;
232
- private fetchSequenceToken;
233
236
  private isResourceAlreadyExistsError;
234
- private isInvalidSequenceTokenError;
237
+ private isResourceNotFoundError;
238
+ private isAwsErrorNamed;
235
239
  }
236
240
 
237
241
  declare class ZohoCliqTransport extends BaseHttpTransport {
package/dist/index.js CHANGED
@@ -4,7 +4,6 @@ var os = require('os');
4
4
  var pino = require('pino');
5
5
  var async_hooks = require('async_hooks');
6
6
  var stream = require('stream');
7
- var promises = require('fs/promises');
8
7
  var rotatingFileStream = require('rotating-file-stream');
9
8
  var clientCloudwatchLogs = require('@aws-sdk/client-cloudwatch-logs');
10
9
  var crypto = require('crypto');
@@ -45,6 +44,89 @@ function assertLogLevel(level) {
45
44
  throw new Error(`Invalid log level: "${level}". Valid levels: ${Object.keys(LOG_LEVELS).join(", ")}`);
46
45
  }
47
46
  }
47
+ function reportFileFailure(error) {
48
+ const code = "code" in error && typeof error.code === "string" ? error.code : error.name;
49
+ process.stderr.write(
50
+ `[Logger] File logging stopped (${code}); other transports remain active.
51
+ `
52
+ );
53
+ }
54
+ function createFileDestination(config) {
55
+ let failed = false;
56
+ let rotatingStream;
57
+ const fail = (error) => {
58
+ if (failed) return;
59
+ failed = true;
60
+ if (config.onError) {
61
+ try {
62
+ config.onError(error);
63
+ } catch {
64
+ reportFileFailure(error);
65
+ }
66
+ } else {
67
+ reportFileFailure(error);
68
+ }
69
+ };
70
+ try {
71
+ rotatingStream = rotatingFileStream.createStream(
72
+ (time, index) => {
73
+ const date = time instanceof Date ? time : /* @__PURE__ */ new Date();
74
+ const dateStr = date.toISOString().split("T")[0];
75
+ const base = config.filename.replace(".log", "");
76
+ return `${base}-${dateStr}${index ? `.${index}` : ""}.log`;
77
+ },
78
+ {
79
+ path: config.dirname,
80
+ size: (config.maxSize || "20M").replace(
81
+ /([kmg])$/i,
82
+ (unit) => unit.toUpperCase()
83
+ ),
84
+ interval: config.interval || (config.datePattern === "YYYY-MM-DD-HH" ? "1h" : "1d"),
85
+ compress: config.zippedArchive ? "gzip" : false,
86
+ maxFiles: parseInt(config.maxFiles?.replace("d", "") || "14")
87
+ }
88
+ );
89
+ rotatingStream.on("error", fail);
90
+ } catch (error) {
91
+ fail(error instanceof Error ? error : new Error(String(error)));
92
+ }
93
+ return new stream.Writable({
94
+ write(chunk, encoding, callback) {
95
+ if (failed || !rotatingStream) {
96
+ callback();
97
+ return;
98
+ }
99
+ try {
100
+ rotatingStream.write(chunk, encoding, (error) => {
101
+ if (error) fail(error);
102
+ callback();
103
+ });
104
+ } catch (error) {
105
+ fail(error instanceof Error ? error : new Error(String(error)));
106
+ callback();
107
+ }
108
+ },
109
+ final(callback) {
110
+ if (!rotatingStream || rotatingStream.destroyed) {
111
+ callback();
112
+ return;
113
+ }
114
+ try {
115
+ rotatingStream.end((error) => {
116
+ if (error) fail(error);
117
+ callback();
118
+ });
119
+ } catch (error) {
120
+ fail(error instanceof Error ? error : new Error(String(error)));
121
+ callback();
122
+ }
123
+ },
124
+ destroy(_error, callback) {
125
+ rotatingStream?.destroy();
126
+ callback();
127
+ }
128
+ });
129
+ }
48
130
 
49
131
  // src/transports/buffer.ts
50
132
  var MessageBuffer = class {
@@ -651,14 +733,21 @@ function resolveLogStreamName(config, configHostname) {
651
733
  }
652
734
  return hostname;
653
735
  }
654
- var CloudWatchTransport = class extends BaseHttpTransport {
736
+ var CloudWatchTransport = class _CloudWatchTransport extends BaseHttpTransport {
655
737
  config;
656
738
  client;
657
- sequenceToken;
658
739
  initialized = false;
659
740
  initPromise = null;
741
+ initFailures = 0;
742
+ nextInitAttempt = 0;
660
743
  resolvedLogStreamName;
661
744
  maskReplacer;
745
+ // Backoff bounds for repeated init failures (e.g. IAM not yet propagated,
746
+ // throttling). Without this, a sustained outage makes every flush re-run
747
+ // initialize() back-to-back and hammer the CloudWatch control-plane APIs,
748
+ // which only deepens the throttling.
749
+ static INIT_BACKOFF_BASE_MS = 1e3;
750
+ static INIT_BACKOFF_MAX_MS = 3e4;
662
751
  constructor(config, configHostname, maskReplacerFn) {
663
752
  super({
664
753
  batchSize: config.batchSize ?? 100,
@@ -694,36 +783,46 @@ var CloudWatchTransport = class extends BaseHttpTransport {
694
783
  }, replacer)
695
784
  }));
696
785
  logEvents.sort((a, b) => (a.timestamp ?? 0) - (b.timestamp ?? 0));
697
- const command = new clientCloudwatchLogs.PutLogEventsCommand({
698
- logGroupName: this.config.logGroupName,
699
- logStreamName: this.resolvedLogStreamName,
700
- logEvents,
701
- sequenceToken: this.sequenceToken
702
- });
703
786
  try {
704
- const response = await this.client.send(command);
705
- this.sequenceToken = response.nextSequenceToken;
787
+ await this.putLogEvents(logEvents);
706
788
  } catch (error) {
707
- if (this.isInvalidSequenceTokenError(error)) {
708
- await this.fetchSequenceToken();
709
- const retryCommand = new clientCloudwatchLogs.PutLogEventsCommand({
710
- logGroupName: this.config.logGroupName,
711
- logStreamName: this.resolvedLogStreamName,
712
- logEvents,
713
- sequenceToken: this.sequenceToken
714
- });
715
- const response = await this.client.send(retryCommand);
716
- this.sequenceToken = response.nextSequenceToken;
789
+ if (this.isResourceNotFoundError(error)) {
790
+ this.initialized = false;
791
+ this.initPromise = null;
792
+ await this.ensureInitialized();
793
+ await this.putLogEvents(logEvents);
717
794
  } else {
718
795
  throw error;
719
796
  }
720
797
  }
721
798
  }
799
+ // PutLogEvents no longer requires a sequence token: since 2023 CloudWatch
800
+ // Logs ignores the `sequenceToken` field for sequential writes, so we omit it
801
+ // entirely. This also removes the need for DescribeLogStreams (and the
802
+ // logs:DescribeLogStreams IAM permission) on the hot path.
803
+ async putLogEvents(logEvents) {
804
+ await this.client.send(
805
+ new clientCloudwatchLogs.PutLogEventsCommand({
806
+ logGroupName: this.config.logGroupName,
807
+ logStreamName: this.resolvedLogStreamName,
808
+ logEvents
809
+ })
810
+ );
811
+ }
722
812
  async ensureInitialized() {
723
813
  if (this.initialized) return;
814
+ if (!this.initPromise && Date.now() < this.nextInitAttempt) {
815
+ throw new Error("CloudWatch transport initialization is backing off");
816
+ }
724
817
  if (!this.initPromise) {
725
818
  this.initPromise = this.initialize().catch((err) => {
726
819
  this.initPromise = null;
820
+ this.initFailures += 1;
821
+ const delay = Math.min(
822
+ _CloudWatchTransport.INIT_BACKOFF_MAX_MS,
823
+ _CloudWatchTransport.INIT_BACKOFF_BASE_MS * 2 ** (this.initFailures - 1)
824
+ );
825
+ this.nextInitAttempt = Date.now() + delay;
727
826
  throw err;
728
827
  });
729
828
  }
@@ -736,8 +835,9 @@ var CloudWatchTransport = class extends BaseHttpTransport {
736
835
  if (this.config.createLogStream !== false) {
737
836
  await this.createLogStreamIfNotExists();
738
837
  }
739
- await this.fetchSequenceToken();
740
838
  this.initialized = true;
839
+ this.initFailures = 0;
840
+ this.nextInitAttempt = 0;
741
841
  }
742
842
  async createLogGroupIfNotExists() {
743
843
  try {
@@ -766,22 +866,16 @@ var CloudWatchTransport = class extends BaseHttpTransport {
766
866
  }
767
867
  }
768
868
  }
769
- async fetchSequenceToken() {
770
- const response = await this.client.send(
771
- new clientCloudwatchLogs.DescribeLogStreamsCommand({
772
- logGroupName: this.config.logGroupName,
773
- logStreamNamePrefix: this.resolvedLogStreamName,
774
- limit: 1
775
- })
776
- );
777
- const stream = response.logStreams?.find((s) => s.logStreamName === this.resolvedLogStreamName);
778
- this.sequenceToken = stream?.uploadSequenceToken;
779
- }
780
869
  isResourceAlreadyExistsError(error) {
781
- return typeof error === "object" && error !== null && "name" in error && error.name === "ResourceAlreadyExistsException";
870
+ return this.isAwsErrorNamed(error, "ResourceAlreadyExistsException");
871
+ }
872
+ isResourceNotFoundError(error) {
873
+ return this.isAwsErrorNamed(error, "ResourceNotFoundException");
782
874
  }
783
- isInvalidSequenceTokenError(error) {
784
- return typeof error === "object" && error !== null && "name" in error && error.name === "InvalidSequenceTokenException";
875
+ isAwsErrorNamed(error, name) {
876
+ if (typeof error !== "object" || error === null) return false;
877
+ if ("name" in error && error.name === name) return true;
878
+ return "__type" in error && typeof error.__type === "string" && error.__type.split("#").at(-1) === name;
785
879
  }
786
880
  };
787
881
 
@@ -943,26 +1037,7 @@ function createStreams(config, state) {
943
1037
  });
944
1038
  if (config.file) {
945
1039
  const fileConfig = config.file;
946
- promises.mkdir(fileConfig.dirname, { recursive: true }).catch(() => {
947
- });
948
- const normalizeSize = (size) => {
949
- return size.replace(/([kmg])$/i, (match) => match.toUpperCase());
950
- };
951
- const rotatingStream = rotatingFileStream.createStream(
952
- (time, index) => {
953
- const date = time instanceof Date ? time : /* @__PURE__ */ new Date();
954
- const dateStr = date.toISOString().split("T")[0];
955
- const base = fileConfig.filename.replace(".log", "");
956
- return `${base}-${dateStr}${index ? `.${index}` : ""}.log`;
957
- },
958
- {
959
- path: fileConfig.dirname,
960
- size: normalizeSize(fileConfig.maxSize || "20M"),
961
- interval: fileConfig.interval || (fileConfig.datePattern === "YYYY-MM-DD-HH" ? "1h" : "1d"),
962
- compress: fileConfig.zippedArchive ? "gzip" : false,
963
- maxFiles: parseInt(fileConfig.maxFiles?.replace("d", "") || "14")
964
- }
965
- );
1040
+ const rotatingStream = createFileDestination(fileConfig);
966
1041
  const fileStream = createFormattedFilterStream(
967
1042
  fileConfig.format,
968
1043
  fileConfig.level,
@@ -1354,7 +1429,8 @@ var FileConfigSchema = zod.z.object({
1354
1429
  interval: zod.z.string().regex(/^\d+[Mdhms]$/).optional(),
1355
1430
  zippedArchive: zod.z.boolean().optional(),
1356
1431
  maxSize: zod.z.string().optional(),
1357
- maxFiles: zod.z.string().optional()
1432
+ maxFiles: zod.z.string().optional(),
1433
+ onError: zod.z.function().optional()
1358
1434
  });
1359
1435
  var HttpTransportBaseConfigSchema = zod.z.object({
1360
1436
  level: LogLevelSchema.optional(),