-
-
Notifications
You must be signed in to change notification settings - Fork 4.3k
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(sourcemaps) - Add symbolicated_in_app
field to error events
#88777
Conversation
Add and populate new field `symbolicated_in_app` which indicates if all in_app frames of an error are symbolicated. Field is calculated during javascript symbolication and passed on to reach snuba and be saved.
Codecov ReportAll modified and coverable lines are covered by tests ✅ ✅ All tests successful. No failed tests found. Additional details and impacted files@@ Coverage Diff @@
## master #88777 +/- ##
===========================================
+ Coverage 46.18% 87.78% +41.59%
===========================================
Files 10071 10092 +21
Lines 569697 571012 +1315
Branches 22433 22433
===========================================
+ Hits 263139 501242 +238103
+ Misses 306115 69327 -236788
Partials 443 443 |
data["symbolicated_in_app"] = ( | ||
True | ||
if has_in_app_frames and all_in_app_frames_symbolicated | ||
else (False if has_in_app_frames else None) | ||
) |
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 can be simplified a bit:
data["symbolicated_in_app"] = ( | |
True | |
if has_in_app_frames and all_in_app_frames_symbolicated | |
else (False if has_in_app_frames else None) | |
) | |
data["symbolicated_in_app"] = all_in_app_frames_symbolicated if has_in_app_frames else None |
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 yea, much better
@@ -296,6 +299,13 @@ def process_js_stacktraces(symbolicator: Symbolicator, data: Any) -> Any: | |||
if in_app is not None: | |||
merged_frame["in_app"] = in_app | |||
|
|||
# Track symbolication status for in-app frames | |||
if merged_frame.get("in_app"): |
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 you please remind me why we need this restriction?
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.
not sure what you mean by restriction, you mean why check for in_app on its own? I mean we do that to set the flag to calculate the "None" case where you dont have any at all, is that what you mean?
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 the frame be not in-app yet be symbolicated=True?
I would remove if merged_frame.get("in_app"):
completely unless there's a clear reason it has to be there.
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.
But if I understand what we are doing correctly, I do not care about symbolification of non in_app frames right?, the flag is meant to indicate problems with your in app symbolification, the case you are talking about is not something I want to check in my logic I think
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.
The only way a frame is symbolicated is because symbols have been provided by the customer.
I do not know if we guarantee that a symbolicated frame is also marked as in-app.
If we don't mark them as in-app when we symbolicate them, we probably should.
@loewenheim do you know?
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.
The logic for when a frame is considered in-app is given by the is_in_app
function in this file. We can't say that a symbolicated frame is definitely in-app because sourcemaps for frameworks might be available publicly.
# Track symbolication status for in-app frames | ||
if merged_frame.get("in_app"): | ||
has_in_app_frames = True | ||
frame_data = merged_frame.get("data", {}) |
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 can be None
.
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.
you mean we can set frame_data to be None instead of {} in case it doesnt exist?
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 have see merged_frame["data"] = None
which would cause an error when calling merged_frame.get("in_app")
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 would merged_frame["data"]
value affect merged_frame.get("in_app")
? or did you mean merged_frame.get("data")?
From what I can tell the only thing that would cause an error in this code is if merged_frame
is None and I dont think thats possible, the rest are protected by get
access no? it would not throw a key error it would simply return None (or default {} when we indicate 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 may be getting confused but I think this would raise an error.
merged_data = {"data": None}
frame_data = merged_frame.get("data", {})
if not None.get("symbolicated", False) // -> raises errors
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 that merged_frame.get("data", {})
would never return None, it would instead return the default we gave it - {}
, so it would become if not {}.get("symbolicated", false)
, unless I am missing something
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 just fixed this specific problem on this PR:
https://sentry.sentry.io/issues/6499816788/
>>> a = {'data': None}
>>> print(a.get('data', {}))
None
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.
ahh interesting, it the field exists and is itself None it does return it, good to know, ill fix it, thanks!
if merged_frame.get("in_app"): | ||
has_in_app_frames = True | ||
frame_data = merged_frame.get("data", {}) | ||
if not frame_data.get("symbolicated", False): |
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 know if this can ever come as None
but we should protect against 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.
lol sorry I dont quite understand your meaning here either, what can come as None? symbolicated
? wouldn't that just be correctly treated as "false" here? Or do you think if we have an in_app frame with symbolicated: None
we should treat it as a non in_app frame for this logic?
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 have seen frame["data"]
being None
. I just don't know if it can also happen with symbolicated
.
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.
like I wrote above though, isnt that the point of get
? if symbolicated
doesnt exist no error should happen here, frame_data.get("symbolicated", False)
would just return False no? I might be missing something about what you are saying
@@ -66,7 +66,7 @@ rfc3339-validator>=0.1.2 | |||
rfc3986-validator>=0.1.1 | |||
# [end] jsonschema format validators | |||
sentry-arroyo>=2.20.6 | |||
sentry-kafka-schemas>=1.1.4 | |||
sentry-kafka-schemas>=1.1.6 |
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.
Why do we need to bump the schemas? Can you please mention it in the description of the PR if we do?
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 am adding the calculated field as a new flag on the event now, symoblicated_in_app
, passing it along to Snuba so I added it in the kafka schema and published, no problem ill mention it on the PR
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.
Are the tests in here necessary if we have already added tests in test_processing
?
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.
yea I wondered about it as well, they are a little redundant, I considered actually removing the more low level tests if anything. I think these tests in test_plugin
assert the behaviour of the entire thing which is great, I get confidence that the event that gets generated holds the correct fields and should contain the value I want.
The more low level unit tests are basically helpers for the low level implementation (I created them first) but they run super fast and I felt like there isn't much harm in keeping them, you think maybe I should?
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 would do all test cases in the fast tests and only one test case in the higher-level tests.
Do whatever you think works best.
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.
yea I understand what you are saying and you may be right, thing is I kind of want to verify both a regular case and the None case on the top level to make sure it is preserved, but then it feels odd only testing the two cases and not the third so I think I will just keep them all if you dont feel strongly about it
) | ||
release.add_project(self.project) | ||
|
||
# Create source files and sourcemaps |
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 could be a helper function since we use it multiple times in this module.
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 will refactor a bit to remove redundancies in the tests
file=f1, | ||
) | ||
|
||
data = { |
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.
You could have a helper function to create this data structure and make the test easier to read.
exception = self.generate_event([file.min.js, file.min.js])
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.
yea youre right, I contemplated it, its also in use in other places in this file - ill add 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 added a method but it was hard to generalize it for the entire file and there was too much variance between cases imo, so I generalized for my tests only. Even for those I decided to still provide the frames from outside to keep it simple, I think its a good balance, let me know what you think
"""Test symbolicated_in_app is None when all frames are non-in-app""" | ||
data, symbolicator = self._get_test_data_and_symbolicator() | ||
|
||
# Replace all frames with non-in-app frames that will pass _handles_frame |
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 you please make _get_test_data_and_symbolicator
receive parameters to create the data the way you want rather than updating it within the tests?
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 would do all test cases in the fast tests and only one test case in the higher-level tests.
Do whatever you think works best.
) | ||
release.add_project(self.project) | ||
|
||
def _create_source_files_and_sourcemaps(self, release): |
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.
Thank you for adding the helper function!
…88777) Add and populate new field `symbolicated_in_app` which indicates if all in_app frames of an error are symbolicated. Field is calculated during javascript symbolication and passed on to Snuba. This required a change to the Kafka event adding the field, hence the kafka schema was bumped to the new 1.1.6 which includes it.
Add and populate new field
symbolicated_in_app
which indicates if all in_app frames of an error are symbolicated. Field is calculated during javascript symbolication and passed on to Snuba.This required a change to the Kafka event adding the field, hence the kafka schema was bumped to the new 1.1.6 which includes it.
Field is calculated according to logic in tech spec:
https://www.notion.so/sentry/Sourcemapped-Frames-Search-Tech-Spec-15f8b10e4b5d803fa561cbeb5d2691a3?pvs=4#15f8b10e4b5d81848492db9bc0559cf0