[RFC] Undertracking errors in the frontend
Everyone can contribute. Help move this issue forward while earning points, leveling up and collecting rewards.
What
We are under tracking errors on frontend. In about 70% of our promises that have a catch callback, we throw the error away. This blocks our ability to observe breaking points on the codebase.
For example, this MR fixes an error caused by bad rendering a successful backend response. It generates an alert, but since there's now logging it makes it difficult to debug and see what the issue was.
What's the size of the problem?
It is hard to understand the impact of the problem, but whenever we see a catch without the error parameter we know that the error parameter is not processed in any way, so no logging. With a quick search we see that we have 251 cases of catches without any parameter, and only 100 cases where it does do anything with it:
grep -r 'catch(() => ' app/assets/javascripts | wc -l
> 251
grep -r 'catch((error) => ' app/assets/javascripts | wc -l
> 100
This is pretty alarming, we are silently disposing of errors in over 70% of promises. Note that this is a lower bound, as the query only check if the error is handled somehow, not that it is actually logged.
Possible solutions
With a helper
-
We can add the following helper:
//error_logger_helper.js import * as Sentry from '@sentry/browser'; export log_error = (error) => { Sentry.captureException(error); throw error; }
-
Replace all instances of
catch(() =>
bycatch(logError).catch(() =>
- Straightforward
- Changes are additive, since it doesn't change current implementation
- Implementing a linter is harder, since we still have the catch with empty parameters
- Does not fix cases where error parameter is used, but not logged
By changing the callbacks
The other option is to add error tracking to all existing callbacks that do not handle it. This is a little harder to automate, require some coordination, but the advantage here is that we can add a linter and block any new addition of catches with empty callback. Similarly to the previous option, it does not fix cases where callbacks do receive the error parameter but do not log it.
Other alternatives
Axios interceptor
First idea was to use an axios interceptor. However, the errors we are throwing away are not in the request, but in the processing of the request, so it won't work
Affected files
Here's a list of all files where errors are not being logged:
assets/javascripts/protected_branches/protected_branch_edit.js
assets/javascripts/protected_branches/protected_branch_edit.js
assets/javascripts/ide/stores/plugins/terminal_sync.js
assets/javascripts/ide/stores/actions/merge_request.js
assets/javascripts/ide/stores/actions/merge_request.js
assets/javascripts/ide/stores/actions/merge_request.js
assets/javascripts/ide/stores/actions/project.js
assets/javascripts/ide/stores/actions/file.js
assets/javascripts/ide/stores/modules/pipelines/actions.js
assets/javascripts/ide/stores/modules/pipelines/actions.js
assets/javascripts/ide/stores/modules/file_templates/actions.js
assets/javascripts/ide/stores/modules/file_templates/actions.js
assets/javascripts/ide/stores/modules/merge_requests/actions.js
assets/javascripts/ide/stores/modules/branches/actions.js
assets/javascripts/ide/components/error_message.vue
assets/javascripts/sidebar/sidebar_mediator.js
assets/javascripts/sidebar/components/move/issuable_move_dropdown.vue
assets/javascripts/sidebar/components/move/move_issues_button.vue
assets/javascripts/sidebar/components/todo_toggle/sidebar_todo_widget.vue
assets/javascripts/sidebar/components/assignees/sidebar_assignees_widget.vue
assets/javascripts/sidebar/components/assignees/sidebar_assignees.vue
assets/javascripts/sidebar/components/reviewers/sidebar_reviewers.vue
assets/javascripts/sidebar/components/date/sidebar_date_widget.vue
assets/javascripts/sidebar/components/confidential/sidebar_confidentiality_form.vue
assets/javascripts/sidebar/components/labels/labels_select_vue/store/actions.js
assets/javascripts/sidebar/components/labels/labels_select_vue/store/actions.js
assets/javascripts/sidebar/components/lock/issuable_lock_form.vue
assets/javascripts/sidebar/components/lock/edit_form_buttons.vue
assets/javascripts/sidebar/components/subscriptions/sidebar_subscriptions_widget.vue
assets/javascripts/alerts_settings/components/alerts_settings_wrapper.vue
assets/javascripts/alerts_settings/components/alerts_settings_wrapper.vue
assets/javascripts/alerts_settings/components/alerts_settings_wrapper.vue
assets/javascripts/alerts_settings/components/alerts_settings_wrapper.vue
assets/javascripts/alerts_settings/components/alerts_settings_wrapper.vue
assets/javascripts/alerts_settings/components/alerts_form.vue
assets/javascripts/terraform/components/states_table_actions.vue
assets/javascripts/feature_flags/components/new_environments_dropdown.vue
assets/javascripts/feature_flags/components/environments_dropdown.vue
assets/javascripts/feature_flags/store/edit/actions.js
assets/javascripts/feature_flags/store/index/actions.js
assets/javascripts/feature_flags/store/index/actions.js
assets/javascripts/feature_flags/store/index/actions.js
assets/javascripts/custom_metrics/components/custom_metrics_form_fields.vue
assets/javascripts/code_navigation/store/actions.js
assets/javascripts/artifacts/components/artifacts_table_row_details.vue
assets/javascripts/repository/commits_service.js
assets/javascripts/repository/components/new_directory_modal.vue
assets/javascripts/repository/components/blob_content_viewer.vue
assets/javascripts/repository/components/upload_blob_modal.vue
assets/javascripts/repository/components/tree_content.vue
assets/javascripts/incidents_settings/components/pagerduty_form.vue
assets/javascripts/incidents_settings/components/pagerduty_form.vue
assets/javascripts/content_editor/extensions/paste_markdown.js
assets/javascripts/gfm_auto_complete.js
assets/javascripts/gfm_auto_complete.js
assets/javascripts/gfm_auto_complete.js
assets/javascripts/merge_request_tabs.js
assets/javascripts/merge_request_tabs.js
assets/javascripts/merge_request_tabs.js
assets/javascripts/merge_request.js
assets/javascripts/add_context_commits_modal/store/actions.js
assets/javascripts/add_context_commits_modal/store/actions.js
assets/javascripts/add_context_commits_modal/store/actions.js
assets/javascripts/add_context_commits_modal/store/actions.js
assets/javascripts/invite_members/components/members_token_select.vue
assets/javascripts/invite_members/components/project_select.vue
assets/javascripts/invite_members/components/group_select.vue
assets/javascripts/search_autocomplete.js
assets/javascripts/environments/mixins/environments_mixin.js
assets/javascripts/environments/mixins/environments_mixin.js
assets/javascripts/environments/components/canary_update_modal.vue
assets/javascripts/environments/graphql/resolvers.js
assets/javascripts/environments/graphql/resolvers.js
assets/javascripts/projects/commit/store/actions.js
assets/javascripts/projects/settings/components/access_dropdown.vue
assets/javascripts/projects/settings/access_dropdown.js
assets/javascripts/projects/settings/access_dropdown.js
assets/javascripts/projects/settings_service_desk/components/service_desk_root.vue
assets/javascripts/projects/compare/components/revision_dropdown_legacy.vue
assets/javascripts/projects/compare/components/revision_dropdown.vue
assets/javascripts/projects/compare/components/revision_dropdown.vue
assets/javascripts/projects/commit_box/info/load_branches.js
assets/javascripts/network/branch_graph.js
assets/javascripts/filterable_list.js
assets/javascripts/user_lists/store/show/actions.js
assets/javascripts/user_lists/store/show/actions.js
assets/javascripts/user_lists/store/index/actions.js
assets/javascripts/deploy_freeze/store/actions.js
assets/javascripts/commits.js
assets/javascripts/related_issues/components/related_issues_root.vue
assets/javascripts/related_issues/components/related_issues_root.vue
assets/javascripts/related_issues/components/related_issuable_input.vue
assets/javascripts/deploy_keys/components/app.vue
assets/javascripts/header_search/store/actions.js
assets/javascripts/header.js
assets/javascripts/pipelines/mixins/pipelines_mixin.js
assets/javascripts/pipelines/stores/test_reports/actions.js
assets/javascripts/pipelines/components/pipeline_mini_graph/pipeline_stage.vue
assets/javascripts/pipelines/components/pipelines_list/pipelines_manual_actions.vue
assets/javascripts/pipelines/components/pipelines_list/pipeline_multi_actions.vue
assets/javascripts/pipelines/components/pipelines_list/pipelines.vue
assets/javascripts/groups/settings/components/access_dropdown.vue
assets/javascripts/groups/components/app.vue
assets/javascripts/groups/components/app.vue
assets/javascripts/frequent_items/store/actions.js
assets/javascripts/jira_import/components/jira_import_form.vue
assets/javascripts/jira_import/components/jira_import_form.vue
assets/javascripts/jira_import/components/jira_import_form.vue
assets/javascripts/contributors/components/contributors.vue
assets/javascripts/contributors/components/contributors.vue
assets/javascripts/search/store/actions.js
assets/javascripts/issuable/issuable_bulk_update_actions.js
assets/javascripts/issuable/issuable_bulk_update_sidebar.js
assets/javascripts/filtered_search/visual_token_value.js
assets/javascripts/filtered_search/dropdown_emoji.js
assets/javascripts/filtered_search/filtered_search_manager.js
assets/javascripts/vue_merge_request_widget/mr_widget_options.vue
assets/javascripts/vue_merge_request_widget/mr_widget_options.vue
assets/javascripts/vue_merge_request_widget/stores/artifacts_list/actions.js
assets/javascripts/vue_merge_request_widget/extensions/terraform/index.js
assets/javascripts/vue_merge_request_widget/components/deployment/memory_usage.vue
assets/javascripts/vue_merge_request_widget/components/deployment/deployment_actions.vue
assets/javascripts/vue_merge_request_widget/components/states/mr_widget_closed.vue
assets/javascripts/vue_merge_request_widget/components/states/mr_widget_auto_merge_enabled.vue
assets/javascripts/vue_merge_request_widget/components/states/mr_widget_auto_merge_enabled.vue
assets/javascripts/vue_merge_request_widget/components/states/mr_widget_rebase.vue
assets/javascripts/vue_merge_request_widget/components/states/ready_to_merge.vue
assets/javascripts/vue_merge_request_widget/components/states/ready_to_merge.vue
assets/javascripts/vue_merge_request_widget/components/states/mr_widget_merging.vue
assets/javascripts/vue_merge_request_widget/components/states/mr_widget_merged.vue
assets/javascripts/notes/mixins/discussion_navigation.js
assets/javascripts/notes/mixins/diff_line_note_form.js
assets/javascripts/notes/mixins/diff_line_note_form.js
assets/javascripts/notes/mixins/resolvable.js
assets/javascripts/notes/stores/actions.js
assets/javascripts/notes/stores/actions.js
assets/javascripts/notes/stores/actions.js
assets/javascripts/notes/stores/actions.js
assets/javascripts/notes/stores/actions.js
assets/javascripts/notes/components/noteable_note.vue
assets/javascripts/notes/components/diff_with_note.vue
assets/javascripts/protected_tags/protected_tag_edit.js
assets/javascripts/prometheus_metrics/prometheus_metrics.js
assets/javascripts/design_management/pages/index.vue
assets/javascripts/design_management/pages/index.vue
assets/javascripts/behaviors/markdown/render_math.js
assets/javascripts/behaviors/markdown/copy_as_gfm.js
assets/javascripts/behaviors/markdown/copy_as_gfm.js
assets/javascripts/behaviors/markdown/copy_as_gfm.js
assets/javascripts/behaviors/markdown/render_gfm.js
assets/javascripts/behaviors/shortcuts.js
assets/javascripts/behaviors/index.js
assets/javascripts/behaviors/shortcuts/shortcuts_issuable.js
assets/javascripts/service_ping_consent.js
assets/javascripts/work_items/components/work_item_links/work_item_links_form.vue
assets/javascripts/work_items/components/work_item_links/work_item_links_form.vue
assets/javascripts/boards/stores/actions.js
assets/javascripts/boards/stores/actions.js
assets/javascripts/boards/stores/actions.js
assets/javascripts/boards/stores/actions.js
assets/javascripts/boards/stores/actions.js
assets/javascripts/boards/stores/actions.js
assets/javascripts/boards/stores/actions.js
assets/javascripts/deprecated_notes.js
assets/javascripts/deprecated_notes.js
assets/javascripts/deprecated_notes.js
assets/javascripts/deprecated_notes.js
assets/javascripts/deprecated_notes.js
assets/javascripts/diffs/components/app.vue
assets/javascripts/diffs/components/app.vue
assets/javascripts/diffs/components/diff_file.vue
assets/javascripts/diffs/components/diff_expansion_cell.vue
assets/javascripts/diffs/store/actions.js
assets/javascripts/diffs/store/actions.js
assets/javascripts/diffs/store/actions.js
assets/javascripts/diffs/store/actions.js
assets/javascripts/layout_nav.js
assets/javascripts/import_entities/import_groups/components/import_table.vue
assets/javascripts/jira_connect/subscriptions/components/add_namespace_modal/groups_list.vue
assets/javascripts/jira_connect/subscriptions/pages/sign_in/sign_in_gitlab_multiversion/index.vue
assets/javascripts/lib/utils/rails_ujs.js
assets/javascripts/lib/utils/icon_utils.js
assets/javascripts/lib/utils/apollo_startup_js_link.js
assets/javascripts/persistent_user_callout.js
assets/javascripts/persistent_user_callout.js
assets/javascripts/single_file_diff.js
assets/javascripts/packages_and_registries/harbor_registry/pages/details.vue
assets/javascripts/packages_and_registries/harbor_registry/pages/harbor_tags.vue
assets/javascripts/packages_and_registries/harbor_registry/pages/list.vue
assets/javascripts/packages_and_registries/settings/project/components/packages_cleanup_policy_form.vue
assets/javascripts/packages_and_registries/infrastructure_registry/details/store/actions.js
assets/javascripts/packages_and_registries/infrastructure_registry/details/store/actions.js
assets/javascripts/packages_and_registries/infrastructure_registry/details/store/actions.js
assets/javascripts/packages_and_registries/infrastructure_registry/list/stores/actions.js
assets/javascripts/packages_and_registries/infrastructure_registry/list/stores/actions.js
assets/javascripts/members/components/table/expiration_datepicker.vue
assets/javascripts/members/components/table/expiration_datepicker.vue
assets/javascripts/jobs/store/actions.js
assets/javascripts/jobs/store/actions.js
assets/javascripts/monitoring/stores/actions.js
assets/javascripts/monitoring/stores/actions.js
assets/javascripts/monitoring/stores/actions.js
assets/javascripts/monitoring/components/charts/column.vue
assets/javascripts/vue_shared/components/diff_viewer/viewers/renamed.vue
assets/javascripts/vue_shared/components/markdown/header.vue
assets/javascripts/vue_shared/components/content_viewer/viewers/markdown_viewer.vue
assets/javascripts/vue_shared/components/filtered_search_bar/filtered_search_bar_root.vue
assets/javascripts/vue_shared/components/filtered_search_bar/tokens/emoji_token.vue
assets/javascripts/vue_shared/components/filtered_search_bar/tokens/release_token.vue
assets/javascripts/vue_shared/components/filtered_search_bar/tokens/milestone_token.vue
assets/javascripts/vue_shared/components/filtered_search_bar/tokens/branch_token.vue
assets/javascripts/vue_shared/security_reports/store/modules/secret_detection/actions.js
assets/javascripts/vue_shared/security_reports/store/modules/sast/actions.js
assets/javascripts/vue_shared/alert_details/components/sidebar/sidebar_todo.vue
assets/javascripts/vue_shared/alert_details/components/sidebar/sidebar_todo.vue
assets/javascripts/vue_shared/alert_details/components/sidebar/sidebar_assignees.vue
assets/javascripts/vue_shared/alert_details/components/sidebar/sidebar_assignees.vue
assets/javascripts/vue_shared/alert_details/components/alert_status.vue
assets/javascripts/issues/manual_ordering.js
assets/javascripts/issues/show/components/app.vue
assets/javascripts/issues/show/components/app.vue
assets/javascripts/issues/show/components/header_actions.vue
assets/javascripts/issues/show/components/header_actions.vue
assets/javascripts/issues/index.js
assets/javascripts/issues/create_merge_request_dropdown.js
assets/javascripts/issues/create_merge_request_dropdown.js
assets/javascripts/issues/related_merge_requests/store/actions.js
assets/javascripts/contextual_sidebar.js
assets/javascripts/contextual_sidebar.js
assets/javascripts/pages/projects/commit/show/index.js
assets/javascripts/pages/projects/show/index.js
assets/javascripts/pages/projects/show/index.js
assets/javascripts/pages/projects/show/index.js
assets/javascripts/pages/projects/show/index.js
assets/javascripts/pages/projects/graphs/components/code_coverage.vue
assets/javascripts/pages/admin/application_settings/payload_downloader.js
assets/javascripts/pages/admin/application_settings/payload_previewer.js
assets/javascripts/pages/dashboard/todos/index/todos.js
assets/javascripts/pages/users/user_tabs.js
assets/javascripts/pages/users/user_tabs.js
assets/javascripts/pages/users/user_overview_block.js
assets/javascripts/pages/users/show/index.js
assets/javascripts/blob/sketch/index.js
assets/javascripts/batch_comments/stores/modules/batch_comments/actions.js
assets/javascripts/batch_comments/stores/modules/batch_comments/actions.js
assets/javascripts/batch_comments/stores/modules/batch_comments/actions.js
assets/javascripts/editor/extensions/source_editor_markdown_livepreview_ext.js
assets/javascripts/error_tracking_settings/store/actions.js
assets/javascripts/analytics/shared/utils.js
assets/javascripts/analytics/cycle_analytics/store/actions.js
assets/javascripts/merge_conflicts/components/diff_file_editor.vue