aboutsummaryrefslogtreecommitdiff
path: root/src/logging.ts
diff options
context:
space:
mode:
authorFlorian Dold <florian.dold@gmail.com>2016-11-18 04:09:04 +0100
committerFlorian Dold <florian.dold@gmail.com>2016-11-18 04:09:04 +0100
commit356ebd2137eb5ca31486ed49ab8223c8256b1554 (patch)
treef3aa2214b928119b3d6e5ba22b8fa348fa929c25 /src/logging.ts
parent2986afb3d4e5f750f34e68cd46e394e2da392a4a (diff)
persistent logging
Diffstat (limited to 'src/logging.ts')
-rw-r--r--src/logging.ts186
1 files changed, 186 insertions, 0 deletions
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 <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();