簡體   English   中英

更好的方法來處理龍卷風請求處理程序中的錯誤

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

有兩個類似的處理程序: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>
"""

通常,最好的方法是覆蓋RequestHandler.write_error 這與您的第一種方法類似,但您不需要處理程序主體中的try / except,因為Tornado會為您處理此問題。

像你的第二個例子那樣的顯式測試也很好,但是以這種方式捕獲所有可能的錯誤是不切實際的,所以你總是需要一些東西來處理未捕獲的異常。

覆蓋write_error非常有效。 我在我的項目中做的是嘗試捕獲任何500狀態代碼。 然后我通過松弛將它們發送給自己(我的流量足夠低,頻率非常低)。

這是從write_error提取干凈堆棧跟蹤的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.

對於大型項目,我會嘗試從錯誤編號中抽象出來,特別是因為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.')
...

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM