Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

pino-roll

Package Overview
Dependencies
Maintainers
2
Versions
9
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

pino-roll - npm Package Compare versions

Comparing version 1.0.0-rc.1 to 1.0.0

47

lib/utils.js
'use strict'
const { readdir, stat } = require('fs/promises')
const { readdir, stat, unlink } = require('fs/promises')
const { dirname, join } = require('path')

@@ -43,2 +43,13 @@

function validateLimitOptions (limit) {
if (limit) {
if (typeof limit !== 'object') {
throw new Error('limit must be an object')
}
if (typeof limit.count !== 'number' || limit.count <= 0) {
throw new Error('limit.count must be a number greater than 0')
}
}
}
function getNextDay (start) {

@@ -66,10 +77,15 @@ return new Date(start + 24 * 60 * 60 * 1000).setHours(0, 0, 0, 0)

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 {

@@ -86,3 +102,3 @@ const numbers = await readFileTrailingNumbers(dirname(fileName), time)

for (const file of await readdir(folder)) {
if (time && !await isMatchingTime(join(folder, file), time)) {
if (time && !(await isMatchingTime(join(folder, file), time))) {
continue

@@ -108,2 +124,19 @@ }

module.exports = { buildFileName, detectLastNumber, parseFrequency, getNext, parseSize }
async function checkFileRemoval (files, { count }) {
if (files.length > count) {
const filesToRemove = files.splice(0, files.length - 1 - count)
await Promise.allSettled(filesToRemove.map(file => unlink(file)))
}
return files
}
module.exports = {
buildFileName,
checkFileRemoval,
detectLastNumber,
parseFrequency,
getNext,
parseSize,
getFileName,
validateLimitOptions
}

15

package.json
{
"name": "pino-roll",
"version": "1.0.0-rc.1",
"version": "1.0.0",
"description": "A Pino transport that automatically rolls your log files",

@@ -14,3 +14,3 @@ "main": "pino-roll.js",

"type": "git",
"url": "git+https://github.com/feugy/pino-roll.git"
"url": "git+https://github.com/mcollina/pino-roll.git"
},

@@ -22,10 +22,13 @@ "keywords": [

"author": "Damien Simonin Feugas <damien.feugas@gmail.com>",
"contributors": [
"Matteo Collina <hello@matteocollina.com>"
],
"license": "MIT",
"dependencies": {
"sonic-boom": "^2.8.0"
"sonic-boom": "^3.8.0"
},
"devDependencies": {
"date-fns": "^2.28.0",
"husky": "^7.0.4",
"pino": "^7.9.2",
"date-fns": "^3.6.0",
"husky": "^9.0.11",
"pino": "^8.19.0",
"snazzy": "^9.0.0",

@@ -32,0 +35,0 @@ "standard": "^17.0.0-2",

'use strict'
const SonicBoom = require('sonic-boom')
const { buildFileName, detectLastNumber, parseSize, parseFrequency, getNext } = require('./lib/utils')
const {
buildFileName,
checkFileRemoval,
detectLastNumber,
parseSize,
parseFrequency,
getNext,
validateLimitOptions
} = 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.

@@ -28,5 +43,13 @@ * If this path does not exist, the logger with throw an error unless you set `mkdir` to `true`.

* @property {string} extension? - When specified, appends a file extension after the file number.
*
* @property {LimitOptions} limit? - strategy used to remove oldest files when rotating them.
*/
/**
* @typedef {object} LimitOptions
*
* @property {number} count? -number of log files, **in addition to the currently used file**.
*/
/**
* @typedef {Options & import('sonic-boom').SonicBoomOpts} PinoRollOptions

@@ -42,3 +65,11 @@ */

*/
module.exports = async function ({ file, size, frequency, extension, ...opts } = {}) {
module.exports = async function ({
file,
size,
frequency,
extension,
limit,
...opts
} = {}) {
validateLimitOptions(limit)
const frequencySpec = parseFrequency(frequency)

@@ -48,6 +79,8 @@

const fileName = buildFileName(file, number, extension)
const createdFileNames = [fileName]
let currentSize = 0
const maxSize = parseSize(size)
const destination = new SonicBoom({ ...opts, dest: buildFileName(file, number, extension) })
const destination = new SonicBoom({ ...opts, dest: fileName })

@@ -74,3 +107,8 @@ let rollTimeout

function roll () {
destination.reopen(buildFileName(file, ++number, extension))
const fileName = buildFileName(file, ++number, extension)
destination.reopen(fileName)
if (limit) {
createdFileNames.push(fileName)
checkFileRemoval(createdFileNames, limit)
}
}

@@ -77,0 +115,0 @@

# pino-roll
[![npm version](https://img.shields.io/npm/v/pino-roll)](https://www.npmjs.com/package/pino-roll)
[![Build Status](https://img.shields.io/github/workflow/status/feugy/pino-roll/CI)](https://github.com/feugy/pino-roll/actions)
[![Known Vulnerabilities](https://snyk.io/test/github/feugy/pino-roll/badge.svg)](https://snyk.io/test/github/feugy/pino-roll)
<!-- [![Coverage Status](https://coveralls.io/repos/github/feugy/pino-roll/badge.svg?branch=master)](https://coveralls.io/github/feugy/pino-roll?branch=master) -->
[![js-standard-style](https://img.shields.io/badge/code%20style-standard-brightgreen.svg?style=flat)](https://standardjs.com/)
A Pino transport that automatically rolls your log files
A Pino transport that automatically rolls your log files.

@@ -49,2 +44,3 @@ ## Install

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.

@@ -63,6 +59,13 @@ * `size?`: the maximum size of a given log file.

* `extension?` appends the provided string after the file number.
* `extension?`: appends the provided string after the file number.
* `limit?`: strategy used to remove oldest files when rotating them:
* `limit.count?`: number of log files, **in addition to the currently used file**.
Please not that `limit` only considers **created log files**. It will not consider any pre-existing files.
Therefore, starting your logger with a limit will never tries deleting older log files, created during previous executions.
## License
MIT

@@ -8,3 +8,11 @@ 'use strict'

const { buildFileName, detectLastNumber, getNext, parseFrequency, parseSize } = require('../../lib/utils')
const {
buildFileName,
detectLastNumber,
getNext,
parseFrequency,
parseSize,
getFileName,
validateLimitOptions
} = require('../../lib/utils')
const { cleanAndCreateFolder, sleep } = require('../utils')

@@ -58,2 +66,9 @@

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 }) => {

@@ -63,2 +78,3 @@ const ext = '.json'

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')

@@ -73,2 +89,3 @@ })

const fileName = join(folder, 'file.5')
const fileNameFunc = () => fileName
await writeFile(join(folder, 'file.1'), '')

@@ -79,2 +96,3 @@ await writeFile(join(folder, 'file.5'), '')

equal(await detectLastNumber(fileName), 10, 'detects highest existing number')
equal(await detectLastNumber(fileNameFunc), 10, 'detects highest existing number when given func')
})

@@ -110,1 +128,10 @@

})
test('validateLimitOptions()', async ({ doesNotThrow, throws }) => {
doesNotThrow(() => validateLimitOptions(), 'allows no limit')
doesNotThrow(() => validateLimitOptions({ count: 2 }), 'allows valid count')
throws(() => validateLimitOptions(true), { message: 'limit must be an object' }, 'throws when limit is not an object')
throws(() => validateLimitOptions({ count: [] }), { message: 'limit.count must be a number greater than 0' }, 'throws when limit.count is not an number')
throws(() => validateLimitOptions({ count: -2 }), { message: 'limit.count must be a number greater than 0' }, 'throws when limit.count is negative')
throws(() => validateLimitOptions({ count: 0 }), { message: 'limit.count must be a number greater than 0' }, 'throws when limit.count is 0')
})
'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')

@@ -38,2 +39,17 @@ const { buildStream, cleanAndCreateFolder, sleep } = require('./utils')

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 }) => {

@@ -67,8 +83,75 @@ const file = join(logFolder, 'log')

equal(await readFile(`${file}.6`, 'utf8'), `${previousContent}${newContent}`, 'old and new content were written')
equal(
await readFile(`${file}.6`, 'utf8'),
`${previousContent}${newContent}`,
'old and new content were written'
)
rejects(stat(`${file}.1`), 'no other files created')
})
test('remove files based on count', async ({ ok, rejects }) => {
const file = join(logFolder, 'log')
const stream = await buildStream({
size: '20b',
file,
limit: { count: 1 }
})
for (let i = 1; i <= 5; i++) {
stream.write(`logged message #${i}\n`)
await sleep(20)
}
stream.end()
await stat(`${file}.2`)
let content = await readFile(`${file}.2`, 'utf8')
ok(content.includes('#3'), 'second file contains thrid log')
ok(content.includes('#4'), 'second file contains fourth log')
await stat(`${file}.3`)
content = await readFile(`${file}.3`, 'utf8')
ok(content.includes('#5'), 'third file contains fifth log')
await rejects(stat(`${file}.1`), 'first file was deleted')
await rejects(stat(`${file}.4`), 'no other files created')
})
test('do not remove pre-existing file when removing files based on count', async ({
ok,
equal,
rejects
}) => {
const file = join(logFolder, 'log')
await writeFile(`${file}.1`, 'oldest content')
await writeFile(`${file}.2`, 'old content')
const stream = await buildStream({
size: '20b',
file,
limit: { count: 2 }
})
for (let i = 1; i <= 7; i++) {
stream.write(`logged message #${i}\n`)
await sleep(20)
}
stream.end()
await stat(`${file}.1`)
let content = await readFile(`${file}.1`, 'utf8')
equal(content, 'oldest content', 'oldest file was not touched')
await stat(`${file}.3`)
content = await readFile(`${file}.3`, 'utf8')
ok(content.includes('#3'), 'second file contains third log')
ok(content.includes('#4'), 'second file contains fourth log')
await stat(`${file}.4`)
content = await readFile(`${file}.4`, 'utf8')
ok(content.includes('#5'), 'third file contains fifth log')
ok(content.includes('#6'), 'third file contains sixth log')
await stat(`${file}.5`)
content = await readFile(`${file}.5`, 'utf8')
ok(content.includes('#7'), 'fourth file contains seventh log')
await rejects(stat(`${file}.2`), 'resumed file was deleted')
await rejects(stat(`${file}.6`), 'no other files created')
})
test('throw on missing file parameter', async ({ rejects }) => {
rejects(buildStream(), { message: 'No file name provided' }, 'throws on missing file parameters')
rejects(
buildStream(),
{ message: 'No file name provided' },
'throws on missing file parameters'
)
})

@@ -90,3 +173,3 @@

{ message: `${size} is not a valid size in KB, MB or GB` },
'throws on unexisting folder'
'throws on unparseable size'
)

@@ -99,5 +182,37 @@ })

buildStream({ file: join(logFolder, 'log'), frequency }),
{ message: `${frequency} is neither a supported frequency or a number of milliseconds` },
'throws on unexisting folder'
{
message: `${frequency} is neither a supported frequency or a number of milliseconds`
},
'throws on unparseable frequency'
)
})
test('throw on unparseable limit object', async ({ rejects }) => {
rejects(
buildStream({ file: join(logFolder, 'log'), limit: 10 }),
{
message: 'limit must be an object'
},
'throws on limit option not being an object'
)
})
test('throw when limit.count is not a number', async ({ rejects }) => {
rejects(
buildStream({ file: join(logFolder, 'log'), limit: { count: true } }),
{
message: 'limit.count must be a number greater than 0'
},
'throws on limit.count not being a number'
)
})
test('throw when limit.count is 0', async ({ rejects }) => {
rejects(
buildStream({ file: join(logFolder, 'log'), limit: { count: 0 } }),
{
message: 'limit.count must be a number greater than 0'
},
'throws on limit.count being 0'
)
})

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc