Skip to content

Improve error reporting to users #3247

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 16 commits into from
Nov 14, 2017
Merged
Show file tree
Hide file tree
Changes from 15 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
6 changes: 5 additions & 1 deletion readthedocs/doc_builder/environments.py
Original file line number Diff line number Diff line change
Expand Up @@ -421,7 +421,11 @@ def update_build(self, state=None):
self.build['error'] = str(self.failure)
else:
self.build['error'] = ugettext_noop(
"An unexpected error occurred")
"A failure in our code has occured. "
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Nitpicks because apparently this is very commonly seen copy:

  • "failure" -> "error": failure is a state we apply, error is what we hit
  • "in our code" is very specific, where this can be any number of things we did. something more generic would be good here

Others:

"There was a problem with Read the Docs while building your documentation"
"We encountered an error with Read the Docs while building your documentation"

"Please report this to us with your build id ({}).".format(
self.build['pk']
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Not sure best way to handle this. Interpolation on localized strings should happen outside gettext. The interpolated variable should be named as well.

)
)

# Attempt to stop unicode errors on build reporting
for key, val in list(self.build.items()):
Expand Down
59 changes: 43 additions & 16 deletions readthedocs/projects/tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -108,20 +108,51 @@ def _log(self, msg):
version=self.version.slug,
msg=msg))

# pylint: disable=arguments-differ
def run(self, pk, version_pk=None, build_pk=None, record=True,
docker=False, search=True, force=False, localmedia=True, **__):
# pylint: disable=arguments-differ
self.project = self.get_project(pk)
self.version = self.get_version(self.project, version_pk)
self.build = self.get_build(build_pk)
self.build_search = search
self.build_localmedia = localmedia
self.build_force = force
self.config = None
"""
Run a documentation build.

setup_successful = self.run_setup(record=record)
if setup_successful:
self.run_build(record=record, docker=docker)
This is fully wrapped in exception handling to account for a number of failure cases.
"""
try:
self.project = self.get_project(pk)
self.version = self.get_version(self.project, version_pk)
self.build = self.get_build(build_pk)
self.build_search = search
self.build_localmedia = localmedia
self.build_force = force
self.config = None

setup_successful = self.run_setup(record=record)
if setup_successful:
self.run_build(record=record, docker=docker)
failure = self.setup_env.failure or self.build_env.failure
except Exception as e: # noqa
log.exception('Top-level build exception has been raised', extra={'build': build_pk})
failure = str(e)

# **Always** report build status.
# This can still fail if the API Is totally down, but should catch more failures
result = {}
error = 'Unknown error. Please include the build id ({}) in any bug reports.'.format(
Copy link
Contributor Author

Choose a reason for hiding this comment

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

  • "Unknown error" -> "Unknown error encountered"
  • Needs localization
  • Named variable for interpolation

build_pk
)
if hasattr(self, 'build'):
self.build['state'] = BUILD_STATE_FINISHED
if failure:
self.build['error'] = error
self.build['success'] = False
result = api_v2.build(build_pk).patch(self.build)
else:
build_updates = {
'state': BUILD_STATE_FINISHED,
'success': False,
'error': error,
}
result = api_v2.build(build_pk).patch(build_updates)
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Reduce duplicated code to:

if hasattr(self, 'build'):
    build_updates = {...}
else:
    build_updates = {...}
return api_v2.build(build_pk).patch(build_updates)

return result

