mirror of
https://github.com/SchildiChat/element-web.git
synced 2024-10-01 01:26:12 -04:00
Persist console logs to an IndexedDB instance
This commit is contained in:
parent
641a5c244c
commit
e9884768f6
@ -36,6 +36,13 @@ require('gfm.css/gfm.css');
|
||||
require('highlight.js/styles/github.css');
|
||||
require('draft-js/dist/Draft.css');
|
||||
|
||||
const rageshake = require("./rageshake");
|
||||
rageshake.init().then(() => {
|
||||
console.log("Initialised rageshake");
|
||||
}, (err) => {
|
||||
console.error("Failed to initialise rageshake: " + err);
|
||||
});
|
||||
|
||||
|
||||
// add React and ReactPerf to the global namespace, to make them easier to
|
||||
// access via the console
|
||||
|
180
src/vector/rageshake.js
Normal file
180
src/vector/rageshake.js
Normal file
@ -0,0 +1,180 @@
|
||||
/*
|
||||
Copyright 2017 OpenMarket Ltd
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
// This module contains all the code needed to log the console, persist it to disk and submit bug reports. Rationale is as follows:
|
||||
// - Monkey-patching the console is preferable to having a log library because we can catch logs by other libraries more easily,
|
||||
// without having to all depend on the same log framework / pass the logger around.
|
||||
// - We use IndexedDB to persists logs because it has generous disk space limits compared to local storage. IndexedDB does not work
|
||||
// in incognito mode, in which case this module will not be able to write logs to disk. However, the logs will still be stored
|
||||
// in-memory, so can still be submitted in a bug report should the user wish to: we can also store more logs in-memory than in
|
||||
// local storage, which does work in incognito mode.
|
||||
// - Bug reports are sent as a POST over HTTPS: it purposefully does not use Matrix as bug reports may be made when Matrix is
|
||||
// not responsive (which may be the cause of the bug).
|
||||
|
||||
const FLUSH_RATE_MS = 30 * 1000;
|
||||
|
||||
// A class which monkey-patches the global console and stores log lines.
|
||||
class ConsoleLogger {
|
||||
constructor() {
|
||||
this.logs = "";
|
||||
|
||||
// Monkey-patch console logging
|
||||
const consoleFunctionsToLevels = {
|
||||
log: "I",
|
||||
info: "I",
|
||||
error: "E",
|
||||
};
|
||||
Object.keys(consoleFunctionsToLevels).forEach((fnName) => {
|
||||
const level = consoleFunctionsToLevels[fnName];
|
||||
let originalFn = window.console[fnName].bind(window.console);
|
||||
window.console[fnName] = (...args) => {
|
||||
this.log(level, ...args);
|
||||
originalFn(...args);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
log(level, ...args) {
|
||||
// We don't know what locale the user may be running so use ISO strings
|
||||
const ts = new Date().toISOString();
|
||||
// Some browsers support string formatting which we're not doing here
|
||||
// so the lines are a little more ugly but easy to implement / quick to run.
|
||||
// Example line:
|
||||
// 2017-01-18T11:23:53.214Z W Failed to set badge count: Error setting badge. Message: Too many badges requests in queue.
|
||||
const line = `${ts} ${level} ${args.join(' ')}\n`;
|
||||
// Using + really is the quickest way in JS
|
||||
// http://jsperf.com/concat-vs-plus-vs-join
|
||||
this.logs += line;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve log lines to flush to disk.
|
||||
* @return {string} \n delimited log lines to flush.
|
||||
*/
|
||||
flush() {
|
||||
// The ConsoleLogger doesn't care how these end up on disk, it just flushes them to the caller.
|
||||
const logsToFlush = this.logs;
|
||||
this.logs = "";
|
||||
return logsToFlush;
|
||||
}
|
||||
}
|
||||
|
||||
// A class which stores log lines in an IndexedDB instance.
|
||||
class IndexedDBLogStore {
|
||||
constructor(indexedDB, logger) {
|
||||
this.indexedDB = indexedDB;
|
||||
this.logger = logger;
|
||||
this.db = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {Promise} Resolves when the store is ready.
|
||||
*/
|
||||
connect() {
|
||||
let req = this.indexedDB.open("logs");
|
||||
return new Promise((resolve, reject) => {
|
||||
req.onsuccess = (event) => {
|
||||
this.db = event.target.result;
|
||||
// Periodically flush logs to local storage / indexeddb
|
||||
setInterval(this.flush.bind(this), FLUSH_RATE_MS);
|
||||
resolve();
|
||||
};
|
||||
|
||||
req.onerror = (event) => {
|
||||
const err = "Failed to open log database: " + event.target.errorCode;
|
||||
console.error(err);
|
||||
reject(new Error(err));
|
||||
};
|
||||
|
||||
// First time: Setup the object store
|
||||
req.onupgradeneeded = (event) => {
|
||||
const db = event.target.result;
|
||||
const objectStore = db.createObjectStore("logs", {
|
||||
autoIncrement: true
|
||||
})
|
||||
objectStore.transaction.oncomplete = function(event) {
|
||||
objectStore.add(
|
||||
new Date() + " ::: Log database was created."
|
||||
);
|
||||
};
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {Promise} Resolved when the logs have been flushed.
|
||||
*/
|
||||
flush() {
|
||||
if (!this.db) {
|
||||
// not connected yet or user rejected access for us to r/w to the db
|
||||
return Promise.reject(new Error("No connected database"));
|
||||
}
|
||||
const lines = this.logger.flush();
|
||||
if (lines.length === 0) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
return new Promise((resolve, reject) => {
|
||||
let txn = this.db.transaction("logs", "readwrite");
|
||||
let objStore = txn.objectStore("logs");
|
||||
objStore.add(lines);
|
||||
txn.oncomplete = (event) => {
|
||||
resolve();
|
||||
};
|
||||
txn.onerror = (event) => {
|
||||
console.error("Failed to flush logs : " + event.target.errorCode);
|
||||
reject(new Error("Failed to write logs: " + event.target.errorCode));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
let store = null;
|
||||
let inited = false;
|
||||
module.exports = {
|
||||
|
||||
/**
|
||||
* Configure rage shaking support for sending bug reports.
|
||||
* Modifies globals.
|
||||
*/
|
||||
init: function() {
|
||||
if (inited || !window.indexedDB) {
|
||||
return;
|
||||
}
|
||||
store = new IndexedDBLogStore(window.indexedDB, new ConsoleLogger());
|
||||
inited = true;
|
||||
return store.connect();
|
||||
},
|
||||
|
||||
/**
|
||||
* Force-flush the logs to storage.
|
||||
* @return {Promise} Resolved when the logs have been flushed.
|
||||
*/
|
||||
flush: function() {
|
||||
if (!store) {
|
||||
return;
|
||||
}
|
||||
return store.flush();
|
||||
},
|
||||
|
||||
/**
|
||||
* Send a bug report.
|
||||
* @param {string} userText Any additional user input.
|
||||
* @return {Promise} Resolved when the bug report is sent.
|
||||
*/
|
||||
sendBugReport: function(userText) {
|
||||
}
|
||||
};
|
Loading…
Reference in New Issue
Block a user