Skip to content

Commit 30fafdd

Browse files
authored
feat: Remove BFJ for async streaming (#227)
* Remove BFJ for async streaming - BFJ has been archived repository for some time now - Its use has been limited as writeErrorLogFile loads the errorLog completely into memory, transforms it, then writes it to a log file using bfj. Obviously if the data is so large that it requires bfj, then loading it completely into to memory would more than likely crash the process. - BFJ does not have @types/bfj available * Apply lint fixes * Fix rename issue * Improve expectations on writeErrorLogFile tests * Fix mocking createWriteStream in tests * Remove bfj from dependabot * Revert to using Writeable from node:stream for mocking createWriteStream
1 parent f466c40 commit 30fafdd

File tree

6 files changed

+172
-347
lines changed

6 files changed

+172
-347
lines changed

.github/dependabot.yml

-3
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,6 @@ updates:
99
timezone: UTC
1010
open-pull-requests-limit: 10
1111
ignore:
12-
- dependency-name: bfj # ignore ESM only versions
13-
versions:
14-
- ">=8.0.0"
1512
- dependency-name: figures # ignore ESM only versions
1613
versions:
1714
- ">=4.0.0"

lib/logging.js

+73-34
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
1-
import EventEmitter from 'events'
2-
3-
import bfj from 'bfj'
4-
import figures from 'figures'
51
import format from 'date-fns/format'
62
import parseISO from 'date-fns/parseISO'
7-
3+
import figures from 'figures'
4+
import EventEmitter from 'node:events'
5+
import { createWriteStream } from 'node:fs'
6+
import { Readable, Transform } from 'node:stream'
7+
import { pipeline } from 'node:stream/promises'
88
import getEntityName from './get-entity-name'
99

1010
export const logEmitter = new EventEmitter()
@@ -47,7 +47,9 @@ export function formatLogMessageOneLine (logMessage) {
4747
errorOutput.push(`Entity: ${getEntityName(data.entity)}`)
4848
}
4949
if ('details' in data && 'errors' in data.details) {
50-
const errorList = data.details.errors.map((error) => error.details || error.name)
50+
const errorList = data.details.errors.map(
51+
(error) => error.details || error.name
52+
)
5153
errorOutput.push(`Details: ${errorList.join(', ')}`)
5254
}
5355
if ('requestId' in data) {
@@ -78,7 +80,9 @@ export function formatLogMessageLogfile (logMessage) {
7880
} catch (err) {
7981
// Fallback for errors without API information
8082
if (logMessage.error.stack) {
81-
logMessage.error.stacktrace = logMessage.error.stack.toString().split(/\n +at /)
83+
logMessage.error.stacktrace = logMessage.error.stack
84+
.toString()
85+
.split(/\n +at /)
8286
}
8387
}
8488

@@ -94,47 +98,78 @@ export function formatLogMessageLogfile (logMessage) {
9498
// Display all errors
9599
export function displayErrorLog (errorLog) {
96100
if (errorLog.length) {
97-
const count = errorLog.reduce((count, curr) => {
98-
if (Object.prototype.hasOwnProperty.call(curr, 'warning')) count.warnings++
99-
else if (Object.prototype.hasOwnProperty.call(curr, 'error')) count.errors++
100-
return count
101-
}, { warnings: 0, errors: 0 })
102-
103-
console.log(`\n\nThe following ${count.errors} errors and ${count.warnings} warnings occurred:\n`)
101+
const count = errorLog.reduce(
102+
(count, curr) => {
103+
if (Object.prototype.hasOwnProperty.call(curr, 'warning')) {
104+
count.warnings++
105+
} else if (Object.prototype.hasOwnProperty.call(curr, 'error')) {
106+
count.errors++
107+
}
108+
return count
109+
},
110+
{ warnings: 0, errors: 0 }
111+
)
112+
113+
console.log(
114+
`\n\nThe following ${count.errors} errors and ${count.warnings} warnings occurred:\n`
115+
)
104116

105117
errorLog
106-
.map((logMessage) => `${format(parseISO(logMessage.ts), 'HH:mm:ss')} - ${formatLogMessageOneLine(logMessage)}`)
118+
.map(
119+
(logMessage) =>
120+
`${format(
121+
parseISO(logMessage.ts),
122+
'HH:mm:ss'
123+
)} - ${formatLogMessageOneLine(logMessage)}`
124+
)
107125
.map((logMessage) => console.log(logMessage))
108126

109127
return
110128
}
111129
console.log('No errors or warnings occurred')
112130
}
113131

114-
// Write all log messages instead of infos to the error log file
115-
export function writeErrorLogFile (destination, errorLog) {
116-
const logFileData = errorLog
117-
.map(formatLogMessageLogfile)
118-
119-
return bfj.write(destination, logFileData, {
120-
circular: 'ignore',
121-
space: 2
132+
/**
133+
* Write all log messages instead of infos to the error log file
134+
* @param {import('node:fs').PathLike} destination
135+
* @param {Record<string, unknown>[]} errorLog
136+
* @returns {Promise<void>}
137+
*/
138+
export async function writeErrorLogFile (destination, errorLog) {
139+
const formatLogTransformer = new Transform({
140+
objectMode: true,
141+
transform: (chunk, encoding, callback) => {
142+
const formattedChunk = formatLogMessageLogfile(chunk)
143+
callback(null, Buffer.from(JSON.stringify(formattedChunk)))
144+
}
122145
})
123-
.then(() => {
124-
console.log('\nStored the detailed error log file at:')
125-
console.log(destination)
126-
})
127-
.catch((e) => {
128-
// avoid crashing when writing the log file fails
129-
console.error(e)
130-
})
146+
147+
const logFileWriteStream = createWriteStream(destination)
148+
149+
try {
150+
await pipeline([
151+
Readable.from(errorLog),
152+
formatLogTransformer,
153+
logFileWriteStream
154+
])
155+
156+
console.log('\nStored the detailed error log file at:')
157+
console.log(destination)
158+
} catch (err) {
159+
// avoid crashing when writing the log file fails
160+
console.error(err)
161+
}
131162
}
132163

133-
// Init listeners for log messages, transform them into proper format and logs/displays them
164+
/**
165+
* Init listeners for log messages, transform them into proper format and logs/displays them
166+
* @param {Record<string,unknown>[]} log
167+
* @returns {Promise<void>}
168+
*/
134169
export function setupLogging (log) {
135170
function errorLogger (level, error) {
136171
const logMessage = {
137-
ts: (new Date()).toJSON(),
172+
ts: new Date().toJSON(),
138173
level,
139174
[level]: error
140175
}
@@ -149,7 +184,11 @@ export function setupLogging (log) {
149184
logEmitter.addListener('error', (error) => errorLogger('error', error))
150185
}
151186

152-
// Format log message to display them as task status
187+
/**
188+
* Format log message to display them as task status
189+
* @template {Ctx}
190+
* @param {import('listr').ListrTaskWrapper<Ctx>} task
191+
*/
153192
export function logToTaskOutput (task) {
154193
function logToTask (logMessage) {
155194
const content = formatLogMessageOneLine(logMessage)

0 commit comments

Comments
 (0)