From 11a676826e83a807eac74a3328996d10bc1c720a Mon Sep 17 00:00:00 2001 From: FoxxMD Date: Wed, 6 Mar 2024 09:35:09 -0500 Subject: [PATCH] feat: Allow file to be a function Enables users to provide a function that dynamically changes the base log file name on roll --- README.md | 1 + lib/utils.js | 15 ++++++++++----- pino-roll.js | 11 +++++++++-- test/lib/utils.test.js | 12 +++++++++++- test/pino-roll.test.js | 18 +++++++++++++++++- 5 files changed, 48 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 882413b..9c52dbd 100644 --- a/README.md +++ b/README.md @@ -46,6 +46,7 @@ You can specify any of [Sonic-Boom options](https://github.com/pinojs/sonic-boom Number will be appened to this file name. When the parent folder already contains numbered files, numbering will continue based on the highest number. If this path does not exist, the logger with throw an error unless you set `mkdir` to `true`. + `file` may also be a function that returns a string. * `size?`: the maximum size of a given log file. Can be combined with frequency. diff --git a/lib/utils.js b/lib/utils.js index c84cd8f..0be7f5e 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -62,14 +62,19 @@ function getNext (frequency) { return getNextCustom(frequency) } -function buildFileName (fileName, lastNumber = 1, extension) { - if (!fileName) { +function getFileName (fileVal) { + if (!fileVal) { throw new Error('No file name provided') } - return `${fileName}.${lastNumber}${extension ?? ''}` + return typeof fileVal === 'function' ? fileVal() : fileVal } -async function detectLastNumber (fileName, time = null) { +function buildFileName (fileVal, lastNumber = 1, extension) { + return `${getFileName(fileVal)}.${lastNumber}${extension ?? ''}` +} + +async function detectLastNumber (fileVal, time = null) { + const fileName = getFileName(fileVal) try { const numbers = await readFileTrailingNumbers(dirname(fileName), time) return numbers.sort((a, b) => b - a)[0] @@ -102,4 +107,4 @@ async function isMatchingTime (filePath, time) { return birthtimeMs >= time } -module.exports = { buildFileName, detectLastNumber, parseFrequency, getNext, parseSize } +module.exports = { buildFileName, detectLastNumber, parseFrequency, getNext, parseSize, getFileName } diff --git a/pino-roll.js b/pino-roll.js index 800d59f..68c42e9 100644 --- a/pino-roll.js +++ b/pino-roll.js @@ -3,12 +3,19 @@ const SonicBoom = require('sonic-boom') const { buildFileName, detectLastNumber, parseSize, parseFrequency, getNext } = require('./lib/utils') +/** + * A function that returns a string path to the base file name + * + * @typedef {function} LogFilePath + * @returns {string} + */ + /** * @typedef {object} Options * - * @property {string} file - Absolute or relative path to the log file. + * @property {string|LogFilePath} file - Absolute or relative path to the log file. * Your application needs the write right on the parent folder. - * Number will be appened to this file name. + * Number will be appended to this file name. * When the parent folder already contains numbered files, numbering will continue based on the highest number. * If this path does not exist, the logger with throw an error unless you set `mkdir` to `true`. * diff --git a/test/lib/utils.test.js b/test/lib/utils.test.js index 6810e7c..ba1225c 100644 --- a/test/lib/utils.test.js +++ b/test/lib/utils.test.js @@ -5,7 +5,7 @@ const { writeFile, rm, stat } = require('fs/promises') const { join } = require('path') const { test } = require('tap') -const { buildFileName, detectLastNumber, getNext, parseFrequency, parseSize } = require('../../lib/utils') +const { buildFileName, detectLastNumber, getNext, parseFrequency, parseSize, getFileName } = require('../../lib/utils') const { cleanAndCreateFolder, sleep } = require('../utils') test('parseSize()', async ({ equal, throws }) => { @@ -54,10 +54,18 @@ test('getNext()', async ({ same, throws }) => { same(getNext(custom), Date.now() + custom, 'supports custom frequency and does not return start') }) +test('getFileName()', async ({ equal, throws }) => { + const strFunc = () => 'my-func' + throws(getFileName, 'throws on empty input') + equal(getFileName('my-file'), 'my-file', 'returns string when string given') + equal(getFileName(strFunc), 'my-func', 'invokes function when function given') +}) + test('buildFileName()', async ({ equal, throws }) => { const ext = '.json' throws(buildFileName, 'throws on empty input') equal(buildFileName('my-file'), 'my-file.1', 'appends 1 by default') + equal(buildFileName(() => 'my-func'), 'my-func.1', 'appends 1 by default') equal(buildFileName('my-file', 5, ext), 'my-file.5.json', 'appends number and extension') }) @@ -67,11 +75,13 @@ test('detectLastNumber()', async ({ test, beforeEach }) => { test('given existing files', async ({ equal }) => { const fileName = join(folder, 'file.5') + const fileNameFunc = () => fileName await writeFile(join(folder, 'file.1'), '') await writeFile(join(folder, 'file.5'), '') await writeFile(join(folder, 'file.10'), '') await writeFile(join(folder, 'file.7'), '') equal(await detectLastNumber(fileName), 10, 'detects highest existing number') + equal(await detectLastNumber(fileNameFunc), 10, 'detects highest existing number when given func') }) test('given existing files and a time', async ({ equal }) => { diff --git a/test/pino-roll.test.js b/test/pino-roll.test.js index cccbb8a..3f6fccc 100644 --- a/test/pino-roll.test.js +++ b/test/pino-roll.test.js @@ -1,9 +1,10 @@ 'use strict' const { once } = require('events') -const { stat, readFile, writeFile } = require('fs/promises') +const { stat, readFile, writeFile, readdir } = require('fs/promises') const { join } = require('path') const { test, beforeEach } = require('tap') +const { format } = require('date-fns') const { buildStream, cleanAndCreateFolder, sleep } = require('./utils') @@ -35,6 +36,21 @@ test('rotate file based on time', async ({ ok, notOk, rejects }) => { rejects(stat(`${file}.4`), 'no other files created') }) +test('rotate file based on time and parse filename func', async ({ ok, notOk, rejects }) => { + const file = join(logFolder, 'log') + const fileFunc = () => `${file}-${format(new Date(), 'HH-mm-ss-SSS')}` + const stream = await buildStream({ frequency: 100, file: fileFunc }) + stream.write('logged message #1\n') + stream.write('logged message #2\n') + await sleep(110) + stream.write('logged message #3\n') + stream.write('logged message #4\n') + await sleep(110) + stream.end() + const files = await readdir(logFolder) + ok(files.length === 3, 'created three files') +}) + test('rotate file based on size', async ({ ok, rejects }) => { const file = join(logFolder, 'log') const size = 20