Skip to content
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

Merged
merged 5 commits into from
Apr 8, 2025

Conversation

yuvmen
Copy link
Member

@yuvmen yuvmen commented Apr 4, 2025

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

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.
@yuvmen yuvmen requested review from a team as code owners April 4, 2025 06:25
@github-actions github-actions bot added the Scope: Backend Automatically applied to PRs that change backend components label Apr 4, 2025
Copy link

codecov bot commented Apr 4, 2025

Codecov Report

All 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               

Comment on lines 319 to 323
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)
)
Copy link
Contributor

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:

Suggested change
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

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oh yea, much better

@yuvmen yuvmen requested review from a team and JoshFerge April 4, 2025 16:54
@@ -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"):
Copy link
Member

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?

Copy link
Member Author

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?

Copy link
Member

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.

Copy link
Member Author

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

Copy link
Member

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?

Copy link
Contributor

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", {})
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This can be None.

Copy link
Member Author

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?

Copy link
Member

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")

Copy link
Member Author

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)

Copy link
Member

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

Copy link
Member Author

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

Copy link
Member

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

Copy link
Member Author

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):
Copy link
Member

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.

Copy link
Member Author

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?

Copy link
Member

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.

Copy link
Member Author

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
Copy link
Member

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?

Copy link
Member Author

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

Copy link
Member

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?

Copy link
Member Author

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?

Copy link
Member

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.

Copy link
Member Author

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
Copy link
Member

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.

Copy link
Member Author

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 = {
Copy link
Member

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])

Copy link
Member Author

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

Copy link
Member Author

@yuvmen yuvmen Apr 7, 2025

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
Copy link
Member

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?

Copy link
Member

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):
Copy link
Member

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!

@yuvmen yuvmen merged commit edd0688 into master Apr 8, 2025
51 checks passed
@yuvmen yuvmen deleted the yuvmen/add_symbolicated_in_app_field_on_errors branch April 8, 2025 20:09
Christinarlong pushed a commit that referenced this pull request Apr 10, 2025
…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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Scope: Backend Automatically applied to PRs that change backend components
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants