Skip to content

Commit

Permalink
feat: Add Valetudo log to UI (#690)
Browse files Browse the repository at this point in the history
* feat: Add Valetudo log to UI

* fix: Implement changes from review

* fix: Comment out MQTT Logging
  • Loading branch information
ccoors committed Feb 15, 2021
1 parent be5df6e commit 89320f4
Show file tree
Hide file tree
Showing 9 changed files with 215 additions and 45 deletions.
12 changes: 12 additions & 0 deletions client/services/api.service.js
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,18 @@ export class ApiService {
return await this.fetch("GET", "api/v2/valetudo/version");
}

static async getValetudoLogContent() {
return await this.fetch("GET", "api/v2/valetudo/log/content");
}

static async getValetudoLogLevel() {
return await this.fetch("GET", "api/v2/valetudo/log/level");
}

static async setValetudoLogLevel(level) {
await this.fetch("PUT", "api/v2/valetudo/log/level", {level: level});
}

static async getRobot() {
return await this.fetch("GET", "api/v2/robot");
}
Expand Down
28 changes: 26 additions & 2 deletions client/settings-info.html
Original file line number Diff line number Diff line change
Expand Up @@ -81,11 +81,24 @@
???
</div>
</ons-list-item>
<ons-list-item>
<div class="center" id="robot-log-controls">
<ons-button id="settings-info-valetudo-get-log" class="button-margin" onclick="getValetudoLog();">Refresh Log</ons-button>
<ons-button id="settings-info-valetudo-loglevel-button" class="button-margin" onclick="handleLoglevelButton()" disabled>Unknown log level</ons-button>
</div>
</ons-list-item>
<ons-list-item>
<label class="right" style="width:100%">
<ons-row>
<ons-col>
<textarea class="textarea" id="settings-info-valetudo-log" rows="20" readonly></textarea>
</ons-col>
</ons-row>
</label>
</ons-list-item>
</ons-list>




<script>
{
let s = document.createElement('script');
Expand All @@ -96,7 +109,18 @@

ons.getScriptPage().onShow = function() {
updateSettingsInfoPage();
initValetudoLog();
};
}
</script>
<style>
#robot-log-controls > ons-button {
margin: 5px;
}

#settings-info-valetudo-log {
font-family: monospace, sans-serif;
width: 100%;
}
</style>
</ons-page>
74 changes: 70 additions & 4 deletions client/settings-info.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
/*global ons */
import {ApiService} from "./services/api.service.js";

async function updateSettingsInfoPage() {
var loadingBarSettingsInfo = document.getElementById("loading-bar-settings-info");
var loglevelButton = document.getElementById("settings-info-valetudo-loglevel-button");
var logTextArea = document.getElementById("settings-info-valetudo-log");
var loadingBarSettingsInfo = document.getElementById("loading-bar-settings-info");

var currentLoglevel = "";
var loglevelPresets = [];

async function updateSettingsInfoPage() {
loadingBarSettingsInfo.setAttribute("indeterminate", "indeterminate");
try {
let valetudoVersionRes = await ApiService.getValetudoVersion();
Expand All @@ -13,8 +18,6 @@ async function updateSettingsInfoPage() {
document.getElementById("info_device_valetudo_implementation").innerText = robotRes.implementation;
document.getElementById("info_device_model_manufacturer").innerText = robotRes.manufacturer;
document.getElementById("info_device_model_name").innerText = robotRes.modelName;


} catch (err) {
ons.notification.toast(err.message,
{buttonLabel: "Dismiss", timeout: window.fn.toastErrorTimeout});
Expand Down Expand Up @@ -51,5 +54,68 @@ async function checkNewValetudoVersion() {
}
}

async function updateValetudoLogLevels() {
var levels = await ApiService.getValetudoLogLevel();

loglevelPresets = levels.presets;
currentLoglevel = levels.current;
if (loglevelPresets) {
loglevelButton.removeAttribute("disabled");
} else {
loglevelButton.setAttribute("disabled","disabled");
}
if (currentLoglevel) {
loglevelButton.innerHTML = "Log level: " + currentLoglevel;
} else {
loglevelButton.innerHTML = "Unknown log level";
}
}

async function initValetudoLog() {
await updateValetudoLogLevels();
await getValetudoLog();
}

async function getValetudoLog() {
var loadingBarSettingsInfo = document.getElementById("loading-bar-settings-info");

loadingBarSettingsInfo.setAttribute("indeterminate", "indeterminate");
try {
var valetudoLogRes = await ApiService.getValetudoLogContent();
console.log(valetudoLogRes);
logTextArea.value = valetudoLogRes || "Empty Logfile";
logTextArea.scrollTop = logTextArea.scrollHeight;
} finally {
loadingBarSettingsInfo.removeAttribute("indeterminate");
}
}

async function handleLoglevelButton() {
var index = await ons.openActionSheet({
title: "Select log level",
cancelable: true,
buttons: [...loglevelPresets, {label: "Cancel", icon: "md-close"}]
});
var logLevel = loglevelPresets[index];

if (logLevel) {
loadingBarSettingsInfo.setAttribute("indeterminate", "indeterminate");
loglevelButton.setAttribute("disabled", "disabled");
try {
await ApiService.setValetudoLogLevel(logLevel);
} catch (err) {
ons.notification.toast(err.message,
{buttonLabel: "Dismiss", timeout: window.fn.toastErrorTimeout});
} finally {
loadingBarSettingsInfo.removeAttribute("indeterminate");
loglevelButton.removeAttribute("disabled");
}
}
await updateValetudoLogLevels();
}

window.updateSettingsInfoPage = updateSettingsInfoPage;
window.checkNewValetudoVersion = checkNewValetudoVersion;
window.initValetudoLog = initValetudoLog;
window.getValetudoLog = getValetudoLog;
window.handleLoglevelButton = handleLoglevelButton;
9 changes: 9 additions & 0 deletions docs/_pages/troubleshooting.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,3 +33,12 @@ And check your Firewall-initscript /etc/rc.d/S51valetudo:

for host in 203.0.113.1 203.0.113.5; do
[…]

## Logging
### Log Level

Valetudo's log level can be set in the UI. It is not persisted across restarts. If you need to permanently set a log level, adjust it in the valetudo config file.

### MQTT

If you want to debug MQTT, you can set the `DEBUG` environment variable to `mqttjs*` (refer to the [MQTT.js README](https://github.com/mqttjs/MQTT.js#debug-logs)).
106 changes: 69 additions & 37 deletions lib/Logger.js
Original file line number Diff line number Diff line change
@@ -1,16 +1,32 @@
const {EventEmitter} = require("events");
const fs = require("fs");
const util = require("util");

const LogFileOptions = Object.freeze({
flags: "as"
});

let LogLevel = 0;
let LogFilename = "/dev/null";
let LogFile = fs.createWriteStream(LogFilename, LogFileOptions);
let LogEventEmitter = new EventEmitter();

const LogLevels = Object.freeze({
"trace": -2,
"debug": -1,
"info": 0,
"warn": 1,
"error": 2
// eslint-disable-next-line no-console
"trace": {"level": -2, "callback": console.debug},
// eslint-disable-next-line no-console
"debug": {"level": -1, "callback": console.debug},
// eslint-disable-next-line no-console
"info": {"level": 0, "callback": console.info},
// eslint-disable-next-line no-console
"warn": {"level": 1, "callback": console.warn},
// eslint-disable-next-line no-console
"error": {"level": 2, "callback": console.error},
});

const BuildPrefix = (LogLevel) => {
let timestamp = new Date().toISOString();
return "[" + timestamp + "] [" + LogLevel + "] ";
return "[" + timestamp + "] [" + LogLevel + "]";
};

class Logger {
Expand All @@ -20,72 +36,88 @@ class Logger {
static get LogLevel() {
return Object.keys(LogLevels).find(key => LogLevels[key] === LogLevel);
}
static get LogLevels() {
return LogLevels;
}
static set LogLevel(value) {
if (LogLevels[value] === undefined) {
throw "invalid log level '" + value + "', valid are '" + Object.keys(LogLevels).join("','") + "'";
}
LogLevel = LogLevels[value];
}
static set LogFile(filename) {
if (LogFilename === filename) {
return;
}
if (LogFile) {
LogFile.close();
}
LogFilename = filename;
LogFile = fs.createWriteStream(LogFilename, LogFileOptions);
}
static get LogFile() {
return LogFilename;
}

static onLogMessage(listener) {
LogEventEmitter.on("LogMessage", listener);
}

static log(level, ...args) {
if (LogLevel <= LogLevels[level]["level"]) {
const logPrefix = BuildPrefix(level.toUpperCase());
LogLevels[level]["callback"](logPrefix, ...args);
const logLine = [logPrefix, ...args].map(arg => {
if (typeof arg === "string") {
return arg;
}
return util.inspect(arg);
}).join(" ");
LogFile.write(logLine);
LogFile.write("\n");
LogEventEmitter.emit("LogMessage", logLine);
}
}

/**
* @see console.trace
* @param {string} message
* @param {...any} args
*/
static trace(message, ...args) {
if (LogLevel <= LogLevels.trace) {
// eslint-disable-next-line no-console
console.debug(BuildPrefix("TRACE") + message, ...args);
}
static trace(...args) {
Logger.log("trace", ...args);
}

/**
* @see console.debug
* @param {string} message
* @param {...any} args
*/
static debug(message, ...args) {
if (LogLevel <= LogLevels.debug) {
// eslint-disable-next-line no-console
console.debug(BuildPrefix("DEBUG") + message, ...args);
}
static debug(...args) {
Logger.log("debug", ...args);
}

/**
* @see console.info
* @param {string} message
* @param {...any} args
*/
static info(message, ...args) {
if (LogLevel <= LogLevels.info) {
// eslint-disable-next-line no-console
console.info(BuildPrefix("INFO") + message, ...args);
}
static info(...args) {
Logger.log("info", ...args);
}

/**
* @see console.warn
* @param {string} message
* @param {...any} args
*/
static warn(message, ...args) {
if (LogLevel <= LogLevels.warn) {
// eslint-disable-next-line no-console
console.warn(BuildPrefix("WARN") + message, ...args);
}
static warn(...args) {
Logger.log("warn", ...args);
}

/**
* @see console.error
* @param {string} message
* @param {...any} args
*/
static error(message, ...args) {
if (LogLevel <= LogLevels.error) {
// eslint-disable-next-line no-console
console.error(BuildPrefix("ERROR") + message, ...args);
}
static error( ...args) {
Logger.log("error", ...args);
}
}

module.exports = Logger;
module.exports = Logger;
3 changes: 3 additions & 0 deletions lib/Valetudo.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
const v8 = require("v8");
const path = require("path");
const os = require("os");
const NTPClient = require("./NTPClient");
const Webserver = require("./webserver/WebServer");
const MqttClient = require("./mqtt/MqttClient");
Expand All @@ -15,6 +17,7 @@ class Valetudo {

try {
Logger.LogLevel = this.config.get("logLevel");
Logger.LogFile = process.env.VALETUDO_LOG_PATH || path.join(os.tmpdir(), "valetudo.log");
} catch (e) {
Logger.error("Initialising Logger: " + e);
}
Expand Down
4 changes: 3 additions & 1 deletion lib/mqtt/MqttAutoConfManager.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,9 @@ class MqttAutoConfManager { //TODO: does this thing even make sense?

custom_command: this.topicPrefix + "/" + this.identifier + "/custom_command", //TODO: maybe merge this with the regular command topic?

map_data: this.topicPrefix + "/" + this.identifier + "/map_data"
map_data: this.topicPrefix + "/" + this.identifier + "/map_data",

log_message: this.topicPrefix + "/" + this.identifier + "/log_message"
};

this.internalAutoconfData = [
Expand Down
4 changes: 4 additions & 0 deletions lib/mqtt/MqttClient.js
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,10 @@ class MqttClient {
}
});

// TODO: To be evaluated due to secrets in the log
// Logger.onLogMessage((line) => {
// this.publish(this.autoConfManager.topics.log_message, line);
// });

this.robot.onStateUpdated(() => {
this.updateStatusTopic();
Expand Down

0 comments on commit 89320f4

Please sign in to comment.