persistent logging
This commit is contained in:
parent
2986afb3d4
commit
356ebd2137
@ -33,7 +33,7 @@ window.addEventListener("load", () => {
|
|||||||
System.import("../wxBackend")
|
System.import("../wxBackend")
|
||||||
.then((wxMessaging: any) => {
|
.then((wxMessaging: any) => {
|
||||||
// Export as global for debugger
|
// Export as global for debugger
|
||||||
(window as any).wxMessaging = wxMessaging;
|
(window as any).wx = wxMessaging;
|
||||||
wxMessaging.wxMain();
|
wxMessaging.wxMain();
|
||||||
}).catch((e: Error) => {
|
}).catch((e: Error) => {
|
||||||
console.error("Loading Taler wallet background page failed.", e);
|
console.error("Loading Taler wallet background page failed.", e);
|
||||||
|
186
src/logging.ts
Normal file
186
src/logging.ts
Normal file
@ -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 <http://www.gnu.org/licenses/>
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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<void> {
|
||||||
|
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<LogEntry[]> {
|
||||||
|
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<void> {
|
||||||
|
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<LogEntry> = new Store<LogEntry>("logs");
|
||||||
|
|
||||||
|
export function openLoggingDb(): Promise<IDBDatabase> {
|
||||||
|
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();
|
36
src/pages/logs.html
Normal file
36
src/pages/logs.html
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<title>Taler Wallet: Logs</title>
|
||||||
|
|
||||||
|
<link rel="stylesheet" type="text/css" href="../style/lang.css">
|
||||||
|
<link rel="stylesheet" type="text/css" href="../style/wallet.css">
|
||||||
|
|
||||||
|
<link rel="icon" href="/img/icon.png">
|
||||||
|
|
||||||
|
<script src="/src/vendor/URI.js"></script>
|
||||||
|
<script src="/src/vendor/react.js"></script>
|
||||||
|
<script src="/src/vendor/react-dom.js"></script>
|
||||||
|
|
||||||
|
<!-- i18n -->
|
||||||
|
<script src="/src/vendor/jed.js"></script>
|
||||||
|
<script src="/src/i18n.js"></script>
|
||||||
|
<script src="/src/i18n/strings.js"></script>
|
||||||
|
|
||||||
|
<script src="/src/vendor/system-csp-production.src.js"></script>
|
||||||
|
<script src="/src/module-trampoline.js"></script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.tree-item {
|
||||||
|
margin: 2em;
|
||||||
|
border-radius: 5px;
|
||||||
|
border: 1px solid gray;
|
||||||
|
padding: 1em;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div id="container"></div>
|
||||||
|
</body>
|
||||||
|
</html>
|
76
src/pages/logs.tsx
Normal file
76
src/pages/logs.tsx
Normal file
@ -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 <http://www.gnu.org/licenses/>
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show wallet logs.
|
||||||
|
*
|
||||||
|
* @author Florian Dold
|
||||||
|
*/
|
||||||
|
|
||||||
|
import {LogEntry, getLogs} from "src/logging";
|
||||||
|
|
||||||
|
interface LogViewProps {
|
||||||
|
log: LogEntry;
|
||||||
|
}
|
||||||
|
|
||||||
|
class LogView extends React.Component<LogViewProps, void> {
|
||||||
|
render(): JSX.Element {
|
||||||
|
let e = this.props.log;
|
||||||
|
return (
|
||||||
|
<div className="tree-item">
|
||||||
|
<ul>
|
||||||
|
<li>id: {e.id || "unknown"}</li>
|
||||||
|
<li>msg: {e.msg}</li>
|
||||||
|
<li>file: {e.source || "(unknown)"}</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
interface LogsState {
|
||||||
|
logs: LogEntry[]|undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
class Logs extends React.Component<any, LogsState> {
|
||||||
|
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 <span>...</span>;
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<div className="tree-item">
|
||||||
|
Logs:
|
||||||
|
{logs.map(e => <LogView log={e} />)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function main() {
|
||||||
|
ReactDOM.render(<Logs />, document.getElementById("container")!);
|
||||||
|
}
|
@ -492,6 +492,9 @@ function WalletDebug(props: any) {
|
|||||||
<button onClick={openExtensionPage("/src/pages/tree.html")}>
|
<button onClick={openExtensionPage("/src/pages/tree.html")}>
|
||||||
show tree
|
show tree
|
||||||
</button>
|
</button>
|
||||||
|
<button onClick={openExtensionPage("/src/pages/logs.html")}>
|
||||||
|
show logs
|
||||||
|
</button>
|
||||||
<br />
|
<br />
|
||||||
<button onClick={confirmReset}>
|
<button onClick={confirmReset}>
|
||||||
reset
|
reset
|
||||||
|
41
src/query.ts
41
src/query.ts
@ -38,9 +38,9 @@ export interface JoinLeftResult<L,R> {
|
|||||||
export class Store<T> {
|
export class Store<T> {
|
||||||
name: string;
|
name: string;
|
||||||
validator?: (v: T) => T;
|
validator?: (v: T) => T;
|
||||||
storeParams: IDBObjectStoreParameters;
|
storeParams?: IDBObjectStoreParameters;
|
||||||
|
|
||||||
constructor(name: string, storeParams: IDBObjectStoreParameters,
|
constructor(name: string, storeParams?: IDBObjectStoreParameters,
|
||||||
validator?: (v: T) => T) {
|
validator?: (v: T) => T) {
|
||||||
this.name = name;
|
this.name = name;
|
||||||
this.validator = validator;
|
this.validator = validator;
|
||||||
@ -450,6 +450,43 @@ export class QueryRoot implements PromiseLike<void> {
|
|||||||
return new IterQueryStream(this, store.name, {});
|
return new IterQueryStream(this, store.name, {});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
count<T>(store: Store<T>): Promise<number> {
|
||||||
|
const {resolve, promise} = openPromise();
|
||||||
|
|
||||||
|
const doCount = (tx: IDBTransaction) => {
|
||||||
|
const s = tx.objectStore(store.name);
|
||||||
|
const req = s.count();
|
||||||
|
req.onsuccess = () => {
|
||||||
|
resolve(req.result);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
this.addWork(doCount, store.name, false);
|
||||||
|
return Promise.resolve()
|
||||||
|
.then(() => this.finish())
|
||||||
|
.then(() => promise);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
deleteIf<T>(store: Store<T>, predicate: (x: T, n: number) => boolean): QueryRoot {
|
||||||
|
const doDeleteIf = (tx: IDBTransaction) => {
|
||||||
|
const s = tx.objectStore(store.name);
|
||||||
|
const req = s.openCursor();
|
||||||
|
let n = 0;
|
||||||
|
req.onsuccess = () => {
|
||||||
|
let cursor: IDBCursorWithValue = req.result;
|
||||||
|
if (cursor) {
|
||||||
|
if (predicate(cursor.value, n++)) {
|
||||||
|
cursor.delete();
|
||||||
|
}
|
||||||
|
cursor.continue();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
this.addWork(doDeleteIf, store.name, true);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
iterIndex<S extends IDBValidKey,T>(index: Index<S,T>,
|
iterIndex<S extends IDBValidKey,T>(index: Index<S,T>,
|
||||||
only?: S): QueryStream<T> {
|
only?: S): QueryStream<T> {
|
||||||
this.stores.add(index.storeName);
|
this.stores.add(index.storeName);
|
||||||
|
@ -30,6 +30,7 @@ import { Notifier } from "./types";
|
|||||||
import { Contract } from "./types";
|
import { Contract } from "./types";
|
||||||
import MessageSender = chrome.runtime.MessageSender;
|
import MessageSender = chrome.runtime.MessageSender;
|
||||||
import { ChromeBadge } from "./chromeBadge";
|
import { ChromeBadge } from "./chromeBadge";
|
||||||
|
import * as logging from "./logging";
|
||||||
|
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
@ -413,6 +414,7 @@ function handleBankRequest(wallet: Wallet, headerList: chrome.webRequest.HttpHea
|
|||||||
// Useful for debugging ...
|
// Useful for debugging ...
|
||||||
export let wallet: Wallet | undefined = undefined;
|
export let wallet: Wallet | undefined = undefined;
|
||||||
export let badge: ChromeBadge | undefined = undefined;
|
export let badge: ChromeBadge | undefined = undefined;
|
||||||
|
export let log = logging.log;
|
||||||
|
|
||||||
// Rate limit cache for executePayment operations, to break redirect loops
|
// Rate limit cache for executePayment operations, to break redirect loops
|
||||||
let rateLimitCache: { [n: number]: number } = {};
|
let rateLimitCache: { [n: number]: number } = {};
|
||||||
@ -422,6 +424,10 @@ function clearRateLimitCache() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function wxMain() {
|
export function wxMain() {
|
||||||
|
window.onerror = (m, source, lineno, colno, error) => {
|
||||||
|
logging.record("error", m + error, source || "(unknown)", lineno || 0, colno || 0);
|
||||||
|
}
|
||||||
|
|
||||||
chrome.browserAction.setBadgeText({ text: "" });
|
chrome.browserAction.setBadgeText({ text: "" });
|
||||||
badge = new ChromeBadge();
|
badge = new ChromeBadge();
|
||||||
|
|
||||||
|
@ -16,14 +16,14 @@
|
|||||||
"src/checkable.ts",
|
"src/checkable.ts",
|
||||||
"decl/lib.es6.d.ts",
|
"decl/lib.es6.d.ts",
|
||||||
"src/chromeBadge.ts",
|
"src/chromeBadge.ts",
|
||||||
"decl/urijs/URIjs.d.ts",
|
|
||||||
"src/cryptoApi-test.ts",
|
"src/cryptoApi-test.ts",
|
||||||
|
"decl/urijs/URIjs.d.ts",
|
||||||
"src/components.ts",
|
"src/components.ts",
|
||||||
"decl/systemjs/systemjs.d.ts",
|
|
||||||
"src/emscriptif-test.ts",
|
"src/emscriptif-test.ts",
|
||||||
|
"decl/systemjs/systemjs.d.ts",
|
||||||
"src/cryptoApi.ts",
|
"src/cryptoApi.ts",
|
||||||
"decl/react-global.d.ts",
|
|
||||||
"src/helpers-test.ts",
|
"src/helpers-test.ts",
|
||||||
|
"decl/react-global.d.ts",
|
||||||
"src/cryptoLib.ts",
|
"src/cryptoLib.ts",
|
||||||
"src/types-test.ts",
|
"src/types-test.ts",
|
||||||
"decl/chrome/chrome.d.ts",
|
"decl/chrome/chrome.d.ts",
|
||||||
@ -32,6 +32,7 @@
|
|||||||
"src/emscriptif.ts",
|
"src/emscriptif.ts",
|
||||||
"src/helpers.ts",
|
"src/helpers.ts",
|
||||||
"src/http.ts",
|
"src/http.ts",
|
||||||
|
"src/logging.ts",
|
||||||
"src/query.ts",
|
"src/query.ts",
|
||||||
"src/taler-wallet-lib.ts",
|
"src/taler-wallet-lib.ts",
|
||||||
"src/types.ts",
|
"src/types.ts",
|
||||||
@ -46,6 +47,7 @@
|
|||||||
"src/pages/show-db.ts",
|
"src/pages/show-db.ts",
|
||||||
"src/pages/confirm-contract.tsx",
|
"src/pages/confirm-contract.tsx",
|
||||||
"src/pages/confirm-create-reserve.tsx",
|
"src/pages/confirm-create-reserve.tsx",
|
||||||
|
"src/pages/logs.tsx",
|
||||||
"src/pages/tree.tsx",
|
"src/pages/tree.tsx",
|
||||||
"src/popup/popup.tsx"
|
"src/popup/popup.tsx"
|
||||||
]
|
]
|
||||||
|
Loading…
Reference in New Issue
Block a user