更好的方法来处理龙卷风请求处理程序中的错误

时间:2014-10-14 21:55:25

标签: python tornado

有两个类似的处理程序: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>
"""

3 个答案:

答案 0 :(得分:5)

一般来说,最好的方法是覆盖RequestHandler.write_error。这与您的第一种方法类似,但您不需要处理程序主体中的try / except,因为Tornado会为您处理此问题。

第二个示例中的显式测试也很好,但以这种方式捕获所有可能的错误是不切实际的,因此您总是需要处理未捕获的异常。

答案 1 :(得分:1)

覆盖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.

答案 2 :(得分:0)

对于大型项目,我会尝试从错误编号中抽象,特别是因为HTTP状态代码的定义不在您的范围内。我记得,至少有一对带有问题语义的状态代码。我不记得他们在哪里。

但是对于一个更大的项目,我建议您定义自己想要支持的错误类别,并根据需要集中将这些类别映射到HTTP代码。如果你以后发现,你应该为某个错误类别使用不同的状态代码,你可以集中进行。

从逻辑上讲,我会尝试尽可能多地从特定的处理程序中分解出来。当然,异常模型在这里很方便,但是可以通过函数调用来实现类似的错误处理:

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

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