简体   繁体   English

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

[英]Better way to handle errors in tornado request handler

There are two similar handlers: AgeHandler1 and AgeHandler2. 有两个类似的处理程序:AgeHandler1和AgeHandler2。 In the first one we simply raise a specific exception to return an error message, in the second - we manually return an error message. 在第一个中,我们只是引发一个特定的异常以返回错误消息,在第二个中 - 我们手动返回一条错误消息。 What You think about these two methods? 您对这两种方法有何看法? Which method is preferable for a large project? 哪种方法适合大型项目? Any other best practices? 还有其他最佳做法吗?

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

Responses: 对策:

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

In general the best approach is to override RequestHandler.write_error . 通常,最好的方法是覆盖RequestHandler.write_error This is similar to your first approach, but you don't need the try/except in the body of the handler because Tornado will handle this for you. 这与您的第一种方法类似,但您不需要处理程序主体中的try / except,因为Tornado会为您处理此问题。

Explicit tests like those in your second example are also good, but it's impractical to catch all possible errors this way so you'll always need something to handle uncaught exceptions. 像你的第二个例子那样的显式测试也很好,但是以这种方式捕获所有可能的错误是不切实际的,所以你总是需要一些东西来处理未捕获的异常。

Overwriting write_error works very well. 覆盖write_error非常有效。 What I do in my projects is I try to catch any 500 status codes. 我在我的项目中做的是尝试捕获任何500状态代码。 I then send them to myself over slack (my traffic is low enough that the frequency is very low). 然后我通过松弛将它们发送给自己(我的流量足够低,频率非常低)。

Here's the code to extract a clean stack trace from write_error . 这是从write_error提取干净堆栈跟踪的write_error Note in this example I also yank out any references to 'gen.py', 'concurrent.py' or 'web.py', which makes for much cleaner stack traces. 请注意,在此示例中,我还提取了对“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")  

The output looks like this: 输出如下所示:

  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.

For large projects, I would try to abstract from error numbers, specially because the definition of the HTTP status codes are not in your scope. 对于大型项目,我会尝试从错误编号中抽象出来,特别是因为HTTP状态代码的定义不在您的范围内。 As much I remember, there is at least one pair of status codes with problematic semantic. 我记得,至少有一对带有问题语义的状态代码。 I don't remember which they where. 我不记得他们在哪里。

But for a larger project I would recommend, that you define your own error categories that you want to support and map those categories to HTTP codes centrally as you need. 但是对于一个更大的项目,我建议您定义自己想要支持的错误类别,并根据需要集中将这些类别映射到HTTP代码。 When you find out later, that you should use a different status code for some error category, you can do it centrally. 当您稍后发现时,您应该为某些错误类别使用不同的状态代码,您可以集中进行。

Logically I would try to factor out as much knowledge from the specific handling routine as possible. 从逻辑上讲,我会尝试尽可能多地从特定的处理程序中分解出来。 The exception model of course comes here handy, but similar could be reached with a function call for error handling like: 当然,异常模型在这里很方便,但是可以通过函数调用来实现类似的错误处理:

...
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.')
...

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM