signal-desktop/ts/util/rotatingPinoDest.ts

100 lines
2.3 KiB
TypeScript
Raw Normal View History

2022-10-03 22:53:41 +00:00
// Copyright 2022 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import fs from 'fs';
import pino from 'pino';
2022-11-09 20:04:54 +00:00
import { DAY, SECOND } from './durations';
2022-10-03 22:53:41 +00:00
import { isMoreRecentThan } from './timestamp';
export const DEFAULT_MAX_ROTATIONS = 3;
2022-11-09 20:04:54 +00:00
const RETRY_DELAY = 5 * SECOND;
// 5 seconds * 12 = 1 minute
const MAX_RETRY_COUNT = 12;
2022-10-03 22:53:41 +00:00
export type RotatingPinoDestOptionsType = Readonly<{
logFile: string;
maxSavedLogFiles?: number;
interval?: number;
}>;
export function createRotatingPinoDest({
logFile,
maxSavedLogFiles = DEFAULT_MAX_ROTATIONS,
interval = DAY,
}: RotatingPinoDestOptionsType): ReturnType<typeof pino.destination> {
const boom = pino.destination({
dest: logFile,
sync: true,
mkdir: true,
});
2022-11-09 20:04:54 +00:00
let retryCount = 0;
const warn = (msg: string) => {
const line = JSON.stringify({
level: 40,
time: new Date(),
msg,
});
boom.write(`${line}\n`);
};
function maybeRotate(startingIndex = maxSavedLogFiles - 1) {
let pendingFileIndex = startingIndex;
2022-10-03 22:53:41 +00:00
try {
const { birthtimeMs } = fs.statSync(logFile);
if (isMoreRecentThan(birthtimeMs, interval)) {
return;
}
2022-11-09 20:04:54 +00:00
for (; pendingFileIndex >= 0; pendingFileIndex -= 1) {
const currentPath =
pendingFileIndex === 0 ? logFile : `${logFile}.${pendingFileIndex}`;
const nextPath = `${logFile}.${pendingFileIndex + 1}`;
2022-10-03 22:53:41 +00:00
if (fs.existsSync(nextPath)) {
fs.unlinkSync(nextPath);
}
if (!fs.existsSync(currentPath)) {
continue;
}
fs.renameSync(currentPath, nextPath);
}
} catch (error) {
2022-11-09 20:04:54 +00:00
// If we can't access the old log files - try rotating after a small
// delay.
if (
retryCount < MAX_RETRY_COUNT &&
2022-12-12 04:09:30 +00:00
(error.code === 'EACCES' || error.code === 'EPERM')
2022-11-09 20:04:54 +00:00
) {
retryCount += 1;
warn(`rotatingPinoDest: retrying rotation, retryCount=${retryCount}`);
setTimeout(() => maybeRotate(pendingFileIndex), RETRY_DELAY);
return;
}
2022-10-03 22:53:41 +00:00
boom.destroy();
boom.emit('error', error);
2022-11-09 20:04:54 +00:00
return;
2022-10-03 22:53:41 +00:00
}
2022-11-09 20:04:54 +00:00
// Success, reopen
2022-10-03 22:53:41 +00:00
boom.reopen();
2022-11-09 20:04:54 +00:00
if (retryCount !== 0) {
warn(`rotatingPinoDest: rotation succeeded after ${retryCount} retries`);
}
retryCount = 0;
2022-10-03 22:53:41 +00:00
}
maybeRotate();
setInterval(maybeRotate, interval);
return boom;
}