feat: structured logging (JSON) (#5179)

Co-authored-by: Frank Elsinga <frank@elsinga.de>
Co-authored-by: Louis Lam <louislam@users.noreply.github.com>
This commit is contained in:
Cassandra
2026-02-26 18:02:58 +01:00
committed by GitHub
parent b36a8b035b
commit 174c63d479
18 changed files with 99 additions and 51 deletions

View File

@@ -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");
@@ -137,7 +136,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);

View File

@@ -598,12 +598,12 @@ class Database {
let title = await setting("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;
}
@@ -645,7 +645,7 @@ class Database {
await setSetting("entryPage", "statusPage-default", "general");
}
console.log("Migrating Status Page - Done");
log.info("database", "Migrating Status Page - Done");
}
}

View File

@@ -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)}`;
}

View File

@@ -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 {
@@ -93,7 +93,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);

View File

@@ -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";
@@ -91,7 +91,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({

View File

@@ -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);

View File

@@ -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 {
@@ -79,7 +79,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);

View File

@@ -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";
@@ -50,7 +50,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);

View File

@@ -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 {
@@ -94,7 +94,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);

View File

@@ -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)}`;
}

View File

@@ -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"];
}

View File

@@ -1584,7 +1584,7 @@ let needSetup = false;
msg,
});
} catch (e) {
console.error(e);
log.error("server", e);
callback({
ok: false,

View File

@@ -59,7 +59,7 @@ module.exports.apiKeySocketHandler = (socket) => {
ok: true,
});
} catch (e) {
console.error(e);
log.error("apikeys", e);
callback({
ok: false,
msg: e.message,

View File

@@ -106,11 +106,11 @@ module.exports.autoStart = async (token) => {
} else {
// Override the current token via args or env var
await setSetting("cloudflaredTunnelToken", token);
console.log("Use cloudflared token from args or env var");
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();
}

View File

@@ -65,7 +65,7 @@ module.exports.maintenanceSocketHandler = (socket) => {
maintenanceID: bean.id,
});
} catch (e) {
console.error(e);
log.error("maintenance", e);
callback({
ok: false,
msg: e.message,
@@ -165,7 +165,7 @@ module.exports.maintenanceSocketHandler = (socket) => {
ok: true,
});
} catch (e) {
console.error(e);
log.error("maintenance", e);
callback({
ok: false,
msg: e.message,
@@ -189,7 +189,7 @@ module.exports.maintenanceSocketHandler = (socket) => {
monitors,
});
} catch (e) {
console.error(e);
log.error("maintenance", e);
callback({
ok: false,
msg: e.message,
@@ -213,7 +213,7 @@ module.exports.maintenanceSocketHandler = (socket) => {
statusPages,
});
} catch (e) {
console.error(e);
log.error("maintenance", e);
callback({
ok: false,
msg: e.message,

View File

@@ -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");
/**
* Validates incident data
@@ -412,7 +413,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();
@@ -469,7 +470,7 @@ module.exports.statusPageSocketHandler = (socket) => {
slug: slug,
});
} catch (error) {
console.error(error);
log.error("socket", error);
callback({
ok: false,
msg: error.message,
@@ -490,7 +491,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.

View File

@@ -136,7 +136,7 @@ function ucfirst(str) {
}
exports.ucfirst = ucfirst;
function debug(msg) {
exports.log.log("", "debug", msg);
exports.log.log("", "DEBUG", msg);
}
exports.debug = debug;
class Logger {
@@ -167,7 +167,6 @@ class Logger {
return;
}
module = module.toUpperCase();
level = level.toUpperCase();
let now;
if (dayjs.tz) {
now = dayjs.tz(new Date()).format();
@@ -175,6 +174,30 @@ class Logger {
else {
now = dayjs().format();
}
if (process.env.UPTIME_KUMA_LOG_FORMAT === "json") {
const msgString = msg
.map((m) => {
if (typeof m === "string") {
return m;
}
else {
try {
return JSON.stringify(m);
}
catch (_a) {
return String(m);
}
}
})
.join(" ");
console.log(JSON.stringify({
time: now,
module: module,
level: level,
msg: msgString,
}));
return;
}
const levelColor = consoleLevelColors[level];
const moduleColor = consoleModuleColors[intHash(module, consoleModuleColors.length)];
let timePart;
@@ -218,19 +241,19 @@ class Logger {
}
}
info(module, ...msg) {
this.log(module, "info", ...msg);
this.log(module, "INFO", ...msg);
}
warn(module, ...msg) {
this.log(module, "warn", ...msg);
this.log(module, "WARN", ...msg);
}
error(module, ...msg) {
this.log(module, "error", ...msg);
this.log(module, "ERROR", ...msg);
}
debug(module, ...msg) {
this.log(module, "debug", ...msg);
this.log(module, "DEBUG", ...msg);
}
exception(module, exception, ...msg) {
this.log(module, "error", ...msg, exception);
this.log(module, "ERROR", ...msg, exception);
}
}
exports.log = new Logger();

View File

@@ -206,7 +206,7 @@ export function ucfirst(str: string) {
* @returns {void}
*/
export function debug(msg: unknown) {
log.log("", "debug", msg);
log.log("", "DEBUG", msg);
}
class Logger {
@@ -254,7 +254,7 @@ class Logger {
* @param msg Message to write
* @returns {void}
*/
log(module: string, level: string, ...msg: unknown[]) {
log(module: string, level: "INFO" | "WARN" | "ERROR" | "DEBUG", ...msg: unknown[]) {
if (level === "DEBUG" && !isDev) {
return;
}
@@ -264,7 +264,6 @@ class Logger {
}
module = module.toUpperCase();
level = level.toUpperCase();
let now;
if (dayjs.tz) {
@@ -273,6 +272,32 @@ class Logger {
now = dayjs().format();
}
if (process.env.UPTIME_KUMA_LOG_FORMAT === "json") {
const msgString = msg
.map((m) => {
if (typeof m === "string") {
return m;
} else {
try {
return JSON.stringify(m);
} catch {
return String(m);
}
}
})
.join(" ");
console.log(
JSON.stringify({
time: now,
module: module,
level: level,
msg: msgString,
})
);
return;
}
const levelColor = consoleLevelColors[level];
const moduleColor = consoleModuleColors[intHash(module, consoleModuleColors.length)];
@@ -329,7 +354,7 @@ class Logger {
* @returns {void}
*/
info(module: string, ...msg: unknown[]) {
this.log(module, "info", ...msg);
this.log(module, "INFO", ...msg);
}
/**
@@ -339,7 +364,7 @@ class Logger {
* @returns {void}
*/
warn(module: string, ...msg: unknown[]) {
this.log(module, "warn", ...msg);
this.log(module, "WARN", ...msg);
}
/**
@@ -349,7 +374,7 @@ class Logger {
* @returns {void}
*/
error(module: string, ...msg: unknown[]) {
this.log(module, "error", ...msg);
this.log(module, "ERROR", ...msg);
}
/**
@@ -359,7 +384,7 @@ class Logger {
* @returns {void}
*/
debug(module: string, ...msg: unknown[]) {
this.log(module, "debug", ...msg);
this.log(module, "DEBUG", ...msg);
}
/**
@@ -370,7 +395,7 @@ class Logger {
* @returns {void}
*/
exception(module: string, exception: unknown, ...msg: unknown[]) {
this.log(module, "error", ...msg, exception);
this.log(module, "ERROR", ...msg, exception);
}
}
@@ -418,7 +443,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");
}