@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 +3 -0
- package/dist/index.d.mts +9 -5
- package/dist/index.d.ts +9 -5
- package/dist/index.js +133 -57
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +134 -58
- package/dist/index.mjs.map +1 -1
- package/dist/transports/relay.d.mts +1 -1
- package/dist/transports/relay.d.ts +1 -1
- package/dist/{types-lfJLhC8J.d.mts → types-CD5hHXR4.d.mts} +1 -0
- package/dist/{types-lfJLhC8J.d.ts → types-CD5hHXR4.d.ts} +1 -0
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -2,9 +2,8 @@ import { hostname } from 'os';
|
|
|
2
2
|
import pino from 'pino';
|
|
3
3
|
import { AsyncLocalStorage } from 'async_hooks';
|
|
4
4
|
import { Transform, Writable } from 'stream';
|
|
5
|
-
import { mkdir } from 'fs/promises';
|
|
6
5
|
import { createStream } from 'rotating-file-stream';
|
|
7
|
-
import { CloudWatchLogsClient, PutLogEventsCommand, CreateLogGroupCommand, CreateLogStreamCommand
|
|
6
|
+
import { CloudWatchLogsClient, PutLogEventsCommand, CreateLogGroupCommand, CreateLogStreamCommand } from '@aws-sdk/client-cloudwatch-logs';
|
|
8
7
|
import { randomUUID } from 'crypto';
|
|
9
8
|
import { z } from 'zod';
|
|
10
9
|
import { relative, basename } from 'path';
|
|
@@ -39,6 +38,89 @@ function assertLogLevel(level) {
|
|
|
39
38
|
throw new Error(`Invalid log level: "${level}". Valid levels: ${Object.keys(LOG_LEVELS).join(", ")}`);
|
|
40
39
|
}
|
|
41
40
|
}
|
|
41
|
+
function reportFileFailure(error) {
|
|
42
|
+
const code = "code" in error && typeof error.code === "string" ? error.code : error.name;
|
|
43
|
+
process.stderr.write(
|
|
44
|
+
`[Logger] File logging stopped (${code}); other transports remain active.
|
|
45
|
+
`
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
function createFileDestination(config) {
|
|
49
|
+
let failed = false;
|
|
50
|
+
let rotatingStream;
|
|
51
|
+
const fail = (error) => {
|
|
52
|
+
if (failed) return;
|
|
53
|
+
failed = true;
|
|
54
|
+
if (config.onError) {
|
|
55
|
+
try {
|
|
56
|
+
config.onError(error);
|
|
57
|
+
} catch {
|
|
58
|
+
reportFileFailure(error);
|
|
59
|
+
}
|
|
60
|
+
} else {
|
|
61
|
+
reportFileFailure(error);
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
try {
|
|
65
|
+
rotatingStream = createStream(
|
|
66
|
+
(time, index) => {
|
|
67
|
+
const date = time instanceof Date ? time : /* @__PURE__ */ new Date();
|
|
68
|
+
const dateStr = date.toISOString().split("T")[0];
|
|
69
|
+
const base = config.filename.replace(".log", "");
|
|
70
|
+
return `${base}-${dateStr}${index ? `.${index}` : ""}.log`;
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
path: config.dirname,
|
|
74
|
+
size: (config.maxSize || "20M").replace(
|
|
75
|
+
/([kmg])$/i,
|
|
76
|
+
(unit) => unit.toUpperCase()
|
|
77
|
+
),
|
|
78
|
+
interval: config.interval || (config.datePattern === "YYYY-MM-DD-HH" ? "1h" : "1d"),
|
|
79
|
+
compress: config.zippedArchive ? "gzip" : false,
|
|
80
|
+
maxFiles: parseInt(config.maxFiles?.replace("d", "") || "14")
|
|
81
|
+
}
|
|
82
|
+
);
|
|
83
|
+
rotatingStream.on("error", fail);
|
|
84
|
+
} catch (error) {
|
|
85
|
+
fail(error instanceof Error ? error : new Error(String(error)));
|
|
86
|
+
}
|
|
87
|
+
return new Writable({
|
|
88
|
+
write(chunk, encoding, callback) {
|
|
89
|
+
if (failed || !rotatingStream) {
|
|
90
|
+
callback();
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
try {
|
|
94
|
+
rotatingStream.write(chunk, encoding, (error) => {
|
|
95
|
+
if (error) fail(error);
|
|
96
|
+
callback();
|
|
97
|
+
});
|
|
98
|
+
} catch (error) {
|
|
99
|
+
fail(error instanceof Error ? error : new Error(String(error)));
|
|
100
|
+
callback();
|
|
101
|
+
}
|
|
102
|
+
},
|
|
103
|
+
final(callback) {
|
|
104
|
+
if (!rotatingStream || rotatingStream.destroyed) {
|
|
105
|
+
callback();
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
try {
|
|
109
|
+
rotatingStream.end((error) => {
|
|
110
|
+
if (error) fail(error);
|
|
111
|
+
callback();
|
|
112
|
+
});
|
|
113
|
+
} catch (error) {
|
|
114
|
+
fail(error instanceof Error ? error : new Error(String(error)));
|
|
115
|
+
callback();
|
|
116
|
+
}
|
|
117
|
+
},
|
|
118
|
+
destroy(_error, callback) {
|
|
119
|
+
rotatingStream?.destroy();
|
|
120
|
+
callback();
|
|
121
|
+
}
|
|
122
|
+
});
|
|
123
|
+
}
|
|
42
124
|
|
|
43
125
|
// src/transports/buffer.ts
|
|
44
126
|
var MessageBuffer = class {
|
|
@@ -645,14 +727,21 @@ function resolveLogStreamName(config, configHostname) {
|
|
|
645
727
|
}
|
|
646
728
|
return hostname$1;
|
|
647
729
|
}
|
|
648
|
-
var CloudWatchTransport = class extends BaseHttpTransport {
|
|
730
|
+
var CloudWatchTransport = class _CloudWatchTransport extends BaseHttpTransport {
|
|
649
731
|
config;
|
|
650
732
|
client;
|
|
651
|
-
sequenceToken;
|
|
652
733
|
initialized = false;
|
|
653
734
|
initPromise = null;
|
|
735
|
+
initFailures = 0;
|
|
736
|
+
nextInitAttempt = 0;
|
|
654
737
|
resolvedLogStreamName;
|
|
655
738
|
maskReplacer;
|
|
739
|
+
// Backoff bounds for repeated init failures (e.g. IAM not yet propagated,
|
|
740
|
+
// throttling). Without this, a sustained outage makes every flush re-run
|
|
741
|
+
// initialize() back-to-back and hammer the CloudWatch control-plane APIs,
|
|
742
|
+
// which only deepens the throttling.
|
|
743
|
+
static INIT_BACKOFF_BASE_MS = 1e3;
|
|
744
|
+
static INIT_BACKOFF_MAX_MS = 3e4;
|
|
656
745
|
constructor(config, configHostname, maskReplacerFn) {
|
|
657
746
|
super({
|
|
658
747
|
batchSize: config.batchSize ?? 100,
|
|
@@ -688,36 +777,46 @@ var CloudWatchTransport = class extends BaseHttpTransport {
|
|
|
688
777
|
}, replacer)
|
|
689
778
|
}));
|
|
690
779
|
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
780
|
try {
|
|
698
|
-
|
|
699
|
-
this.sequenceToken = response.nextSequenceToken;
|
|
781
|
+
await this.putLogEvents(logEvents);
|
|
700
782
|
} 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;
|
|
783
|
+
if (this.isResourceNotFoundError(error)) {
|
|
784
|
+
this.initialized = false;
|
|
785
|
+
this.initPromise = null;
|
|
786
|
+
await this.ensureInitialized();
|
|
787
|
+
await this.putLogEvents(logEvents);
|
|
711
788
|
} else {
|
|
712
789
|
throw error;
|
|
713
790
|
}
|
|
714
791
|
}
|
|
715
792
|
}
|
|
793
|
+
// PutLogEvents no longer requires a sequence token: since 2023 CloudWatch
|
|
794
|
+
// Logs ignores the `sequenceToken` field for sequential writes, so we omit it
|
|
795
|
+
// entirely. This also removes the need for DescribeLogStreams (and the
|
|
796
|
+
// logs:DescribeLogStreams IAM permission) on the hot path.
|
|
797
|
+
async putLogEvents(logEvents) {
|
|
798
|
+
await this.client.send(
|
|
799
|
+
new PutLogEventsCommand({
|
|
800
|
+
logGroupName: this.config.logGroupName,
|
|
801
|
+
logStreamName: this.resolvedLogStreamName,
|
|
802
|
+
logEvents
|
|
803
|
+
})
|
|
804
|
+
);
|
|
805
|
+
}
|
|
716
806
|
async ensureInitialized() {
|
|
717
807
|
if (this.initialized) return;
|
|
808
|
+
if (!this.initPromise && Date.now() < this.nextInitAttempt) {
|
|
809
|
+
throw new Error("CloudWatch transport initialization is backing off");
|
|
810
|
+
}
|
|
718
811
|
if (!this.initPromise) {
|
|
719
812
|
this.initPromise = this.initialize().catch((err) => {
|
|
720
813
|
this.initPromise = null;
|
|
814
|
+
this.initFailures += 1;
|
|
815
|
+
const delay = Math.min(
|
|
816
|
+
_CloudWatchTransport.INIT_BACKOFF_MAX_MS,
|
|
817
|
+
_CloudWatchTransport.INIT_BACKOFF_BASE_MS * 2 ** (this.initFailures - 1)
|
|
818
|
+
);
|
|
819
|
+
this.nextInitAttempt = Date.now() + delay;
|
|
721
820
|
throw err;
|
|
722
821
|
});
|
|
723
822
|
}
|
|
@@ -730,8 +829,9 @@ var CloudWatchTransport = class extends BaseHttpTransport {
|
|
|
730
829
|
if (this.config.createLogStream !== false) {
|
|
731
830
|
await this.createLogStreamIfNotExists();
|
|
732
831
|
}
|
|
733
|
-
await this.fetchSequenceToken();
|
|
734
832
|
this.initialized = true;
|
|
833
|
+
this.initFailures = 0;
|
|
834
|
+
this.nextInitAttempt = 0;
|
|
735
835
|
}
|
|
736
836
|
async createLogGroupIfNotExists() {
|
|
737
837
|
try {
|
|
@@ -760,22 +860,16 @@ var CloudWatchTransport = class extends BaseHttpTransport {
|
|
|
760
860
|
}
|
|
761
861
|
}
|
|
762
862
|
}
|
|
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
863
|
isResourceAlreadyExistsError(error) {
|
|
775
|
-
return
|
|
864
|
+
return this.isAwsErrorNamed(error, "ResourceAlreadyExistsException");
|
|
865
|
+
}
|
|
866
|
+
isResourceNotFoundError(error) {
|
|
867
|
+
return this.isAwsErrorNamed(error, "ResourceNotFoundException");
|
|
776
868
|
}
|
|
777
|
-
|
|
778
|
-
|
|
869
|
+
isAwsErrorNamed(error, name) {
|
|
870
|
+
if (typeof error !== "object" || error === null) return false;
|
|
871
|
+
if ("name" in error && error.name === name) return true;
|
|
872
|
+
return "__type" in error && typeof error.__type === "string" && error.__type.split("#").at(-1) === name;
|
|
779
873
|
}
|
|
780
874
|
};
|
|
781
875
|
|
|
@@ -937,26 +1031,7 @@ function createStreams(config, state) {
|
|
|
937
1031
|
});
|
|
938
1032
|
if (config.file) {
|
|
939
1033
|
const fileConfig = config.file;
|
|
940
|
-
|
|
941
|
-
});
|
|
942
|
-
const normalizeSize = (size) => {
|
|
943
|
-
return size.replace(/([kmg])$/i, (match) => match.toUpperCase());
|
|
944
|
-
};
|
|
945
|
-
const rotatingStream = createStream(
|
|
946
|
-
(time, index) => {
|
|
947
|
-
const date = time instanceof Date ? time : /* @__PURE__ */ new Date();
|
|
948
|
-
const dateStr = date.toISOString().split("T")[0];
|
|
949
|
-
const base = fileConfig.filename.replace(".log", "");
|
|
950
|
-
return `${base}-${dateStr}${index ? `.${index}` : ""}.log`;
|
|
951
|
-
},
|
|
952
|
-
{
|
|
953
|
-
path: fileConfig.dirname,
|
|
954
|
-
size: normalizeSize(fileConfig.maxSize || "20M"),
|
|
955
|
-
interval: fileConfig.interval || (fileConfig.datePattern === "YYYY-MM-DD-HH" ? "1h" : "1d"),
|
|
956
|
-
compress: fileConfig.zippedArchive ? "gzip" : false,
|
|
957
|
-
maxFiles: parseInt(fileConfig.maxFiles?.replace("d", "") || "14")
|
|
958
|
-
}
|
|
959
|
-
);
|
|
1034
|
+
const rotatingStream = createFileDestination(fileConfig);
|
|
960
1035
|
const fileStream = createFormattedFilterStream(
|
|
961
1036
|
fileConfig.format,
|
|
962
1037
|
fileConfig.level,
|
|
@@ -1348,7 +1423,8 @@ var FileConfigSchema = z.object({
|
|
|
1348
1423
|
interval: z.string().regex(/^\d+[Mdhms]$/).optional(),
|
|
1349
1424
|
zippedArchive: z.boolean().optional(),
|
|
1350
1425
|
maxSize: z.string().optional(),
|
|
1351
|
-
maxFiles: z.string().optional()
|
|
1426
|
+
maxFiles: z.string().optional(),
|
|
1427
|
+
onError: z.function().optional()
|
|
1352
1428
|
});
|
|
1353
1429
|
var HttpTransportBaseConfigSchema = z.object({
|
|
1354
1430
|
level: LogLevelSchema.optional(),
|