-
-
Notifications
You must be signed in to change notification settings - Fork 2.1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Implement bug reporting logic #3000
Conversation
- Fetches all logs in order and concatenates correctly. - Purges old logs correctly.
Failing test has nothing to do with this PR AFAICT:
and is also failing on other PRs, so I think can be disregarded. This has been failing since at least as far back as the last release: https://travis-ci.org/vector-im/riot-web/builds/191602911 |
import PlatformPeg from 'matrix-react-sdk/lib/PlatformPeg'; | ||
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: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not super-excited by whatever you have your fill-column set to, if only because github doesn't show it without scrolling.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pick a number for me to wrap then.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
80?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've probably missed some things, but here is some stuff to go on with
|
||
log(level, ...args) { | ||
// We don't know what locale the user may be running so use ISO strings | ||
const ts = new Date().toISOString(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
how about using UTC?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Err, sure if you want that. You do realise toUTCString()
logs as:
Mon, 03 Jul 2006 21:44:38 GMT
right? I don't think you really want to know the day of the week and the 3 letter timezone for every log line. ISO logs as:
2017-01-20T11:13:40.389Z
which is more "log-like", but I'm doing this mainly for you so I defer to whatever you want.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
sorry, I had assumed that toISOString
used localtime. Looks like that was wrong. Ignore me.
constructor() { | ||
this.logs = ""; | ||
|
||
// Monkey-patch console logging |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
doing this here would prevent a ConsoleLogger ever being instantiated in a test env, or having more than one of them for some reason, or whatever. Feels like the monkey-patching should be done in init
. (Possibly via a logger.monkeyPatch(window.console)
call, but that's unimportant)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
doing this here would prevent a ConsoleLogger ever being instantiated in a test env
Test environments have a console
global afaik.
having more than one of them for some reason
It will just wrap the previous console logger in that case.
Feels like the monkey-patching should be done in init. (Possibly via a logger.monkeyPatch(window.console) call
Sure, that sounds nicer.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
// 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`; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
sooooo
when you console.log an Object, Chome/Firefox allow you to expand said object. In places I've relied on that ability. We could perhaps achieve something similar by JSON.stringify
ing arguments.
otoh, that would then break if we tried to log a circular structure, and I think it would be reasonable to say that if we care about the contents of the console.log params, the caller should be doing the JSON.stringifying.
wdyt?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agreed. I thought about it and came to the conclusion that if the caller cares enough about something, they should log it specifically rather than rely on browser-specific expandy bits (which also subtly break if you accidentally do string concatenation when logging).
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 = ""; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
uh. there's a sadness if the caller fails to actually write the logs.
I guess giving this proper transactional semantics is a pita though, and probably not worth it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That was basically my thinking :(
* | ||
* @param {boolean} clearAll True to clear the most recent logs returned in addition to the | ||
* older logs. This is desirable when sending bug reports as we do not want to submit the | ||
* same logs twice. This is not desirable when doing house-keeping at startup in case they |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
uh, why not? I don't really want to have to piece together different rageshakes to establish history,
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't follow. At startup, all we want to do is clear old logs, not all logs since we're just doing housekeeping. When rageshakes are submitted, we want to clear all logs because otherwise you'll get duplicates if they submit a 2nd or 3rd bug report.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes. I'm saying I'd like those duplicates in the 2nd and 3rd bug report.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh! Okay then, I'll modify it accordingly.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
size += lines.length; | ||
if (size > MAX_LOG_SIZE) { | ||
// the remaining log IDs should be removed. If we go out of bounds this is just [] | ||
removeLogIds = allLogIds.slice(i + 1); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we're deleting here based on the time the tab was opened - so we'll delete the logs from long-running tabs which are still logging in preference to tabs which were opened briefly a long time ago. This is a bit sadpanda.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm. I can add a last modified timestamp to each chunk and sort based on that, but I cannot easily query IndexedDB to work out which log IDs that will then correspond to, because they aren't keyed off that.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can we not store a last-modified time per-log-ID?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I guess I can denormalise the data a bit and store a tuple of last-modified-time and log ID in a separate object store, and then just query that when working out which logs to send. This is a bit of an architectural change, but I agree this sounds better.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
// the in-memory console logs. | ||
let logs = []; | ||
if (store) { | ||
logs = await store.consume(true); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
how can we be sure that this won't race against a flush process, leading to duplicate or missing log lines?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't really intend for flush()
to ever be called outside this class, so it's dubious as to whether I should keep it in or not. You can never get duplicates because flush()
wipes the ConsoleLogger.logs
so only one function will win at getting the logs. It's possible that if rageshake.flush()
is called and then sendBugReport()
is called and store.consume()
finishes before rageshake.flush()
does, that you can get a missing chunk. Will guard.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't really intend for flush() to ever be called outside this class, so it's dubious as to whether I should keep it in or not.
ya, but either way, flush()
gets called on a timer. I think you're right that we can't get dups, but we can certainly get missing chunks.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it's worth worrying about the races. I don't want to be poring over some logs and wondering if missing/duplicate lines are due to a problem in the app or the crashdump framework.
How about forcing a flush-to-db on rageshake, and then taking all of the logs for the rageshake from the DB? (with a completely different codepath for the no-indexdb case). I feel like that would be easier to reason about.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How about forcing a flush-to-db on rageshake, and then taking all of the logs for the rageshake from the DB? (with a completely different codepath for the no-indexdb case). I feel like that would be easier to reason about.
Can you please give a bit more information on what you would like? So only read from the database, and force a flush on submitting a bug report?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
This makes it easier to get a list of all the log IDs. It also makes it possible to order the logs by the *LAST* log line and not the first as was the case previously, which is important in the case of long-running tabs.
Eurgh. Two problems:
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
lgtm otherwise.
}); | ||
else { | ||
logs.push({ | ||
lines: logger.flush(), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this wouldn't destroy the store, ideally.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
Hum. Yes. Can't think of a way around that. You can do some interesting hacks with
Something to do with indexeddb, then? |
The promise would resolve immediately, nulling out `flushPromise`. This would then immediately be set from `new Promise((resolve, reject) => {...})` turning it back into non-null `flushPromise`. The resolve handler was called so the next `flush()` would see "oh yes, there is a non-null `flushPromise`" then promptly try to set `flushAgainPromise` which chains off the resolved `flushPromise` which relied on `flushPromise` being `null`ed out after `resolve()`, causing the chained `flush()` to see "oh yes, there is a non-null `flushPromise`" which... ad infinitum. This PR fixes it by making the nulling out asynchronous but the fact it took me this long to debug this issue indicates to me that this is a terrible piece of code. Will re-write.
@richvdh PTAL AFAIK the only outstanding thing is:
Not sure what I can do about that. |
@@ -16,6 +16,7 @@ limitations under the License. | |||
|
|||
import PlatformPeg from 'matrix-react-sdk/lib/PlatformPeg'; | |||
import request from "browser-request"; | |||
import q from "q"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
/me kills some puppies
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
still some questions
@@ -101,17 +106,14 @@ class IndexedDBLogStore { | |||
this.db = null; | |||
// Promise is not null when a flush is IN PROGRESS |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this looks slightly misleading - I think it is never reset to null?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oops, that was true previously, but not now. Fixing.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
// that a complete flush() is done. This does mean that if there are | ||
// 3 calls to flush() in one go, the 2nd and 3rd promises will run | ||
// concurrently, but this is fine since they can safely race when | ||
// collecting logs. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can they? Isn't there a danger of getting the logs out-of-order when writing to the db?
... and if we're happy for flushes to run concurrently, why do we bother with this.flushPromise
at all? Why not always start a new flush?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OH, you're right. You can get out of order logs, but you won't lose/get duplicate data which is what I was originally focussing on when fixing this. Will fix.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
Right, this should do it. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
lgtm
The aim of this PR is to implement all the logic behind storing console logs and sending them to a remote endpoint. It does not handle any of the UI surrounding this yet, in attempts to keep the size of this PR down.
This PR:
console
.This PR does not: