From b287a25de735d14c6e078d57e99b4587f10697cd Mon Sep 17 00:00:00 2001
From: Cassandra <little.tea1531@cloak.sapphiccode.net>
Date: Tue, 24 Sep 2024 11:51:21 +0200
Subject: [PATCH] feat: structured logging (JSON) (#5118)

---
 server/auth.js                                |  3 +-
 server/check-version.js                       |  8 +--
 server/client.js                              |  4 +-
 server/database.js                            | 34 ++++-----
 server/jobs/clear-old-data.js                 |  8 +--
 server/model/monitor.js                       | 10 +--
 server/modules/apicache/apicache.js           | 61 ++++++----------
 .../real-browser-monitor-type.js              |  5 +-
 server/notification-providers/alertnow.js     |  4 +-
 server/notification-providers/flashduty.js    |  4 +-
 server/notification-providers/google-chat.js  |  4 +-
 server/notification-providers/pagerduty.js    |  4 +-
 server/notification-providers/pagertree.js    |  4 +-
 server/notification-providers/rocket-chat.js  |  4 +-
 server/notification-providers/slack.js        | 15 ++--
 server/notification-providers/splunk.js       |  4 +-
 server/notification-providers/stackfield.js   |  4 +-
 server/proxy.js                               |  8 +--
 server/routers/api-router.js                  |  4 +-
 server/server.js                              | 17 +++--
 .../socket-handlers/api-key-socket-handler.js |  2 +-
 .../cloudflared-socket-handler.js             | 19 ++---
 .../maintenance-socket-handler.js             |  8 +--
 .../status-page-socket-handler.js             |  9 +--
 server/util-server.js                         | 41 -----------
 src/util.js                                   | 70 +++++++++++--------
 src/util.ts                                   | 67 ++++++++----------
 27 files changed, 186 insertions(+), 239 deletions(-)

diff --git a/server/auth.js b/server/auth.js
index 597cf3d75..36316241c 100644
--- a/server/auth.js
+++ b/server/auth.js
@@ -1,7 +1,6 @@
 const basicAuth = require("express-basic-auth");
 const passwordHash = require("./password-hash");
 const { R } = require("redbean-node");
-const { setting } = require("./util-server");
 const { log } = require("../src/util");
 const { loginRateLimiter, apiRateLimiter } = require("./rate-limiter");
 const { Settings } = require("./settings");
@@ -139,7 +138,7 @@ exports.basicAuth = async function (req, res, next) {
         challenge: true,
     });
 
