@wireio/shared 1.0.2 → 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/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,254 +0,0 @@
|
|
|
1
|
-
import type { LogRecord } from "../LogRecord"
|
|
2
|
-
import type { Appender } from "../Appender"
|
|
3
|
-
import type { LevelKind } from "../Level"
|
|
4
|
-
import Debug from "debug"
|
|
5
|
-
import {
|
|
6
|
-
ConsoleAppender, ConsoleAppenderConfig, kDefaultConsoleAppenderConfig
|
|
7
|
-
} from "./ConsoleAppender.js"
|
|
8
|
-
import type { Formatter } from "../Formatter"
|
|
9
|
-
import { isEmpty, negate } from "lodash"
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
// let chalk: any = null
|
|
13
|
-
// if (
|
|
14
|
-
// typeof process !== "undefined" &&
|
|
15
|
-
// (process as any).type !== "renderer" &&
|
|
16
|
-
// (process as any).browser !== true
|
|
17
|
-
// ) {
|
|
18
|
-
// chalk = require("chalk")
|
|
19
|
-
// }
|
|
20
|
-
|
|
21
|
-
const useColors = false
|
|
22
|
-
|
|
23
|
-
const makeColorFn = (fn: (chalk: any, s: string) => string) => {
|
|
24
|
-
// if (!useColors) {
|
|
25
|
-
// return (s: string) => s
|
|
26
|
-
// }
|
|
27
|
-
//
|
|
28
|
-
// return (s: string) => fn(chalk, s)
|
|
29
|
-
return (s: string) => s
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
const colorFns: Record<LevelKind, (s: string) => string> = {
|
|
33
|
-
debug: makeColorFn((c, s) => c.green(s)),
|
|
34
|
-
error: makeColorFn((c, s) => c.red(s)),
|
|
35
|
-
fatal: makeColorFn((c, s) => c.bgRed.black(s)),
|
|
36
|
-
info: makeColorFn((c, s) => c.blue(s)),
|
|
37
|
-
trace: makeColorFn((c, s) => c.gray(s)),
|
|
38
|
-
warn: makeColorFn((c, s) => c.yellow(s))
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
/**
|
|
42
|
-
* DebugAppender Configuration
|
|
43
|
-
*/
|
|
44
|
-
export interface DebugAppenderConfig extends ConsoleAppenderConfig {
|
|
45
|
-
hideDate: boolean
|
|
46
|
-
timeOnly: boolean
|
|
47
|
-
useConsole: boolean
|
|
48
|
-
useStdout: boolean
|
|
49
|
-
categoryPrefix: string
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
/**
|
|
53
|
-
* Partial of config, used for shortening instead of Partial<...>
|
|
54
|
-
*/
|
|
55
|
-
export type DebugAppenderOptions = Partial<DebugAppenderConfig>
|
|
56
|
-
|
|
57
|
-
const debuggers = new Map<string, Debug.Debugger>()
|
|
58
|
-
|
|
59
|
-
//const debugAppPrefix = "app"
|
|
60
|
-
|
|
61
|
-
function getDebug({categoryPrefix}: DebugAppenderConfig, cat: string) {
|
|
62
|
-
let debug = debuggers.get(cat)
|
|
63
|
-
if (!debug) {
|
|
64
|
-
debug = Debug([categoryPrefix, cat].filter(negate(isEmpty)).join(":"))
|
|
65
|
-
debuggers.set(cat, debug)
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
return debug
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
// const timeFormatter = new Intl.DateTimeFormat('en-US', {
|
|
72
|
-
// timeStyle: "short"
|
|
73
|
-
// })
|
|
74
|
-
|
|
75
|
-
/**
|
|
76
|
-
*
|
|
77
|
-
* Get ISO date if `hideDate !== true`
|
|
78
|
-
*
|
|
79
|
-
* @param {DebugFormatterContext}
|
|
80
|
-
* @returns
|
|
81
|
-
*/
|
|
82
|
-
function getDate({ debugConfig: config }: DebugFormatterContext) {
|
|
83
|
-
if (config.hideDate) {
|
|
84
|
-
return ""
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
const date = new Date()
|
|
88
|
-
return config.timeOnly
|
|
89
|
-
? date.toLocaleTimeString("default", {
|
|
90
|
-
hour12: false
|
|
91
|
-
})
|
|
92
|
-
: date.toISOString()
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
/**
|
|
96
|
-
*
|
|
97
|
-
* Formats args for output, directly based on Debug.formatArgs
|
|
98
|
-
*
|
|
99
|
-
* @param {DebugFormatterContext} context
|
|
100
|
-
* @param {any[]} args
|
|
101
|
-
*/
|
|
102
|
-
function formatArgs(
|
|
103
|
-
context: DebugFormatterContext,
|
|
104
|
-
record: LogRecord,
|
|
105
|
-
args: any[]
|
|
106
|
-
) {
|
|
107
|
-
const { debug } = context
|
|
108
|
-
const { namespace: name } = debug as any
|
|
109
|
-
|
|
110
|
-
const prefix = colorFns[record.level](
|
|
111
|
-
[
|
|
112
|
-
getDate(context),
|
|
113
|
-
Debug.humanize(debug.diff),
|
|
114
|
-
`(` + record.level.toUpperCase() + `)`,
|
|
115
|
-
`[${name}]`
|
|
116
|
-
]
|
|
117
|
-
.filter(negate(isEmpty))
|
|
118
|
-
.join(" ")
|
|
119
|
-
)
|
|
120
|
-
const msg = [prefix, args[0]].filter(negate(isEmpty)).join(" ")
|
|
121
|
-
|
|
122
|
-
args[0] = msg
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
/**
|
|
126
|
-
* Context for using `debugFormatter`
|
|
127
|
-
*/
|
|
128
|
-
export type DebugFormatterContext = {
|
|
129
|
-
debugConfig: DebugAppenderConfig
|
|
130
|
-
debug: Debug.Debugger
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
/**
|
|
134
|
-
* Uses `debug` package for logging, very useful
|
|
135
|
-
*
|
|
136
|
-
* @param {LogRecord} record to format
|
|
137
|
-
* @param {DebugFormatterContext} context for this formatter
|
|
138
|
-
* @returns
|
|
139
|
-
*/
|
|
140
|
-
export const debugFormatter: Formatter<Array<any>, DebugFormatterContext> = (
|
|
141
|
-
record,
|
|
142
|
-
{ debug, debugConfig: config }
|
|
143
|
-
): any[] => {
|
|
144
|
-
const { level, message, data, args: argsIn, category, timestamp } = record
|
|
145
|
-
const args = [message, ...argsIn]
|
|
146
|
-
args[0] = Debug.coerce(args[0])
|
|
147
|
-
|
|
148
|
-
if (typeof args[0] !== "string") {
|
|
149
|
-
// Anything else let's inspect with %O
|
|
150
|
-
args.unshift("%O")
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
// Apply any `formatters` transformations
|
|
154
|
-
let index = 0
|
|
155
|
-
args[0] = args[0].replace(/%([a-zA-Z%])/g, (match, format) => {
|
|
156
|
-
// If we encounter an escaped % then don't increase the array index
|
|
157
|
-
if (match === "%%") {
|
|
158
|
-
return "%"
|
|
159
|
-
}
|
|
160
|
-
index++
|
|
161
|
-
const formatter = Debug.formatters[format]
|
|
162
|
-
if (typeof formatter === "function") {
|
|
163
|
-
const val = args[index]
|
|
164
|
-
match = formatter.call(debug, val)
|
|
165
|
-
|
|
166
|
-
// Now we need to remove `args[index]` since it's inlined in the `format`
|
|
167
|
-
args.splice(index, 1)
|
|
168
|
-
index--
|
|
169
|
-
}
|
|
170
|
-
return match
|
|
171
|
-
})
|
|
172
|
-
|
|
173
|
-
// Apply env-specific formatting (colors, etc.)
|
|
174
|
-
formatArgs({ debug, debugConfig: config }, record, args)
|
|
175
|
-
|
|
176
|
-
return args
|
|
177
|
-
}
|
|
178
|
-
// [
|
|
179
|
-
// `[${category}] (${level}) ${message}`,
|
|
180
|
-
// ...(Array.isArray(args) ? args : [args])
|
|
181
|
-
// ]
|
|
182
|
-
|
|
183
|
-
/**
|
|
184
|
-
* Default console config
|
|
185
|
-
* @type {DebugAppenderConfig}
|
|
186
|
-
*/
|
|
187
|
-
const defaultConfig: DebugAppenderConfig = {
|
|
188
|
-
hideDate: false,
|
|
189
|
-
timeOnly: true,
|
|
190
|
-
useConsole: false,
|
|
191
|
-
useStdout: true,
|
|
192
|
-
categoryPrefix: null,
|
|
193
|
-
...kDefaultConsoleAppenderConfig,
|
|
194
|
-
formatter: debugFormatter
|
|
195
|
-
}
|
|
196
|
-
/**
|
|
197
|
-
* Debug appender, the simple default appender used
|
|
198
|
-
* everywhere OOB
|
|
199
|
-
*/
|
|
200
|
-
export class DebugAppender<Record extends LogRecord>
|
|
201
|
-
extends ConsoleAppender<Record>
|
|
202
|
-
implements Appender<Record>
|
|
203
|
-
{
|
|
204
|
-
readonly debugConfig: DebugAppenderConfig
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
/**
|
|
208
|
-
* Handle log records, transform, push to ES
|
|
209
|
-
*
|
|
210
|
-
* @param record
|
|
211
|
-
*/
|
|
212
|
-
append(record: Record): void {
|
|
213
|
-
const { level, message, args: argsIn, category } = record
|
|
214
|
-
|
|
215
|
-
// if (!this.levels.includes(level)) {
|
|
216
|
-
// super.append(record)
|
|
217
|
-
// return
|
|
218
|
-
// }
|
|
219
|
-
|
|
220
|
-
const debug = getDebug(this.debugConfig, category) as any
|
|
221
|
-
let prevTime = debug.prevTime
|
|
222
|
-
// Set `diff` timestamp
|
|
223
|
-
const curr = Number(new Date())
|
|
224
|
-
const ms = curr - (prevTime || curr)
|
|
225
|
-
debug.diff = ms
|
|
226
|
-
debug.prev = prevTime
|
|
227
|
-
debug.curr = curr
|
|
228
|
-
debug.prevTime = curr
|
|
229
|
-
const logFn = debug.log || Debug.log
|
|
230
|
-
const args = debugFormatter!(record, { debug, debugConfig: this.debugConfig })
|
|
231
|
-
|
|
232
|
-
if (this.debugConfig.useConsole) {
|
|
233
|
-
super.write(level, ...args)
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
if (this.debugConfig.useStdout) {
|
|
237
|
-
logFn.apply(debug, args)
|
|
238
|
-
}
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
/**
|
|
242
|
-
*
|
|
243
|
-
* @param {Partial<DebugAppenderOptions>} options
|
|
244
|
-
*/
|
|
245
|
-
constructor(options: Partial<Omit<DebugAppenderOptions, "formatter">> = {}) {
|
|
246
|
-
super({
|
|
247
|
-
...defaultConfig,
|
|
248
|
-
...options,
|
|
249
|
-
formatter: debugFormatter
|
|
250
|
-
})
|
|
251
|
-
|
|
252
|
-
this.debugConfig = this.config as DebugAppenderConfig
|
|
253
|
-
}
|
|
254
|
-
}
|
|
@@ -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
|