@rawnodes/logger 2.11.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.d.mts +7 -3
- package/dist/index.d.ts +7 -3
- package/dist/index.js +45 -35
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +46 -36
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
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
|
|
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
|
-
|
|
699
|
-
this.sequenceToken = response.nextSequenceToken;
|
|
699
|
+
await this.putLogEvents(logEvents);
|
|
700
700
|
} catch (error) {
|
|
701
|
-
if (this.
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
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
|
|
782
|
+
return this.isAwsErrorNamed(error, "ResourceAlreadyExistsException");
|
|
783
|
+
}
|
|
784
|
+
isResourceNotFoundError(error) {
|
|
785
|
+
return this.isAwsErrorNamed(error, "ResourceNotFoundException");
|
|
776
786
|
}
|
|
777
|
-
|
|
778
|
-
return typeof error === "object" && error !== null && "name" in error && error.name ===
|
|
787
|
+
isAwsErrorNamed(error, name) {
|
|
788
|
+
return typeof error === "object" && error !== null && "name" in error && error.name === name;
|
|
779
789
|
}
|
|
780
790
|
};
|
|
781
791
|
|