-    const disabledAuth = await setting("disableAuth");
+    const disabledAuth = await Settings.get("disableAuth");
 
     if (!disabledAuth) {
         middleware(req, res, next);
diff --git a/server/check-version.js b/server/check-version.js
index c6d5cfb92..154ebe373 100644
--- a/server/check-version.js
+++ b/server/check-version.js
@@ -1,7 +1,7 @@
-const { setSetting, setting } = require("./util-server");
 const axios = require("axios");
 const compareVersions = require("compare-versions");
 const { log } = require("../src/util");
+const { Settings } = require("./settings");
 
 exports.version = require("../package.json").version;
 exports.latestVersion = null;
@@ -14,7 +14,7 @@ let interval;
 
 exports.startInterval = () => {
     let check = async () => {
-        if (await setting("checkUpdate") === false) {
+        if (await Settings.get("checkUpdate") === false) {
             return;
         }
 
@@ -28,7 +28,7 @@ exports.startInterval = () => {
                 res.data.slow = "1000.0.0";
             }
 
-            let checkBeta = await setting("checkBeta");
+            let checkBeta = await Settings.get("checkBeta");
 
             if (checkBeta && res.data.beta) {
                 if (compareVersions.compare(res.data.beta, res.data.slow, ">")) {
@@ -57,7 +57,7 @@ exports.startInterval = () => {
  * @returns {Promise<void>}
  */
 exports.enableCheckUpdate = async (value) => {
-    await setSetting("checkUpdate", value);
+    await Settings.set("checkUpdate", value);
 
     clearInterval(interval);
 
diff --git a/server/client.js b/server/client.js
index 72f0a4e8e..6929f81aa 100644
--- a/server/client.js
+++ b/server/client.js
@@ -6,8 +6,8 @@ const { R } = require("redbean-node");
 const { UptimeKumaServer } = require("./uptime-kuma-server");
 const server = UptimeKumaServer.getInstance();
 const io = server.io;
-const { setting } = require("./util-server");
 const checkVersion = require("./check-version");
+const { Settings } = require("./settings");
 const Database = require("./database");
 
 /**
@@ -158,8 +158,8 @@ async function sendInfo(socket, hideVersion = false) {
         version,
         latestVersion,
         isContainer,
+        primaryBaseURL: await Settings.get("primaryBaseURL"),
         dbType,
-        primaryBaseURL: await setting("primaryBaseURL"),
         serverTimezone: await server.getTimezone(),
         serverTimezoneOffset: server.getTimezoneOffset(),
     });
diff --git a/server/database.js b/server/database.js
index 3374aff9e..46e6ecaf3 100644
--- a/server/database.js
+++ b/server/database.js
@@ -1,11 +1,11 @@
 const fs = require("fs");
 const { R } = require("redbean-node");
-const { setSetting, setting } = require("./util-server");
 const { log, sleep } = require("../src/util");
 const knex = require("knex");
 const path = require("path");
 const { EmbeddedMariaDB } = require("./embedded-mariadb");
 const mysql = require("mysql2/promise");
+const { Settings } = require("./settings");
 
 /**
  * Database & App Data Folder
@@ -420,7 +420,7 @@ class Database {
      * @deprecated
      */
     static async patchSqlite() {
-        let version = parseInt(await setting("database_version"));
+        let version = parseInt(await Settings.get("database_version"));
 
         if (! version) {
             version = 0;
@@ -445,7 +445,7 @@ class Database {
                     log.info("db", `Patching ${sqlFile}`);
                     await Database.importSQLFile(sqlFile);
                     log.info("db", `Patched ${sqlFile}`);
-                    await setSetting("database_version", i);
+                    await Settings.set("database_version", i);
                 }
             } catch (ex) {
                 await Database.close();
@@ -471,7 +471,7 @@ class Database {
      */
     static async patchSqlite2() {
         log.debug("db", "Database Patch 2.0 Process");
-        let databasePatchedFiles = await setting("databasePatchedFiles");
+        let databasePatchedFiles = await Settings.get("databasePatchedFiles");
 
         if (! databasePatchedFiles) {
             databasePatchedFiles = {};
@@ -499,7 +499,7 @@ class Database {
             process.exit(1);
         }
 
-        await setSetting("databasePatchedFiles", databasePatchedFiles);
+        await Settings.set("databasePatchedFiles", databasePatchedFiles);
     }
 
     /**
@@ -512,27 +512,27 @@ class Database {
         // Fix 1.13.0 empty slug bug
         await R.exec("UPDATE status_page SET slug = 'empty-slug-recover' WHERE TRIM(slug) = ''");
 
-        let title = await setting("title");
+        let title = await Settings.get("title");
 
         if (title) {
-            console.log("Migrating Status Page");
+            log.info("database", "Migrating Status Page");
 
             let statusPageCheck = await R.findOne("status_page", " slug = 'default' ");
 
             if (statusPageCheck !== null) {
-                console.log("Migrating Status Page - Skip, default slug record is already existing");
+                log.info("database", "Migrating Status Page - Skip, default slug record is already existing");
                 return;
             }
 
             let statusPage = R.dispense("status_page");
             statusPage.slug = "default";
             statusPage.title = title;
-            statusPage.description = await setting("description");
-            statusPage.icon = await setting("icon");
-            statusPage.theme = await setting("statusPageTheme");
-            statusPage.published = !!await setting("statusPagePublished");
-            statusPage.search_engine_index = !!await setting("searchEngineIndex");
-            statusPage.show_tags = !!await setting("statusPageTags");
+            statusPage.description = await Settings.get("description");
+            statusPage.icon = await Settings.get("icon");
+            statusPage.theme = await Settings.get("statusPageTheme");
+            statusPage.published = !!await Settings.get("statusPagePublished");
+            statusPage.search_engine_index = !!await Settings.get("searchEngineIndex");
+            statusPage.show_tags = !!await Settings.get("statusPageTags");
             statusPage.password = null;
 
             if (!statusPage.title) {
@@ -560,13 +560,13 @@ class Database {
             await R.exec("DELETE FROM setting WHERE type = 'statusPage'");
 
             // Migrate Entry Page if it is status page
-            let entryPage = await setting("entryPage");
+            let entryPage = await Settings.get("entryPage");
 
             if (entryPage === "statusPage") {
-                await setSetting("entryPage", "statusPage-default", "general");
+                await Settings.set("entryPage", "statusPage-default", "general");
             }
 
-            console.log("Migrating Status Page - Done");
+            log.info("database", "Migrating Status Page - Done");
         }
 
     }
diff --git a/server/jobs/clear-old-data.js b/server/jobs/clear-old-data.js
index 248a4d409..b3a1676dc 100644
--- a/server/jobs/clear-old-data.js
+++ b/server/jobs/clear-old-data.js
@@ -1,7 +1,7 @@
 const { R } = require("redbean-node");
 const { log } = require("../../src/util");
-const { setSetting, setting } = require("../util-server");
 const Database = require("../database");
+const { Settings } = require("../settings");
 
 const DEFAULT_KEEP_PERIOD = 180;
 
@@ -11,11 +11,11 @@ const DEFAULT_KEEP_PERIOD = 180;
  */
 
 const clearOldData = async () => {
-    let period = await setting("keepDataPeriodDays");
+    let period = await Settings.get("keepDataPeriodDays");
 
     // Set Default Period
     if (period == null) {
-        await setSetting("keepDataPeriodDays", DEFAULT_KEEP_PERIOD, "general");
+        await Settings.set("keepDataPeriodDays", DEFAULT_KEEP_PERIOD, "general");
         period = DEFAULT_KEEP_PERIOD;
     }
 
@@ -25,7 +25,7 @@ const clearOldData = async () => {
         parsedPeriod = parseInt(period);
     } catch (_) {
         log.warn("clearOldData", "Failed to parse setting, resetting to default..");
-        await setSetting("keepDataPeriodDays", DEFAULT_KEEP_PERIOD, "general");
+        await Settings.set("keepDataPeriodDays", DEFAULT_KEEP_PERIOD, "general");
         parsedPeriod = DEFAULT_KEEP_PERIOD;
     }
 
diff --git a/server/model/monitor.js b/server/model/monitor.js
index b55e98918..4beeb0036 100644
--- a/server/model/monitor.js
+++ b/server/model/monitor.js
@@ -4,7 +4,7 @@ const { Prometheus } = require("../prometheus");
 const { log, UP, DOWN, PENDING, MAINTENANCE, flipStatus, MAX_INTERVAL_SECOND, MIN_INTERVAL_SECOND,
     SQL_DATETIME_FORMAT, evaluateJsonQuery
 } = require("../../src/util");
-const { tcping, ping, checkCertificate, checkStatusCode, getTotalClientInRoom, setting, mssqlQuery, postgresQuery, mysqlQuery, setSetting, httpNtlm, radius, grpcQuery,
+const { tcping, ping, checkCertificate, checkStatusCode, getTotalClientInRoom, mssqlQuery, postgresQuery, mysqlQuery, httpNtlm, radius, grpcQuery,
     redisPingAsync, kafkaProducerAsync, getOidcTokenClientCredentials, rootCertificatesFingerprints, axiosAbortSignal
 } = require("../util-server");
 const { R } = require("redbean-node");
@@ -24,6 +24,7 @@ const { CookieJar } = require("tough-cookie");
 const { HttpsCookieAgent } = require("http-cookie-agent/http");
 const https = require("https");
 const http = require("http");
+const { Settings } = require("../settings");
 
 const rootCertificates = rootCertificatesFingerprints();
 
@@ -672,7 +673,7 @@ class Monitor extends BeanModel {
 
                 } else if (this.type === "steam") {
                     const steamApiUrl = "https://api.steampowered.com/IGameServersService/GetServerList/v1/";
-                    const steamAPIKey = await setting("steamAPIKey");
+                    const steamAPIKey = await Settings.get("steamAPIKey");
                     const filter = `addr\\${this.hostname}:${this.port}`;
 
                     if (!steamAPIKey) {
@@ -1379,11 +1380,12 @@ class Monitor extends BeanModel {
                 return;
             }
 
-            let notifyDays = await setting("tlsExpiryNotifyDays");
+            let notifyDays = await Settings.get("tlsExpiryNotifyDays");
             if (notifyDays == null || !Array.isArray(notifyDays)) {
                 // Reset Default
-                await setSetting("tlsExpiryNotifyDays", [ 7, 14, 21 ], "general");
+                await Settings.set("tlsExpiryNotifyDays", [ 7, 14, 21 ], "general");
                 notifyDays = [ 7, 14, 21 ];
+                await Settings.set("tlsExpiryNotifyDays", notifyDays, "general");
             }
 
             if (Array.isArray(notifyDays)) {
diff --git a/server/modules/apicache/apicache.js b/server/modules/apicache/apicache.js
index 41930b24d..804fa93ed 100644
--- a/server/modules/apicache/apicache.js
+++ b/server/modules/apicache/apicache.js
@@ -1,5 +1,6 @@
 let url = require("url");
 let MemoryCache = require("./memory-cache");
+const { log } = require("../../../src/util");
 
 let t = {
     ms: 1,
@@ -90,24 +91,6 @@ function ApiCache() {
     instances.push(this);
     this.id = instances.length;
 
-    /**
-     * Logs a message to the console if the `DEBUG` environment variable is set.
-     * @param {string} a The first argument to log.
-     * @param {string} b The second argument to log.
-     * @param {string} c The third argument to log.
-     * @param {string} d The fourth argument to log, and so on... (optional)
-     *
-     * Generated by Trelent
-     */
-    function debug(a, b, c, d) {
-        let arr = ["\x1b[36m[apicache]\x1b[0m", a, b, c, d].filter(function (arg) {
-            return arg !== undefined;
-        });
-        let debugEnv = process.env.DEBUG && process.env.DEBUG.split(",").indexOf("apicache") !== -1;
-
-        return (globalOptions.debug || debugEnv) && console.log.apply(null, arr);
-    }
-
     /**
      * Returns true if the given request and response should be logged.
      * @param {Object} request The HTTP request object.
@@ -146,7 +129,7 @@ function ApiCache() {
         let groupName = req.apicacheGroup;
 
         if (groupName) {
-            debug("group detected \"" + groupName + "\"");
+            log.debug("apicache", `group detected "${groupName}"`);
             let group = (index.groups[groupName] = index.groups[groupName] || []);
             group.unshift(key);
         }
@@ -212,7 +195,7 @@ function ApiCache() {
                 redis.hset(key, "duration", duration);
                 redis.expire(key, duration / 1000, expireCallback || function () {});
             } catch (err) {
-                debug("[apicache] error in redis.hset()");
+                log.debug("apicache", `error in redis.hset(): ${err}`);
             }
         } else {
             memCache.add(key, value, duration, expireCallback);
@@ -320,10 +303,10 @@ function ApiCache() {
 
                     // display log entry
                     let elapsed = new Date() - req.apicacheTimer;
-                    debug("adding cache entry for \"" + key + "\" @ " + strDuration, logDuration(elapsed));
-                    debug("_apicache.headers: ", res._apicache.headers);
-                    debug("res.getHeaders(): ", getSafeHeaders(res));
-                    debug("cacheObject: ", cacheObject);
+                    log.debug("apicache", `adding cache entry for "${key}" @ ${strDuration} ${logDuration(elapsed)}`);
+                    log.debug("apicache", `_apicache.headers: ${JSON.stringify(res._apicache.headers)}`);
+                    log.debug("apicache", `res.getHeaders(): ${JSON.stringify(getSafeHeaders(res))}`);
+                    log.debug("apicache", `cacheObject: ${JSON.stringify(cacheObject)}`);
                 }
             }
 
@@ -402,10 +385,10 @@ function ApiCache() {
         let redis = globalOptions.redisClient;
 
         if (group) {
-            debug("clearing group \"" + target + "\"");
+            log.debug("apicache", `clearing group "${target}"`);
 
             group.forEach(function (key) {
-                debug("clearing cached entry for \"" + key + "\"");
+                log.debug("apicache", `clearing cached entry for "${key}"`);
                 clearTimeout(timers[key]);
                 delete timers[key];
                 if (!globalOptions.redisClient) {
@@ -414,7 +397,7 @@ function ApiCache() {
                     try {
                         redis.del(key);
                     } catch (err) {
-                        console.log("[apicache] error in redis.del(\"" + key + "\")");
+                        log.info("apicache", "error in redis.del(\"" + key + "\")");
                     }
                 }
                 index.all = index.all.filter(doesntMatch(key));
@@ -422,7 +405,7 @@ function ApiCache() {
 
             delete index.groups[target];
         } else if (target) {
-            debug("clearing " + (isAutomatic ? "expired" : "cached") + " entry for \"" + target + "\"");
+            log.debug("apicache", `clearing ${isAutomatic ? "expired" : "cached"} entry for "${target}"`);
             clearTimeout(timers[target]);
             delete timers[target];
             // clear actual cached entry
@@ -432,7 +415,7 @@ function ApiCache() {
                 try {
                     redis.del(target);
                 } catch (err) {
-                    console.log("[apicache] error in redis.del(\"" + target + "\")");
+                    log.error("apicache", "error in redis.del(\"" + target + "\")");
                 }
             }
 
@@ -449,7 +432,7 @@ function ApiCache() {
                 }
             });
         } else {
-            debug("clearing entire index");
+            log.debug("apicache", "clearing entire index");
 
             if (!redis) {
                 memCache.clear();
@@ -461,7 +444,7 @@ function ApiCache() {
                     try {
                         redis.del(key);
                     } catch (err) {
-                        console.log("[apicache] error in redis.del(\"" + key + "\")");
+                        log.error("apicache", `error in redis.del("${key}"): ${err}`);
                     }
                 });
             }
@@ -528,7 +511,7 @@ function ApiCache() {
 
     /**
      * Get index of a group
-     * @param {string} group 
+     * @param {string} group
      * @returns {number}
      */
     this.getIndex = function (group) {
@@ -543,9 +526,9 @@ function ApiCache() {
      * Express middleware
      * @param {(string|number)} strDuration Duration to cache responses
      * for.
-     * @param {function(Object, Object):boolean} middlewareToggle 
+     * @param {function(Object, Object):boolean} middlewareToggle
      * @param {Object} localOptions Options for APICache
-     * @returns 
+     * @returns
      */
     this.middleware = function cache(strDuration, middlewareToggle, localOptions) {
         let duration = instance.getDuration(strDuration);
@@ -752,7 +735,7 @@ function ApiCache() {
          */
         let cache = function (req, res, next) {
             function bypass() {
-                debug("bypass detected, skipping cache.");
+                log.debug("apicache", "bypass detected, skipping cache.");
                 return next();
             }
 
@@ -805,7 +788,7 @@ function ApiCache() {
             // send if cache hit from memory-cache
             if (cached) {
                 let elapsed = new Date() - req.apicacheTimer;
-                debug("sending cached (memory-cache) version of", key, logDuration(elapsed));
+                log.debug("apicache", `sending cached (memory-cache) version of ${key} ${logDuration(elapsed)}`);
 
                 perf.hit(key);
                 return sendCachedResponse(req, res, cached, middlewareToggle, next, duration);
@@ -817,7 +800,7 @@ function ApiCache() {
                     redis.hgetall(key, function (err, obj) {
                         if (!err && obj && obj.response) {
                             let elapsed = new Date() - req.apicacheTimer;
-                            debug("sending cached (redis) version of", key, logDuration(elapsed));
+                            log.debug("apicache", "sending cached (redis) version of "+ key+" "+ logDuration(elapsed));
 
                             perf.hit(key);
                             return sendCachedResponse(
@@ -859,7 +842,7 @@ function ApiCache() {
 
     /**
      * Process options
-     * @param {Object} options 
+     * @param {Object} options
      * @returns {Object}
      */
     this.options = function (options) {
@@ -873,7 +856,7 @@ function ApiCache() {
             }
 
             if (globalOptions.trackPerformance) {
-                debug("WARNING: using trackPerformance flag can cause high memory usage!");
+                log.debug("apicache", "WARNING: using trackPerformance flag can cause high memory usage!");
             }
 
             return this;
diff --git a/server/monitor-types/real-browser-monitor-type.js b/server/monitor-types/real-browser-monitor-type.js
index f1219af18..ce3abcb2d 100644
--- a/server/monitor-types/real-browser-monitor-type.js
+++ b/server/monitor-types/real-browser-monitor-type.js
@@ -63,7 +63,7 @@ if (process.platform === "win32") {
  * @returns {Promise<boolean>} The executable is allowed?
  */
 async function isAllowedChromeExecutable(executablePath) {
-    console.log(config.args);
+    log.info("Chromium", config.args);
     if (config.args["allow-all-chrome-exec"] || process.env.UPTIME_KUMA_ALLOW_ALL_CHROME_EXEC === "1") {
         return true;
     }
@@ -102,7 +102,8 @@ async function getBrowser() {
  */
 async function getRemoteBrowser(remoteBrowserID, userId) {
     let remoteBrowser = await RemoteBrowser.get(remoteBrowserID, userId);
-    log.debug("MONITOR", `Using remote browser: ${remoteBrowser.name} (${remoteBrowser.id})`);
+    log.debug("Chromium", `Using remote browser: ${remoteBrowser.name} (${remoteBrowser.id})`);
+    browser = chromium.connect(remoteBrowser.url);
     browser = await chromium.connect(remoteBrowser.url);
     return browser;
 }
diff --git a/server/notification-providers/alertnow.js b/server/notification-providers/alertnow.js
index 4257ca9cd..ecc03e84b 100644
--- a/server/notification-providers/alertnow.js
+++ b/server/notification-providers/alertnow.js
@@ -1,7 +1,7 @@
 const NotificationProvider = require("./notification-provider");
 const axios = require("axios");
-const { setting } = require("../util-server");
 const { getMonitorRelativeURL, UP, DOWN } = require("../../src/util");
+const { Settings } = require("../settings");
 
 class AlertNow extends NotificationProvider {
     name = "AlertNow";
@@ -29,7 +29,7 @@ class AlertNow extends NotificationProvider {
 
             textMsg += ` - ${msg}`;
 
-            const baseURL = await setting("primaryBaseURL");
+            const baseURL = await Settings.get("primaryBaseURL");
             if (baseURL && monitorJSON) {
                 textMsg += ` >> ${baseURL + getMonitorRelativeURL(monitorJSON.id)}`;
             }
diff --git a/server/notification-providers/flashduty.js b/server/notification-providers/flashduty.js
index c340ed06f..09ee8913a 100644
--- a/server/notification-providers/flashduty.js
+++ b/server/notification-providers/flashduty.js
@@ -1,7 +1,7 @@
 const NotificationProvider = require("./notification-provider");
 const axios = require("axios");
 const { UP, DOWN, getMonitorRelativeURL } = require("../../src/util");
-const { setting } = require("../util-server");
+const { Settings } = require("../settings");
 const successMessage = "Sent Successfully.";
 
 class FlashDuty extends NotificationProvider {
@@ -84,7 +84,7 @@ class FlashDuty extends NotificationProvider {
             }
         };
 
-        const baseURL = await setting("primaryBaseURL");
+        const baseURL = await Settings.get("primaryBaseURL");
         if (baseURL && monitorInfo) {
             options.client = "Uptime Kuma";
             options.client_url = baseURL + getMonitorRelativeURL(monitorInfo.id);
diff --git a/server/notification-providers/google-chat.js b/server/notification-providers/google-chat.js
index 0b72fea95..e683a2070 100644
--- a/server/notification-providers/google-chat.js
+++ b/server/notification-providers/google-chat.js
@@ -1,7 +1,7 @@
 const NotificationProvider = require("./notification-provider");
 const axios = require("axios");
-const { setting } = require("../util-server");
 const { getMonitorRelativeURL, UP } = require("../../src/util");
+const { Settings } = require("../settings");
 
 class GoogleChat extends NotificationProvider {
     name = "GoogleChat";
@@ -45,7 +45,7 @@ class GoogleChat extends NotificationProvider {
             }
 
             // add button for monitor link if available
-            const baseURL = await setting("primaryBaseURL");
+            const baseURL = await Settings.get("primaryBaseURL");
             if (baseURL) {
                 const urlPath = monitorJSON ? getMonitorRelativeURL(monitorJSON.id) : "/";
                 sectionWidgets.push({
diff --git a/server/notification-providers/pagerduty.js b/server/notification-providers/pagerduty.js
index c60d782e7..7aa19bb4b 100644
--- a/server/notification-providers/pagerduty.js
+++ b/server/notification-providers/pagerduty.js
@@ -1,7 +1,7 @@
 const NotificationProvider = require("./notification-provider");
 const axios = require("axios");
 const { UP, DOWN, getMonitorRelativeURL } = require("../../src/util");
-const { setting } = require("../util-server");
+const { Settings } = require("../settings");
 let successMessage = "Sent Successfully.";
 
 class PagerDuty extends NotificationProvider {
@@ -95,7 +95,7 @@ class PagerDuty extends NotificationProvider {
             }
         };
 
-        const baseURL = await setting("primaryBaseURL");
+        const baseURL = await Settings.get("primaryBaseURL");
         if (baseURL && monitorInfo) {
             options.client = "Uptime Kuma";
             options.client_url = baseURL + getMonitorRelativeURL(monitorInfo.id);
diff --git a/server/notification-providers/pagertree.js b/server/notification-providers/pagertree.js
index c7a5338d1..62f229ace 100644
--- a/server/notification-providers/pagertree.js
+++ b/server/notification-providers/pagertree.js
@@ -1,7 +1,7 @@
 const NotificationProvider = require("./notification-provider");
 const axios = require("axios");
 const { UP, DOWN, getMonitorRelativeURL } = require("../../src/util");
-const { setting } = require("../util-server");
+const { Settings } = require("../settings");
 let successMessage = "Sent Successfully.";
 
 class PagerTree extends NotificationProvider {
@@ -74,7 +74,7 @@ class PagerTree extends NotificationProvider {
             }
         };
 
-        const baseURL = await setting("primaryBaseURL");
+        const baseURL = await Settings.get("primaryBaseURL");
         if (baseURL && monitorJSON) {
             options.client = "Uptime Kuma";
             options.client_url = baseURL + getMonitorRelativeURL(monitorJSON.id);
diff --git a/server/notification-providers/rocket-chat.js b/server/notification-providers/rocket-chat.js
index 690e33a86..174a94950 100644
--- a/server/notification-providers/rocket-chat.js
+++ b/server/notification-providers/rocket-chat.js
@@ -1,8 +1,8 @@
 const NotificationProvider = require("./notification-provider");
 const axios = require("axios");
 const Slack = require("./slack");
-const { setting } = require("../util-server");
 const { getMonitorRelativeURL, DOWN } = require("../../src/util");
+const { Settings } = require("../settings");
 
 class RocketChat extends NotificationProvider {
     name = "rocket.chat";
@@ -49,7 +49,7 @@ class RocketChat extends NotificationProvider {
                 await Slack.deprecateURL(notification.rocketbutton);
             }
 
-            const baseURL = await setting("primaryBaseURL");
+            const baseURL = await Settings.get("primaryBaseURL");
 
             if (baseURL) {
                 data.attachments[0].title_link = baseURL + getMonitorRelativeURL(monitorJSON.id);
diff --git a/server/notification-providers/slack.js b/server/notification-providers/slack.js
index 702256429..98ed1a908 100644
--- a/server/notification-providers/slack.js
+++ b/server/notification-providers/slack.js
@@ -1,7 +1,8 @@
 const NotificationProvider = require("./notification-provider");
 const axios = require("axios");
-const { setSettings, setting } = require("../util-server");
 const { getMonitorRelativeURL, UP } = require("../../src/util");
+const { Settings } = require("../settings");
+const { log } = require("../../src/util");
 
 class Slack extends NotificationProvider {
     name = "slack";
@@ -14,15 +15,13 @@ class Slack extends NotificationProvider {
      * @returns {Promise<void>}
      */
     static async deprecateURL(url) {
-        let currentPrimaryBaseURL = await setting("primaryBaseURL");
+        let currentPrimaryBaseURL = await Settings.get("primaryBaseURL");
 
         if (!currentPrimaryBaseURL) {
-            console.log("Move the url to be the primary base URL");
-            await setSettings("general", {
-                primaryBaseURL: url,
-            });
+            log.error("notification", "Move the url to be the primary base URL");
+            await Settings.set("primaryBaseURL", url, "general");
         } else {
-            console.log("Already there, no need to move the primary base URL");
+            log.debug("notification", "Already there, no need to move the primary base URL");
         }
     }
 
@@ -136,7 +135,7 @@ class Slack extends NotificationProvider {
                 return okMsg;
             }
 
-            const baseURL = await setting("primaryBaseURL");
+            const baseURL = await Settings.get("primaryBaseURL");
 
             const title = "Uptime Kuma Alert";
             let data = {
diff --git a/server/notification-providers/splunk.js b/server/notification-providers/splunk.js
index e07c51039..ad4dc6b39 100644
--- a/server/notification-providers/splunk.js
+++ b/server/notification-providers/splunk.js
@@ -1,7 +1,7 @@
 const NotificationProvider = require("./notification-provider");
 const axios = require("axios");
 const { UP, DOWN, getMonitorRelativeURL } = require("../../src/util");
-const { setting } = require("../util-server");
+const { Settings } = require("../settings");
 let successMessage = "Sent Successfully.";
 
 class Splunk extends NotificationProvider {
@@ -95,7 +95,7 @@ class Splunk extends NotificationProvider {
             }
         };
 
-        const baseURL = await setting("primaryBaseURL");
+        const baseURL = await Settings.get("primaryBaseURL");
         if (baseURL && monitorInfo) {
             options.client = "Uptime Kuma";
             options.client_url = baseURL + getMonitorRelativeURL(monitorInfo.id);
diff --git a/server/notification-providers/stackfield.js b/server/notification-providers/stackfield.js
index 65a92459a..e4e31a9ab 100644
--- a/server/notification-providers/stackfield.js
+++ b/server/notification-providers/stackfield.js
@@ -1,7 +1,7 @@
 const NotificationProvider = require("./notification-provider");
 const axios = require("axios");
-const { setting } = require("../util-server");
 const { getMonitorRelativeURL } = require("../../src/util");
+const { Settings } = require("../settings");
 
 class Stackfield extends NotificationProvider {
     name = "stackfield";
@@ -23,7 +23,7 @@ class Stackfield extends NotificationProvider {
 
             textMsg += `\n${msg}`;
 
-            const baseURL = await setting("primaryBaseURL");
+            const baseURL = await Settings.get("primaryBaseURL");
             if (baseURL) {
                 textMsg += `\n${baseURL + getMonitorRelativeURL(monitorJSON.id)}`;
             }
diff --git a/server/proxy.js b/server/proxy.js
index 3f3771ab9..3f1215b25 100644
--- a/server/proxy.js
+++ b/server/proxy.js
@@ -2,7 +2,7 @@ const { R } = require("redbean-node");
 const HttpProxyAgent = require("http-proxy-agent");
 const HttpsProxyAgent = require("https-proxy-agent");
 const SocksProxyAgent = require("socks-proxy-agent");
-const { debug } = require("../src/util");
+const { log } = require("../src/util");
 const { UptimeKumaServer } = require("./uptime-kuma-server");
 const { CookieJar } = require("tough-cookie");
 const { createCookieAgent } = require("http-cookie-agent/http");
@@ -110,9 +110,9 @@ class Proxy {
             proxyOptions.auth = `${proxy.username}:${proxy.password}`;
         }
 
-        debug(`Proxy Options: ${JSON.stringify(proxyOptions)}`);
-        debug(`HTTP Agent Options: ${JSON.stringify(httpAgentOptions)}`);
-        debug(`HTTPS Agent Options: ${JSON.stringify(httpsAgentOptions)}`);
+        log.debug("update-proxy", `Proxy Options: ${JSON.stringify(proxyOptions)}`);
+        log.debug("update-proxy", `HTTP Agent Options: ${JSON.stringify(httpAgentOptions)}`);
+        log.debug("update-proxy", `HTTPS Agent Options: ${JSON.stringify(httpsAgentOptions)}`);
 
         switch (proxy.protocol) {
             case "http":
diff --git a/server/routers/api-router.js b/server/routers/api-router.js
index ed6db2cd1..9e7c9348c 100644
--- a/server/routers/api-router.js
+++ b/server/routers/api-router.js
@@ -1,6 +1,5 @@
 let express = require("express");
 const {
-    setting,
     allowDevAllOrigin,
     allowAllOrigin,
     percentageToColor,
@@ -18,6 +17,7 @@ const { makeBadge } = require("badge-maker");
 const { Prometheus } = require("../prometheus");
 const Database = require("../database");
 const { UptimeCalculator } = require("../uptime-calculator");
+const { Settings } = require("../settings");
 
 let router = express.Router();
 
@@ -30,7 +30,7 @@ router.get("/api/entry-page", async (request, response) => {
 
     let result = { };
     let hostname = request.hostname;
-    if ((await setting("trustProxy")) && request.headers["x-forwarded-host"]) {
+    if ((await Settings.get("trustProxy")) && request.headers["x-forwarded-host"]) {
         hostname = request.headers["x-forwarded-host"];
     }
 
diff --git a/server/server.js b/server/server.js
index 1f27b3c43..d040d6e87 100644
--- a/server/server.js
+++ b/server/server.js
@@ -90,8 +90,7 @@ const Monitor = require("./model/monitor");
 const User = require("./model/user");
 
 log.debug("server", "Importing Settings");
-const { getSettings, setSettings, setting, initJWTSecret, checkLogin, doubleCheckPassword, shake256, SHAKE256_LENGTH, allowDevAllOrigin,
-} = require("./util-server");
+const { initJWTSecret, checkLogin, doubleCheckPassword, shake256, SHAKE256_LENGTH, allowDevAllOrigin } = require("./util-server");
 
 log.debug("server", "Importing Notification");
 const { Notification } = require("./notification");
@@ -201,7 +200,7 @@ let needSetup = false;
     // Entry Page
     app.get("/", async (request, response) => {
         let hostname = request.hostname;
-        if (await setting("trustProxy")) {
+        if (await Settings.get("trustProxy")) {
             const proxy = request.headers["x-forwarded-host"];
             if (proxy) {
                 hostname = proxy;
@@ -281,7 +280,7 @@ let needSetup = false;
     // Robots.txt
     app.get("/robots.txt", async (_request, response) => {
         let txt = "User-agent: *\nDisallow:";
-        if (!await setting("searchEngineIndex")) {
+        if (!await Settings.get("searchEngineIndex")) {
             txt += " /";
         }
         response.setHeader("Content-Type", "text/plain");
@@ -1325,7 +1324,7 @@ let needSetup = false;
         socket.on("getSettings", async (callback) => {
             try {
                 checkLogin(socket);
-                const data = await getSettings("general");
+                const data = await Settings.getSettings("general");
 
                 if (!data.serverTimezone) {
                     data.serverTimezone = await server.getTimezone();
@@ -1353,7 +1352,7 @@ let needSetup = false;
                 // Disabled Auth + Want to Enable Auth => No Check
                 // Enabled Auth + Want to Disable Auth => Check!!
                 // Enabled Auth + Want to Enable Auth => No Check
-                const currentDisabledAuth = await setting("disableAuth");
+                const currentDisabledAuth = await Settings.get("disableAuth");
                 if (!currentDisabledAuth && data.disableAuth) {
                     await doubleCheckPassword(socket, currentPassword);
                 }
@@ -1367,7 +1366,7 @@ let needSetup = false;
                 const previousChromeExecutable = await Settings.get("chromeExecutable");
                 const previousNSCDStatus = await Settings.get("nscd");
 
-                await setSettings("general", data);
+                await Settings.setSettings("general", data);
                 server.entryPage = data.entryPage;
 
                 // Also need to apply timezone globally
@@ -1463,7 +1462,7 @@ let needSetup = false;
                 });
 
             } catch (e) {
-                console.error(e);
+                log.error("server", e);
 
                 callback({
                     ok: false,
@@ -1576,7 +1575,7 @@ let needSetup = false;
         // ***************************
 
         log.debug("auth", "check auto login");
-        if (await setting("disableAuth")) {
+        if (await Settings.get("disableAuth")) {
             log.info("auth", "Disabled Auth: auto login to admin");
             await afterLogin(socket, await R.findOne("user"));
             socket.emit("autoLogin");
diff --git a/server/socket-handlers/api-key-socket-handler.js b/server/socket-handlers/api-key-socket-handler.js
index f76b90991..1394f8044 100644
--- a/server/socket-handlers/api-key-socket-handler.js
+++ b/server/socket-handlers/api-key-socket-handler.js
@@ -60,7 +60,7 @@ module.exports.apiKeySocketHandler = (socket) => {
                 ok: true,
             });
         } catch (e) {
-            console.error(e);
+            log.error("apikeys", e);
             callback({
                 ok: false,
                 msg: e.message,
diff --git a/server/socket-handlers/cloudflared-socket-handler.js b/server/socket-handlers/cloudflared-socket-handler.js
index 809191fe8..1cd0c53d0 100644
--- a/server/socket-handlers/cloudflared-socket-handler.js
+++ b/server/socket-handlers/cloudflared-socket-handler.js
@@ -1,7 +1,8 @@
-const { checkLogin, setSetting, setting, doubleCheckPassword } = require("../util-server");
+const { checkLogin, doubleCheckPassword } = require("../util-server");
 const { CloudflaredTunnel } = require("node-cloudflared-tunnel");
 const { UptimeKumaServer } = require("../uptime-kuma-server");
 const { log } = require("../../src/util");
+const { Settings } = require("../settings");
 const io = UptimeKumaServer.getInstance().io;
 
 const prefix = "cloudflared_";
@@ -40,7 +41,7 @@ module.exports.cloudflaredSocketHandler = (socket) => {
             socket.join("cloudflared");
             io.to(socket.userID).emit(prefix + "installed", cloudflared.checkInstalled());
             io.to(socket.userID).emit(prefix + "running", cloudflared.running);
-            io.to(socket.userID).emit(prefix + "token", await setting("cloudflaredTunnelToken"));
+            io.to(socket.userID).emit(prefix + "token", await Settings.get("cloudflaredTunnelToken"));
         } catch (error) { }
     });
 
@@ -55,7 +56,7 @@ module.exports.cloudflaredSocketHandler = (socket) => {
         try {
             checkLogin(socket);
             if (token && typeof token === "string") {
-                await setSetting("cloudflaredTunnelToken", token);
+                await Settings.set("cloudflaredTunnelToken", token);
                 cloudflared.token = token;
             } else {
                 cloudflared.token = null;
@@ -67,7 +68,7 @@ module.exports.cloudflaredSocketHandler = (socket) => {
     socket.on(prefix + "stop", async (currentPassword, callback) => {
         try {
             checkLogin(socket);
-            const disabledAuth = await setting("disableAuth");
+            const disabledAuth = await Settings.get("disableAuth");
             if (!disabledAuth) {
                 await doubleCheckPassword(socket, currentPassword);
             }
@@ -83,7 +84,7 @@ module.exports.cloudflaredSocketHandler = (socket) => {
     socket.on(prefix + "removeToken", async () => {
         try {
             checkLogin(socket);
-            await setSetting("cloudflaredTunnelToken", "");
+            await Settings.set("cloudflaredTunnelToken", "");
         } catch (error) { }
     });
 
@@ -96,15 +97,15 @@ module.exports.cloudflaredSocketHandler = (socket) => {
  */
 module.exports.autoStart = async (token) => {
     if (!token) {
-        token = await setting("cloudflaredTunnelToken");
+        token = await Settings.get("cloudflaredTunnelToken");
     } else {
         // Override the current token via args or env var
-        await setSetting("cloudflaredTunnelToken", token);
-        console.log("Use cloudflared token from args or env var");
+        await Settings.set("cloudflaredTunnelToken", token);
+        log.info("cloudflare", "Use cloudflared token from args or env var");
     }
 
     if (token) {
-        console.log("Start cloudflared");
+        log.info("cloudflare", "Start cloudflared");
         cloudflared.token = token;
         cloudflared.start();
     }
diff --git a/server/socket-handlers/maintenance-socket-handler.js b/server/socket-handlers/maintenance-socket-handler.js
index 7de13fe57..201014c22 100644
--- a/server/socket-handlers/maintenance-socket-handler.js
+++ b/server/socket-handlers/maintenance-socket-handler.js
@@ -67,7 +67,7 @@ module.exports.maintenanceSocketHandler = (socket) => {
             });
 
         } catch (e) {
-            console.error(e);
+            log.error("maintenance", e);
             callback({
                 ok: false,
                 msg: e.message,
@@ -177,7 +177,7 @@ module.exports.maintenanceSocketHandler = (socket) => {
                 ok: true,
             });
         } catch (e) {
-            console.error(e);
+            log.error("maintenance", e);
             callback({
                 ok: false,
                 msg: e.message,
@@ -201,7 +201,7 @@ module.exports.maintenanceSocketHandler = (socket) => {
             });
 
         } catch (e) {
-            console.error(e);
+            log.error("maintenance", e);
             callback({
                 ok: false,
                 msg: e.message,
@@ -225,7 +225,7 @@ module.exports.maintenanceSocketHandler = (socket) => {
             });
 
         } catch (e) {
-            console.error(e);
+            log.error("maintenance", e);
             callback({
                 ok: false,
                 msg: e.message,
diff --git a/server/socket-handlers/status-page-socket-handler.js b/server/socket-handlers/status-page-socket-handler.js
index 0804da15d..cf0accc23 100644
--- a/server/socket-handlers/status-page-socket-handler.js
+++ b/server/socket-handlers/status-page-socket-handler.js
@@ -1,5 +1,5 @@
 const { R } = require("redbean-node");
-const { checkLogin, setSetting } = require("../util-server");
+const { checkLogin } = require("../util-server");
 const dayjs = require("dayjs");
 const { log } = require("../../src/util");
 const ImageDataURI = require("../image-data-uri");
@@ -7,6 +7,7 @@ const Database = require("../database");
 const apicache = require("../modules/apicache");
 const StatusPage = require("../model/status_page");
 const { UptimeKumaServer } = require("../uptime-kuma-server");
+const { Settings } = require("../settings");
 
 /**
  * Socket handlers for status page
@@ -233,7 +234,7 @@ module.exports.statusPageSocketHandler = (socket) => {
             // Also change entry page to new slug if it is the default one, and slug is changed.
             if (server.entryPage === "statusPage-" + slug && statusPage.slug !== slug) {
                 server.entryPage = "statusPage-" + statusPage.slug;
-                await setSetting("entryPage", server.entryPage, "general");
+                await Settings.set("entryPage", server.entryPage, "general");
             }
 
             apicache.clear();
@@ -291,7 +292,7 @@ module.exports.statusPageSocketHandler = (socket) => {
             });
 
         } catch (error) {
-            console.error(error);
+            log.error("socket", error);
             callback({
                 ok: false,
                 msg: error.message,
@@ -313,7 +314,7 @@ module.exports.statusPageSocketHandler = (socket) => {
                 // Reset entry page if it is the default one.
                 if (server.entryPage === "statusPage-" + slug) {
                     server.entryPage = "dashboard";
-                    await setSetting("entryPage", server.entryPage, "general");
+                    await Settings.set("entryPage", server.entryPage, "general");
                 }
 
                 // No need to delete records from `status_page_cname`, because it has cascade foreign key.
diff --git a/server/util-server.js b/server/util-server.js
index 5ebc62ac5..f4a4a67fe 100644
--- a/server/util-server.js
+++ b/server/util-server.js
@@ -12,7 +12,6 @@ const { Client } = require("pg");
 const postgresConParse = require("pg-connection-string").parse;
 const mysql = require("mysql2");
 const { NtlmClient } = require("./modules/axios-ntlm/lib/ntlmClient.js");
-const { Settings } = require("./settings");
 const grpc = require("@grpc/grpc-js");
 const protojs = require("protobufjs");
 const radiusClient = require("node-radius-client");
@@ -521,46 +520,6 @@ exports.redisPingAsync = function (dsn, rejectUnauthorized) {
     });
 };
 
-/**
- * Retrieve value of setting based on key
- * @param {string} key Key of setting to retrieve
- * @returns {Promise<any>} Value
- * @deprecated Use await Settings.get(key)
- */
-exports.setting = async function (key) {
-    return await Settings.get(key);
-};
-
-/**
- * Sets the specified setting to specified value
- * @param {string} key Key of setting to set
- * @param {any} value Value to set to
- * @param {?string} type Type of setting
- * @returns {Promise<void>}
- */
-exports.setSetting = async function (key, value, type = null) {
-    await Settings.set(key, value, type);
-};
-
-/**
- * Get settings based on type
- * @param {string} type The type of setting
- * @returns {Promise<Bean>} Settings of requested type
- */
-exports.getSettings = async function (type) {
-    return await Settings.getSettings(type);
-};
-
-/**
- * Set settings based on type
- * @param {string} type Type of settings to set
- * @param {object} data Values of settings
- * @returns {Promise<void>}
- */
-exports.setSettings = async function (type, data) {
-    await Settings.setSettings(type, data);
-};
-
 // ssl-checker by @dyaa
 //https://github.com/dyaa/ssl-checker/blob/master/src/index.ts
 
diff --git a/src/util.js b/src/util.js
index c88d04968..8b2c2b9d7 100644
--- a/src/util.js
+++ b/src/util.js
@@ -8,17 +8,34 @@
 // Backend uses the compiled file util.js
 // Frontend uses util.ts
 */
+var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
+    if (k2 === undefined) k2 = k;
+    Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
+}) : (function(o, m, k, k2) {
+    if (k2 === undefined) k2 = k;
+    o[k2] = m[k];
+}));
+var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
+    Object.defineProperty(o, "default", { enumerable: true, value: v });
+}) : function(o, v) {
+    o["default"] = v;
+});
+var __importStar = (this && this.__importStar) || function (mod) {
+    if (mod && mod.__esModule) return mod;
+    var result = {};
+    if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
+    __setModuleDefault(result, mod);
+    return result;
+};
 var __importDefault = (this && this.__importDefault) || function (mod) {
     return (mod && mod.__esModule) ? mod : { "default": mod };
 };
 var _a;
 Object.defineProperty(exports, "__esModule", { value: true });
 exports.sleep = exports.flipStatus = exports.badgeConstants = 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.isNode = exports.isDev = void 0;
-exports.evaluateJsonQuery = 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 = exports.debug = exports.ucfirst = 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 = exports.debug = exports.ucfirst = void 0;
+exports.evaluateJsonQuery = 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 = exports.ucfirst = void 0;
 const dayjs_1 = __importDefault(require("dayjs"));
-const dayjs = require("dayjs");
-const jsonata = require("jsonata");
+const jsonata = __importStar(require("jsonata"));
 exports.isDev = process.env.NODE_ENV === "development";
 exports.isNode = typeof process !== "undefined" && ((_a = process === null || process === void 0 ? void 0 : process.versions) === null || _a === void 0 ? void 0 : _a.node);
 exports.appName = "Uptime Kuma";
@@ -124,10 +141,6 @@ function ucfirst(str) {
     return firstLetter.toUpperCase() + str.substr(1);
 }
 exports.ucfirst = ucfirst;
-function debug(msg) {
-    exports.log.log("", msg, "debug");
-}
-exports.debug = debug;
 class Logger {
     constructor() {
         this.hideLog = {
@@ -155,8 +168,6 @@ class Logger {
         if (this.hideLog[level] && this.hideLog[level].includes(module.toLowerCase())) {
             return;
         }
-        module = module.toUpperCase();
-        level = level.toUpperCase();
         let now;
         if (dayjs_1.default.tz) {
             now = dayjs_1.default.tz(new Date()).format();
@@ -166,10 +177,20 @@ class Logger {
         }
         const levelColor = consoleLevelColors[level];
         const moduleColor = consoleModuleColors[intHash(module, consoleModuleColors.length)];
-        let timePart;
-        let modulePart;
-        let levelPart;
-        let msgPart;
+        let timePart = now;
+        let modulePart = module;
+        let levelPart = level;
+        let msgPart = msg;
+        if (process.env.UPTIME_KUMA_LOG_FORMAT === "json") {
+            console.log(JSON.stringify({
+                time: timePart,
+                module: modulePart,
+                level: levelPart,
+                msg: typeof msg === "string" ? msg : JSON.stringify(msg),
+            }));
+            return;
+        }
+        module = module.toUpperCase();
         if (exports.isNode) {
             switch (level) {
                 case "DEBUG":
@@ -186,28 +207,17 @@ class Logger {
                     if (typeof msg === "string") {
                         msgPart = exports.CONSOLE_STYLE_FgRed + msg + exports.CONSOLE_STYLE_Reset;
                     }
-                    else {
-                        msgPart = msg;
-                    }
                     break;
                 case "DEBUG":
                     if (typeof msg === "string") {
                         msgPart = exports.CONSOLE_STYLE_FgGray + msg + exports.CONSOLE_STYLE_Reset;
                     }
-                    else {
-                        msgPart = msg;
-                    }
-                    break;
-                default:
-                    msgPart = msg;
                     break;
             }
         }
         else {
-            timePart = now;
             modulePart = `[${module}]`;
             levelPart = `${level}:`;
-            msgPart = msg;
         }
         switch (level) {
             case "ERROR":
@@ -230,23 +240,23 @@ class Logger {
         }
     }
     info(module, msg) {
-        this.log(module, msg, "info");
+        this.log(module, msg, "INFO");
     }
     warn(module, msg) {
-        this.log(module, msg, "warn");
+        this.log(module, msg, "WARN");
     }
     error(module, msg) {
-        this.log(module, msg, "error");
+        this.log(module, msg, "ERROR");
     }
     debug(module, msg) {
-        this.log(module, msg, "debug");
+        this.log(module, msg, "DEBUG");
     }
     exception(module, exception, msg) {
         let finalMessage = exception;
         if (msg) {
             finalMessage = `${msg}: ${exception}`;
         }
-        this.log(module, finalMessage, "error");
+        this.log(module, finalMessage, "ERROR");
     }
 }
 exports.log = new Logger();
diff --git a/src/util.ts b/src/util.ts
index efc8f3a89..fa71a48fa 100644
--- a/src/util.ts
+++ b/src/util.ts
@@ -156,15 +156,6 @@ export function ucfirst(str: string) {
     return firstLetter.toUpperCase() + str.substr(1);
 }
 
-/**
- * @deprecated Use log.debug (https://github.com/louislam/uptime-kuma/pull/910)
- * @param msg Message to write
- * @returns {void}
- */
-export function debug(msg: unknown) {
-    log.log("", msg, "debug");
-}
-
 class Logger {
 
     /**
@@ -206,12 +197,13 @@ class Logger {
 
     /**
      * Write a message to the log
+     * @private
      * @param module The module the log comes from
      * @param msg Message to write
-     * @param level Log level. One of INFO, WARN, ERROR, DEBUG or can be customized.
+     * @param level {"INFO"|"WARN"|"ERROR"|"DEBUG"} Log level
      * @returns {void}
      */
-    log(module: string, msg: any, level: string) {
+    log(module: string, msg: unknown, level: "INFO"|"WARN"|"ERROR"|"DEBUG"): void {
         if (level === "DEBUG" && !isDev) {
             return;
         }
@@ -220,9 +212,6 @@ class Logger {
             return;
         }
 
-        module = module.toUpperCase();
-        level = level.toUpperCase();
-
         let now;
         if (dayjs.tz) {
             now = dayjs.tz(new Date()).format();
@@ -233,10 +222,23 @@ class Logger {
         const levelColor = consoleLevelColors[level];
         const moduleColor = consoleModuleColors[intHash(module, consoleModuleColors.length)];
 
-        let timePart: string;
-        let modulePart: string;
-        let levelPart: string;
-        let msgPart: string;
+        let timePart: string = now;
+        let modulePart: string = module;
+        let levelPart: string = level;
+        let msgPart: unknown = msg;
+
+        if (process.env.UPTIME_KUMA_LOG_FORMAT === "json") {
+            console.log(JSON.stringify({
+                time: timePart,
+                module: modulePart,
+                level: levelPart,
+                msg: typeof msg === "string" ? msg : JSON.stringify(msg),
+            }));
+            return;
+        }
+
+        // Console rendering:
+        module = module.toUpperCase();
 
         if (isNode) {
             // Add console colors
@@ -257,27 +259,18 @@ class Logger {
                 case "ERROR":
                     if (typeof msg === "string") {
                         msgPart = CONSOLE_STYLE_FgRed + msg + CONSOLE_STYLE_Reset;
-                    } else {
-                        msgPart = msg;
                     }
                     break;
                 case "DEBUG":
                     if (typeof msg === "string") {
                         msgPart = CONSOLE_STYLE_FgGray + msg + CONSOLE_STYLE_Reset;
-                    } else {
-                        msgPart = msg;
                     }
                     break;
-                default:
-                    msgPart = msg;
-                    break;
             }
         } else {
             // No console colors
-            timePart = now;
             modulePart = `[${module}]`;
             levelPart = `${level}:`;
-            msgPart = msg;
         }
 
         // Write to console
@@ -308,8 +301,8 @@ class Logger {
      * @param msg Message to write
      * @returns {void}
      */
-    info(module: string, msg: unknown) {
-        this.log(module, msg, "info");
+    info(module: string, msg: string): void {
+        this.log(module, msg, "INFO");
     }
 
     /**
@@ -318,8 +311,8 @@ class Logger {
      * @param msg Message to write
      * @returns {void}
      */
-    warn(module: string, msg: unknown) {
-        this.log(module, msg, "warn");
+    warn(module: string, msg: string): void {
+        this.log(module, msg, "WARN");
     }
 
     /**
@@ -328,8 +321,8 @@ class Logger {
      * @param msg Message to write
      * @returns {void}
      */
-    error(module: string, msg: unknown) {
-        this.log(module, msg, "error");
+    error(module: string, msg: string): void {
+        this.log(module, msg, "ERROR");
     }
 
     /**
@@ -338,8 +331,8 @@ class Logger {
      * @param msg Message to write
      * @returns {void}
      */
-    debug(module: string, msg: unknown) {
-        this.log(module, msg, "debug");
+    debug(module: string, msg: string): void {
+        this.log(module, msg, "DEBUG");
     }
 
     /**
@@ -356,7 +349,7 @@ class Logger {
             finalMessage = `${msg}: ${exception}`;
         }
 
-        this.log(module, finalMessage, "error");
+        this.log(module, finalMessage, "ERROR");
     }
 }
 
@@ -400,7 +393,7 @@ export class TimeLogger {
      * @param name Name of monitor
      * @returns {void}
      */
-    print(name: string) {
+    print(name: string): void {
         if (isDev && process.env.TIMELOGGER === "1") {
             console.log(name + ": " + (dayjs().valueOf() - this.startTime) + "ms");
         }