diff --git a/docs/transports.md b/docs/transports.md index 7e0295476..9fd377abf 100644 --- a/docs/transports.md +++ b/docs/transports.md @@ -15,7 +15,7 @@ now referred to as [Legacy Transports](#legacy-transports). From Pino v7 and upwards transports can also operate inside a [Worker Thread][worker-thread] and can be used or configured via the options object passed to `pino` on initialization. -In this case the transports would always operate asynchronously, and logs would be +In this case the transports would always operate asynchronously (unless `options.sync` is set to `true` in transport options), and logs would be flushed as quickly as possible (there is nothing to do). [worker-thread]: https://nodejs.org/dist/latest-v14.x/docs/api/worker_threads.html @@ -124,6 +124,19 @@ const transport = pino.transport({ pino(transport) ``` +To make pino log synchronously, pass `sync: true` to transport options. +```js +const pino = require('pino') +const transport = pino.transport({ + targets: [ + { target: '/absolute/path/to/my-transport.mjs', level: 'error' }, + ], + dedupe: true, + sync: true, +}); +pino(transport); +``` + For more details on `pino.transport` see the [API docs for `pino.transport`][pino-transport]. [pino-transport]: /docs/api.md#pino-transport diff --git a/lib/transport.js b/lib/transport.js index e768d35bb..8b5b48aba 100644 --- a/lib/transport.js +++ b/lib/transport.js @@ -17,11 +17,12 @@ function setupOnExit (stream) { }) } -function buildStream (filename, workerData, workerOpts) { +function buildStream (filename, workerData, workerOpts, sync) { const stream = new ThreadStream({ filename, workerData, - workerOpts + workerOpts, + sync }) stream.on('ready', onReady) @@ -71,7 +72,7 @@ function flush (stream) { } function transport (fullOptions) { - const { pipeline, targets, levels, dedupe, worker = {}, caller = getCallers() } = fullOptions + const { pipeline, targets, levels, dedupe, worker = {}, caller = getCallers(), sync = false } = fullOptions const options = { ...fullOptions.options @@ -126,7 +127,7 @@ function transport (fullOptions) { options.pinoWillSendConfig = true - return buildStream(fixTarget(target), options, worker) + return buildStream(fixTarget(target), options, worker, sync) function fixTarget (origin) { origin = bundlerOverrides[origin] || origin diff --git a/test/transport/syncTrue.test.js b/test/transport/syncTrue.test.js new file mode 100644 index 000000000..1d34ebce1 --- /dev/null +++ b/test/transport/syncTrue.test.js @@ -0,0 +1,55 @@ +'use strict' + +const pino = require('../..') +const { join } = require('node:path') +const { readFileSync } = require('node:fs') +const { test } = require('tap') +const { file } = require('../helper') + +test('thread-stream sync true should log synchronously', async (t) => { + const outputPath = file() + + function getOutputLogLines () { + return (readFileSync(outputPath)).toString().trim().split('\n').map(JSON.parse) + } + + const transport = pino.transport({ + target: join(__dirname, '..', 'fixtures', 'to-file-transport.js'), + options: { destination: outputPath, flush: true }, + sync: true + }) + const instance = pino(transport) + + var value = { message: 'sync' } + instance.info(value) + instance.info(value) + instance.info(value) + instance.info(value) + instance.info(value) + instance.info(value) + let interrupt = false + let flushData + let loopCounter = 0 + + // Start a synchronous loop + while (!interrupt && loopCounter < (process.env.MAX_TEST_LOOP_ITERATION || 20000)) { + try { + loopCounter++ + const data = getOutputLogLines() + flushData = data + if (data) { + interrupt = true + break + } + } catch (error) { + // File may not exist yet + // Wait till MAX_TEST_LOOP_ITERATION iterations + } + } + + if (!interrupt) { + throw new Error('Sync loop did not get interrupt') + } + + t.equal(flushData.length, 6) +})