def run_setup(self, record=True):
"""Run setup in the local environment.
Expand Down Expand Up @@ -166,7 +197,6 @@ def run_setup(self, record=True):
if not isinstance(self.setup_env.failure, vcs_support_utils.LockTimeout):
self.send_notifications()

self.setup_env.update_build(state=BUILD_STATE_FINISHED)
return False

if self.setup_env.successful and not self.project.has_valid_clone:
Expand Down Expand Up @@ -230,14 +260,11 @@ def run_build(self, docker=False, record=True):
self.send_notifications()
build_complete.send(sender=Build, build=self.build_env.build)

self.build_env.update_build(state=BUILD_STATE_FINISHED)

@staticmethod
def get_project(project_pk):
"""Get project from API"""
project_data = api_v2.project(project_pk).get()
project = APIProject(**project_data)
return project
return APIProject(**project_data)

@staticmethod
def get_version(project, version_pk):
Expand Down
6 changes: 4 additions & 2 deletions readthedocs/restapi/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
import logging

from slumber import API, serialize
from requests import Session
import requests
from django.conf import settings
from rest_framework.renderers import JSONRenderer
from rest_framework.parsers import JSONParser
Expand Down Expand Up @@ -32,8 +32,10 @@ def dumps(self, data):


def setup_api():
session = Session()
session = requests.Session()
session.headers.update({'Host': PRODUCTION_DOMAIN})
retry_adapter = requests.adapters.HTTPAdapter(max_retries=3)
session.mount(API_HOST, retry_adapter)
api_config = {
'base_url': '%s/api/v2/' % API_HOST,
'serializer': serialize.Serializer(
Expand Down
25 changes: 11 additions & 14 deletions readthedocs/rtd_tests/tests/test_celery.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,10 +64,9 @@ def test_clear_artifacts(self):
self.assertTrue(result.successful())
self.assertFalse(exists(directory))

@patch('readthedocs.projects.tasks.UpdateDocsTask.build_docs',
new=MagicMock)
@patch('readthedocs.projects.tasks.UpdateDocsTask.setup_vcs',
new=MagicMock)
@patch('readthedocs.projects.tasks.UpdateDocsTask.setup_environment', new=MagicMock)
@patch('readthedocs.projects.tasks.UpdateDocsTask.build_docs', new=MagicMock)
@patch('readthedocs.projects.tasks.UpdateDocsTask.setup_vcs', new=MagicMock)
def test_update_docs(self):
build = get(Build, project=self.project,
version=self.project.versions.first())
Expand All @@ -80,11 +79,11 @@ def test_update_docs(self):
intersphinx=False)
self.assertTrue(result.successful())

@patch('readthedocs.projects.tasks.UpdateDocsTask.build_docs',
new=MagicMock)
@patch('readthedocs.projects.tasks.UpdateDocsTask.setup_environment', new=MagicMock)
@patch('readthedocs.projects.tasks.UpdateDocsTask.build_docs', new=MagicMock)
@patch('readthedocs.doc_builder.environments.BuildEnvironment.update_build', new=MagicMock)
@patch('readthedocs.projects.tasks.UpdateDocsTask.setup_vcs')
@patch('readthedocs.doc_builder.environments.BuildEnvironment.update_build')
def test_update_docs_unexpected_setup_exception(self, mock_update_build, mock_setup_vcs):
def test_update_docs_unexpected_setup_exception(self, mock_setup_vcs):
exc = Exception()
mock_setup_vcs.side_effect = exc
build = get(Build, project=self.project,
Expand All @@ -97,13 +96,12 @@ def test_update_docs_unexpected_setup_exception(self, mock_update_build, mock_se
record=False,
intersphinx=False)
self.assertTrue(result.successful())
mock_update_build.assert_called_once_with(state=BUILD_STATE_FINISHED)

@patch('readthedocs.projects.tasks.UpdateDocsTask.setup_environment', new=MagicMock)
@patch('readthedocs.projects.tasks.UpdateDocsTask.setup_vcs', new=MagicMock)
@patch('readthedocs.doc_builder.environments.BuildEnvironment.update_build', new=MagicMock)
@patch('readthedocs.projects.tasks.UpdateDocsTask.build_docs')
@patch('readthedocs.projects.tasks.UpdateDocsTask.setup_vcs',
new=MagicMock)
@patch('readthedocs.doc_builder.environments.BuildEnvironment.update_build')
def test_update_docs_unexpected_build_exception(self, mock_update_build, mock_build_docs):
def test_update_docs_unexpected_build_exception(self, mock_build_docs):
exc = Exception()
mock_build_docs.side_effect = exc
build = get(Build, project=self.project,
Expand All @@ -116,7 +114,6 @@ def test_update_docs_unexpected_build_exception(self, mock_update_build, mock_bu
record=False,
intersphinx=False)
self.assertTrue(result.successful())
mock_update_build.assert_called_with(state=BUILD_STATE_FINISHED)

def test_update_imported_doc(self):
with mock_api(self.repo):
Expand Down
9 changes: 4 additions & 5 deletions readthedocs/settings/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -355,15 +355,14 @@ def INSTALLED_APPS(self): # noqa
},
},
'loggers': {
'readthedocs': {
'': { # root logger
'handlers': ['debug', 'console'],
'level': 'DEBUG',
'propagate': True,
'level': 'DEBUG', # Always send from the root, handlers can filter levels
},
'': {
'readthedocs': {
'handlers': ['debug', 'console'],
'level': 'DEBUG',
'propagate': True,
'propagate': False, # Don't double log at the root logger for these.
},
},
}
2 changes: 0 additions & 2 deletions readthedocs/settings/dev.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,8 +56,6 @@ def DATABASES(self): # noqa
def LOGGING(self): # noqa - avoid pep8 N802
logging = super(CommunityDevSettings, self).LOGGING
logging['formatters']['default']['format'] = '[%(asctime)s] ' + self.LOG_FORMAT
# Remove double logging
logging['loggers']['']['handlers'] = []
return logging


Expand Down