Line length 80 like we're still in the 80s
This commit is contained in:
parent
ea860807c4
commit
ba1e166ac8
|
@ -17,18 +17,26 @@ limitations under the License.
|
||||||
import PlatformPeg from 'matrix-react-sdk/lib/PlatformPeg';
|
import PlatformPeg from 'matrix-react-sdk/lib/PlatformPeg';
|
||||||
import request from "browser-request";
|
import request from "browser-request";
|
||||||
|
|
||||||
// This module contains all the code needed to log the console, persist it to disk and submit bug reports. Rationale is as follows:
|
// This module contains all the code needed to log the console, persist it to
|
||||||
// - Monkey-patching the console is preferable to having a log library because we can catch logs by other libraries more easily,
|
// disk and submit bug reports. Rationale is as follows:
|
||||||
// without having to all depend on the same log framework / pass the logger around.
|
// - Monkey-patching the console is preferable to having a log library because
|
||||||
// - We use IndexedDB to persists logs because it has generous disk space limits compared to local storage. IndexedDB does not work
|
// we can catch logs by other libraries more easily, without having to all
|
||||||
// in incognito mode, in which case this module will not be able to write logs to disk. However, the logs will still be stored
|
// depend on the same log framework / pass the logger around.
|
||||||
// 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
|
// - We use IndexedDB to persists logs because it has generous disk space
|
||||||
// local storage, which does work in incognito mode. We also need to handle the case where there are 2+ tabs. Each JS runtime
|
// limits compared to local storage. IndexedDB does not work in incognito
|
||||||
// generates a random string which serves as the "ID" for that tab/session. These IDs are stored along with the log lines.
|
// mode, in which case this module will not be able to write logs to disk.
|
||||||
// - Bug reports are sent as a POST over HTTPS: it purposefully does not use Matrix as bug reports may be made when Matrix is
|
// However, the logs will still be stored in-memory, so can still be
|
||||||
// not responsive (which may be the cause of the bug). We send the most recent N MB of UTF-8 log data, starting with the most
|
// submitted in a bug report should the user wish to: we can also store more
|
||||||
// recent, which we know because the "ID"s are actually timestamps. We then purge the remaining logs. We also do this purge
|
// logs in-memory than in local storage, which does work in incognito mode.
|
||||||
// on startup to prevent logs from accumulating.
|
// We also need to handle the case where there are 2+ tabs. Each JS runtime
|
||||||
|
// generates a random string which serves as the "ID" for that tab/session.
|
||||||
|
// These IDs are stored along with the log lines.
|
||||||
|
// - 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). We send the most recent N MB of UTF-8 log data,
|
||||||
|
// starting with the most recent, which we know because the "ID"s are
|
||||||
|
// actually timestamps. We then purge the remaining logs. We also do this
|
||||||
|
// purge on startup to prevent logs from accumulating.
|
||||||
|
|
||||||
const FLUSH_RATE_MS = 30 * 1000;
|
const FLUSH_RATE_MS = 30 * 1000;
|
||||||
|
|
||||||
|
@ -60,9 +68,10 @@ class ConsoleLogger {
|
||||||
// We don't know what locale the user may be running so use ISO strings
|
// We don't know what locale the user may be running so use ISO strings
|
||||||
const ts = new Date().toISOString();
|
const ts = new Date().toISOString();
|
||||||
// Some browsers support string formatting which we're not doing here
|
// 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.
|
// so the lines are a little more ugly but easy to implement / quick to
|
||||||
|
// run.
|
||||||
// Example line:
|
// Example line:
|
||||||
// 2017-01-18T11:23:53.214Z W Failed to set badge count: Error setting badge. Message: Too many badges requests in queue.
|
// 2017-01-18T11:23:53.214Z W Failed to set badge count
|
||||||
const line = `${ts} ${level} ${args.join(' ')}\n`;
|
const line = `${ts} ${level} ${args.join(' ')}\n`;
|
||||||
// Using + really is the quickest way in JS
|
// Using + really is the quickest way in JS
|
||||||
// http://jsperf.com/concat-vs-plus-vs-join
|
// http://jsperf.com/concat-vs-plus-vs-join
|
||||||
|
@ -74,7 +83,8 @@ class ConsoleLogger {
|
||||||
* @return {string} \n delimited log lines to flush.
|
* @return {string} \n delimited log lines to flush.
|
||||||
*/
|
*/
|
||||||
flush() {
|
flush() {
|
||||||
// The ConsoleLogger doesn't care how these end up on disk, it just flushes them to the caller.
|
// The ConsoleLogger doesn't care how these end up on disk, it just
|
||||||
|
// flushes them to the caller.
|
||||||
const logsToFlush = this.logs;
|
const logsToFlush = this.logs;
|
||||||
this.logs = "";
|
this.logs = "";
|
||||||
return logsToFlush;
|
return logsToFlush;
|
||||||
|
@ -105,7 +115,9 @@ class IndexedDBLogStore {
|
||||||
};
|
};
|
||||||
|
|
||||||
req.onerror = (event) => {
|
req.onerror = (event) => {
|
||||||
const err = "Failed to open log database: " + event.target.errorCode;
|
const err = (
|
||||||
|
"Failed to open log database: " + event.target.errorCode
|
||||||
|
);
|
||||||
console.error(err);
|
console.error(err);
|
||||||
reject(new Error(err));
|
reject(new Error(err));
|
||||||
};
|
};
|
||||||
|
@ -117,8 +129,10 @@ class IndexedDBLogStore {
|
||||||
keyPath: ["id", "index"]
|
keyPath: ["id", "index"]
|
||||||
});
|
});
|
||||||
// Keys in the database look like: [ "instance-148938490", 0 ]
|
// Keys in the database look like: [ "instance-148938490", 0 ]
|
||||||
// Later on we need to query for everything with index=0, and query everything for an instance id.
|
// Later on we need to query for everything with index=0, and
|
||||||
// In order to do this, we need to set up indexes on both "id" and "index".
|
// query everything for an instance id.
|
||||||
|
// In order to do this, we need to set up indexes on both "id"
|
||||||
|
// and "index".
|
||||||
objectStore.createIndex("index", "index", { unique: false });
|
objectStore.createIndex("index", "index", { unique: false });
|
||||||
objectStore.createIndex("id", "id", { unique: false });
|
objectStore.createIndex("id", "id", { unique: false });
|
||||||
|
|
||||||
|
@ -151,23 +165,30 @@ class IndexedDBLogStore {
|
||||||
resolve();
|
resolve();
|
||||||
};
|
};
|
||||||
txn.onerror = (event) => {
|
txn.onerror = (event) => {
|
||||||
console.error("Failed to flush logs : " + event.target.errorCode);
|
console.error(
|
||||||
reject(new Error("Failed to write logs: " + event.target.errorCode));
|
"Failed to flush logs : " + event.target.errorCode
|
||||||
|
);
|
||||||
|
reject(
|
||||||
|
new Error("Failed to write logs: " + event.target.errorCode)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Consume the most recent logs and return them. Older logs which are not returned are deleted at the same time,
|
* Consume the most recent logs and return them. Older logs which are not
|
||||||
* so this can be called at startup to do house-keeping to keep the logs from growing too large.
|
* returned are deleted at the same time, so this can be called at startup
|
||||||
|
* to do house-keeping to keep the logs from growing too large.
|
||||||
*
|
*
|
||||||
* @param {boolean} clearAll True to clear the most recent logs returned in addition to the
|
* @param {boolean} clearAll True to clear the most recent logs returned in
|
||||||
* older logs. This is desirable when sending bug reports as we do not want to submit the
|
* addition to the older logs. This is desirable when sending bug reports as
|
||||||
* same logs twice. This is not desirable when doing house-keeping at startup in case they
|
* we do not want to submit the same logs twice. This is not desirable when
|
||||||
* want to submit a bug report later.
|
* doing house-keeping at startup in case they want to submit a bug report
|
||||||
* @return {Promise<Object[]>} Resolves to an array of objects. The array is sorted in time (oldest first) based on
|
* later.
|
||||||
* when the log file was created (the log ID). The objects have said log ID in an "id" field and "lines" which is a
|
* @return {Promise<Object[]>} Resolves to an array of objects. The array is
|
||||||
* big string with all the new-line delimited logs.
|
* sorted in time (oldest first) based on when the log file was created (the
|
||||||
|
* log ID). The objects have said log ID in an "id" field and "lines" which
|
||||||
|
* is a big string with all the new-line delimited logs.
|
||||||
*/
|
*/
|
||||||
async consume(clearAll) {
|
async consume(clearAll) {
|
||||||
const MAX_LOG_SIZE = 1024 * 1024 * 50; // 50 MB
|
const MAX_LOG_SIZE = 1024 * 1024 * 50; // 50 MB
|
||||||
|
@ -176,13 +197,15 @@ class IndexedDBLogStore {
|
||||||
// Returns: a string representing the concatenated logs for this ID.
|
// Returns: a string representing the concatenated logs for this ID.
|
||||||
function fetchLogs(id) {
|
function fetchLogs(id) {
|
||||||
const o = db.transaction("logs", "readonly").objectStore("logs");
|
const o = db.transaction("logs", "readonly").objectStore("logs");
|
||||||
return selectQuery(o.index("id"), IDBKeyRange.only(id), (cursor) => {
|
return selectQuery(o.index("id"), IDBKeyRange.only(id),
|
||||||
|
(cursor) => {
|
||||||
return {
|
return {
|
||||||
lines: cursor.value.lines,
|
lines: cursor.value.lines,
|
||||||
index: cursor.value.index,
|
index: cursor.value.index,
|
||||||
}
|
}
|
||||||
}).then((linesArray) => {
|
}).then((linesArray) => {
|
||||||
// We have been storing logs periodically, so string them all together *in order of index* now
|
// We have been storing logs periodically, so string them all
|
||||||
|
// together *in order of index* now
|
||||||
linesArray.sort((a, b) => {
|
linesArray.sort((a, b) => {
|
||||||
return a.index - b.index;
|
return a.index - b.index;
|
||||||
})
|
})
|
||||||
|
@ -192,13 +215,18 @@ class IndexedDBLogStore {
|
||||||
|
|
||||||
// Returns: A sorted array of log IDs. (newest first)
|
// Returns: A sorted array of log IDs. (newest first)
|
||||||
function fetchLogIds() {
|
function fetchLogIds() {
|
||||||
// To gather all the log IDs, query for every log entry with index "0", this will let us
|
// To gather all the log IDs, query for every log entry with index
|
||||||
// know all the IDs from different tabs/sessions.
|
// "0", this will let us know all the IDs from different
|
||||||
|
// tabs/sessions.
|
||||||
const o = db.transaction("logs", "readonly").objectStore("logs");
|
const o = db.transaction("logs", "readonly").objectStore("logs");
|
||||||
return selectQuery(o.index("index"), IDBKeyRange.only(0), (cursor) => cursor.value.id).then((res) => {
|
return selectQuery(o.index("index"), IDBKeyRange.only(0),
|
||||||
// we know each entry has a unique ID, and we know IDs are timestamps, so accumulate all the IDs,
|
(cursor) => cursor.value.id).then((res) => {
|
||||||
// ignoring the logs for now, and sort them to work out the correct log ID ordering, newest first.
|
// we know each entry has a unique ID, and we know IDs are
|
||||||
// E.g. [ "instance-1484827160051", "instance-1374827160051", "instance-1000007160051"]
|
// timestamps, so accumulate all the IDs, ignoring the logs for
|
||||||
|
// now, and sort them to work out the correct log ID ordering,
|
||||||
|
// newest first.
|
||||||
|
// E.g. [ "instance-1484827160051", "instance-1374827160051",
|
||||||
|
// "instance-1000007160051"]
|
||||||
return res.sort().reverse();
|
return res.sort().reverse();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -221,7 +249,12 @@ class IndexedDBLogStore {
|
||||||
resolve();
|
resolve();
|
||||||
};
|
};
|
||||||
txn.onerror = (event) => {
|
txn.onerror = (event) => {
|
||||||
reject(new Error(`Failed to delete logs for '${id}' : ${event.target.errorCode}`));
|
reject(
|
||||||
|
new Error(
|
||||||
|
"Failed to delete logs for " +
|
||||||
|
`'${id}' : ${event.target.errorCode}`
|
||||||
|
)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -238,7 +271,8 @@ class IndexedDBLogStore {
|
||||||
});
|
});
|
||||||
size += lines.length;
|
size += lines.length;
|
||||||
if (size > MAX_LOG_SIZE) {
|
if (size > MAX_LOG_SIZE) {
|
||||||
// the remaining log IDs should be removed. If we go out of bounds this is just []
|
// the remaining log IDs should be removed. If we go out of
|
||||||
|
// bounds this is just []
|
||||||
removeLogIds = allLogIds.slice(i + 1);
|
removeLogIds = allLogIds.slice(i + 1);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -248,7 +282,8 @@ class IndexedDBLogStore {
|
||||||
}
|
}
|
||||||
if (removeLogIds.length > 0) {
|
if (removeLogIds.length > 0) {
|
||||||
console.log("Removing logs: ", removeLogIds);
|
console.log("Removing logs: ", removeLogIds);
|
||||||
// Don't await this because it's non-fatal if we can't clean up logs.
|
// Don't await this because it's non-fatal if we can't clean up
|
||||||
|
// logs.
|
||||||
Promise.all(removeLogIds.map((id) => deleteLogs(id))).then(() => {
|
Promise.all(removeLogIds.map((id) => deleteLogs(id))).then(() => {
|
||||||
console.log(`Removed ${removeLogIds.length} old logs.`);
|
console.log(`Removed ${removeLogIds.length} old logs.`);
|
||||||
}, (err) => {
|
}, (err) => {
|
||||||
|
@ -271,9 +306,11 @@ class IndexedDBLogStore {
|
||||||
* Helper method to collect results from a Cursor and promiseify it.
|
* Helper method to collect results from a Cursor and promiseify it.
|
||||||
* @param {ObjectStore|Index} store The store to perform openCursor on.
|
* @param {ObjectStore|Index} store The store to perform openCursor on.
|
||||||
* @param {IDBKeyRange=} keyRange Optional key range to apply on the cursor.
|
* @param {IDBKeyRange=} keyRange Optional key range to apply on the cursor.
|
||||||
* @param {Function} resultMapper A function which is repeatedly called with a Cursor.
|
* @param {Function} resultMapper A function which is repeatedly called with a
|
||||||
|
* Cursor.
|
||||||
* Return the data you want to keep.
|
* Return the data you want to keep.
|
||||||
* @return {Promise<T[]>} Resolves to an array of whatever you returned from resultMapper.
|
* @return {Promise<T[]>} Resolves to an array of whatever you returned from
|
||||||
|
* resultMapper.
|
||||||
*/
|
*/
|
||||||
function selectQuery(store, keyRange, resultMapper) {
|
function selectQuery(store, keyRange, resultMapper) {
|
||||||
const query = store.openCursor(keyRange);
|
const query = store.openCursor(keyRange);
|
||||||
|
@ -355,7 +392,9 @@ module.exports = {
|
||||||
*/
|
*/
|
||||||
sendBugReport: async function(userText) {
|
sendBugReport: async function(userText) {
|
||||||
if (!logger) {
|
if (!logger) {
|
||||||
throw new Error("No console logger, did you forget to call init()?");
|
throw new Error(
|
||||||
|
"No console logger, did you forget to call init()?"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
if (!bugReportEndpoint) {
|
if (!bugReportEndpoint) {
|
||||||
throw new Error("No bug report endpoint has been set.");
|
throw new Error("No bug report endpoint has been set.");
|
||||||
|
@ -372,8 +411,8 @@ module.exports = {
|
||||||
userAgent = window.navigator.userAgent;
|
userAgent = window.navigator.userAgent;
|
||||||
}
|
}
|
||||||
|
|
||||||
// If in incognito mode, store is null, but we still want bug report sending to work going off
|
// If in incognito mode, store is null, but we still want bug report
|
||||||
// the in-memory console logs.
|
// sending to work going off the in-memory console logs.
|
||||||
let logs = [];
|
let logs = [];
|
||||||
if (store) {
|
if (store) {
|
||||||
logs = await store.consume(true);
|
logs = await store.consume(true);
|
||||||
|
@ -391,7 +430,9 @@ module.exports = {
|
||||||
url: bugReportEndpoint,
|
url: bugReportEndpoint,
|
||||||
body: {
|
body: {
|
||||||
logs: logs,
|
logs: logs,
|
||||||
text: userText || "User did not supply any additional text.",
|
text: (
|
||||||
|
userText || "User did not supply any additional text."
|
||||||
|
),
|
||||||
version: version,
|
version: version,
|
||||||
user_agent: userAgent,
|
user_agent: userAgent,
|
||||||
},
|
},
|
||||||
|
|
Loading…
Reference in New Issue