@wireio/shared 1.0.1 → 1.0.3
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 +424 -0
- package/package.json +5 -1
- package/src/guards/applyTypeGuardExtras.ts +0 -9
- package/src/guards/array.ts +0 -5
- package/src/guards/assert.ts +0 -83
- package/src/guards/class.ts +0 -56
- package/src/guards/guard-tools.ts +0 -78
- package/src/guards/index.ts +0 -7
- package/src/guards/primitive.ts +0 -77
- package/src/guards/types.ts +0 -15
- package/src/helpers/Deferred.ts +0 -149
- package/src/helpers/index.ts +0 -1
- package/src/index.ts +0 -3
- package/src/logging/Appender.ts +0 -5
- package/src/logging/Formatter.ts +0 -8
- package/src/logging/InternalLogger.ts +0 -6
- package/src/logging/Level.ts +0 -19
- package/src/logging/LogRecord.ts +0 -17
- package/src/logging/Logger.ts +0 -222
- package/src/logging/LoggingManager.ts +0 -306
- package/src/logging/appenders/ConsoleAppender.ts +0 -110
- package/src/logging/appenders/DebugAppender.ts +0 -254
- package/src/logging/appenders/FileAppender.ts +0 -281
- package/src/logging/appenders/aws-firehose/AWSFirehoseAppender.ts +0 -176
- package/src/logging/appenders/aws-firehose/AWSFirehoseCredentialManager.ts +0 -132
- package/src/logging/appenders/index.ts +0 -3
- package/src/logging/context/LogContext.ts +0 -74
- package/src/logging/context/LogContextContainer.ts +0 -28
- package/src/logging/context/LogContextProvider.ts +0 -43
- package/src/logging/context/index.ts +0 -3
- package/src/logging/context/providers/node/NodeContextProvider.ts +0 -45
- package/src/logging/context/providers/node/index.ts +0 -3
- package/src/logging/examples/example-debug-appender.ts +0 -19
- package/src/logging/examples/example-file-appender.ts +0 -19
- package/src/logging/getLogger.ts +0 -9
- package/src/logging/index-node.ts +0 -2
- package/src/logging/index.ts +0 -11
- package/src/logging/util.ts +0 -6
- package/tsconfig.json +0 -9
- package/tsconfig.tsbuildinfo +0 -1
|
@@ -1,281 +0,0 @@
|
|
|
1
|
-
import { assign, defaults, padStart } from "lodash"
|
|
2
|
-
import * as Path from "path"
|
|
3
|
-
import * as Fs from "fs"
|
|
4
|
-
import type { LogRecord } from "../LogRecord"
|
|
5
|
-
import type { Appender } from "../Appender"
|
|
6
|
-
import { Option, Future } from "prelude-ts"
|
|
7
|
-
import { Buffer } from "buffer"
|
|
8
|
-
import Debug from "debug"
|
|
9
|
-
import Bluebird from "bluebird"
|
|
10
|
-
|
|
11
|
-
const FsAsync = Fs.promises
|
|
12
|
-
|
|
13
|
-
const debug = Debug("3fv:logger:FileAppender")
|
|
14
|
-
const osTempDir = process.env.TMP ?? process.env.TEMP ?? "/tmp"
|
|
15
|
-
const tempDir = Fs.mkdtempSync(Path.join(osTempDir, "logger-proxy-example")) //
|
|
16
|
-
const getDefaultConfig = (): FileAppenderConfig => ({
|
|
17
|
-
filename: Path.join(tempDir, "app.log"),
|
|
18
|
-
prettyPrint: process.env.NODE_ENV !== "production",
|
|
19
|
-
maxSize: -1,
|
|
20
|
-
maxFiles: 5,
|
|
21
|
-
enableRolling: false,
|
|
22
|
-
sync: false
|
|
23
|
-
})
|
|
24
|
-
|
|
25
|
-
function applyConfigDefaults(options: FileAppenderOptions): FileAppenderConfig {
|
|
26
|
-
return defaults(options, getDefaultConfig())
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
export interface FileAppenderConfig<Record extends LogRecord = any> {
|
|
30
|
-
filename: string
|
|
31
|
-
|
|
32
|
-
/**
|
|
33
|
-
* For rolling
|
|
34
|
-
*/
|
|
35
|
-
maxFiles: number
|
|
36
|
-
|
|
37
|
-
/**
|
|
38
|
-
* For rolling
|
|
39
|
-
*/
|
|
40
|
-
maxSize: number
|
|
41
|
-
|
|
42
|
-
enableRolling: boolean
|
|
43
|
-
prettyPrint: boolean
|
|
44
|
-
sync: boolean
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
export type FileAppenderOptions<Record extends LogRecord = any> = Partial<
|
|
48
|
-
FileAppenderConfig<Record>
|
|
49
|
-
>
|
|
50
|
-
|
|
51
|
-
export class FileAppender<Record extends LogRecord>
|
|
52
|
-
implements Appender<Record>
|
|
53
|
-
{
|
|
54
|
-
readonly config: FileAppenderConfig<Record>
|
|
55
|
-
|
|
56
|
-
private readonly state: {
|
|
57
|
-
filename: string
|
|
58
|
-
file: number
|
|
59
|
-
flushing: boolean
|
|
60
|
-
queue: Array<Buffer>
|
|
61
|
-
ready: boolean
|
|
62
|
-
error?: Error
|
|
63
|
-
currentSize: number
|
|
64
|
-
archivedFilenames: string[]
|
|
65
|
-
} = {
|
|
66
|
-
archivedFilenames: [],
|
|
67
|
-
filename: undefined,
|
|
68
|
-
file: undefined,
|
|
69
|
-
ready: false,
|
|
70
|
-
currentSize: 0,
|
|
71
|
-
flushing: false,
|
|
72
|
-
queue: []
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
get enableRolling() {
|
|
76
|
-
return this.config.enableRolling
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
isReady() {
|
|
80
|
-
return this.state.ready
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
/**
|
|
84
|
-
* Initialize and setup the appender
|
|
85
|
-
*
|
|
86
|
-
* @returns {FileAppender<Record>}
|
|
87
|
-
*/
|
|
88
|
-
setup(): FileAppender<Record> {
|
|
89
|
-
const { state } = this
|
|
90
|
-
if (!!state.ready) {
|
|
91
|
-
return this
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
try {
|
|
95
|
-
const { filename } = this
|
|
96
|
-
const file = Fs.openSync(filename, "a")
|
|
97
|
-
|
|
98
|
-
assign(state, {
|
|
99
|
-
filename,
|
|
100
|
-
file,
|
|
101
|
-
ready: true
|
|
102
|
-
})
|
|
103
|
-
|
|
104
|
-
return this
|
|
105
|
-
} catch (err) {
|
|
106
|
-
assign(state, {
|
|
107
|
-
error: err,
|
|
108
|
-
file: undefined,
|
|
109
|
-
ready: false
|
|
110
|
-
})
|
|
111
|
-
throw err
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
/**
|
|
116
|
-
* Only exposed for the sake of tests & examples
|
|
117
|
-
* DO NOT USE
|
|
118
|
-
*
|
|
119
|
-
* This should all be async, but i was in a rush :(
|
|
120
|
-
*
|
|
121
|
-
* @returns
|
|
122
|
-
*/
|
|
123
|
-
rollFile() {
|
|
124
|
-
const { state, config } = this
|
|
125
|
-
let { file, filename, archivedFilenames } = state
|
|
126
|
-
if (!this.isReady() || !file) {
|
|
127
|
-
debug(`Can not roll file before ready`)
|
|
128
|
-
return
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
if (!this.enableRolling) {
|
|
132
|
-
return
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
const fileCount = archivedFilenames.length + 1
|
|
136
|
-
if (config.maxFiles < 1 || fileCount < config.maxFiles) {
|
|
137
|
-
archivedFilenames.push(
|
|
138
|
-
filename + "." + padStart(fileCount.toString(10), 4, "0")
|
|
139
|
-
)
|
|
140
|
-
}
|
|
141
|
-
Fs.fdatasyncSync(file)
|
|
142
|
-
Fs.closeSync(file)
|
|
143
|
-
file = state.file = undefined
|
|
144
|
-
const filenames = [filename, ...archivedFilenames]
|
|
145
|
-
|
|
146
|
-
let dest: string = filenames.pop()
|
|
147
|
-
while (filenames.length) {
|
|
148
|
-
let src = filenames.pop()
|
|
149
|
-
if (!Fs.existsSync(src)) {
|
|
150
|
-
debug(`src does not exist, skipping ${src}`)
|
|
151
|
-
} else {
|
|
152
|
-
debug(`Moving src (${src}) to dest (${dest})`)
|
|
153
|
-
if (Fs.existsSync(dest)) {
|
|
154
|
-
Fs.unlinkSync(dest)
|
|
155
|
-
}
|
|
156
|
-
Fs.renameSync(src, dest)
|
|
157
|
-
// if (src === filename) {
|
|
158
|
-
// Fs.copyFileSync(src, dest)
|
|
159
|
-
// Fs.truncateSync(src, 0)
|
|
160
|
-
// } else {
|
|
161
|
-
// Fs.renameSync(src, dest)
|
|
162
|
-
// }
|
|
163
|
-
}
|
|
164
|
-
dest = src
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
assign(state, {
|
|
168
|
-
currentSize: 0,
|
|
169
|
-
file: Fs.openSync(filename, "a")
|
|
170
|
-
})
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
/**
|
|
174
|
-
* Close the handler
|
|
175
|
-
*
|
|
176
|
-
* @returns {Promise<void>}
|
|
177
|
-
*/
|
|
178
|
-
close() {
|
|
179
|
-
return this.flush().onComplete(() =>
|
|
180
|
-
Option.of(this.state.file).map(Fs.closeSync)
|
|
181
|
-
).toPromise()
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
get queue() {
|
|
185
|
-
return this.state.queue
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
get file() {
|
|
189
|
-
return this.state.file
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
get flushing() {
|
|
193
|
-
return this.state.flushing
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
get filename() {
|
|
197
|
-
return (this.state.filename = this.state.filename ?? this.config.filename)
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
/**
|
|
201
|
-
* Appends the log queue records to the file
|
|
202
|
-
*/
|
|
203
|
-
private flush() {
|
|
204
|
-
const { state } = this
|
|
205
|
-
return Future.do(async () => {
|
|
206
|
-
state.flushing = true
|
|
207
|
-
try {
|
|
208
|
-
const { file } = this
|
|
209
|
-
if (!file) {
|
|
210
|
-
throw Error(`${this.filename} was not opened properly`)
|
|
211
|
-
}
|
|
212
|
-
while (this.queue.length) {
|
|
213
|
-
const buf: Buffer = this.queue.shift()
|
|
214
|
-
|
|
215
|
-
await Bluebird.fromCallback((cb) =>
|
|
216
|
-
Fs.appendFile(file, buf, "utf-8", cb)
|
|
217
|
-
)
|
|
218
|
-
|
|
219
|
-
state.currentSize += buf.byteLength
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
const { maxSize } = this.config
|
|
223
|
-
if (maxSize > 0 && state.currentSize >= maxSize) {
|
|
224
|
-
this.rollFile()
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
//await Bluebird.fromCallback((cb) => Fs.fdatasync(this.file, cb))
|
|
228
|
-
} catch (err) {
|
|
229
|
-
console.error(`Failed to append file ${this.filename}`, err)
|
|
230
|
-
} finally {
|
|
231
|
-
this.state.flushing = false
|
|
232
|
-
if (this.queue.length) {
|
|
233
|
-
queueMicrotask(() => this.flush())
|
|
234
|
-
}
|
|
235
|
-
}
|
|
236
|
-
})
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
/**
|
|
240
|
-
* Handle log records, transform, push to ES
|
|
241
|
-
*
|
|
242
|
-
* @param record
|
|
243
|
-
*/
|
|
244
|
-
append(record: Record): void {
|
|
245
|
-
try {
|
|
246
|
-
const { queue, filename } = this.state
|
|
247
|
-
const count = queue.length
|
|
248
|
-
if (count > 999) {
|
|
249
|
-
debug(
|
|
250
|
-
`Too many log records (${count}) are in the queue without the file (${filename}) opening, skipping %O`,
|
|
251
|
-
record
|
|
252
|
-
)
|
|
253
|
-
return
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
const data = this.config.prettyPrint
|
|
257
|
-
? JSON.stringify(record, null, 2)
|
|
258
|
-
: JSON.stringify(record)
|
|
259
|
-
queue.push(Buffer.from(data + "\n", "utf-8"))
|
|
260
|
-
this.flush()
|
|
261
|
-
} catch (err) {
|
|
262
|
-
console.warn(`Failed to synchronize `, err)
|
|
263
|
-
}
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
/**
|
|
267
|
-
*
|
|
268
|
-
* @param {Partial<FileAppenderOptions<Record>>} options
|
|
269
|
-
*/
|
|
270
|
-
constructor(options: Partial<FileAppenderOptions<Record>> = {}) {
|
|
271
|
-
this.config = applyConfigDefaults(options)
|
|
272
|
-
|
|
273
|
-
assign(this.state, {
|
|
274
|
-
filename: this.config.filename
|
|
275
|
-
})
|
|
276
|
-
|
|
277
|
-
this.setup()
|
|
278
|
-
debug(`File appender is ready, logging to ${this.state.filename}`)
|
|
279
|
-
// return this
|
|
280
|
-
}
|
|
281
|
-
}
|
|
@@ -1,176 +0,0 @@
|
|
|
1
|
-
/* eslint-disable typescript/no-explicit-any */
|
|
2
|
-
import { isString } from '../../../guards/primitive.js'
|
|
3
|
-
import { FirehoseClient, PutRecordBatchCommand, PutRecordBatchInput } from "@aws-sdk/client-firehose"
|
|
4
|
-
import {
|
|
5
|
-
Credentials as IAWSTemporaryCredentials,
|
|
6
|
-
ExpiredTokenException,
|
|
7
|
-
} from "@aws-sdk/client-sts"
|
|
8
|
-
import { Level } from "../../Level.js"
|
|
9
|
-
|
|
10
|
-
import AWSFirehoseCredentialManager from "./AWSFirehoseCredentialManager.js"
|
|
11
|
-
import { getInternalLogger } from "../../InternalLogger.js"
|
|
12
|
-
import { getValue, guard } from "../../../guards/index.js"
|
|
13
|
-
import { pick } from "lodash"
|
|
14
|
-
import { Deferred } from "../../../helpers/index.js"
|
|
15
|
-
import { LogRecord } from "../../LogRecord.js"
|
|
16
|
-
import { Appender } from "../../Appender.js"
|
|
17
|
-
|
|
18
|
-
const LogQueueMaxRecords = 10000
|
|
19
|
-
|
|
20
|
-
const log = getInternalLogger(import.meta.filename)
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
export class AWSFirehoseAppender<Record extends LogRecord = LogRecord>
|
|
24
|
-
implements Appender<Record> {
|
|
25
|
-
private pendingRecords = Array<LogRecord>()
|
|
26
|
-
private flushDeferred: Deferred<void>
|
|
27
|
-
private flushIntervalMs = 1000;
|
|
28
|
-
|
|
29
|
-
constructor(public readonly credentialManager: AWSFirehoseCredentialManager = new AWSFirehoseCredentialManager()) {
|
|
30
|
-
credentialManager.on("received", () => {
|
|
31
|
-
log.info("Credentials received, initializing firehose client")
|
|
32
|
-
if (!this.pendingRecords.length || !!this.flushDeferred) {
|
|
33
|
-
log.debug("Nothing to flush upon credential receipt OR flush already in progress")
|
|
34
|
-
return
|
|
35
|
-
}
|
|
36
|
-
this.flush()
|
|
37
|
-
.catch((e: Error) => {
|
|
38
|
-
log.error(`Error flushing logs after credential receipt: ${e.message}`, e)
|
|
39
|
-
})
|
|
40
|
-
})
|
|
41
|
-
}
|
|
42
|
-
append(record: Record): void {
|
|
43
|
-
const
|
|
44
|
-
pendingCount = this.pendingRecords.length + 1,
|
|
45
|
-
removeCount = LogQueueMaxRecords - pendingCount
|
|
46
|
-
|
|
47
|
-
if (removeCount < 0) {
|
|
48
|
-
this.pendingRecords.splice(0, Math.abs(removeCount))
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
this.pendingRecords.push({
|
|
52
|
-
...record,
|
|
53
|
-
app: "web",
|
|
54
|
-
timestamp: Date.now(),
|
|
55
|
-
env: typeof process === "undefined" ? "WEB" : process.env.WIRE_ENV ?? "SHARED",
|
|
56
|
-
url: typeof window === "undefined" ? "local://node" : window.location.href,
|
|
57
|
-
})
|
|
58
|
-
|
|
59
|
-
this.flush()
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
/**
|
|
63
|
-
* Create the firehose client and return
|
|
64
|
-
*
|
|
65
|
-
* @param {IAWSTemporaryCredentials} credentials
|
|
66
|
-
* @param {boolean} flush
|
|
67
|
-
* @returns {Firehose}
|
|
68
|
-
*/
|
|
69
|
-
private getFirehose(): FirehoseClient {
|
|
70
|
-
const creds = this.credentialManager.getCredentials()
|
|
71
|
-
|
|
72
|
-
if (!creds) {
|
|
73
|
-
log.warn("No credentials available for flush")
|
|
74
|
-
return null
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
const { region, streamName } = creds
|
|
78
|
-
|
|
79
|
-
if (!region || !streamName) {
|
|
80
|
-
throw new Error("Region or stream name not available")
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
const client = new FirehoseClient({
|
|
84
|
-
region: creds.region,
|
|
85
|
-
credentials: pick(creds, "accessKeyId", "secretAccessKey", "sessionToken"),
|
|
86
|
-
})
|
|
87
|
-
|
|
88
|
-
return client
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
/**
|
|
92
|
-
* Flush pending records
|
|
93
|
-
*
|
|
94
|
-
* @returns {Promise<void>}
|
|
95
|
-
*/
|
|
96
|
-
private async flush(): Promise<void> {
|
|
97
|
-
if (!!this.flushDeferred) {
|
|
98
|
-
log.debug("Flush already in progress, skipping")
|
|
99
|
-
return
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
if (!this.pendingRecords.length) {
|
|
103
|
-
log.debug("No pending records to flush")
|
|
104
|
-
return
|
|
105
|
-
}
|
|
106
|
-
const creds = this.credentialManager.getCredentials()
|
|
107
|
-
if (!creds) {
|
|
108
|
-
log.warn("No credentials available for flush")
|
|
109
|
-
return
|
|
110
|
-
}
|
|
111
|
-
const { streamName } = creds,
|
|
112
|
-
client = this.getFirehose()
|
|
113
|
-
if (!client) {
|
|
114
|
-
log.warn("No firehose client available, skipping flush")
|
|
115
|
-
return
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
const deferred = this.flushDeferred = new Deferred<void>()
|
|
119
|
-
try {
|
|
120
|
-
while (this.pendingRecords.length) {
|
|
121
|
-
const
|
|
122
|
-
chunkSize = Math.min(this.pendingRecords.length, 10),
|
|
123
|
-
records = this.pendingRecords.splice(0, chunkSize),
|
|
124
|
-
recordJsons = records.map(record => JSON.stringify(record))
|
|
125
|
-
|
|
126
|
-
// log.info("Record JSONs", recordJsons)
|
|
127
|
-
|
|
128
|
-
const
|
|
129
|
-
textEncoder = new TextEncoder(),
|
|
130
|
-
batch: PutRecordBatchInput["Records"] = recordJsons.map(jsonStr => ({
|
|
131
|
-
Data: textEncoder.encode(jsonStr)
|
|
132
|
-
}))
|
|
133
|
-
|
|
134
|
-
log.info(`Pushing ${records.length} records to firehose`)
|
|
135
|
-
|
|
136
|
-
const cmd = new PutRecordBatchCommand({
|
|
137
|
-
DeliveryStreamName: streamName,
|
|
138
|
-
Records: batch,
|
|
139
|
-
})
|
|
140
|
-
|
|
141
|
-
const resp = await client.send(cmd)
|
|
142
|
-
const failed = resp.FailedPutCount || 0
|
|
143
|
-
if (failed) {
|
|
144
|
-
const rr = resp.RequestResponses || []
|
|
145
|
-
for (let i = 0; i < rr.length; i++) {
|
|
146
|
-
if (rr[i].ErrorCode) {
|
|
147
|
-
log.warn(`Record failed: ${rr[i].ErrorCode} ${rr[i].ErrorMessage}`)
|
|
148
|
-
//this.pendingRecords.unshift(records[i])
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
log.info(`PutRecordBatch failed=${failed}; requeued failed records`)
|
|
152
|
-
} else {
|
|
153
|
-
log.info(`PutRecordBatch ok count=${records.length}`)
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
}
|
|
157
|
-
} catch (err) {
|
|
158
|
-
log.error("Failed to push logs", err)
|
|
159
|
-
if (err.code === ExpiredTokenException) {
|
|
160
|
-
this.credentialManager.forceUpdateCredentials()
|
|
161
|
-
}
|
|
162
|
-
deferred.reject(err)
|
|
163
|
-
} finally {
|
|
164
|
-
this.flushDeferred = null
|
|
165
|
-
|
|
166
|
-
// if (this.pendingRecords.length)
|
|
167
|
-
// this.flush()
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
return deferred.promise
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
export default AWSFirehoseAppender
|
|
@@ -1,132 +0,0 @@
|
|
|
1
|
-
import { FirehoseClient, PutRecordBatchCommand } from "@aws-sdk/client-firehose"
|
|
2
|
-
import { Deferred } from "../../../helpers/index.js"
|
|
3
|
-
import { getInternalLogger } from "../../InternalLogger.js"
|
|
4
|
-
import EventEmitter3 from "eventemitter3"
|
|
5
|
-
|
|
6
|
-
const API_BASE = "https://log-ingest.shared.wire-dev.com"
|
|
7
|
-
const CRED_BUFFER_PADDING_MS = 3 * 60 * 1000 // 3 minutes
|
|
8
|
-
|
|
9
|
-
const log = getInternalLogger(import.meta.filename)
|
|
10
|
-
|
|
11
|
-
export interface AWSCredentials {
|
|
12
|
-
accessKeyId: string
|
|
13
|
-
secretAccessKey: string
|
|
14
|
-
sessionToken: string
|
|
15
|
-
streamName: string
|
|
16
|
-
region: string
|
|
17
|
-
expiration: string
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
export interface AWSFirehoseCredentialManagerEventMap {
|
|
21
|
-
received: (creds: AWSCredentials) => void
|
|
22
|
-
expired: () => void
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
export class AWSFirehoseCredentialManager extends EventEmitter3<AWSFirehoseCredentialManagerEventMap> {
|
|
26
|
-
|
|
27
|
-
private refreshTimer: ReturnType<typeof setTimeout> = null;
|
|
28
|
-
private loadDeferred: Deferred<AWSCredentials> = null;
|
|
29
|
-
|
|
30
|
-
forceUpdateCredentials(): void {
|
|
31
|
-
if (this.loadDeferred && !this.loadDeferred.isSettled()) {
|
|
32
|
-
this.loadDeferred.reject(new Error("force update"))
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
this.loadDeferred = null
|
|
36
|
-
this.scheduleRefresh(1)
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
getCredentials(): AWSCredentials {
|
|
40
|
-
if (this.loadDeferred && this.loadDeferred.isFulfilled()) {
|
|
41
|
-
const creds = this.loadDeferred.getResult(),
|
|
42
|
-
expMs = Date.parse(creds.expiration)
|
|
43
|
-
|
|
44
|
-
if (expMs <= Date.now()) {
|
|
45
|
-
log.info("Credentials expired")
|
|
46
|
-
this.loadDeferred = null
|
|
47
|
-
this.scheduleRefresh(1)
|
|
48
|
-
this.emit("expired")
|
|
49
|
-
return null
|
|
50
|
-
}
|
|
51
|
-
return creds
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
this.scheduleRefresh(1)
|
|
55
|
-
return null
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
hasCredentials(): boolean {
|
|
59
|
-
return this.getCredentials() != null
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
private async load(): Promise<AWSCredentials> {
|
|
63
|
-
if (this.loadDeferred && !this.loadDeferred.isRejected())
|
|
64
|
-
return this.loadDeferred.promise
|
|
65
|
-
|
|
66
|
-
const deferred = this.loadDeferred = new Deferred<AWSCredentials>()
|
|
67
|
-
try {
|
|
68
|
-
const r = await fetch(API_BASE + "/creds", {
|
|
69
|
-
method: "GET",
|
|
70
|
-
credentials: "include",
|
|
71
|
-
})
|
|
72
|
-
if (!r.ok) throw new Error(`creds http ${r.status}`)
|
|
73
|
-
const creds: AWSCredentials = await r.json()
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
this.scheduleRefresh(creds)
|
|
77
|
-
log.info(`Got creds, expire=${creds.expiration}, stream=${creds.streamName}, region=${creds.region}`)
|
|
78
|
-
deferred.resolve(creds)
|
|
79
|
-
this.emit("received", creds)
|
|
80
|
-
return deferred.promise
|
|
81
|
-
} catch (e) {
|
|
82
|
-
deferred.reject(e)
|
|
83
|
-
this.loadDeferred = null
|
|
84
|
-
throw e
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
scheduleRefresh(credsOrMillis: AWSCredentials | number): void {
|
|
88
|
-
if (this.refreshTimer) {
|
|
89
|
-
clearTimeout(this.refreshTimer)
|
|
90
|
-
this.refreshTimer = null
|
|
91
|
-
}
|
|
92
|
-
if (!credsOrMillis)
|
|
93
|
-
return
|
|
94
|
-
|
|
95
|
-
let refreshInMs: number
|
|
96
|
-
if (typeof credsOrMillis === "number") {
|
|
97
|
-
refreshInMs = credsOrMillis
|
|
98
|
-
} else {
|
|
99
|
-
const
|
|
100
|
-
creds = credsOrMillis,
|
|
101
|
-
expMs = Date.parse(creds.expiration)
|
|
102
|
-
|
|
103
|
-
refreshInMs = expMs - Date.now() - CRED_BUFFER_PADDING_MS
|
|
104
|
-
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
refreshInMs = Math.max(100, refreshInMs)
|
|
108
|
-
|
|
109
|
-
this.refreshTimer = setTimeout(() => {
|
|
110
|
-
this.loadDeferred = null
|
|
111
|
-
this.load().catch((e: Error) => log.error(`refresh failed: ${e.message}`, e))
|
|
112
|
-
}, refreshInMs)
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
getStreamName(): string {
|
|
116
|
-
return this.getCredentials()?.streamName
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
getRegion(): string {
|
|
120
|
-
return this.getCredentials()?.region
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
destroy(): void {
|
|
124
|
-
this.scheduleRefresh(null)
|
|
125
|
-
if (!this.loadDeferred.isSettled())
|
|
126
|
-
this.loadDeferred.reject(new Error("destroyed"))
|
|
127
|
-
this.loadDeferred = null
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
export default AWSFirehoseCredentialManager
|
|
@@ -1,74 +0,0 @@
|
|
|
1
|
-
import { Appender } from "../Appender.js"
|
|
2
|
-
import { Level } from "../Level.js"
|
|
3
|
-
import { LogContextProvider } from "./LogContextProvider.js"
|
|
4
|
-
import { LogContextContainer } from "./LogContextContainer.js"
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
export class LogContext {
|
|
12
|
-
/**
|
|
13
|
-
* Threshold override level
|
|
14
|
-
*
|
|
15
|
-
* @type {Level}
|
|
16
|
-
*/
|
|
17
|
-
readonly thresholdLevel: Level
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
* If specified, only categories that match will
|
|
21
|
-
* use this context
|
|
22
|
-
*
|
|
23
|
-
* @type {RegExp}
|
|
24
|
-
*/
|
|
25
|
-
readonly pattern: RegExp
|
|
26
|
-
|
|
27
|
-
/**
|
|
28
|
-
* Exclusive, only use this config in context. Defaults to `false`
|
|
29
|
-
*
|
|
30
|
-
* @type {boolean}
|
|
31
|
-
*/
|
|
32
|
-
readonly exclusive: RegExp
|
|
33
|
-
|
|
34
|
-
/**
|
|
35
|
-
* Use a logger context within
|
|
36
|
-
*
|
|
37
|
-
* @param {() => Promise<T>} fn
|
|
38
|
-
* @returns {Promise<T>}
|
|
39
|
-
*/
|
|
40
|
-
use<T = unknown>(fn: () => Promise<T>) {
|
|
41
|
-
return LogContextContainer.runInContext(this, fn)
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
/**
|
|
45
|
-
* Creates a new logger context
|
|
46
|
-
*
|
|
47
|
-
* @param {Appender[]} appenders - additional appenders for the context
|
|
48
|
-
* @param {LoggerContextOptions} options -
|
|
49
|
-
*/
|
|
50
|
-
protected constructor(
|
|
51
|
-
readonly appenders: Appender[],
|
|
52
|
-
options: LoggerContextOptions
|
|
53
|
-
) {
|
|
54
|
-
Object.assign(this, {
|
|
55
|
-
exclusive: false,
|
|
56
|
-
thresholdLevel: null,
|
|
57
|
-
pattern: null,
|
|
58
|
-
...options
|
|
59
|
-
})
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
/**
|
|
63
|
-
* Create a new logger context
|
|
64
|
-
*
|
|
65
|
-
* @param {Appender[]} appenders
|
|
66
|
-
* @param {LoggerContextOptions} options
|
|
67
|
-
* @returns {LogContext}
|
|
68
|
-
*/
|
|
69
|
-
static with(appenders: Appender[], options: LoggerContextOptions = {}) {
|
|
70
|
-
return new LogContext(appenders, options)
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
export type LoggerContextOptions = Partial<Pick<LogContext, "pattern" | "exclusive">>
|
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
import { LogContext } from "./LogContext.js"
|
|
2
|
-
import { DefaultLogContextProvider, LogContextProvider } from "./LogContextProvider.js"
|
|
3
|
-
|
|
4
|
-
const state = {
|
|
5
|
-
provider: DefaultLogContextProvider as LogContextProvider
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
export namespace LogContextContainer {
|
|
10
|
-
|
|
11
|
-
export function setProvider(provider: LogContextProvider) {
|
|
12
|
-
state.provider = provider ?? DefaultLogContextProvider
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
export function getProvider():LogContextProvider {
|
|
16
|
-
return state.provider ?? DefaultLogContextProvider
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
export function currentContext(): Array<LogContext> {
|
|
20
|
-
return getProvider().currentContext?.() ?? []
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
export function runInContext<T = unknown>(context: LogContext, fn: () => Promise<T>): Promise<T> {
|
|
24
|
-
const provider = getProvider()
|
|
25
|
-
return provider.runInContext(context,fn)
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
}
|