From 356ebd2137eb5ca31486ed49ab8223c8256b1554 Mon Sep 17 00:00:00 2001 From: Florian Dold Date: Fri, 18 Nov 2016 04:09:04 +0100 Subject: persistent logging --- src/background/background.ts | 2 +- src/logging.ts | 186 +++++++++++++++++++++++++++++++++++++++++++ src/pages/logs.html | 36 +++++++++ src/pages/logs.tsx | 76 ++++++++++++++++++ src/popup/popup.tsx | 3 + src/query.ts | 41 +++++++++- src/wxBackend.ts | 6 ++ tsconfig.json | 8 +- 8 files changed, 352 insertions(+), 6 deletions(-) create mode 100644 src/logging.ts create mode 100644 src/pages/logs.html create mode 100644 src/pages/logs.tsx diff --git a/src/background/background.ts b/src/background/background.ts index c11ff0bd9..8a3f2477d 100644 --- a/src/background/background.ts +++ b/src/background/background.ts @@ -33,7 +33,7 @@ window.addEventListener("load", () => { System.import("../wxBackend") .then((wxMessaging: any) => { // Export as global for debugger - (window as any).wxMessaging = wxMessaging; + (window as any).wx = wxMessaging; wxMessaging.wxMain(); }).catch((e: Error) => { console.error("Loading Taler wallet background page failed.", e); diff --git a/src/logging.ts b/src/logging.ts new file mode 100644 index 000000000..3f8757a07 --- /dev/null +++ b/src/logging.ts @@ -0,0 +1,186 @@ +/* + This file is part of TALER + (C) 2016 Inria + + TALER is free software; you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + TALER 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 General Public License for more details. + + You should have received a copy of the GNU General Public License along with + TALER; see the file COPYING. If not, see + */ + +/** + * Configurable logging. + * + * @author Florian Dold + */ + +import {Store, QueryRoot} from "./query"; + +export type Level = "error" | "debug" | "info" | "warn"; + +function makeInfo() { + return console.info.bind(console, "%o"); +} + +function makeWarn() { + return console.warn.bind(console, "%o"); +} + +function makeError() { + return console.error.bind(console, "%o"); +} + +function makeDebug() { + return console.log.bind(console, "%o"); +} + +export async function log(msg: string, level: Level = "info"): Promise { + let ci = getCallInfo(2); + return record(level, msg, ci.file, ci.line, ci.column); +} + +function getCallInfo(level: number) { + // see https://github.com/v8/v8/wiki/Stack-Trace-API + let stack = Error().stack; + if (!stack) { + return unknownFrame; + } + let lines = stack.split("\n"); + return parseStackLine(lines[level + 1]); +} + +interface Frame { + file?: string; + method?: string; + column?: number; + line?: number; +} + +const unknownFrame: Frame = { + file: "(unknown)", + method: "(unknown)", + line: 0, + column: 0 +}; + +/** + * Adapted from https://github.com/errwischt/stacktrace-parser. + */ +function parseStackLine(stackLine: string): Frame { + const chrome = /^\s*at (?:(?:(?:Anonymous function)?|((?:\[object object\])?\S+(?: \[as \S+\])?)) )?\(?((?:file|http|https):.*?):(\d+)(?::(\d+))?\)?\s*$/i; + const gecko = /^(?:\s*([^@]*)(?:\((.*?)\))?@)?(\S.*?):(\d+)(?::(\d+))?\s*$/i; + const node = /^\s*at (?:((?:\[object object\])?\S+(?: \[as \S+\])?) )?\(?(.*?):(\d+)(?::(\d+))?\)?\s*$/i; + let parts; + + if ((parts = gecko.exec(stackLine))) { + let f: Frame = { + file: parts[3], + method: parts[1] || "(unknown)", + line: +parts[4], + column: parts[5] ? +parts[5] : undefined, + }; + return f; + } else if ((parts = chrome.exec(stackLine))) { + let f: Frame = { + file: parts[2], + method: parts[1] || "(unknown)", + line: +parts[3], + column: parts[4] ? +parts[4] : undefined, + }; + return f; + } else if ((parts = node.exec(stackLine))) { + let f: Frame = { + file: parts[2], + method: parts[1] || "(unknown)", + line: +parts[3], + column: parts[4] ? +parts[4] : undefined, + }; + return f; + } + return unknownFrame; +} + + +let db: IDBDatabase|undefined = undefined; + +export interface LogEntry { + timestamp: number; + level: string; + msg: string; + source: string|undefined; + col: number|undefined; + line: number|undefined; + id?: number; +} + +export async function getLogs(): Promise { + if (!db) { + db = await openLoggingDb(); + } + return await new QueryRoot(db).iter(logsStore).toArray(); +} + +export async function record(level: Level, msg: string, source?: string, line?: number, col?: number): Promise { + if (typeof indexedDB === "undefined") { + return; + } + if (!db) { + db = await openLoggingDb(); + } + + let count = await new QueryRoot(db).count(logsStore); + + console.log("count is", count); + + if (count > 1000) { + await new QueryRoot(db).deleteIf(logsStore, (e, i) => (i < 200)); + } + + let entry: LogEntry = { + timestamp: new Date().getTime(), + level, + msg, + source, + line, + col, + }; + await new QueryRoot(db).put(logsStore, entry); +} + +const loggingDbVersion = 1; + +const logsStore: Store = new Store("logs"); + +export function openLoggingDb(): Promise { + return new Promise((resolve, reject) => { + const req = indexedDB.open("taler-logging", loggingDbVersion); + req.onerror = (e) => { + reject(e); + }; + req.onsuccess = (e) => { + resolve(req.result); + }; + req.onupgradeneeded = (e) => { + const db = req.result; + if (e.oldVersion != 0) { + try { + db.deleteObjectStore("logs"); + } catch (e) { + console.error(e); + } + } + db.createObjectStore("logs", {keyPath: "id", autoIncrement: true}); + }; + }); +} + +export const info = makeInfo(); +export const debug = makeDebug(); +export const warn = makeWarn(); +export const error = makeError(); diff --git a/src/pages/logs.html b/src/pages/logs.html new file mode 100644 index 000000000..8d35bcbd7 --- /dev/null +++ b/src/pages/logs.html @@ -0,0 +1,36 @@ + + + + + Taler Wallet: Logs + + + + + + + + + + + + + + + + + + + + + +
+ + diff --git a/src/pages/logs.tsx b/src/pages/logs.tsx new file mode 100644 index 000000000..f971f3f85 --- /dev/null +++ b/src/pages/logs.tsx @@ -0,0 +1,76 @@ +/* + This file is part of TALER + (C) 2016 Inria + + TALER is free software; you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + TALER 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 General Public License for more details. + + You should have received a copy of the GNU General Public License along with + TALER; see the file COPYING. If not, see + */ + +/** + * Show wallet logs. + * + * @author Florian Dold + */ + +import {LogEntry, getLogs} from "src/logging"; + +interface LogViewProps { + log: LogEntry; +} + +class LogView extends React.Component { + render(): JSX.Element { + let e = this.props.log; + return ( +
+
    +
  • id: {e.id || "unknown"}
  • +
  • msg: {e.msg}
  • +
  • file: {e.source || "(unknown)"}
  • +
+
+ ); + } +} + +interface LogsState { + logs: LogEntry[]|undefined; +} + +class Logs extends React.Component { + constructor() { + super(); + this.update(); + this.state = {} as any; + } + + async update() { + let logs = await getLogs(); + this.setState({logs}); + } + + render(): JSX.Element { + let logs = this.state.logs; + if (!logs) { + return ...; + } + return ( +
+ Logs: + {logs.map(e => )} +
+ ); + } +} + +export function main() { + ReactDOM.render(, document.getElementById("container")!); +} diff --git a/src/popup/popup.tsx b/src/popup/popup.tsx index 711650bc3..e9fa6a87b 100644 --- a/src/popup/popup.tsx +++ b/src/popup/popup.tsx @@ -492,6 +492,9 @@ function WalletDebug(props: any) { +