Finish basic log viewer

This commit is contained in:
Tulir Asokan 2018-11-12 00:00:37 +02:00
parent 6f2162d5f3
commit c2a5451401
9 changed files with 124 additions and 17 deletions

View file

@ -38,6 +38,12 @@ class WebSocketHandler(logging.Handler):
self.formatter = logging.Formatter()
def emit(self, record: logging.LogRecord) -> None:
try:
self._emit(record)
except Exception as e:
print("Logging error:", e)
def _emit(self, record: logging.LogRecord) -> None:
# JSON conversion based on Marsel Mavletkulov's json-log-formatter (MIT license)
# https://github.com/marselester/json-log-formatter
content = {
@ -45,6 +51,7 @@ class WebSocketHandler(logging.Handler):
for name, value in record.__dict__.items()
if name not in EXCLUDE_ATTRS
}
content["id"] = record.relativeCreated
content["msg"] = record.getMessage()
content["time"] = datetime.utcnow()
@ -61,7 +68,7 @@ class WebSocketHandler(logging.Handler):
try:
await self.ws.send_json(record)
except Exception as e:
pass
print("Log sending error:", e)
log_root = logging.getLogger("maubot")

View file

@ -82,6 +82,7 @@ export async function openLogSocket() {
socket: null,
connected: false,
authenticated: false,
onLog: data => {},
fails: -1,
}
const openHandler = () => {
@ -100,7 +101,9 @@ export async function openLogSocket() {
console.info("Websocket connection authentication failed")
}
} else {
data.time = new Date(data.time)
console.log("SERVLOG", data)
wrapper.onLog(data)
}
}
const closeHandler = evt => {

View file

@ -21,6 +21,7 @@ import { PrefTable, PrefSwitch, PrefInput } from "../../components/PreferenceTab
import Spinner from "../../components/Spinner"
import api from "../../api"
import BaseMainView from "./BaseMainView"
import Log from "./Log"
const ClientListEntry = ({ entry }) => {
const classes = ["client", "entry"]
@ -200,14 +201,17 @@ class Client extends BaseMainView {
</>
render() {
return <div className="client">
{this.renderSidebar()}
<div className="info">
{this.renderPreferences()}
{this.renderPrefButtons()}
{this.renderInstances()}
return <>
<div className="client">
{this.renderSidebar()}
<div className="info">
{this.renderPreferences()}
{this.renderPrefButtons()}
{this.renderInstances()}
</div>
</div>
</div>
<Log showName={false} lines={this.props.log}/>
</>
}
}

View file

@ -0,0 +1,30 @@
// maubot - A plugin-based Matrix bot system.
// Copyright (C) 2018 Tulir Asokan
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
import React, { Component } from "react"
import Log from "./Log"
class Home extends Component {
render() {
return <>
<div className="home">
See sidebar to get started
</div>
<Log lines={this.props.log}/>
</>
}
}
export default Home

View file

@ -23,6 +23,7 @@ import PrefTable, { PrefInput, PrefSelect, PrefSwitch } from "../../components/P
import api from "../../api"
import Spinner from "../../components/Spinner"
import BaseMainView from "./BaseMainView"
import Log from "./Log"
const InstanceListEntry = ({ entry }) => (
<NavLink className="instance entry" to={`/instance/${entry.id}`}>
@ -167,6 +168,7 @@ class Instance extends BaseMainView {
</button>
</div>
<div className="error">{this.state.error}</div>
<Log showName={false} lines={this.props.log}/>
</div>
}
}

View file

@ -0,0 +1,29 @@
// maubot - A plugin-based Matrix bot system.
// Copyright (C) 2018 Tulir Asokan
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
import React from "react"
const Log = ({ lines, showName = true }) => <div className="log">
{lines.map(data =>
<div className="row" key={data.id}>
<span className="time">{data.time.toLocaleTimeString()}</span>
<span className="level">{data.levelname}</span>
{showName && <span className="logger">{data.name}</span>}
<span className="text">{data.msg}</span>
</div>,
)}
</div>
export default Log

View file

@ -21,6 +21,7 @@ import PrefTable, { PrefInput } from "../../components/PreferenceTable"
import Spinner from "../../components/Spinner"
import api from "../../api"
import BaseMainView from "./BaseMainView"
import Log from "./Log"
const PluginListEntry = ({ entry }) => (
<NavLink className="plugin entry" to={`/plugin/${entry.id}`}>
@ -90,6 +91,7 @@ class Plugin extends BaseMainView {
</div>}
<div className="error">{this.state.error}</div>
{!this.isNew && this.renderInstances()}
<Log showName={false} lines={this.props.log}/>
</div>
}
}

View file

@ -20,6 +20,7 @@ import { ReactComponent as Plus } from "../../res/plus.svg"
import Instance from "./Instance"
import Client from "./Client"
import Plugin from "./Plugin"
import Home from "./Home"
class Dashboard extends Component {
constructor(props) {
@ -30,6 +31,8 @@ class Dashboard extends Component {
plugins: {},
sidebarOpen: false,
}
this.logLines = []
this.logMap = {}
window.maubot = this
}
@ -55,9 +58,13 @@ class Dashboard extends Component {
plugins[plugin.id] = plugin
}
this.setState({ instances, clients, plugins })
const logs = await api.openLogSocket()
console.log("WebSocket opened:", logs)
window.logs = logs
logs.onLog = data => {
this.logLines.push(data)
;(this.logMap[data.name] || (this.logMap[data.name] = [])).push(data)
this.setState({})
}
}
renderList(field, type) {
@ -85,11 +92,13 @@ class Dashboard extends Component {
if (!entry) {
return this.renderNotFound(field.slice(0, -1))
}
console.log(`maubot.${field.slice(0, -1)}.${id}`)
return React.createElement(type, {
entry,
onDelete: () => this.delete(field, id),
onChange: newEntry => this.add(field, newEntry, id),
ctx: this.state,
log: this.logMap[`maubot.${field.slice(0, -1)}.${id}`] || [],
})
}
@ -142,7 +151,7 @@ class Dashboard extends Component {
<main className="view">
<Switch>
<Route path="/" exact render={() => "Hello, World!"}/>
<Route path="/" exact render={() => <Home log={this.logLines}/>}/>
<Route path="/new/instance" render={() =>
<Instance onChange={newEntry => this.add("instances", newEntry)}
ctx={this.state}/>}/>

View file

@ -78,17 +78,38 @@
@import instance
@import plugin
> .not-found
text-align: center
margin-top: 5rem
font-size: 1.5rem
> div:not(.not-found)
> div
margin: 2rem 4rem
@media screen and (max-width: 50rem)
margin: 2rem 1rem
div.log > div.row
span.level, span.logger
display: none
> div.not-found, > div.home
text-align: center
margin-top: 5rem
font-size: 1.5rem
div.log
text-align: left
font-size: 12px
max-height: 20rem
font-family: "Fira Code", monospace
overflow: auto
> div.row
white-space: nowrap
> span.level:before
content: " ["
> span.logger:before
content: "@"
> span.text:before
content: "] "
div.buttons
+button-group
display: flex