[英]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.