From fd1f3329bdd0e0d067c305497e5d36f95b85ab66 Mon Sep 17 00:00:00 2001 From: Hornwitser Date: Wed, 15 Dec 2021 16:37:36 +0100 Subject: [PATCH] Split logs by date Move the default location of the log files into a configurable logs folder and split the written logs up by UTC date to more easily allow deleting of old logs to be performed. The log rotation is implemented by winston-daily-rotate-file. The old log format will automatically be migrated to the new format and location if the target folder for the new logs don't yet exists. --- packages/lib/logging_utils.js | 61 +++++++++++++++++++++++++++++++++++ packages/master/master.js | 40 ++++++++++++++++++++--- packages/master/package.json | 1 + packages/slave/package.json | 1 + packages/slave/slave.js | 31 +++++++++++++++--- 5 files changed, 125 insertions(+), 9 deletions(-) diff --git a/packages/lib/logging_utils.js b/packages/lib/logging_utils.js index c80b6997..6a8ac8da 100644 --- a/packages/lib/logging_utils.js +++ b/packages/lib/logging_utils.js @@ -1,4 +1,7 @@ "use strict"; +const events = require("events"); +const fs = require("fs-extra"); +const path = require("path"); const winston = require("winston"); const Transport = require("winston-transport"); const { LEVEL, MESSAGE } = require("triple-beam"); @@ -6,6 +9,7 @@ const chalk = require("chalk"); const libLink = require("./link"); const libErrors = require("./errors"); +const libStream = require("./stream"); /** @@ -121,10 +125,67 @@ class LinkTransport extends Transport { } } +async function migrateLogs(log, directory, pattern) { + const dateRegExp = /^\d{4}-\d{2}-\d{2}/; + let lineStream = new libStream.LineSplitter({ readableObjectMode: true }); + let fileStream = fs.createReadStream(log); + fileStream.pipe(lineStream); + + let currentDate = null; + let output = null; + + async function switchOutput(newOutputPath) { + if (output) { + output.end(); + await events.once(output, "close"); + } + + output = fs.createWriteStream(newOutputPath, { flags: "ax" }); + try { + await events.once(output, "open"); + // eslint-disable-next-line no-console + console.log(`writing ${newOutputPath}`); + } catch (err) { + if (err.code === "EEXIST") { + // eslint-disable-next-line no-console + console.warn(`Discarding logs for ${newDate} due to target logfile already existing`); + output = null; + } else { + throw err; + } + } + } + + for await (let line of lineStream) { + let info = {}; + try { + info = JSON.parse(line); + } catch (err) { + // Ignore + } + + if (typeof info.timestamp === "string" && dateRegExp.test(info.timestamp)) { + let newDate = info.timestamp.slice(0, 10); + // Only update date forward in time. + if (newDate !== currentDate && (!currentDate || newDate > currentDate)) { + await switchOutput(path.join(directory, pattern.replace("%DATE%", newDate))); + currentDate = newDate; + } + } + + if (output) { + if (!output.write(Buffer.concat([line, Buffer.from("\n")]))) { + await events.once(output, "drain"); + } + } + } +} + module.exports = { TerminalFormat, LinkTransport, + migrateLogs, // for testing only _formatServerOutput: formatServerOutput, diff --git a/packages/master/master.js b/packages/master/master.js index 89d60bf8..f2a09f0d 100755 --- a/packages/master/master.js +++ b/packages/master/master.js @@ -22,6 +22,7 @@ const setBlocking = require("set-blocking"); const yargs = require("yargs"); const util = require("util"); const winston = require("winston"); +require("winston-daily-rotate-file"); const jwt = require("jsonwebtoken"); // homebrew modules @@ -189,6 +190,12 @@ async function initialize() { choices: ["none"].concat(Object.keys(levels)), type: "string", }) + .option("log-directory", { + nargs: 1, + describe: "Directory to place logs in", + default: "logs", + type: "string", + }) .option("config", { nargs: 1, describe: "master config file to use", @@ -227,20 +234,45 @@ async function initialize() { .argv ; + { + // Migration from alpha-10 single file logs, note that we can't use + // the logger here as it's not initalized yet. + /* eslint-disable no-console */ + let clusterLogDirectory = path.join(parameters.args.logDirectory, "cluster"); + if (!await fs.pathExists(clusterLogDirectory) && await fs.pathExists("cluster.log")) { + console.log("Migrating cluster log..."); + await fs.ensureDir(clusterLogDirectory); + await libLoggingUtils.migrateLogs("cluster.log", clusterLogDirectory, "cluster-%DATE%.log"); + console.log("Migration complete, you should delete cluster.log now"); + } + + let masterLogDirectory = path.join(parameters.args.logDirectory, "master"); + if (!await fs.pathExists(masterLogDirectory) && await fs.pathExists("master.log")) { + console.log("Migrating master log..."); + await fs.ensureDir(masterLogDirectory); + await libLoggingUtils.migrateLogs("master.log", masterLogDirectory, "master-%DATE%.log"); + console.log("Migration complete, you should delete master.log now"); + } + /* eslint-enable no-console */ + } + // Combined log stream of the whole cluster. parameters.clusterLogger = winston.createLogger({ format: winston.format.json(), level: "verbose", levels, }); - parameters.clusterLogger.add(new winston.transports.File({ - filename: "cluster.log", + parameters.clusterLogger.add(new winston.transports.DailyRotateFile({ + filename: "cluster-%DATE%.log", + utc: true, + dirname: path.join(parameters.args.logDirectory, "cluster"), })); // Log stream for the master server. - logger.add(new winston.transports.File({ + logger.add(new winston.transports.DailyRotateFile({ format: winston.format.json(), - filename: "master.log", + filename: "master-%DATE%.log", + dirname: path.join(parameters.args.logDirectory, "master"), })); logger.add(new winston.transports.Stream({ stream: parameters.clusterLogger, diff --git a/packages/master/package.json b/packages/master/package.json index 76283a82..a5bf1a14 100644 --- a/packages/master/package.json +++ b/packages/master/package.json @@ -28,6 +28,7 @@ "jszip": "^3.2.2", "set-blocking": "^2.0.0", "winston": "^3.3.3", + "winston-daily-rotate-file": "^4.5.5", "ws": "^7.2.3", "yargs": "^14.2.0" }, diff --git a/packages/slave/package.json b/packages/slave/package.json index eed960b0..3aad3aa5 100644 --- a/packages/slave/package.json +++ b/packages/slave/package.json @@ -24,6 +24,7 @@ "pidusage": "^2.0.18", "set-blocking": "^2.0.0", "winston": "^3.3.3", + "winston-daily-rotate-file": "^4.5.5", "yargs": "^14.2.0" }, "publishConfig": { diff --git a/packages/slave/slave.js b/packages/slave/slave.js index 46fea491..f33ae4dc 100755 --- a/packages/slave/slave.js +++ b/packages/slave/slave.js @@ -23,6 +23,7 @@ const phin = require("phin"); const util = require("util"); const version = require("./package").version; const winston = require("winston"); +require("winston-daily-rotate-file"); // internal libraries const libFileOps = require("@clusterio/lib/file_ops"); @@ -1734,6 +1735,12 @@ async function startSlave() { choices: ["none"].concat(Object.keys(levels)), type: "string", }) + .option("log-directory", { + nargs: 1, + describe: "Directory to place logs in", + default: "logs", + type: "string", + }) .option("config", { nargs: 1, describe: "slave config file to use", @@ -1754,11 +1761,25 @@ async function startSlave() { .argv ; - logger.add(new winston.transports.File({ - format: winston.format.combine( - winston.format.json(), - ), - filename: "slave.log", + { + // Migration from alpha-10 single file logs, note that we can't use + // the logger here as it's not initalized yet. + /* eslint-disable no-console */ + let slaveLogDirectory = path.join(args.logDirectory, "slave"); + if (!await fs.pathExists(slaveLogDirectory) && await fs.pathExists("slave.log")) { + console.log("Migrating slave log..."); + await fs.ensureDir(slaveLogDirectory); + await libLoggingUtils.migrateLogs("slave.log", slaveLogDirectory, "slave-%DATE%.log"); + console.log("Migration complete, you should delete slave.log now"); + } + /* eslint-enable no-console */ + } + + logger.add(new winston.transports.DailyRotateFile({ + format: winston.format.json(), + filename: "slave-%DATE%.log", + utc: true, + dirname: path.join(args.logDirectory, "slave"), })); if (args.logLevel !== "none") { logger.add(new ConsoleTransport({