По-добър начин за обработка на грешки в инструмента за обработка на заявки за торнадо

Има два подобни манипулатора: AgeHandler1 и AgeHandler2. В първия просто повдигаме конкретно изключение, за да върнем съобщение за грешка, във втория - ръчно връщаме съобщение за грешка. Какво мислите за тези два метода? Кой метод е за предпочитане за голям проект? Някакви други най-добри практики?

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()

Отговори:

"""
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>
"""

person nanvel    schedule 14.10.2014    source източник


Отговори (3)


Като цяло най-добрият подход е да се замени RequestHandler.write_error. Това е подобно на първия ви подход, но нямате нужда от опита/освен в тялото на манипулатора, защото Tornado ще се справи с това вместо вас.

Изричните тестове като тези във втория ви пример също са добри, но е непрактично да улавяте всички възможни грешки по този начин, така че винаги ще имате нужда от нещо, което да обработва неуловени изключения.

person Ben Darnell    schedule 15.10.2014

Презаписването на write_error работи много добре. Това, което правя в проектите си, е, че се опитвам да уловя всички 500 кодове за състояние. След това ги изпращам на себе си през застой (трафикът ми е достатъчно нисък, че честотата е много ниска).

Ето кода за извличане на чиста следа на стека от write_error. Имайте предвид, че в този пример аз също изваждам всички препратки към „gen.py“, „concurrent.py“ или „web.py“, което прави много по-чисти следи на стека.

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... e.g., 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")  

Резултатът изглежда така:

  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.
person Mike N    schedule 21.03.2018

За големи проекти бих се опитал да се абстрахирам от номерата на грешките, особено защото дефиницията на HTTP кодовете за състояние не е във вашия обхват. Доколкото си спомням, има поне една двойка кодове за състояние с проблемна семантика. Не помня кои къде са.

Но за по-голям проект бих препоръчал да дефинирате свои собствени категории грешки, които искате да поддържате, и да картографирате тези категории към HTTP кодове централно, както ви е необходимо. Когато по-късно разберете, че трябва да използвате различен код на състояние за дадена категория грешка, можете да го направите централно.

Логично бих се опитал да отделя възможно най-много знания от конкретната рутинна работа. Моделът на изключение, разбира се, е полезен тук, но подобно може да се постигне с извикване на функция за обработка на грешки като:

...
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.')
...
person Juergen    schedule 15.10.2014