Skip to content

Commit

Permalink
Send log warnings and errors to ui (#128)
Browse files Browse the repository at this point in the history
  • Loading branch information
andig committed May 15, 2020
1 parent da2cefe commit 97ee689
Show file tree
Hide file tree
Showing 5 changed files with 188 additions and 101 deletions.
36 changes: 23 additions & 13 deletions assets/index.html
Expand Up @@ -56,22 +56,10 @@
</div>
</div>
</nav>
<message-toasts></message-toasts>
<router-view></router-view>
</div>

<div id="error">
<div class="toast" data-delay="2000" style="position: absolute; top: 4rem; right: 0.5rem;">
<div class="toast-header">
<strong class="mr-auto"><i class="text-danger fas fa-exclamation-triangle"></i> Error</strong>
<small v-if="error.status">HTTP {{error.status}}</small>
<button type="button" class="ml-2 mb-1 close" data-dismiss="toast" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="toast-body">{{error.message}}</div>
</div>
</div>

<script type="text/x-template" id="embed-template">
<div class="container mx-auto text-center">
<h1 class="display-4 pt-3" v-if="title">{{title}}</h1>
Expand All @@ -87,6 +75,28 @@ <h1 class="display-4 pt-3" v-if="title">{{title}}</h1>
</div>
</script>

<div id="toasts">
<div aria-atomic="true" style="position: absolute; top: 4rem; right: 0.5rem; min-height: 200px; min-width: 300px">
<!-- Position it -->
<message-toast v-for="item in items" v-bind:item="item" :id="'message-id-'+item.id" :key="item.id">
</message-toast>
</div>
</div>

<script type="text/x-template" id="message-template">
<div class="toast" data-delay="10000" v-bind:data-autohide="item.type == 'warn' ? 'true' : 'false'">
<div class="toast-header">
<strong class="mr-auto" v-if="item.type != 'warn'"><i class="text-danger fas fa-exclamation-triangle"></i> Error</strong>
<strong class="mr-auto" v-if="item.type == 'warn'"><i class="text-warning fas fa-exclamation-triangle"></i> Warning</strong>
<small v-if="item.status">HTTP {{item.status}}</small>
<button type="button" class="ml-2 mb-1 close" data-dismiss="toast" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="toast-body">{{item.message}}</div>
</div>
</script>

<script type="text/x-template" id="main-template">
<div class="container">
<div class="pricing-header px-3 pt-3 mx-auto text-center">
Expand Down
48 changes: 39 additions & 9 deletions assets/js/app.js
Expand Up @@ -42,7 +42,13 @@ let store = {
if (force || this[k] !== undefined) {
this[k] = msg[k];
} else {
console.log("invalid key: " + k);
if (k == "error") {
toasts.error({message: msg[k]});
} else if (k == "warn") {
toasts.warn({message: msg[k]});
} else {
console.log("invalid key: " + k);
}
}
}, this.state);
},
Expand All @@ -51,7 +57,7 @@ let store = {
axios.get("config").then(function(response) {
store.update(response.data);
store.initialized = true;
}).catch(error.raise);
}).catch(toasts.error);
}
}
};
Expand All @@ -63,24 +69,48 @@ let store = {
window.setInterval(function() {
axios.get("health").catch(function(res) {
res.message = "Server unavailable";
error.raise(res)
toasts.error(res)
});
}, 5000);

//
// Components
//

const error = new Vue({
el: '#error',
const toasts = new Vue({
el: "#toasts",
data: {
error: {},
items: {},
count: 0,
},
methods: {
raise: function(error) {
this.error = error;
$('.toast').toast('show');
raise: function (msg) {
msg.id = this.count++;
Vue.set(this.items, msg.id, msg);
},
error: function (error) {
error.type = "error";
this.raise(error)
},
warn: function (error) {
error.type = "warn";
this.raise(error);
},
remove: function (msg) {
Vue.delete(this.items, msg.id);
},
}
});

Vue.component('message-toast', {
props: ['item'],
template: '#message-template',
mounted: function () {
const id = "#message-id-" + this.item.id;
$(id).toast('show');
$(id).on('hidden.bs.toast', function () {
toasts.remove(this.item);
})
},
});

Expand Down
3 changes: 3 additions & 0 deletions cmd/root.go
Expand Up @@ -201,6 +201,9 @@ func run(cmd *cobra.Command, args []string) {
valueChan := make(chan util.Param)
go tee.Run(valueChan)

// capture log messages for UI
util.CaptureLogs(valueChan)

// start all loadpoints
for _, lp := range loadPoints {
lp.Dump()
Expand Down
164 changes: 85 additions & 79 deletions server/assets.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

38 changes: 38 additions & 0 deletions util/log.go
@@ -1,9 +1,11 @@
package util

import (
"io"
"io/ioutil"
"log"
"os"
"strconv"
"strings"

jww "github.com/spf13/jwalterweatherman"
Expand Down Expand Up @@ -68,3 +70,39 @@ func LogLevelToThreshold(level string) jww.Threshold {
panic("invalid log level " + level)
}
}

var uiChan chan<- Param

type uiWriter struct {
level string
}

func (w *uiWriter) Write(p []byte) (n int, err error) {
uiChan <- Param{
LoadPoint: "",
Key: w.level,
Val: strings.Trim(strconv.Quote(string(p)), "\""),
}

return 0, nil
}

// CaptureLogs appends uiWriter to relevant log levels
func CaptureLogs(c chan<- Param) {
uiChan = c

for _, l := range loggers {
captureLogger("warn", l.Notepad.WARN)
captureLogger("error", l.Notepad.ERROR)
captureLogger("error", l.Notepad.FATAL)
}
}

func captureLogger(level string, l *log.Logger) {
ui := uiWriter{
level: level,
}

mw := io.MultiWriter(l.Writer(), &ui)
l.SetOutput(mw)
}

0 comments on commit 97ee689

Please sign in to comment.