-
Notifications
You must be signed in to change notification settings - Fork 63
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
feat: combine contents from GlobalStore when reporting. #845
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -82,7 +82,7 @@ export default abstract class Client { | |
|
||
// First, we go with the global (shared) store. | ||
// Webserver middleware can then switch to the AsyncStore for async context tracking. | ||
this.__store = new GlobalStore({ context: {}, breadcrumbs: [] }) | ||
this.__store = GlobalStore | ||
this.__transport = transport | ||
this.logger = logger(this) | ||
} | ||
|
@@ -125,7 +125,9 @@ export default abstract class Client { | |
setContext(context: Record<string, unknown>): Client { | ||
if (typeof context === 'object') { | ||
const store = this.__store.getStore() | ||
store.context = merge(store.context, context) | ||
if (store) { | ||
store.context = merge(store.context, context) | ||
} | ||
} | ||
return this | ||
} | ||
|
@@ -134,6 +136,8 @@ export default abstract class Client { | |
this.logger.warn('Deprecation warning: `Honeybadger.resetContext()` has been deprecated; please use `Honeybadger.clear()` instead.') | ||
const store = this.__store.getStore() | ||
|
||
if (store === undefined) return this | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same comment as above. |
||
|
||
if (typeof context === 'object' && context !== null) { | ||
store.context = context | ||
} | ||
|
@@ -146,8 +150,10 @@ export default abstract class Client { | |
|
||
clear(): Client { | ||
const store = this.__store.getStore() | ||
store.context = {} | ||
store.breadcrumbs = [] | ||
if (store) { | ||
store.context = {} | ||
store.breadcrumbs = [] | ||
} | ||
|
||
return this | ||
} | ||
|
@@ -347,13 +353,17 @@ export default abstract class Client { | |
const category = opts.category || 'custom' | ||
const timestamp = new Date().toISOString() | ||
|
||
const store = this.__store.getStore() | ||
let store = this.__store.getStore() | ||
if (!store) { | ||
this.__setStore(GlobalStore) | ||
store = this.__store.getStore() | ||
} | ||
let breadcrumbs = store.breadcrumbs | ||
breadcrumbs.push({ | ||
category: category as string, | ||
message: message, | ||
metadata: metadata as Record<string, unknown>, | ||
timestamp: timestamp | ||
message, | ||
timestamp, | ||
}) | ||
|
||
const limit = this.config.maxBreadcrumbs | ||
|
@@ -426,11 +436,9 @@ export default abstract class Client { | |
*/ | ||
protected __getStoreContentsOrDefault(): DefaultStoreContents { | ||
const existingStoreContents = this.__store.getStore(); | ||
const storeContents = existingStoreContents || {}; | ||
return { | ||
context: {}, | ||
breadcrumbs: [], | ||
...storeContents | ||
...GlobalStore.getStore(), | ||
...existingStoreContents || {} | ||
}; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -11,6 +11,7 @@ describe('browser client', function () { | |
client = Singleton.factory({ | ||
logger: nullLogger() | ||
}); | ||
client.clear() | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I suspect you had to do this because now there is a singleton of the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. True. But the inconsistency always was a bit jarring. However, a big reason why it's a singleton is because we need to be able to recover breadcrumbs from before we switched to async. ie this flow:
On line a: when gathering breadcrumbs to report, we also need breadcrumbs that happened before we switched to Async. That's what this line does. But now that I think of it, we don't really need a singleton for that. We can do it earlier, by initialising the ALS store with the global one: AsyncStore.run({ ...GlobalStore.getStore() }, () => {
// code
}); But the second point is stage b: when we exit the async context, we lose access to the async store, so the only way to recover any breadcrumbs that happened before entering the async context is by having the global store be a singleton. I think the singleton approach is fine, since the Honeybadger client is also a global singleton. But now that you mention it, I see that a user could manually create another client with |
||
|
||
// @ts-ignore - no need to test this in here | ||
client.__getSourceFileHandler = 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.
In
addBreadcrumb
, you set the storeGlobalStore
if it'sundefined
. Shouldn't you do the same here? And perhaps wrap into a private function to re-use (callthis.getStore()
instead ofthis._store.getStore()
)?My concern with this approach is that we may accidentally switch to a
GlobalStore
when we originally wanted theAsyncStore
. Then again, we always set the store toAsyncStore
before we to use 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.
I did this at first, but it's slightly more complicated. I went with a
getStore()
method, but it caused trouble, as some places mutated the store, while others didn't. I couldn't simply return a copy, because some locations wanted to mutate the store. For example,addBreadcrumb()
definitely wants to add to the store, hence the switch.I admit that it's confusing, but I decided to go with this for now, but I'll continue looking for improvements. I'll take another look now, so you can go ahead and release the main fix in the other PR.
The place you linked where we always use the async store is in AWS Lambda handlers. In regular apps, we don't do that, because an app may or may not be a web app, and errors can happen before we start handling requests (eg in our setup code).