Skip to content

Commit ddba0dc

Browse files
committed
Merge branch 'feature/exception-metrics' into develop
2 parents b607e50 + 25f8f39 commit ddba0dc

File tree

23 files changed

+638
-138
lines changed

23 files changed

+638
-138
lines changed

mfr/core/exceptions.py

Lines changed: 177 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,57 +1,211 @@
11
import waterbutler.core.exceptions
22

3+
from mfr import settings
4+
35

46
class PluginError(waterbutler.core.exceptions.PluginError):
5-
"""The MFR related errors raised from a plugin
6-
should inherit from PluginError
7+
"""The MFR related errors raised from a plugin should inherit from PluginError
78
"""
89

10+
__TYPE = 'plugin'
11+
12+
def __init__(self, message, *args, code=500, **kwargs):
13+
super().__init__(message, code)
14+
self.attr_stack = [
15+
['error', {'message': self.message, 'code': self.code}],
16+
[self.__TYPE, {}],
17+
]
18+
919
def as_html(self):
1020
return '''
1121
<link rel="stylesheet" href="/static/css/bootstrap.min.css">
1222
<div class="alert alert-warning" role="alert">{}</div>
13-
<div style="display: none;">This text and the text below is only presented because IE consumes error messages below 512 bytes</div>
14-
<div style="display: none;">Want to help save science? Want to get paid to develop free, open source software? Check out our openings!</div>
15-
'''.format(self.message)
23+
<div style="display: none;">This text and the text below is only presented because
24+
IE consumes error messages below 512 bytes</div>
25+
<div style="display: none;">Want to help save science? Want to get paid to develop
26+
free, open source software? Check out our openings!</div>
27+
'''.format(self.message)
28+
29+
def _format_original_exception(self, exc):
30+
"""Sometimes we catch an error from an external library, but would like to throw our own
31+
error instead. This method will take in an external error class and format it for
32+
consistent representation in the error metrics.
33+
"""
34+
formatted_exc = {'class': '', 'message': ''}
35+
if exc is not None:
36+
formatted_exc['class'] = exc.__class__.__name__
37+
formatted_exc['message'] = str(exc)
38+
return formatted_exc
1639

1740

1841
class ExtensionError(PluginError):
19-
"""The MFR related errors raised
20-
from a :class:`mfr.core.extension` should
21-
inherit from ExtensionError
42+
"""The MFR related errors raised from a :class:`mfr.core.extension` should inherit from
43+
ExtensionError
2244
"""
2345

46+
__TYPE = 'extension'
47+
48+
def __init__(self, message, *args, extension: str='', **kwargs):
49+
super().__init__(message, *args, **kwargs)
50+
self.extension = extension
51+
self.attr_stack.append([self.__TYPE, {'extension': self.extension}])
52+
2453

2554
class RendererError(ExtensionError):
26-
"""The MFR related errors raised
27-
from a :class:`mfr.core.extension` and relating
28-
to rendering should inherit from RendererError
55+
"""The MFR related errors raised from a :class:`mfr.core.extension` and relating to rendering
56+
should inherit from RendererError
2957
"""
3058

59+
__TYPE = 'renderer'
60+
61+
def __init__(self, message, *args, renderer_class: str='', **kwargs):
62+
super().__init__(message, *args, **kwargs)
63+
self.renderer_class = renderer_class
64+
self.attr_stack.append([self.__TYPE, {'class': self.renderer_class}])
65+
3166

3267
class ExporterError(ExtensionError):
33-
"""The MFR related errors raised
34-
from a :class:`mfr.core.extension` and relating
35-
to exporting should inherit from ExporterError
68+
"""The MFR related errors raised from a :class:`mfr.core.extension` and relating to exporting
69+
should inherit from ExporterError
3670
"""
3771

72+
__TYPE = 'exporter'
73+
74+
def __init__(self, message, *args, exporter_class: str='', **kwargs):
75+
super().__init__(message, *args, **kwargs)
76+
self.exporter_class = exporter_class
77+
self.attr_stack.append([self.__TYPE, {'exporter_class': self.exporter_class}])
78+
79+
80+
class SubprocessError(ExporterError):
81+
"""The MFR related errors raised from a :class:`mfr.core.extension` and relating to subprocess
82+
should inherit from SubprocessError
83+
"""
84+
85+
__TYPE = 'subprocess'
86+
87+
def __init__(self, message, *args, code: int=500, process: str='', cmd: str='',
88+
returncode: int=None, path: str='', **kwargs):
89+
super().__init__(message, *args, code=code, **kwargs)
90+
self.process = process
91+
self.cmd = cmd
92+
self.return_code = returncode
93+
self.path = path
94+
self.attr_stack.append([self.__TYPE, {
95+
'process': self.process,
96+
'cmd': self.cmd,
97+
'returncode': self.return_code,
98+
'path': self.path,
99+
}])
100+
38101

39102
class ProviderError(PluginError):
40-
"""The MFR related errors raised
41-
from a :class:`mfr.core.provider` should
42-
inherit from ProviderError
103+
"""The MFR related errors raised from a :class:`mfr.core.provider` should inherit from
104+
ProviderError
43105
"""
44106

107+
__TYPE = 'provider'
108+
109+
def __init__(self, message, *args, provider: str='', **kwargs):
110+
super().__init__(message, *args, **kwargs)
111+
self.provider = provider
112+
self.attr_stack.append([self.__TYPE, {'provider': self.provider}])
113+
45114

46115
class DownloadError(ProviderError):
47-
"""The MFR related errors raised
48-
from a :class:`mfr.core.provider` and relating
49-
to downloads should inherit from DownloadError
116+
"""The MFR related errors raised from a :class:`mfr.core.provider` and relating to downloads
117+
should inherit from DownloadError
50118
"""
51119

120+
__TYPE = 'download'
121+
122+
def __init__(self, message, *args, download_url: str='', response: str='', **kwargs):
123+
super().__init__(message, *args, **kwargs)
124+
self.download_url = download_url
125+
self.response = response
126+
self.attr_stack.append([self.__TYPE, {
127+
'download_url': self.download_url,
128+
'response': self.response
129+
}])
130+
52131

