@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.
Files changed (39) hide show
  1. package/package.json +5 -1
  2. package/src/guards/applyTypeGuardExtras.ts +0 -9
  3. package/src/guards/array.ts +0 -5
  4. package/src/guards/assert.ts +0 -83
  5. package/src/guards/class.ts +0 -56
  6. package/src/guards/guard-tools.ts +0 -78
  7. package/src/guards/index.ts +0 -7
  8. package/src/guards/primitive.ts +0 -77
  9. package/src/guards/types.ts +0 -15
  10. package/src/helpers/Deferred.ts +0 -149
  11. package/src/helpers/index.ts +0 -1
  12. package/src/index.ts +0 -3
  13. package/src/logging/Appender.ts +0 -5
  14. package/src/logging/Formatter.ts +0 -8
  15. package/src/logging/InternalLogger.ts +0 -6
  16. package/src/logging/Level.ts +0 -19
  17. package/src/logging/LogRecord.ts +0 -17
  18. package/src/logging/Logger.ts +0 -222
  19. package/src/logging/LoggingManager.ts +0 -306
  20. package/src/logging/appenders/ConsoleAppender.ts +0 -110
  21. package/src/logging/appenders/DebugAppender.ts +0 -254
  22. package/src/logging/appenders/FileAppender.ts +0 -281
  23. package/src/logging/appenders/aws-firehose/AWSFirehoseAppender.ts +0 -176
  24. package/src/logging/appenders/aws-firehose/AWSFirehoseCredentialManager.ts +0 -132
  25. package/src/logging/appenders/index.ts +0 -3
  26. package/src/logging/context/LogContext.ts +0 -74
  27. package/src/logging/context/LogContextContainer.ts +0 -28
  28. package/src/logging/context/LogContextProvider.ts +0 -43
  29. package/src/logging/context/index.ts +0 -3
  30. package/src/logging/context/providers/node/NodeContextProvider.ts +0 -45
  31. package/src/logging/context/providers/node/index.ts +0 -3
  32. package/src/logging/examples/example-debug-appender.ts +0 -19
  33. package/src/logging/examples/example-file-appender.ts +0 -19
  34. package/src/logging/getLogger.ts +0 -9
  35. package/src/logging/index-node.ts +0 -2
  36. package/src/logging/index.ts +0 -11
  37. package/src/logging/util.ts +0 -6
  38. package/tsconfig.json +0 -9
  39. 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