Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: building sonic boom safe #316

Merged
merged 4 commits into from
Mar 17, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 2 additions & 3 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,7 @@ const { isColorSupported } = require('colorette')
const pump = require('pump')
const { Transform } = require('readable-stream')
const abstractTransport = require('pino-abstract-transport')
const sonic = require('sonic-boom')
const sjs = require('secure-json-parse')

const colors = require('./lib/colors')
const { ERROR_LIKE_KEYS, MESSAGE_KEY, TIMESTAMP_KEY, LEVEL_KEY, LEVEL_NAMES } = require('./lib/constants')
const {
Expand All @@ -17,6 +15,7 @@ const {
prettifyMetadata,
prettifyObject,
prettifyTime,
buildSafeSonicBoom,
filterLog
} = require('./lib/utils')

Expand Down Expand Up @@ -227,7 +226,7 @@ function build (opts = {}) {
if (typeof opts.destination === 'object' && typeof opts.destination.write === 'function') {
destination = opts.destination
} else {
destination = sonic({
destination = buildSafeSonicBoom({
dest: opts.destination || 1,
append: opts.append,
mkdir: opts.mkdir,
Expand Down
67 changes: 67 additions & 0 deletions lib/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@

const clone = require('rfdc')({ circles: true })
const dateformat = require('dateformat')
const SonicBoom = require('sonic-boom')
const stringifySafe = require('fast-safe-stringify')
const { isMainThread } = require('worker_threads')
const defaultColorizer = require('./colors')()
const {
DATE_FORMAT,
Expand All @@ -23,6 +25,7 @@ module.exports = {
prettifyMetadata,
prettifyObject,
prettifyTime,
buildSafeSonicBoom,
filterLog
}

Expand Down Expand Up @@ -580,3 +583,67 @@ function filterLog (log, ignoreKeys) {
})
return logCopy
}

function noop () {}

/**
* Creates a safe SonicBoom instance
*
* @param {object} opts Options for SonicBoom
*
* @returns {object} A new SonicBoom stream
*/
function buildSafeSonicBoom (opts) {
const stream = new SonicBoom(opts)
stream.on('error', filterBrokenPipe)
// if we are sync: false, we must flush on exit
if (!opts.sync && isMainThread) {
setupOnExit(stream)
}
return stream

function filterBrokenPipe (err) {
if (err.code === 'EPIPE') {
stream.write = noop
stream.end = noop
stream.flushSync = noop
stream.destroy = noop
return
}
stream.removeListener('error', filterBrokenPipe)
}
}

function setupOnExit (stream) {
/* istanbul ignore next */
if (global.WeakRef && global.WeakMap && global.FinalizationRegistry) {
// This is leak free, it does not leave event handlers
const onExit = require('on-exit-leak-free')

onExit.register(stream, autoEnd)

stream.on('close', function () {
onExit.unregister(stream)
})
}
}

/* istanbul ignore next */
function autoEnd (stream, eventName) {
// This check is needed only on some platforms

if (stream.destroyed) {
return
}

if (eventName === 'beforeExit') {
// We still have an event loop, let's use it
stream.flush()
stream.on('drain', function () {
stream.end()
})
} else {
// We do not have an event loop, so flush synchronously
stream.flushSync()
}
}
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
"dateformat": "^4.6.3",
"fast-safe-stringify": "^2.0.7",
"joycon": "^3.1.1",
"on-exit-leak-free": "^0.2.0",
"pino-abstract-transport": "^0.5.0",
"pump": "^3.0.0",
"readable-stream": "^3.6.0",
Expand Down
48 changes: 48 additions & 0 deletions test/lib/utils.public.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@
const tap = require('tap')
const getColorizer = require('../../lib/colors')
const utils = require('../../lib/utils')
const rimraf = require('rimraf')
const { join } = require('path')
const fs = require('fs')

tap.test('prettifyErrorLog', t => {
const { prettifyErrorLog } = utils
Expand Down Expand Up @@ -428,3 +431,48 @@ tap.test('#filterLog with circular references', t => {

t.end()
})

tap.test('buildSafeSonicBoom', t => {
const { buildSafeSonicBoom } = utils

function noop () {}

const file = () => {
const dest = join(__dirname, `${process.pid}-${process.hrtime().toString()}`)
const fd = fs.openSync(dest, 'w')
return { dest, fd }
}

t.test('should not write when error emitted and code is "EPIPE"', async t => {
t.plan(1)

const { fd, dest } = file()
const stream = buildSafeSonicBoom({ sync: true, fd, mkdir: true })
t.teardown(() => rimraf(dest, noop))

stream.emit('error', { code: 'EPIPE' })
stream.write('will not work')

const dataFile = fs.readFileSync(dest)
t.equal(dataFile.length, 0)
})

t.test('should stream.write works when error code is not "EPIPE"', async t => {
t.plan(3)
const { fd, dest } = file()
const stream = buildSafeSonicBoom({ sync: true, fd, mkdir: true })

t.teardown(() => rimraf(dest, noop))

stream.on('error', () => t.pass('error emitted'))

stream.emit('error', 'fake error description')

t.ok(stream.write('will work'))

const dataFile = fs.readFileSync(dest)
t.equal(dataFile.toString(), 'will work')
})

t.end()
})