Skip to content

feat(processing): Enable Node.js Source Maps #6626

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

Merged
merged 3 commits into from
Dec 21, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/sentry/interfaces/stacktrace.py
Original file line number Diff line number Diff line change
Expand Up @@ -537,7 +537,7 @@ def get_culprit_string(self, platform=None):
fileloc = self.module or self.filename
if not fileloc:
return ''
elif platform == 'javascript':
elif platform in ('javascript', 'node'):
# function and fileloc might be unicode here, so let it coerce
# to a unicode string if needed.
return '%s(%s)' % (self.function or '?', fileloc)
Expand Down
9 changes: 5 additions & 4 deletions src/sentry/lang/javascript/plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@
def preprocess_event(data):
rewrite_exception(data)
fix_culprit(data)
inject_device_data(data)
if data.get('platform') == 'javascript':
inject_device_data(data)
generate_modules(data)
return data

Expand All @@ -23,7 +24,7 @@ def generate_modules(data):
for info in find_stacktraces_in_data(data):
for frame in info.stacktrace['frames']:
platform = frame.get('platform') or data['platform']
if platform != 'javascript' or frame.get('module'):
if platform not in ('javascript', 'node') or frame.get('module'):
continue
abs_path = frame.get('abs_path')
if abs_path and abs_path.startswith(('http:', 'https:', 'webpack:', 'app:')):
Expand Down Expand Up @@ -129,10 +130,10 @@ def can_configure_for_project(self, project, **kwargs):
def get_event_preprocessors(self, data, **kwargs):
# XXX: rewrite_exception we probably also want if the event
# platform is something else? unsure
if data.get('platform') == 'javascript':
if data.get('platform') in ('javascript', 'node'):
return [preprocess_event]
return []

def get_stacktrace_processors(self, data, stacktrace_infos, platforms, **kwargs):
if 'javascript' in platforms:
if 'javascript' in platforms or 'node' in platforms:
return [JavaScriptStacktraceProcessor]
28 changes: 22 additions & 6 deletions src/sentry/lang/javascript/processor.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ class ZeroReturnError(Exception):
VERSION_RE = re.compile(r'^[a-f0-9]{32}|[a-f0-9]{40}$', re.I)
NODE_MODULES_RE = re.compile(r'\bnode_modules/')
SOURCE_MAPPING_URL_RE = re.compile(r'\/\/# sourceMappingURL=(.*)$')
# the maximum number of remote resources (i.e. sourc eifles) that should be
# the maximum number of remote resources (i.e. source files) that should be
# fetched
MAX_RESOURCE_FETCHES = 100

Expand Down Expand Up @@ -495,7 +495,7 @@ def preprocess_step(self, processing_task):

def handles_frame(self, frame, stacktrace_info):
platform = frame.get('platform') or self.data.get('platform')
return (settings.SENTRY_SCRAPE_JAVASCRIPT_CONTEXT and platform == 'javascript')
return (settings.SENTRY_SCRAPE_JAVASCRIPT_CONTEXT and platform in ('javascript', 'node'))

def preprocess_frame(self, processable_frame):
# Stores the resolved token. This is used to cross refer to other
Expand All @@ -517,6 +517,13 @@ def process_frame(self, processable_frame, processing_task):
if not frame.get('abs_path') or not frame.get('lineno'):
return

# can't fetch if this is internal node module as well
# therefore we only process user-land frames (starting with /)
# or those created by bundle/webpack internals
if self.data.get('platform') == 'node' and \
not frame.get('abs_path').startswith(('/', 'app:', 'webpack:')):
return

errors = cache.get_errors(frame['abs_path'])
if errors:
all_errors.extend(errors)
Expand Down Expand Up @@ -588,7 +595,7 @@ def process_frame(self, processable_frame, processing_task):
)
source = self.get_sourceview(abs_path)

if not source:
if source is None:
errors = cache.get_errors(abs_path)
if errors:
all_errors.extend(errors)
Expand Down Expand Up @@ -639,9 +646,14 @@ def process_frame(self, processable_frame, processing_task):
else:
filename = filename.split('webpack:///', 1)[-1]

# As noted above, '~/' means they're coming from node_modules,
# so these are not app dependencies
if filename.startswith('~/'):
# As noted above:
# * [js/node] '~/' means they're coming from node_modules, so these are not app dependencies
# * [node] sames goes for `./node_modules/`, which is used when bundling node apps
# * [node] and webpack, which includes it's own code to bootstrap all modules and its internals
# eg. webpack:///webpack/bootstrap, webpack:///external
if filename.startswith('~/') or \
filename.startswith('./node_modules/') or \
not filename.startswith('./'):
in_app = False
# And conversely, local dependencies start with './'
elif filename.startswith('./'):
Expand Down Expand Up @@ -791,6 +803,10 @@ def populate_source_cache(self, frames):
# a fetch error that may be confusing.
if f['abs_path'] == '<anonymous>':
continue
# we cannot fetch any other files than those uploaded by user
if self.data.get('platform') == 'node' and \
not f.get('abs_path').startswith('app:'):
continue
pending_file_list.add(f['abs_path'])

for idx, filename in enumerate(pending_file_list):
Expand Down
2 changes: 1 addition & 1 deletion src/sentry/models/featureadoption.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@
)
manager.add(43, "user_tracking", "User Tracking", "code", prerequisite=["first_event"])
manager.add(44, "custom_tags", "Custom Tags", "code", prerequisite=["first_event"])
manager.add(45, "source_maps", "Source Maps", "code", prerequisite=["first_event", "javascript"])
manager.add(45, "source_maps", "Source Maps", "code", prerequisite=["first_event", ("javascript", "node")])
Copy link
Contributor

Choose a reason for hiding this comment

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

Does this work?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, see

prerequisite=["first_event", ("python", "javascript", "node", "php")]

Copy link
Contributor

Choose a reason for hiding this comment

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

Just some feedback here, but this is ultimately @ehfeng's decision:

I'm not sure I'd group node under this feature tracking flag thing. Source maps in node are generally less common and I wouldn't say "required" in the same sense as they are for browser js. And not sure if we'd want to say, using node sourcemaps also toggles this for browser js since I assume setup is going to be entirely different.

manager.add(46, "user_feedback", "User Feedback", "code", prerequisite=["user_tracking"])
# manager.add(47, "api", "API", "code", prerequisite=["first_event"]) #
# Challenging to determine what organization (i.e. api/0/organizations/)
Expand Down
2 changes: 1 addition & 1 deletion src/sentry/models/organizationonboardingtask.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ class OnboardingTask(object):
SECOND_PLATFORM = 4 # dependent on FIRST_EVENT.
USER_CONTEXT = 5 # dependent on FIRST_EVENT
RELEASE_TRACKING = 6 # dependent on FIRST_EVENT
SOURCEMAPS = 7 # dependent on RELEASE_TRACKING and one of the platforms being javascript
SOURCEMAPS = 7 # dependent on RELEASE_TRACKING and one of the platforms being javascript or node
USER_REPORTS = 8 # Only for web frameworks
ISSUE_TRACKER = 9
NOTIFICATION_SERVICE = 10
Expand Down
2 changes: 1 addition & 1 deletion src/sentry/utils/javascript.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@


def has_sourcemap(event):
if event.platform != 'javascript':
if event.platform not in ('javascript', 'node'):
return False
data = event.data

Expand Down
Loading