@rawnodes/logger 2.10.0 → 2.12.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
@@ -4,7 +4,7 @@ import { AsyncLocalStorage } from 'async_hooks';
4
4
  import { Transform, Writable } from 'stream';
5
5
  import { mkdir } from 'fs/promises';
6
6
  import { createStream } from 'rotating-file-stream';
7
- import { CloudWatchLogsClient, PutLogEventsCommand, CreateLogGroupCommand, CreateLogStreamCommand, DescribeLogStreamsCommand } from '@aws-sdk/client-cloudwatch-logs';
7
+ import { CloudWatchLogsClient, PutLogEventsCommand, CreateLogGroupCommand, CreateLogStreamCommand } from '@aws-sdk/client-cloudwatch-logs';
8
8
  import { randomUUID } from 'crypto';
9
9
  import { z } from 'zod';
10
10
  import { relative, basename } from 'path';
@@ -645,14 +645,21 @@ function resolveLogStreamName(config, configHostname) {
645
645
  }
646
646
  return hostname$1;
647
647
  }
648
- var CloudWatchTransport = class extends BaseHttpTransport {
648
+ var CloudWatchTransport = class _CloudWatchTransport extends BaseHttpTransport {
649
649
  config;
650
650
  client;
651
- sequenceToken;
652
651
  initialized = false;
653
652
  initPromise = null;
653
+ initFailures = 0;
654
+ nextInitAttempt = 0;
654
655
  resolvedLogStreamName;
655
656
  maskReplacer;
657
+ // Backoff bounds for repeated init failures (e.g. IAM not yet propagated,
658
+ // throttling). Without this, a sustained outage makes every flush re-run
659
+ // initialize() back-to-back and hammer the CloudWatch control-plane APIs,
660
+ // which only deepens the throttling.
661
+ static INIT_BACKOFF_BASE_MS = 1e3;
662
+ static INIT_BACKOFF_MAX_MS = 3e4;
656
663
  constructor(config, configHostname, maskReplacerFn) {
657
664
  super({
658
665
  batchSize: config.batchSize ?? 100,
@@ -688,36 +695,46 @@ var CloudWatchTransport = class extends BaseHttpTransport {
688
695
  }, replacer)
689
696
  }));
690
697
  logEvents.sort((a, b) => (a.timestamp ?? 0) - (b.timestamp ?? 0));
691
- const command = new PutLogEventsCommand({
692
- logGroupName: this.config.logGroupName,
693
- logStreamName: this.resolvedLogStreamName,
694
- logEvents,
695
- sequenceToken: this.sequenceToken
696
- });
697
698
  try {
698
- const response = await this.client.send(command);
699
- this.sequenceToken = response.nextSequenceToken;
699
+ await this.putLogEvents(logEvents);
700
700
  } catch (error) {
701
- if (this.isInvalidSequenceTokenError(error)) {
702
- await this.fetchSequenceToken();
703
- const retryCommand = new PutLogEventsCommand({
704
- logGroupName: this.config.logGroupName,
705
- logStreamName: this.resolvedLogStreamName,
706
- logEvents,
707
- sequenceToken: this.sequenceToken
708
- });
709
- const response = await this.client.send(retryCommand);
710
- this.sequenceToken = response.nextSequenceToken;
701
+ if (this.isResourceNotFoundError(error)) {
702
+ this.initialized = false;
703
+ this.initPromise = null;
704
+ await this.ensureInitialized();
705
+ await this.putLogEvents(logEvents);
711
706
  } else {
712
707
  throw error;
713
708
  }
714
709
  }
715
710
  }
711
+ // PutLogEvents no longer requires a sequence token: since 2023 CloudWatch
712
+ // Logs ignores the `sequenceToken` field for sequential writes, so we omit it
713
+ // entirely. This also removes the need for DescribeLogStreams (and the
714
+ // logs:DescribeLogStreams IAM permission) on the hot path.
715
+ async putLogEvents(logEvents) {
716
+ await this.client.send(
717
+ new PutLogEventsCommand({
718
+ logGroupName: this.config.logGroupName,
719
+ logStreamName: this.resolvedLogStreamName,
720
+ logEvents
721
+ })
722
+ );
723
+ }
716
724
  async ensureInitialized() {
717
725
  if (this.initialized) return;
726
+ if (!this.initPromise && Date.now() < this.nextInitAttempt) {
727
+ throw new Error("CloudWatch transport initialization is backing off");
728
+ }
718
729
  if (!this.initPromise) {
719
730
  this.initPromise = this.initialize().catch((err) => {
720
731
  this.initPromise = null;
732
+ this.initFailures += 1;
733
+ const delay = Math.min(
734
+ _CloudWatchTransport.INIT_BACKOFF_MAX_MS,
735
+ _CloudWatchTransport.INIT_BACKOFF_BASE_MS * 2 ** (this.initFailures - 1)
736
+ );
737
+ this.nextInitAttempt = Date.now() + delay;
721
738
  throw err;
722
739
  });
723
740
  }
@@ -730,8 +747,9 @@ var CloudWatchTransport = class extends BaseHttpTransport {
730
747
  if (this.config.createLogStream !== false) {
731
748
  await this.createLogStreamIfNotExists();
732
749
  }
733
- await this.fetchSequenceToken();
734
750
  this.initialized = true;
751
+ this.initFailures = 0;
752
+ this.nextInitAttempt = 0;
735
753
  }
736
754
  async createLogGroupIfNotExists() {
737
755
  try {
@@ -760,22 +778,14 @@ var CloudWatchTransport = class extends BaseHttpTransport {
760
778
  }
761
779
  }
762
780
  }
763
- async fetchSequenceToken() {
764
- const response = await this.client.send(
765
- new DescribeLogStreamsCommand({
766
- logGroupName: this.config.logGroupName,
767
- logStreamNamePrefix: this.resolvedLogStreamName,
768
- limit: 1
769
- })
770
- );
771
- const stream = response.logStreams?.find((s) => s.logStreamName === this.resolvedLogStreamName);
772
- this.sequenceToken = stream?.uploadSequenceToken;
773
- }
774
781
  isResourceAlreadyExistsError(error) {
775
- return typeof error === "object" && error !== null && "name" in error && error.name === "ResourceAlreadyExistsException";
782
+ return this.isAwsErrorNamed(error, "ResourceAlreadyExistsException");
783
+ }
784
+ isResourceNotFoundError(error) {
785
+ return this.isAwsErrorNamed(error, "ResourceNotFoundException");
776
786
  }
777
- isInvalidSequenceTokenError(error) {
778
- return typeof error === "object" && error !== null && "name" in error && error.name === "InvalidSequenceTokenException";
787
+ isAwsErrorNamed(error, name) {
788
+ return typeof error === "object" && error !== null && "name" in error && error.name === name;
779
789
  }
780
790
  };
781
791
 
@@ -1682,13 +1692,22 @@ function extractAxiosHttpData(error) {
1682
1692
  }
1683
1693
  }
1684
1694
  if (error.config) {
1685
- const { url, baseURL, method } = error.config;
1695
+ const { url, baseURL, method, params, data, headers } = error.config;
1686
1696
  if (url) {
1687
1697
  httpData.url = baseURL && !url.startsWith("http") ? `${baseURL}${url}` : url;
1688
1698
  }
1689
1699
  if (method) {
1690
1700
  httpData.method = method.toUpperCase();
1691
1701
  }
1702
+ if (params !== void 0) {
1703
+ httpData.params = sanitizeResponseData(params);
1704
+ }
1705
+ if (data !== void 0) {
1706
+ httpData.requestData = sanitizeResponseData(data);
1707
+ }
1708
+ if (headers !== void 0) {
1709
+ httpData.requestHeaders = sanitizeResponseData(headers);
1710
+ }
1692
1711
  }
1693
1712
  return httpData;
1694
1713
  }
@@ -1726,6 +1745,18 @@ function extractGenericHttpData(error) {
1726
1745
  httpData.method = config.method.toUpperCase();
1727
1746
  hasData = true;
1728
1747
  }
1748
+ if (config.params !== void 0) {
1749
+ httpData.params = sanitizeResponseData(config.params);
1750
+ hasData = true;
1751
+ }
1752
+ if (config.data !== void 0) {
1753
+ httpData.requestData = sanitizeResponseData(config.data);
1754
+ hasData = true;
1755
+ }
1756
+ if (config.headers !== void 0) {
1757
+ httpData.requestHeaders = sanitizeResponseData(config.headers);
1758
+ hasData = true;
1759
+ }
1729
1760
  }
1730
1761
  return hasData ? httpData : void 0;
1731
1762
  }