From 03e43ab3643e0f8b610d0ed86b1857675e4e7233 Mon Sep 17 00:00:00 2001 From: Louis Lam Date: Sat, 14 Oct 2023 03:00:34 +0800 Subject: [PATCH] Log color and simplify startup log for production (#3889) --- .eslintrc.js | 1 + server/database.js | 22 +++++---- server/notification.js | 2 +- server/server.js | 29 +++++------ server/setup-database.js | 2 +- server/uptime-kuma-server.js | 2 +- src/util.js | 79 +++++++++++++++++++++++++++--- src/util.ts | 95 +++++++++++++++++++++++++++++++++--- 8 files changed, 191 insertions(+), 41 deletions(-) diff --git a/.eslintrc.js b/.eslintrc.js index f703293b3..4b60d6728 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -178,6 +178,7 @@ module.exports = { "jsdoc/require-returns-type": "off", "jsdoc/require-param-type": "off", "@typescript-eslint/no-explicit-any": "off", + "prefer-const": "off", } } ] diff --git a/server/database.js b/server/database.js index 358472ec1..f064fb8be 100644 --- a/server/database.js +++ b/server/database.js @@ -131,7 +131,7 @@ class Database { fs.mkdirSync(Database.dockerTLSDir, { recursive: true }); } - log.info("db", `Data Dir: ${Database.dataDir}`); + log.info("server", `Data Dir: ${Database.dataDir}`); } /** @@ -318,10 +318,10 @@ class Database { await R.exec("PRAGMA synchronous = NORMAL"); if (!noLog) { - log.info("db", "SQLite config:"); - log.info("db", await R.getAll("PRAGMA journal_mode")); - log.info("db", await R.getAll("PRAGMA cache_size")); - log.info("db", "SQLite Version: " + await R.getCell("SELECT sqlite_version()")); + log.debug("db", "SQLite config:"); + log.debug("db", await R.getAll("PRAGMA journal_mode")); + log.debug("db", await R.getAll("PRAGMA cache_size")); + log.debug("db", "SQLite Version: " + await R.getCell("SELECT sqlite_version()")); } } @@ -390,13 +390,15 @@ class Database { version = 0; } - log.info("db", "Your database version: " + version); - log.info("db", "Latest database version: " + this.latestVersion); + if (version !== this.latestVersion) { + log.info("db", "Your database version: " + version); + log.info("db", "Latest database version: " + this.latestVersion); + } if (version === this.latestVersion) { - log.info("db", "Database patch not needed"); + log.debug("db", "Database patch not needed"); } else if (version > this.latestVersion) { - log.info("db", "Warning: Database version is newer than expected"); + log.warn("db", "Warning: Database version is newer than expected"); } else { log.info("db", "Database patch is needed"); @@ -432,7 +434,7 @@ class Database { * @returns {Promise} */ static async patchSqlite2() { - log.info("db", "Database Patch 2.0 Process"); + log.debug("db", "Database Patch 2.0 Process"); let databasePatchedFiles = await setting("databasePatchedFiles"); if (! databasePatchedFiles) { diff --git a/server/notification.js b/server/notification.js index 1e7170220..80530e553 100644 --- a/server/notification.js +++ b/server/notification.js @@ -65,7 +65,7 @@ class Notification { * @throws Duplicate notification providers in list */ static init() { - log.info("notification", "Prepare Notification Providers"); + log.debug("notification", "Prepare Notification Providers"); this.providerList = {}; diff --git a/server/server.js b/server/server.js index e48c720d7..03f1cff2a 100644 --- a/server/server.js +++ b/server/server.js @@ -39,7 +39,6 @@ if (!semver.satisfies(nodeVersion, requiredNodeVersions)) { const args = require("args-parser")(process.argv); const { sleep, log, getRandomInt, genSecret, isDev } = require("../src/util"); -log.info("server", "Welcome to Uptime Kuma"); log.debug("server", "Arguments"); log.debug("server", args); @@ -47,8 +46,13 @@ if (! process.env.NODE_ENV) { process.env.NODE_ENV = "production"; } -log.info("server", "Node Env: " + process.env.NODE_ENV); -log.info("server", "Inside Container: " + (process.env.UPTIME_KUMA_IS_CONTAINER === "1")); +log.info("server", "Env: " + process.env.NODE_ENV); +log.debug("server", "Inside Container: " + (process.env.UPTIME_KUMA_IS_CONTAINER === "1")); + +const checkVersion = require("./check-version"); +log.info("server", "Uptime Kuma Version: " + checkVersion.version); + +log.info("server", "Loading modules"); log.debug("server", "Importing express"); const express = require("express"); @@ -75,7 +79,6 @@ const server = UptimeKumaServer.getInstance(args); const io = module.exports.io = server.io; const app = server.app; -log.info("server", "Importing this project modules"); log.debug("server", "Importing Monitor"); const Monitor = require("./model/monitor"); const User = require("./model/user"); @@ -102,9 +105,6 @@ const { apiAuth } = require("./auth"); const { login } = require("./auth"); const passwordHash = require("./password-hash"); -const checkVersion = require("./check-version"); -log.info("server", "Version: " + checkVersion.version); - // If host is omitted, the server will accept connections on the unspecified IPv6 address (::) when IPv6 is available and the unspecified IPv4 address (0.0.0.0) otherwise. // Dual-stack support for (::) // Also read HOST if not FreeBSD, as HOST is a system environment variable in FreeBSD @@ -195,7 +195,7 @@ let needSetup = false; server.entryPage = await Settings.get("entryPage"); await StatusPage.loadDomainMappingList(); - log.info("server", "Adding route"); + log.debug("server", "Adding route"); // *************************** // Normal Router here @@ -295,7 +295,7 @@ let needSetup = false; } }); - log.info("server", "Adding socket handler"); + log.debug("server", "Adding socket handler"); io.on("connection", async (socket) => { sendInfo(socket, true); @@ -1735,11 +1735,12 @@ let needSetup = false; }); - log.info("server", "Init the server"); + log.debug("server", "Init the server"); server.httpServer.once("error", async (err) => { - console.error("Cannot listen: " + err.message); + log.error("server", "Cannot listen: " + err.message); await shutdownFunction(); + process.exit(1); }); server.start(); @@ -1851,9 +1852,9 @@ async function afterLogin(socket, user) { * @returns {Promise} */ async function initDatabase(testMode = false) { - log.info("server", "Connecting to the Database"); + log.debug("server", "Connecting to the database"); await Database.connect(testMode); - log.info("server", "Connected"); + log.info("server", "Connected to the database"); // Patch the database await Database.patch(); @@ -1867,7 +1868,7 @@ async function initDatabase(testMode = false) { jwtSecretBean = await initJWTSecret(); log.info("server", "Stored JWT secret into database"); } else { - log.info("server", "Load JWT secret from database."); + log.debug("server", "Load JWT secret from database."); } // If there is no record in user table, it is a new Uptime Kuma instance, need to setup diff --git a/server/setup-database.js b/server/setup-database.js index 920d4237f..db1e9c156 100644 --- a/server/setup-database.js +++ b/server/setup-database.js @@ -48,7 +48,7 @@ class SetupDatabase { try { dbConfig = Database.readDBConfig(); - log.info("setup-database", "db-config.json is found and is valid"); + log.debug("setup-database", "db-config.json is found and is valid"); this.needSetup = false; } catch (e) { diff --git a/server/uptime-kuma-server.js b/server/uptime-kuma-server.js index da3d08bdf..9d5749f2a 100644 --- a/server/uptime-kuma-server.js +++ b/server/uptime-kuma-server.js @@ -83,7 +83,7 @@ class UptimeKumaServer { const sslCert = args["ssl-cert"] || process.env.UPTIME_KUMA_SSL_CERT || process.env.SSL_CERT || undefined; const sslKeyPassphrase = args["ssl-key-passphrase"] || process.env.UPTIME_KUMA_SSL_KEY_PASSPHRASE || process.env.SSL_KEY_PASSPHRASE || undefined; - log.info("server", "Creating express and socket.io instance"); + log.debug("server", "Creating express and socket.io instance"); this.app = express(); if (sslKey && sslCert) { log.info("server", "Server Type: HTTPS"); diff --git a/src/util.js b/src/util.js index b52a3fe39..2368f7c10 100644 --- a/src/util.js +++ b/src/util.js @@ -9,7 +9,8 @@ // Frontend uses util.ts */ Object.defineProperty(exports, "__esModule", { value: true }); -exports.localToUTC = exports.utcToLocal = exports.utcToISODateTime = exports.isoToUTCDateTime = exports.parseTimeFromTimeObject = exports.parseTimeObject = exports.getMaintenanceRelativeURL = exports.getMonitorRelativeURL = exports.genSecret = exports.getCryptoRandomInt = exports.getRandomInt = exports.getRandomArbitrary = exports.TimeLogger = exports.polyfill = exports.log = exports.debug = exports.ucfirst = exports.sleep = exports.flipStatus = exports.MIN_INTERVAL_SECOND = exports.MAX_INTERVAL_SECOND = exports.SQL_DATETIME_FORMAT_WITHOUT_SECOND = exports.SQL_DATETIME_FORMAT = exports.SQL_DATE_FORMAT = exports.STATUS_PAGE_MAINTENANCE = exports.STATUS_PAGE_PARTIAL_DOWN = exports.STATUS_PAGE_ALL_UP = exports.STATUS_PAGE_ALL_DOWN = exports.MAINTENANCE = exports.PENDING = exports.UP = exports.DOWN = exports.appName = exports.isDev = void 0; +exports.debug = exports.ucfirst = exports.sleep = exports.flipStatus = exports.CONSOLE_STYLE_BgGray = exports.CONSOLE_STYLE_BgWhite = exports.CONSOLE_STYLE_BgCyan = exports.CONSOLE_STYLE_BgMagenta = exports.CONSOLE_STYLE_BgBlue = exports.CONSOLE_STYLE_BgYellow = exports.CONSOLE_STYLE_BgGreen = exports.CONSOLE_STYLE_BgRed = exports.CONSOLE_STYLE_BgBlack = exports.CONSOLE_STYLE_FgPink = exports.CONSOLE_STYLE_FgBrown = exports.CONSOLE_STYLE_FgViolet = exports.CONSOLE_STYLE_FgLightBlue = exports.CONSOLE_STYLE_FgLightGreen = exports.CONSOLE_STYLE_FgOrange = exports.CONSOLE_STYLE_FgGray = exports.CONSOLE_STYLE_FgWhite = exports.CONSOLE_STYLE_FgCyan = exports.CONSOLE_STYLE_FgMagenta = exports.CONSOLE_STYLE_FgBlue = exports.CONSOLE_STYLE_FgYellow = exports.CONSOLE_STYLE_FgGreen = exports.CONSOLE_STYLE_FgRed = exports.CONSOLE_STYLE_FgBlack = exports.CONSOLE_STYLE_Hidden = exports.CONSOLE_STYLE_Reverse = exports.CONSOLE_STYLE_Blink = exports.CONSOLE_STYLE_Underscore = exports.CONSOLE_STYLE_Dim = exports.CONSOLE_STYLE_Bright = exports.CONSOLE_STYLE_Reset = exports.MIN_INTERVAL_SECOND = exports.MAX_INTERVAL_SECOND = exports.SQL_DATETIME_FORMAT_WITHOUT_SECOND = exports.SQL_DATETIME_FORMAT = exports.SQL_DATE_FORMAT = exports.STATUS_PAGE_MAINTENANCE = exports.STATUS_PAGE_PARTIAL_DOWN = exports.STATUS_PAGE_ALL_UP = exports.STATUS_PAGE_ALL_DOWN = exports.MAINTENANCE = exports.PENDING = exports.UP = exports.DOWN = exports.appName = exports.isDev = void 0; +exports.intHash = exports.localToUTC = exports.utcToLocal = exports.utcToISODateTime = exports.isoToUTCDateTime = exports.parseTimeFromTimeObject = exports.parseTimeObject = exports.getMaintenanceRelativeURL = exports.getMonitorRelativeURL = exports.genSecret = exports.getCryptoRandomInt = exports.getRandomInt = exports.getRandomArbitrary = exports.TimeLogger = exports.polyfill = exports.log = void 0; const dayjs = require("dayjs"); exports.isDev = process.env.NODE_ENV === "development"; exports.appName = "Uptime Kuma"; @@ -26,6 +27,55 @@ exports.SQL_DATETIME_FORMAT = "YYYY-MM-DD HH:mm:ss"; exports.SQL_DATETIME_FORMAT_WITHOUT_SECOND = "YYYY-MM-DD HH:mm"; exports.MAX_INTERVAL_SECOND = 2073600; exports.MIN_INTERVAL_SECOND = 20; +exports.CONSOLE_STYLE_Reset = "\x1b[0m"; +exports.CONSOLE_STYLE_Bright = "\x1b[1m"; +exports.CONSOLE_STYLE_Dim = "\x1b[2m"; +exports.CONSOLE_STYLE_Underscore = "\x1b[4m"; +exports.CONSOLE_STYLE_Blink = "\x1b[5m"; +exports.CONSOLE_STYLE_Reverse = "\x1b[7m"; +exports.CONSOLE_STYLE_Hidden = "\x1b[8m"; +exports.CONSOLE_STYLE_FgBlack = "\x1b[30m"; +exports.CONSOLE_STYLE_FgRed = "\x1b[31m"; +exports.CONSOLE_STYLE_FgGreen = "\x1b[32m"; +exports.CONSOLE_STYLE_FgYellow = "\x1b[33m"; +exports.CONSOLE_STYLE_FgBlue = "\x1b[34m"; +exports.CONSOLE_STYLE_FgMagenta = "\x1b[35m"; +exports.CONSOLE_STYLE_FgCyan = "\x1b[36m"; +exports.CONSOLE_STYLE_FgWhite = "\x1b[37m"; +exports.CONSOLE_STYLE_FgGray = "\x1b[90m"; +exports.CONSOLE_STYLE_FgOrange = "\x1b[38;5;208m"; +exports.CONSOLE_STYLE_FgLightGreen = "\x1b[38;5;119m"; +exports.CONSOLE_STYLE_FgLightBlue = "\x1b[38;5;117m"; +exports.CONSOLE_STYLE_FgViolet = "\x1b[38;5;141m"; +exports.CONSOLE_STYLE_FgBrown = "\x1b[38;5;130m"; +exports.CONSOLE_STYLE_FgPink = "\x1b[38;5;219m"; +exports.CONSOLE_STYLE_BgBlack = "\x1b[40m"; +exports.CONSOLE_STYLE_BgRed = "\x1b[41m"; +exports.CONSOLE_STYLE_BgGreen = "\x1b[42m"; +exports.CONSOLE_STYLE_BgYellow = "\x1b[43m"; +exports.CONSOLE_STYLE_BgBlue = "\x1b[44m"; +exports.CONSOLE_STYLE_BgMagenta = "\x1b[45m"; +exports.CONSOLE_STYLE_BgCyan = "\x1b[46m"; +exports.CONSOLE_STYLE_BgWhite = "\x1b[47m"; +exports.CONSOLE_STYLE_BgGray = "\x1b[100m"; +const consoleModuleColors = [ + exports.CONSOLE_STYLE_FgCyan, + exports.CONSOLE_STYLE_FgGreen, + exports.CONSOLE_STYLE_FgLightGreen, + exports.CONSOLE_STYLE_FgBlue, + exports.CONSOLE_STYLE_FgLightBlue, + exports.CONSOLE_STYLE_FgMagenta, + exports.CONSOLE_STYLE_FgOrange, + exports.CONSOLE_STYLE_FgViolet, + exports.CONSOLE_STYLE_FgBrown, + exports.CONSOLE_STYLE_FgPink, +]; +const consoleLevelColors = { + "INFO": exports.CONSOLE_STYLE_FgCyan, + "WARN": exports.CONSOLE_STYLE_FgYellow, + "ERROR": exports.CONSOLE_STYLE_FgRed, + "DEBUG": exports.CONSOLE_STYLE_FgGray, +}; function flipStatus(s) { if (s === exports.UP) { return exports.DOWN; @@ -85,23 +135,30 @@ class Logger { else { now = dayjs().format(); } - const formattedMessage = (typeof msg === "string") ? `${now} [${module}] ${level}: ${msg}` : msg; + const levelColor = consoleLevelColors[level]; + const moduleColor = consoleModuleColors[intHash(module, consoleModuleColors.length)]; + let timePart = exports.CONSOLE_STYLE_FgCyan + now + exports.CONSOLE_STYLE_Reset; + let modulePart = "[" + moduleColor + module + exports.CONSOLE_STYLE_Reset + "]"; + let levelPart = levelColor + `${level}:` + exports.CONSOLE_STYLE_Reset; if (level === "INFO") { - console.info(formattedMessage); + console.info(timePart, modulePart, levelPart, msg); } else if (level === "WARN") { - console.warn(formattedMessage); + console.warn(timePart, modulePart, levelPart, msg); } else if (level === "ERROR") { - console.error(formattedMessage); + let msgPart = exports.CONSOLE_STYLE_FgRed + msg + exports.CONSOLE_STYLE_Reset; + console.error(timePart, modulePart, levelPart, msgPart); } else if (level === "DEBUG") { if (exports.isDev) { - console.log(formattedMessage); + timePart = exports.CONSOLE_STYLE_FgGray + now + exports.CONSOLE_STYLE_Reset; + let msgPart = exports.CONSOLE_STYLE_FgGray + msg + exports.CONSOLE_STYLE_Reset; + console.debug(timePart, modulePart, levelPart, msgPart); } } else { - console.log(formattedMessage); + console.log(timePart, modulePart, msg); } } info(module, msg) { @@ -269,3 +326,11 @@ function localToUTC(input, format = exports.SQL_DATETIME_FORMAT) { return dayjs(input).utc().format(format); } exports.localToUTC = localToUTC; +function intHash(str, length = 10) { + let hash = 0; + for (let i = 0; i < str.length; i++) { + hash += str.charCodeAt(i); + } + return (hash % length + length) % length; +} +exports.intHash = intHash; diff --git a/src/util.ts b/src/util.ts index 59ccad1fb..56b6dad52 100644 --- a/src/util.ts +++ b/src/util.ts @@ -35,6 +35,62 @@ export const SQL_DATETIME_FORMAT_WITHOUT_SECOND = "YYYY-MM-DD HH:mm"; export const MAX_INTERVAL_SECOND = 2073600; // 24 days export const MIN_INTERVAL_SECOND = 20; // 20 seconds +// Console colors +// https://stackoverflow.com/questions/9781218/how-to-change-node-jss-console-font-color +export const CONSOLE_STYLE_Reset = "\x1b[0m"; +export const CONSOLE_STYLE_Bright = "\x1b[1m"; +export const CONSOLE_STYLE_Dim = "\x1b[2m"; +export const CONSOLE_STYLE_Underscore = "\x1b[4m"; +export const CONSOLE_STYLE_Blink = "\x1b[5m"; +export const CONSOLE_STYLE_Reverse = "\x1b[7m"; +export const CONSOLE_STYLE_Hidden = "\x1b[8m"; + +export const CONSOLE_STYLE_FgBlack = "\x1b[30m"; +export const CONSOLE_STYLE_FgRed = "\x1b[31m"; +export const CONSOLE_STYLE_FgGreen = "\x1b[32m"; +export const CONSOLE_STYLE_FgYellow = "\x1b[33m"; +export const CONSOLE_STYLE_FgBlue = "\x1b[34m"; +export const CONSOLE_STYLE_FgMagenta = "\x1b[35m"; +export const CONSOLE_STYLE_FgCyan = "\x1b[36m"; +export const CONSOLE_STYLE_FgWhite = "\x1b[37m"; +export const CONSOLE_STYLE_FgGray = "\x1b[90m"; +export const CONSOLE_STYLE_FgOrange = "\x1b[38;5;208m"; +export const CONSOLE_STYLE_FgLightGreen = "\x1b[38;5;119m"; +export const CONSOLE_STYLE_FgLightBlue = "\x1b[38;5;117m"; +export const CONSOLE_STYLE_FgViolet = "\x1b[38;5;141m"; +export const CONSOLE_STYLE_FgBrown = "\x1b[38;5;130m"; +export const CONSOLE_STYLE_FgPink = "\x1b[38;5;219m"; + +export const CONSOLE_STYLE_BgBlack = "\x1b[40m"; +export const CONSOLE_STYLE_BgRed = "\x1b[41m"; +export const CONSOLE_STYLE_BgGreen = "\x1b[42m"; +export const CONSOLE_STYLE_BgYellow = "\x1b[43m"; +export const CONSOLE_STYLE_BgBlue = "\x1b[44m"; +export const CONSOLE_STYLE_BgMagenta = "\x1b[45m"; +export const CONSOLE_STYLE_BgCyan = "\x1b[46m"; +export const CONSOLE_STYLE_BgWhite = "\x1b[47m"; +export const CONSOLE_STYLE_BgGray = "\x1b[100m"; + +const consoleModuleColors = [ + CONSOLE_STYLE_FgCyan, + CONSOLE_STYLE_FgGreen, + CONSOLE_STYLE_FgLightGreen, + CONSOLE_STYLE_FgBlue, + CONSOLE_STYLE_FgLightBlue, + CONSOLE_STYLE_FgMagenta, + CONSOLE_STYLE_FgOrange, + CONSOLE_STYLE_FgViolet, + CONSOLE_STYLE_FgBrown, + CONSOLE_STYLE_FgPink, +]; + +const consoleLevelColors : Record = { + "INFO": CONSOLE_STYLE_FgCyan, + "WARN": CONSOLE_STYLE_FgYellow, + "ERROR": CONSOLE_STYLE_FgRed, + "DEBUG": CONSOLE_STYLE_FgGray, +}; + /** * Flip the status of s * @param s @@ -125,7 +181,7 @@ class Logger { * @param msg Message to write * @param level Log level. One of INFO, WARN, ERROR, DEBUG or can be customized. */ - log(module: string, msg: unknown, level: string) { + log(module: string, msg: any, level: string) { if (this.hideLog[level] && this.hideLog[level].includes(module.toLowerCase())) { return; } @@ -139,20 +195,29 @@ class Logger { } else { now = dayjs().format(); } - const formattedMessage = (typeof msg === "string") ? `${now} [${module}] ${level}: ${msg}` : msg; + + const levelColor = consoleLevelColors[level]; + const moduleColor = consoleModuleColors[intHash(module, consoleModuleColors.length)]; + + let timePart = CONSOLE_STYLE_FgCyan + now + CONSOLE_STYLE_Reset; + let modulePart = "[" + moduleColor + module + CONSOLE_STYLE_Reset + "]"; + let levelPart = levelColor + `${level}:` + CONSOLE_STYLE_Reset; if (level === "INFO") { - console.info(formattedMessage); + console.info(timePart, modulePart, levelPart, msg); } else if (level === "WARN") { - console.warn(formattedMessage); + console.warn(timePart, modulePart, levelPart, msg); } else if (level === "ERROR") { - console.error(formattedMessage); + let msgPart = CONSOLE_STYLE_FgRed + msg + CONSOLE_STYLE_Reset; + console.error(timePart, modulePart, levelPart, msgPart); } else if (level === "DEBUG") { if (isDev) { - console.log(formattedMessage); + timePart = CONSOLE_STYLE_FgGray + now + CONSOLE_STYLE_Reset; + let msgPart = CONSOLE_STYLE_FgGray + msg + CONSOLE_STYLE_Reset; + console.debug(timePart, modulePart, levelPart, msgPart ); } } else { - console.log(formattedMessage); + console.log(timePart, modulePart, msg); } } @@ -469,3 +534,19 @@ export function utcToLocal(input : string, format = SQL_DATETIME_FORMAT) : strin export function localToUTC(input : string, format = SQL_DATETIME_FORMAT) { return dayjs(input).utc().format(format); } + +/** + * Generate a decimal integer number from a string + * @param str Input + * @param length Default is 10 which means 0 - 9 + */ +export function intHash(str : string, length = 10) : number { + // A simple hashing function (you can use more complex hash functions if needed) + let hash = 0; + for (let i = 0; i < str.length; i++) { + hash += str.charCodeAt(i); + } + // Normalize the hash to the range [0, 10] + return (hash % length + length) % length; // Ensure the result is non-negative +} +