Skip to content

[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

  1. 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;
    }
  2. Replace all instances of catch(() => by catch(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
Edited by 🤖 GitLab Bot 🤖