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