53132
class MetadataError(ProviderError):
54-
"""The MFR related errors raised
55-
from a :class:`mfr.core.provider` and relating
56-
to metadata should inherit from MetadataError
133+
"""The MFR related errors raised from a :class:`mfr.core.provider` and relating to metadata
134+
should inherit from MetadataError
57135
"""
136+
137+
__TYPE = 'metadata'
138+
139+
def __init__(self, message, *args, metadata_url: str='', response: str='', **kwargs):
140+
super().__init__(message, *args, **kwargs)
141+
self.metadata_url = metadata_url
142+
self.response = response
143+
self.attr_stack.append([self.__TYPE, {
144+
'metadata_url': self.metadata_url,
145+
'response': self.response
146+
}])
147+
148+
149+
class DriverManagerError(PluginError):
150+
151+
__TYPE = 'drivermanager'
152+
153+
def __init__(self, message, *args, namespace: str='', name: str='', invoke_on_load: bool=None,
154+
invoke_args: dict=None, **kwargs):
155+
super().__init__(message, *args, **kwargs)
156+
157+
self.namespace = namespace
158+
self.name = name
159+
self.invoke_on_load = invoke_on_load
160+
self.invoke_args = invoke_args or {}
161+
162+
self.attr_stack.append([self.__TYPE, {
163+
'namespace': self.namespace,
164+
'name': self.name,
165+
'invoke_on_load': self.invoke_on_load,
166+
'invoke_args': self.invoke_args,
167+
}])
168+
169+
170+
class MakeProviderError(DriverManagerError):
171+
"""Thrown when MFR can't find an applicable provider class. This indicates programmer error,
172+
so ``code`` defaults to ``500``."""
173+
174+
def __init__(self, message, *args, code: int=500, **kwargs):
175+
super().__init__(message, *args, code=code, **kwargs)
176+
177+
178+
class UnsupportedExtensionError(DriverManagerError):
179+
"""When make_renderer and make_exporter fail, it's usually because MFR doesn't support that
180+
extension yet. This error inherits from DriverManagerError (since it's the DriverManager that
181+
trips this) and includes a handler_type argsument
182+
"""
183+
184+
__TYPE = 'unsupported_extension'
185+
186+
def __init__(self, *args, code: int=400, handler_type: str='', **kwargs):
187+
super().__init__(*args, code=code, **kwargs)
188+
189+
self.handler_type = handler_type
190+
191+
self.attr_stack.append([self.__TYPE, {'handler_type': self.handler_type}])
192+
193+
194+
class MakeRendererError(UnsupportedExtensionError):
195+
"""The MFR related errors raised from a :def:`mfr.core.utils.make_renderer` should inherit from
196+
MakeRendererError
197+
"""
198+
199+
def __init__(self, *args, **kwargs):
200+
super().__init__(settings.UNSUPPORTED_RENDER_MSG, *args, handler_type='renderer',
201+
**kwargs)
202+
203+
204+
class MakeExporterError(UnsupportedExtensionError):
205+
"""The MFR related errors raised from a :def:`mfr.core.utils.make_exporter` should inherit from
206+
MakeExporterError
207+
"""
208+
209+
def __init__(self, *args, **kwargs):
210+
super().__init__(settings.UNSUPPORTED_EXPORTER_MSG, *args, handler_type='exporter',
211+
**kwargs)

mfr/core/remote_logging.py

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
logger = logging.getLogger(__name__)
1616

1717

18-
async def log_analytics(request, metrics):
18+
async def log_analytics(request, metrics, is_error=False):
1919
"""Send events to Keen describing the action that occurred."""
2020
if settings.KEEN_PRIVATE_PROJECT_ID is None:
2121
return
@@ -70,21 +70,22 @@ async def log_analytics(request, metrics):
7070
'output': 'referrer.info',
7171
})
7272

73-
# maassage file data, if available
73+
# massage file data, if available
7474
file_metadata = None
7575
try:
7676
file_metadata = metrics['provider']['provider_osf']['metadata']['raw']['data']
77-
except KeyError:
77+
except (KeyError, TypeError):
7878
pass
7979
else:
8080
_munge_file_metadata(file_metadata)
8181

8282
# send the private payload
83-
await _send_to_keen(keen_payload, 'mfr_action', settings.KEEN_PRIVATE_PROJECT_ID,
83+
private_collection = 'mfr_errors' if is_error else 'mfr_action'
84+
await _send_to_keen(keen_payload, private_collection, settings.KEEN_PRIVATE_PROJECT_ID,
8485
settings.KEEN_PRIVATE_WRITE_KEY, keen_payload['handler']['type'],
8586
domain='private')
8687

87-
if keen_payload['handler']['type'] != 'render' or file_metadata is None:
88+
if keen_payload['handler']['type'] != 'render' or file_metadata is None or is_error:
8889
return
8990

9091
# build and ship the public file stats payload

mfr/core/utils.py

Lines changed: 45 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,35 @@
11
from stevedore import driver
22

3-
from mfr import settings
43
from mfr.core import exceptions
54

65

76
def make_provider(name, request, url):
87
"""Returns an instance of :class:`mfr.core.provider.BaseProvider`
98
109
:param str name: The name of the provider to instantiate. (osf)
10+
:param request:
1111
:param dict url:
1212
1313
:rtype: :class:`mfr.core.provider.BaseProvider`
1414
"""
15-
manager = driver.DriverManager(
16-
namespace='mfr.providers',
17-
name=name.lower(),
18-
invoke_on_load=True,
19-
invoke_args=(request, url, ),
20-
)
21-
return manager.driver
15+
try:
16+
return driver.DriverManager(
17+
namespace='mfr.providers',
18+
name=name.lower(),
19+
invoke_on_load=True,
20+
invoke_args=(request, url, ),
21+
).driver
22+
except RuntimeError:
23+
raise exceptions.MakeProviderError(
24+
'"{}" is not a supported provider'.format(name.lower()),
25+
namespace='mfr.providers',
26+
name=name.lower(),
27+
invoke_on_load=True,
28+
invoke_args={
29+
'request': request,
30+
'url': url,
31+
}
32+
)
2233

2334

2435
def make_exporter(name, source_file_path, output_file_path, format):
@@ -31,15 +42,25 @@ def make_exporter(name, source_file_path, output_file_path, format):
3142
3243
:rtype: :class:`mfr.core.extension.BaseExporter`
3344
"""
45+
normalized_name = (name and name.lower()) or 'none'
3446
try:
3547
return driver.DriverManager(
3648
namespace='mfr.exporters',
37-
name=(name and name.lower()) or 'none',
49+
name=normalized_name,
3850
invoke_on_load=True,
3951
invoke_args=(source_file_path, output_file_path, format),
4052
).driver
4153
except RuntimeError:
42-
raise exceptions.RendererError(settings.UNSUPPORTED_EXPORTER_MSG, code=400)
54+
raise exceptions.MakeExporterError(
55+
namespace='mfr.exporters',
56+
name=normalized_name,
57+
invoke_on_load=True,
58+
invoke_args={
59+
'source_file_path': source_file_path,
60+
'output_file_path': output_file_path,
61+
'format': format,
62+
}
63+
)
4364

4465

4566
def make_renderer(name, metadata, file_path, url, assets_url, export_url):
@@ -54,12 +75,24 @@ def make_renderer(name, metadata, file_path, url, assets_url, export_url):
5475
5576
:rtype: :class:`mfr.core.extension.BaseRenderer`
5677
"""
78+
normalized_name = (name and name.lower()) or 'none'
5779
try:
5880
return driver.DriverManager(
5981
namespace='mfr.renderers',
60-
name=(name and name.lower()) or 'none',
82+
name=normalized_name,
6183
invoke_on_load=True,
6284
invoke_args=(metadata, file_path, url, assets_url, export_url),
6385
).driver
6486
except RuntimeError:
65-
raise exceptions.RendererError(settings.UNSUPPORTED_RENDER_MSG, code=400)
87+
raise exceptions.MakeRendererError(
88+
namespace='mfr.renderers',
89+
name=normalized_name,
90+
invoke_on_load=True,
91+
invoke_args={
92+
'metadata': metadata.serialize(),
93+
'file_path': file_path,
94+
'url': url,
95+
'assets_url': assets_url,
96+
'export_url': export_url,
97+
}
98+
)

0 commit comments

Comments
 (0)