diff --git a/js/logger.js b/js/logger.js index a31b22a0ad..ccd74dbb99 100644 --- a/js/logger.js +++ b/js/logger.js @@ -1,131 +1,115 @@ -// This logger is very simple, but needs to be extended. -(function (root, factory) { - if (typeof exports === "object") { +// Logger for MagicMirror² — works both in Node.js (CommonJS) and the browser (global). +(function () { + if (typeof module !== "undefined") { if (process.env.mmTestMode !== "true") { const { styleText } = require("node:util"); - // add timestamps in front of log messages - require("console-stamp")(console, { - format: ":date(yyyy-mm-dd HH:MM:ss.l) :label(7) :pre() :msg", - tokens: { - pre: () => { - const err = new Error(); - Error.prepareStackTrace = (_, stack) => stack; - const stack = err.stack; - Error.prepareStackTrace = undefined; - try { - for (const line of stack) { - const file = line.getFileName(); - if (file && !file.includes("node:") && !file.includes("js/logger.js") && !file.includes("node_modules")) { - const filename = file.replace(/.*\/(.*).js/, "$1"); - const filepath = file.replace(/.*\/(.*)\/.*.js/, "$1"); - if (filepath === "js") { - return styleText("grey", `[${filename}]`); - } else { - return styleText("grey", `[${filepath}]`); - } - } - } - } catch (err) { - return styleText("grey", "[unknown]"); - } - }, - label: (arg) => { - const { method, defaultTokens } = arg; - let label = defaultTokens.label(arg); - switch (method) { - case "error": - label = styleText("red", label); - break; - case "warn": - label = styleText("yellow", label); - break; - case "debug": - label = styleText("bgBlue", label); - break; - case "info": - label = styleText("blue", label); - break; - } - return label; - }, - msg: (arg) => { - const { method, defaultTokens } = arg; - let msg = defaultTokens.msg(arg); - switch (method) { - case "error": - msg = styleText("red", msg); - break; - case "warn": - msg = styleText("yellow", msg); - break; - case "info": - msg = styleText("blue", msg); - break; + const LABEL_COLORS = { error: "red", warn: "yellow", debug: "bgBlue", info: "blue" }; + const MSG_COLORS = { error: "red", warn: "yellow", info: "blue" }; + + const formatTimestamp = () => { + const d = new Date(); + const pad2 = (n) => String(n).padStart(2, "0"); + const pad3 = (n) => String(n).padStart(3, "0"); + const date = `${d.getFullYear()}-${pad2(d.getMonth() + 1)}-${pad2(d.getDate())}`; + const time = `${pad2(d.getHours())}:${pad2(d.getMinutes())}:${pad2(d.getSeconds())}.${pad3(d.getMilliseconds())}`; + return `[${date} ${time}]`; + }; + + const getCallerPrefix = () => { + try { + const lines = new Error().stack.split("\n"); + for (const line of lines) { + if (line.includes("node:") || line.includes("js/logger.js") || line.includes("node_modules")) continue; + const match = line.match(/\((.+?\.js):\d+:\d+\)/) || line.match(/at\s+(.+?\.js):\d+:\d+/); + if (match) { + const file = match[1]; + const baseName = file.replace(/.*\/(.*)\.js/, "$1"); + const parentDir = file.replace(/.*\/(.*)\/.*\.js/, "$1"); + return styleText("gray", parentDir === "js" ? `[${baseName}]` : `[${parentDir}]`); } - return msg; } - } - }); + } catch (err) { /* ignore */ } + return styleText("gray", "[unknown]"); + }; + + // Patch console methods to prepend timestamp, level label, and caller prefix. + for (const method of ["debug", "log", "info", "warn", "error"]) { + const original = console[method].bind(console); + const labelRaw = `[${method.toUpperCase()}]`.padEnd(7); + const label = LABEL_COLORS[method] ? styleText(LABEL_COLORS[method], labelRaw) : labelRaw; + console[method] = (...args) => { + const prefix = `${formatTimestamp()} ${label} ${getCallerPrefix()}`; + const msgColor = MSG_COLORS[method]; + if (msgColor && args.length > 0 && typeof args[0] === "string") { + original(prefix, styleText(msgColor, args[0]), ...args.slice(1)); + } else { + original(prefix, ...args); + } + }; + } } - // Node, CommonJS-like - module.exports = factory(root.config); - } else { - // Browser globals (root is window) - root.Log = factory(root.config); - } -}(this, function (config) { - let logLevel; - let enableLog; - if (typeof exports === "object") { - // in nodejs and not running in test mode - enableLog = process.env.mmTestMode !== "true"; + // Node, CommonJS + module.exports = makeLogger(); } else { - // in browser and not running with jsdom - enableLog = typeof window === "object" && window.name !== "jsdom"; + // Browser globals + window.Log = makeLogger(); } - if (enableLog) { - logLevel = { - debug: Function.prototype.bind.call(console.debug, console), - log: Function.prototype.bind.call(console.log, console), - info: Function.prototype.bind.call(console.info, console), - warn: Function.prototype.bind.call(console.warn, console), - error: Function.prototype.bind.call(console.error, console), - group: Function.prototype.bind.call(console.group, console), - groupCollapsed: Function.prototype.bind.call(console.groupCollapsed, console), - groupEnd: Function.prototype.bind.call(console.groupEnd, console), - time: Function.prototype.bind.call(console.time, console), - timeEnd: Function.prototype.bind.call(console.timeEnd, console), - timeStamp: console.timeStamp ? Function.prototype.bind.call(console.timeStamp, console) : function () {} - }; + /** + * Creates the logger object. Logging is disabled when running in test mode + * (Node.js) or inside jsdom (browser). + * @returns {object} The logger object with log level methods. + */ + function makeLogger () { + const enableLog = typeof module !== "undefined" + ? process.env.mmTestMode !== "true" + : typeof window === "object" && window.name !== "jsdom"; - logLevel.setLogLevel = function (newLevel) { - if (newLevel) { - Object.keys(logLevel).forEach(function (key) { - if (!newLevel.includes(key.toLocaleUpperCase())) { - logLevel[key] = function () {}; - } - }); - } - }; - } else { - logLevel = { - debug () {}, - log () {}, - info () {}, - warn () {}, - error () {}, - group () {}, - groupCollapsed () {}, - groupEnd () {}, - time () {}, - timeEnd () {}, - timeStamp () {} - }; + let logLevel; - logLevel.setLogLevel = function () {}; - } + if (enableLog) { + logLevel = { + debug: Function.prototype.bind.call(console.debug, console), + log: Function.prototype.bind.call(console.log, console), + info: Function.prototype.bind.call(console.info, console), + warn: Function.prototype.bind.call(console.warn, console), + error: Function.prototype.bind.call(console.error, console), + group: Function.prototype.bind.call(console.group, console), + groupCollapsed: Function.prototype.bind.call(console.groupCollapsed, console), + groupEnd: Function.prototype.bind.call(console.groupEnd, console), + time: Function.prototype.bind.call(console.time, console), + timeEnd: Function.prototype.bind.call(console.timeEnd, console), + timeStamp: console.timeStamp ? Function.prototype.bind.call(console.timeStamp, console) : function () {} + }; + + logLevel.setLogLevel = function (newLevel) { + if (newLevel) { + Object.keys(logLevel).forEach(function (key) { + if (!newLevel.includes(key.toLocaleUpperCase())) { + logLevel[key] = function () {}; + } + }); + } + }; + } else { + logLevel = { + debug () {}, + log () {}, + info () {}, + warn () {}, + error () {}, + group () {}, + groupCollapsed () {}, + groupEnd () {}, + time () {}, + timeEnd () {}, + timeStamp () {} + }; - return logLevel; -})); + logLevel.setLogLevel = function () {}; + } + + return logLevel; + } +}()); diff --git a/package-lock.json b/package-lock.json index fd92837595..735a7a6729 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,7 +15,6 @@ "@fortawesome/fontawesome-free": "^7.2.0", "ajv": "^8.18.0", "animate.css": "^4.1.1", - "console-stamp": "^3.1.2", "croner": "^10.0.1", "eslint": "^9.39.3", "express": "^5.2.1", @@ -249,6 +248,7 @@ "integrity": "sha512-CYDD3SOtsHtyXeEORYRx2qBtpDJFjRTGXUtmNEMGyzYOKj1TE3tycdlho7kA1Ufx9OYWZzg52QFBGALTirzDSw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@keyv/serialize": "^1.1.1" } @@ -407,6 +407,7 @@ "integrity": "sha512-Tdfx4eH2uS+gv9V9NCr3Rz+c7RSS6ntXp3Blliud18ibRUlRxO9dTaOjG4iv4x0nAmMeedP1ORkEpeXSkh2QiQ==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=20" } @@ -488,7 +489,8 @@ "resolved": "https://registry.npmjs.org/@cspell/dict-css/-/dict-css-4.0.19.tgz", "integrity": "sha512-VYHtPnZt/Zd/ATbW3rtexWpBnHUohUrQOHff/2JBhsVgxOrksAxJnLAO43Q1ayLJBJUUwNVo+RU0sx0aaysZfg==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/@cspell/dict-dart": { "version": "2.3.2", @@ -628,14 +630,16 @@ "resolved": "https://registry.npmjs.org/@cspell/dict-html/-/dict-html-4.0.14.tgz", "integrity": "sha512-2bf7n+kS92g+cMKV0wr9o/Oq9n8JzU7CcrB96gIh2GHgnF+0xDOqO2W/1KeFAqOfqosoOVE48t+4dnEMkkoJ2Q==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/@cspell/dict-html-symbol-entities": { "version": "4.0.5", "resolved": "https://registry.npmjs.org/@cspell/dict-html-symbol-entities/-/dict-html-symbol-entities-4.0.5.tgz", "integrity": "sha512-429alTD4cE0FIwpMucvSN35Ld87HCyuM8mF731KU5Rm4Je2SG6hmVx7nkBsLyrmH3sQukTcr1GaiZsiEg8svPA==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/@cspell/dict-java": { "version": "5.0.12", @@ -833,7 +837,8 @@ "resolved": "https://registry.npmjs.org/@cspell/dict-typescript/-/dict-typescript-3.2.3.tgz", "integrity": "sha512-zXh1wYsNljQZfWWdSPYwQhpwiuW0KPW1dSd8idjMRvSD0aSvWWHoWlrMsmZeRl4qM4QCEAjua8+cjflm41cQBg==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/@cspell/dict-vue": { "version": "3.0.5", @@ -991,6 +996,7 @@ } ], "license": "MIT", + "peer": true, "engines": { "node": ">=20.19.0" }, @@ -1031,6 +1037,7 @@ } ], "license": "MIT", + "peer": true, "engines": { "node": ">=20.19.0" } @@ -3708,6 +3715,7 @@ "integrity": "sha512-CGJ25bc8fRi8Lod/3GHSvXRKi7nBo3kxh0ApW4yCjmrWmRmlT53B5E08XRSZRliygG0aVNxLrBEqPYdz/KcCtQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@vitest/utils": "4.0.18", "fflate": "^0.8.2", @@ -3762,6 +3770,7 @@ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", "license": "MIT", + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -4344,6 +4353,7 @@ "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", "license": "MIT", + "peer": true, "dependencies": { "anymatch": "~3.1.2", "braces": "~3.0.2", @@ -4637,19 +4647,6 @@ "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", "license": "MIT" }, - "node_modules/console-stamp": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/console-stamp/-/console-stamp-3.1.2.tgz", - "integrity": "sha512-ab66x3NxOTxPuq71dI6gXEiw2X6ql4Le5gZz0bm7FW3FSCB00eztra/oQUuCoCGlsyKOxtULnHwphzMrRtzMBg==", - "license": "MIT", - "dependencies": { - "chalk": "^4.1.2", - "dateformat": "^4.6.3" - }, - "engines": { - "node": ">=12" - } - }, "node_modules/content-disposition": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.1.tgz", @@ -5064,15 +5061,6 @@ "node": "^20.19.0 || ^22.12.0 || >=24.0.0" } }, - "node_modules/dateformat": { - "version": "4.6.3", - "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-4.6.3.tgz", - "integrity": "sha512-2P0p0pFGzHS5EMnhdxQi7aJN+iMheud0UhG4dlE1DLAlvL8JHjJJTX/CSm4JXwV0Ka5nGk3zC5mcb5bUQUxxMA==", - "license": "MIT", - "engines": { - "node": "*" - } - }, "node_modules/dayjs": { "version": "1.11.15", "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.15.tgz", @@ -5703,6 +5691,7 @@ "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.3.tgz", "integrity": "sha512-VmQ+sifHUbI/IcSopBCF/HO3YiHQx/AVd3UVyYL6weuwW+HvON9VYn5l6Zl1WZzPWXPNZrSQpxwkkZ/VuvJZzg==", "license": "MIT", + "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", @@ -7557,6 +7546,7 @@ "integrity": "sha512-0+MoQNYyr2rBHqO1xilltfDjV9G7ymYGlAUazgcDLQaUf8JDHbuGwsxN6U9qWaElZ4w1B2r7yEGIL3GdeW3Rug==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@acemir/cssom": "^0.9.31", "@asamuzakjp/dom-selector": "^6.8.1", @@ -7630,7 +7620,6 @@ "integrity": "sha512-75EA7EWZExL/j+MDKQrRbdzcRI2HOkRlmUw8fZJc1ioqFEOvBsq7Rt+A6yCxOt9w/TYNpkt52gC6nm/g5tFIng==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "acorn": "^8.5.0", "eslint-visitor-keys": "^5.0.0", @@ -7649,7 +7638,6 @@ "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==", "dev": true, "license": "Apache-2.0", - "peer": true, "engines": { "node": "^20.19.0 || ^22.13.0 || >=24" }, @@ -7962,6 +7950,7 @@ "integrity": "sha512-DzzmbqfMW3EzHsunP66x556oZDzjcdjjlL2bHG4PubwnL58ZPAfz07px4GqteZkoCGnBYi779Y2mg7+vgNCwbw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "globby": "16.1.0", "js-yaml": "4.1.1", @@ -9680,6 +9669,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", @@ -9722,6 +9712,7 @@ "integrity": "sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" @@ -9752,6 +9743,7 @@ "integrity": "sha512-UOnG6LftzbdaHZcKoPFtOcCKztrQ57WkHDeRD9t/PTQtmT0NHSeWWepj6pS0z/N7+08BHFDQVUrfmfMRcZwbMg==", "dev": true, "license": "MIT", + "peer": true, "bin": { "prettier": "bin/prettier.cjs" }, @@ -11010,6 +11002,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "@csstools/css-calc": "^3.1.1", "@csstools/css-parser-algorithms": "^4.0.0", @@ -11622,7 +11615,6 @@ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "dev": true, "license": "Apache-2.0", - "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -11692,6 +11684,7 @@ "dev": true, "hasInstallScript": true, "license": "MIT", + "peer": true, "dependencies": { "napi-postinstall": "^0.3.0" }, @@ -11793,6 +11786,7 @@ "integrity": "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "esbuild": "^0.27.0", "fdir": "^6.5.0", @@ -11883,6 +11877,7 @@ "integrity": "sha512-hOQuK7h0FGKgBAas7v0mSAsnvrIgAvWmRFjmzpJ7SwFHH3g1k2u37JtYwOwmEKhK6ZO3v9ggDBBm0La1LCK4uQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@vitest/expect": "4.0.18", "@vitest/mocker": "4.0.18", diff --git a/package.json b/package.json index 8bc5c4b30b..17cf13765b 100644 --- a/package.json +++ b/package.json @@ -92,7 +92,6 @@ "@fortawesome/fontawesome-free": "^7.2.0", "ajv": "^8.18.0", "animate.css": "^4.1.1", - "console-stamp": "^3.1.2", "croner": "^10.0.1", "eslint": "^9.39.3", "express": "^5.2.1",