1
0
mirror of https://github.com/AvengeMedia/DankMaterialShell.git synced 2026-04-30 09:32:05 -04:00
Files
DankMaterialShell/quickshell/Services/Log.qml
bbedward f76724f7cd logger: add a dedicated QML logging Singleton
- adds log.info/error/debug/warn/fatal
- adds ability to write logs to any file
- add CLI options in addition to env to set log levels
2026-04-29 15:42:30 -04:00

227 lines
5.9 KiB
QML

pragma Singleton
pragma ComponentBehavior: Bound
import QtQuick
import Quickshell
import Quickshell.Io
Singleton {
id: root
enum Level {
Debug,
Info,
Warn,
Error,
Fatal
}
readonly property int level: _parseLevel(Quickshell.env("DMS_LOG_LEVEL"))
readonly property string levelName: _levelName(level)
readonly property string _logFilePath: Quickshell.env("DMS_LOG_FILE") || ""
readonly property bool _useColor: !Quickshell.env("NO_COLOR") && Quickshell.env("DMS_LOG_NO_COLOR") !== "1"
function scoped(module) {
return {
debug: function () {
root._emit(Log.Level.Debug, module, arguments);
},
info: function () {
root._emit(Log.Level.Info, module, arguments);
},
warn: function () {
root._emit(Log.Level.Warn, module, arguments);
},
error: function () {
root._emit(Log.Level.Error, module, arguments);
},
fatal: function () {
root._emit(Log.Level.Fatal, module, arguments);
}
};
}
function debug() {
_emit(Log.Level.Debug, "", arguments);
}
function info() {
_emit(Log.Level.Info, "", arguments);
}
function warn() {
_emit(Log.Level.Warn, "", arguments);
}
function error() {
_emit(Log.Level.Error, "", arguments);
}
function fatal() {
_emit(Log.Level.Fatal, "", arguments);
}
function callStack() {
const trace = _captureStack(0).split("\n").map(l => l.trim()).filter(l => l.length > 0);
_emit(Log.Level.Info, "Debug", ["--------------------------"]);
_emit(Log.Level.Info, "Debug", ["Current call stack"]);
for (const line of trace)
_emit(Log.Level.Info, "Debug", ["- " + line]);
_emit(Log.Level.Info, "Debug", ["--------------------------"]);
}
function _parseLevel(name) {
switch ((name || "").toLowerCase()) {
case "debug":
return Log.Level.Debug;
case "warn":
case "warning":
return Log.Level.Warn;
case "error":
return Log.Level.Error;
case "fatal":
return Log.Level.Fatal;
default:
return Log.Level.Info;
}
}
function _levelName(lvl) {
switch (lvl) {
case Log.Level.Debug:
return "debug";
case Log.Level.Info:
return "info";
case Log.Level.Warn:
return "warn";
case Log.Level.Error:
return "error";
case Log.Level.Fatal:
return "fatal";
}
return "info";
}
function _levelTag(lvl, color) {
let tag, ansi;
switch (lvl) {
case Log.Level.Fatal:
tag = " FATAL";
ansi = "\x1b[31m";
break;
case Log.Level.Error:
tag = " ERROR";
ansi = "\x1b[91m";
break;
case Log.Level.Warn:
tag = " WARN";
ansi = "\x1b[33m";
break;
case Log.Level.Info:
tag = " INFO";
ansi = "\x1b[32m";
break;
case Log.Level.Debug:
tag = " DEBUG";
ansi = "\x1b[34m";
break;
default:
return " INFO";
}
if (!color)
return tag;
return ansi + tag + "\x1b[0m";
}
function _stringify(v) {
if (v === null)
return "null";
if (v === undefined)
return "undefined";
if (typeof v === "string")
return v;
if (v instanceof Error)
return v.toString();
try {
return JSON.stringify(v);
} catch (e) {
return String(v);
}
}
function _captureStack(skip) {
try {
throw new Error();
} catch (e) {
const lines = (e.stack || "").split("\n");
return lines.slice(1 + (skip || 0)).join("\n");
}
}
function _callerLocation() {
const stack = _captureStack(2);
const lines = stack.split("\n");
for (const line of lines) {
const m = line.match(/([^/@\s]+\.qml):(\d+)/);
if (!m)
continue;
if (m[1] === "Log.qml")
continue;
return {
file: m[1],
line: m[2]
};
}
return null;
}
function _emit(lvl, module, args) {
if (lvl < root.level)
return;
const argList = Array.from(args);
const loc = _callerLocation();
const msg = argList.map(_stringify).join(" ");
let tag;
if (module && loc && loc.file === module + ".qml")
tag = "[" + module + ":" + loc.line + "] ";
else if (module && loc)
tag = "[" + module + "] (" + loc.file + ":" + loc.line + ") ";
else if (module)
tag = "[" + module + "] ";
else if (loc)
tag = "(" + loc.file + ":" + loc.line + ") ";
else
tag = "";
const body = tag + msg;
switch (lvl) {
case Log.Level.Debug:
console.debug(body);
break;
case Log.Level.Info:
console.info(body);
break;
case Log.Level.Warn:
console.warn(body);
break;
case Log.Level.Error:
case Log.Level.Fatal:
console.error(body);
break;
}
if (root._logFilePath && fileTee.running)
fileTee.write(_levelTag(lvl, false) + " qml: " + body + "\n");
if (lvl === Log.Level.Fatal)
Qt.callLater(() => Qt.exit(1));
}
Process {
id: fileTee
command: ["sh", "-c", "exec tee -a \"$0\" >/dev/null", root._logFilePath]
stdinEnabled: true
running: root._logFilePath.length > 0
}
}