Skip to content

Commit

Permalink
Allows custom frequency to generate filenames when specified with dat…
Browse files Browse the repository at this point in the history
…eFormat (#103)

* allow custom (number) frequency to have timestamp generated

* update readme to explain the changed behavior
  • Loading branch information
benedictjohannes authored Feb 3, 2025
1 parent de52742 commit 6f34808
Show file tree
Hide file tree
Showing 5 changed files with 32 additions and 21 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ You can specify any of [Sonic-Boom options](https://github.com/pinojs/sonic-boom
Use `daily` or `hourly` to rotate file every day (or every hour).
Existing file within the current day (or hour) will be re-used.
Numerical values will be considered as a number of milliseconds.
Using a numerical value will always create a new file upon startup.
Using a numerical value will result in a file during start/end of the frequency specified.

* `extension?`: appends the provided string after the file number.

Expand Down
8 changes: 5 additions & 3 deletions lib/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,8 @@ function parseFrequency (frequency) {
return { frequency, start, next: getNextHour(start) }
}
if (typeof frequency === 'number') {
return { frequency, next: getNextCustom(frequency) }
const start = today.getTime() - today.getTime() % frequency
return { frequency, start, next: getNextCustom(frequency) }
}
if (frequency) {
throw new Error(`${frequency} is neither a supported frequency or a number of milliseconds`)
Expand Down Expand Up @@ -61,7 +62,8 @@ function getNextHour (start) {
}

function getNextCustom (frequency) {
return Date.now() + frequency
const time = Date.now()
return time - time % frequency + frequency
}

function getNext (frequency) {
Expand Down Expand Up @@ -171,7 +173,7 @@ function validateDateFormat (formatStr) {
}

function parseDate (formatStr, frequencySpec, parseStart = false) {
if (!(formatStr && (frequencySpec?.frequency === 'daily' || frequencySpec?.frequency === 'hourly'))) return null
if (!(formatStr && frequencySpec?.start && frequencySpec.next)) return null

try {
return format(parseStart ? frequencySpec.start : frequencySpec.next, formatStr)
Expand Down
23 changes: 13 additions & 10 deletions test/date-format-option.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ test('rotate file with date format based on frequency', async ({ ok, rejects })

test('rotate file based on custom time and date format', async ({ ok, notOk, rejects }) => {
const file = join(logFolder, 'log')
await sleep(100 - Date.now() % 100)
const fileName = `${file}.${format(new Date(), 'yyyy-MM-dd-hh')}`
const stream = await buildStream({ frequency: 100, file, dateFormat: 'yyyy-MM-dd-hh' })
stream.write('logged message #1\n')
stream.write('logged message #2\n')
Expand All @@ -36,18 +38,18 @@ test('rotate file based on custom time and date format', async ({ ok, notOk, rej
stream.write('logged message #4\n')
await sleep(110)
stream.end()
await stat(`${file}.1`)
let content = await readFile(`${file}.1`, 'utf8')
await stat(`${fileName}.1`)
let content = await readFile(`${fileName}.1`, 'utf8')
ok(content.includes('#1'), 'first file contains first log')
ok(content.includes('#2'), 'first file contains second log')
notOk(content.includes('#3'), 'first file does not contains third log')
await stat(`${file}.2`)
content = await readFile(`${file}.2`, 'utf8')
await stat(`${fileName}.2`)
content = await readFile(`${fileName}.2`, 'utf8')
ok(content.includes('#3'), 'first file contains third log')
ok(content.includes('#4'), 'first file contains fourth log')
notOk(content.includes('#2'), 'first file does not contains second log')
await stat(`${file}.3`)
rejects(stat(`${file}.4`), 'no other files created')
await stat(`${fileName}.3`)
rejects(stat(`${fileName}.4`), 'no other files created')
})

test('rotate file based on size and date format', async ({ ok, rejects }) => {
Expand All @@ -72,6 +74,7 @@ test('rotate file based on size and date format', async ({ ok, rejects }) => {

test('rotate file based on size and date format with custom frequency', async ({ ok, rejects }) => {
const file = join(logFolder, 'log')
const fileWithDate = `${file}.${format(startOfHour(new Date()).getTime(), 'yyyy-MM-dd-hh')}`
const size = 20
const stream = await buildStream({ frequency: 1000, size: `${size}b`, file, dateFormat: 'yyyy-MM-dd-hh' })
stream.write('logged message #1\n')
Expand All @@ -82,15 +85,15 @@ test('rotate file based on size and date format with custom frequency', async ({
stream.write('logged message #4\n')
stream.end()

let stats = await stat(`${file}.1`)
let stats = await stat(`${fileWithDate}.1`)
ok(
size <= stats.size && stats.size <= size * 2,
`first file size: ${size} <= ${stats.size} <= ${size * 2}`
)
stats = await stat(`${file}.2`)
stats = await stat(`${fileWithDate}.2`)
ok(stats.size <= size, `second file size: ${stats.size} <= ${size}`)
stats = await stat(`${file}.3`)
const content = await readFile(`${file}.3`, 'utf8')
stats = await stat(`${fileWithDate}.3`)
const content = await readFile(`${fileWithDate}.3`, 'utf8')
ok(content.includes('#4'), 'Rotated file should have the log')
rejects(stat(`${file}.4`), 'no other files created')
})
Expand Down
11 changes: 7 additions & 4 deletions test/lib/utils.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -53,10 +53,12 @@ test('parseFrequency()', async ({ same, throws }) => {
'supports hourly frequency'
)
const custom = 3000
const start = today.getTime() - today.getTime() % custom
const next = start + custom
same(
parseFrequency(custom),
{ frequency: custom, next: Date.now() + custom },
'supports custom frequency and does not return start'
{ frequency: custom, start, next },
'supports custom frequency'
)
throws(() => parseFrequency('null'), 'throws on non parseable string')
})
Expand All @@ -67,7 +69,9 @@ test('getNext()', async ({ same }) => {
same(getNext('daily'), startOfDay(addDays(today, 1)).getTime(), 'supports daily frequency')
same(getNext('hourly'), startOfHour(addHours(today, 1)).getTime(), 'supports hourly frequency')
const custom = 3000
same(getNext(custom), Date.now() + custom, 'supports custom frequency and does not return start')
const time = Date.now()
const next = time - time % custom + custom
same(getNext(custom), next, 'supports custom frequency')
})

test('getNext() on dates transitioning from DST to Standard Time', async ({ same }) => {
Expand Down Expand Up @@ -161,7 +165,6 @@ test('parseDate()', async ({ equal, throws }) => {
const today = new Date()
const frequencySpec = { frequency: 'hourly', start: startOfHour(today).getTime(), next: startOfHour(addHours(today, 1)).getTime() }
equal(parseDate(null, frequencySpec), null, 'returns null on empty format')
equal(parseDate('yyyy-MM-dd', { frequency: 100 }), null, 'returns null on custom frequency')
equal(parseDate('yyyy-MM-dd-hh', frequencySpec, true), format(frequencySpec.start, 'yyyy-MM-dd-hh'), 'parse start date time')
equal(parseDate('yyyy-MM-dd-hh', frequencySpec), format(frequencySpec.next, 'yyyy-MM-dd-hh'), 'parse next date time')
throws(() => parseDate('yyyy-MM-dd-hhU', frequencySpec), 'throws on invalid date format with character U')
Expand Down
9 changes: 6 additions & 3 deletions test/pino-roll.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ beforeEach(() => cleanAndCreateFolder(logFolder))

test('rotate file based on time', async ({ ok, notOk, rejects }) => {
const file = join(logFolder, 'log')
await sleep(100 - (Date.now() % 100))
const stream = await buildStream({ frequency: 100, file })
stream.write('logged message #1\n')
stream.write('logged message #2\n')
Expand All @@ -29,15 +30,16 @@ test('rotate file based on time', async ({ ok, notOk, rejects }) => {
notOk(content.includes('#3'), 'first file does not contains third log')
await stat(`${file}.2`)
content = await readFile(`${file}.2`, 'utf8')
ok(content.includes('#3'), 'first file contains third log')
ok(content.includes('#4'), 'first file contains fourth log')
notOk(content.includes('#2'), 'first file does not contains second log')
ok(content.includes('#3'), 'second file contains third log')
ok(content.includes('#4'), 'second file contains fourth log')
notOk(content.includes('#2'), 'second file does not contains second log')
await stat(`${file}.3`)
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')
await sleep(100 - (Date.now() % 100))
const fileFunc = () => `${file}-${format(new Date(), 'HH-mm-ss-SSS')}`
const stream = await buildStream({ frequency: 100, file: fileFunc })
stream.write('logged message #1\n')
Expand Down Expand Up @@ -229,6 +231,7 @@ test('creates symlink if prop is set', async ({ equal, resolves }) => {
test('symlink rotates on roll', async ({ equal, ok, resolves }) => {
const file = join(logFolder, 'log')
const linkPath = join(logFolder, 'current.log')
await sleep(100 - (Date.now() % 100))
const stream = await buildStream({ frequency: 100, file, symlink: true })
stream.write('logged message #1\n')
stream.write('logged message #2\n')
Expand Down

0 comments on commit 6f34808

Please sign in to comment.