The best way to handle errors in a tornado request handler

There are two similar handlers: AgeHandler1 and AgeHandler2. In the first, we simply raise a specific exception to return an error message, in the second, we manually return an error message. What do you think of these two methods? Which method is preferable for a large project? Any other recommendations?

import logging import os.path import traceback from sys import exc_info from tornado import web, options, ioloop logger = logging.getLogger(__name__) class MyAppException(Exception): def __init__(self, message, code=400, *args, **kwargs): self.message = message self.code = code return super(MyAppException, self).__init__(*args, **kwargs) def __str__(self): return self.message class MyAppBaseHandler(web.RequestHandler): def handle_exception(self, e): exc_type, exc_obj, exc_tb = exc_info() logger.error(''.join([line for line in traceback.format_exception( exc_type, exc_obj, exc_tb)])) if isinstance(exc_obj, MyAppException): self.set_status(exc_obj.code) self.write({'error': { 'message': u'{exc_obj}'.format(exc_obj=exc_obj.message)}}) else: self.set_status(500) fname = os.path.split(exc_tb.tb_frame.f_code.co_filename)[1] self.write({'error': { 'message': u'{exc_obj} in {fname} at {line}'.format( exc_obj=exc_obj, fname=fname, line=exc_tb.tb_lineno)}}) class AgeHandler1(MyAppBaseHandler): def get(self): try: age = self.get_argument('age') age = int(age) if age < 1 or age > 200: raise MyAppException('Wrong age value.') self.write('Your age is {age}'.format(age=age)) except Exception as e: self.handle_exception(e) class AgeHandler2(MyAppBaseHandler): def get(self): age = self.get_argument('age') age = int(age) if age < 1 or age > 200: self.set_status(400) self.write('Wrong age value.') return self.write('Your age is {age}'.format(age=age)) class MyApplication(web.Application): def __init__(self, **kwargs): kwargs['handlers'] = [ web.url(r'/age1', AgeHandler1, name='age1'), web.url(r'/age2', AgeHandler2, name='age2'), ] kwargs['debug'] = False super(MyApplication, self).__init__(**kwargs) if __name__ == '__main__': options.parse_command_line() application = MyApplication() application.listen(5000) ioloop.IOLoop.instance().start() 

Total answers:

 """ http://127.0.0.1:5000/age1 500: {"error": {"message": "HTTP 400: Bad Request (Missing argument age) in app.py at 44"}} --- http://127.0.0.1:5000/age1?age=10 200: Your age is 10 --- http://127.0.0.1:5000/age1?age=201 400: {"error": {"message": "Wrong age value."}} --- http://127.0.0.1:5000/age1?age=abc 500: {"error": {"message": "invalid literal for int() with base 10: 'abc' in app.py at 45"}} http://127.0.0.1:5000/age2 400: <html><title>400: Bad Request</title><body>400: Bad Request</body></html> --- http://127.0.0.1:5000/age2?age=10 200: Your age is 10 --- http://127.0.0.1:5000/age2?age=201 400: Wrong age value. --- http://127.0.0.1:5000/age2?age=abc] 500: <html><title>500: Internal Server Error</title><body>500: Internal Server Error</body></html> """ 
+5
source share
3 answers

In general, a better approach is to override RequestHandler.write_error . This is similar to your first approach, but you do not need try / except in the body of the handler, because Tornado will handle this for you.

Explicit tests, like the ones in your second example, are also good, but it’s impractical to catch all possible errors in such a way that you always need to handle something that is not caught.

+5
source

For large projects, I would try to ignore the error numbers, especially because the definition of HTTP status codes is not in your scope. As far as I remember, there is at least one pair of status codes with problematic semantics. I don’t remember where they are.

But for a larger project, I would recommend that you define your own error categories that you want to support and match these categories by HTTP codes in the center, as needed. When you find out later that you should use a different status code for a certain category of errors, you can do this centrally.

Logically, I will try to share as much knowledge as possible from a specific processing procedure. Of course, the exception model fits here, but similar ones can be achieved when calling the function for error handling, for example:

 ... if age < 1 or age > 200: return self.errorResult('Wrong age value.', WRONG_VALUE) ... 

or

 ... if age < 1 or age > 200: return self.wrongValue('Wrong age value.') ... 
0
source

Overwriting write_error works very well. What I do in my projects, I try to catch any 500 status codes. Then I send them to my slack (my traffic is low enough so that the frequency is very low).

Here is the code to retrieve a clean stack trace from write_error . Note that in this example, I also refer to any links to "gen.py", "concurrent.py" or "web.py", which greatly simplifies stack tracing.

 import tornado.web, traceback, logging class MyRequestHandler(tornado.web.RequestHandler): def write_error(self,status_code,**kwargs): if status_code == 500: excp = kwargs['exc_info'][1] tb = kwargs['exc_info'][2] stack = traceback.extract_tb(tb) clean_stack = [i for i in stack if i[0][-6:] != 'gen.py' and i[0][-13:] != 'concurrent.py'] error_msg = '{}\n Exception: {}'.format(''.join(traceback.format_list(clean_stack)),excp) # do something with this error now... eg, send it to yourself # on slack, or log it. logging.error(error_msg) # do something with your error... # don't forget to show a user friendly error page! self.render("oops.html") 

The result is as follows:

  File "app.py", line 55, in get assert 1==2,"A fake error to trigger a critical alert." Exception: A fake error to trigger a critical alert. 
0
source

Source: https://habr.com/ru/post/1204678/


All Articles