在Google App Engine中调试Jinja2
当我在Google App Engine上运行Jinja2时,得到的调试信息没什么用。我了解到这是因为常见问题解答中的一项内容:
我的错误追踪信息看起来很奇怪,发生了什么事?
如果没有编译加速模块,并且你使用的Python版本没有ctypes(比如没有ctypes的Python 2.4、Jython或Google的AppEngine),那么Jinja2就无法提供正确的调试信息,错误追踪信息可能会不完整。目前对于Jython或AppEngine没有好的解决办法,因为那里没有ctypes,也无法使用加速扩展。
虽然目前没有“好的”解决办法,但有没有其他方法可以让出现异常时打印的信息更有帮助呢?
谢谢你的阅读。
布莱恩
6 个回答
我使用了以下的猴子补丁(monkeypatch),目的是在Jinja2模板渲染时,如果出现错误,可以得到更有用的信息:
# Enabling this monkeypatch can help track down hard to find errors that crop
# up during template rendering (since Jinja's own error reporting is so
# unhelpful on AppEngine).
real_handle_exception = environment.handle_exception
def handle_exception(self, *args, **kwargs):
import logging, traceback
logging.error('Template exception:\n%s', traceback.format_exc())
real_handle_exception(self, *args, **kwargs)
environment.handle_exception = handle_exception
这样做会让你的错误日志中显示更准确的错误追踪信息。我觉得它通常不会告诉你具体出了什么问题(不过如果没记错的话,有时候会),但至少能把错误缩小到正确的模板上。
为什么这样有效,我也不知道(或者说不太记得了)。
举个例子,我在我的一个模板中添加了一些代码,这样就会触发一个错误。在开发服务器下,这就是“正常”的错误处理器给我的信息:
Traceback (most recent call last):
File "/Users/will/Applications/GoogleAppEngineLauncher.app/Contents/Resources/GoogleAppEngine-default.bundle/Contents/Resources/google_appengine/google/appengine/ext/webapp/__init__.py", line 511, in __call__
handler.get(*groups)
File "/Users/will/workspace/keypremium/ki/shared/decorators.py", line 27, in inner
return func(self, *args, **kwargs)
File "/Users/will/workspace/keypremium/account/views.py", line 114, in get
self.render_jinja('accounts/edit_card.html', ctx)
File "/Users/will/workspace/keypremium/ki/webapp/handlers.py", line 186, in render_jinja
return self.response.out.write(jinja.render(template_path, new_context))
File "/Users/will/workspace/keypremium/ki/shared/jinja/__init__.py", line 21, in render
return template.render(context)
File "/Users/will/workspace/keypremium/ki/ext/jinja2/environment.py", line 705, in render
return self.environment.handle_exception(exc_info, True)
File "/Users/will/workspace/keypremium/ki/shared/jinja/environment.py", line 24, in handle_exception
real_handle_exception(self, *args, **kwargs)
File "/Users/will/workspace/keypremium/templates/accounts/edit_card.html", line 1, in top-level template code
{% extends 'accounts/base.html' %}
UndefinedError: 'sequence' is undefined
但是错误并不在accounts/base.html
模板中,而是在accounts/edit_card.html
中。这是我在App Engine上调试Jinja2模板错误时最让人沮丧的部分:错误的来源几乎总是被错误地表示。在我的经验中,错误的来源通常被报告为父模板或者某个模板宏。
安装了这个错误日志猴子补丁后,同样的错误在日志中会生成这样的追踪信息:
Traceback (most recent call last):
File "/Users/will/workspace/keypremium/ki/ext/jinja2/environment.py", line 702, in render
return concat(self.root_render_func(self.new_context(vars)))
File "/Users/will/workspace/keypremium/templates/accounts/edit_card.html", line 11, in root
<div class="errors">
File "/Users/will/workspace/keypremium/templates/accounts/base.html", line 11, in root
</html>
File "/Users/will/workspace/keypremium/templates/accounts/edit_card.html", line 54, in block_content
<td>{{ form.cvv2|safe }}</td>
File "/Users/will/workspace/keypremium/ki/ext/jinja2/environment.py", line 352, in getattr
return getattr(obj, attribute)
File "/Users/will/workspace/keypremium/ki/ext/jinja2/runtime.py", line 445, in _fail_with_undefined_error
raise self._undefined_exception(hint)
UndefinedError: 'sequence' is undefined
这里仍然有很多多余的信息,但这个追踪信息至少能让我朝着正确的方向去找。它声称问题出在accounts/edit_card.html
的第54行(这是正确的模板),但实际上错误发生在第86行。
不过,既然知道了正确的模板和具体的错误,我就能很容易找到问题代码是这个:
{% for x in sequence.sequence() %}
{{ x.y }}
{% endfor %}
因为在模板上下文中没有sequence
变量。
这不是一个完美的解决方案,但我发现它非常有帮助。
也许你可以直接使用PyCharm的交互式调试器,逐步查看代码的执行过程:
你可以通过在开发服务器的C模块白名单中添加_ctypes和gestalt来解决这个问题,这个过程叫做猴子补丁(monkeypatching)。
要做到这一点,只需在你的main.py文件的顶部放入以下代码:
import os
if os.environ.get('SERVER_SOFTWARE', '').startswith('Dev'):
# Enable ctypes for Jinja debugging
from google.appengine.tools.dev_appserver import HardenedModulesHook
HardenedModulesHook._WHITE_LIST_C_MODULES += ['_ctypes', 'gestalt']
如果你有其他本地模块的需求,也可以用这个方法来启用其他C模块。不过要注意,这些模块在你部署后仍然无法正常工作,所以要小心使用。
在使用SDK 1.6.3和python2.7时,你需要把上面的代码改成:
import os
if os.environ.get('SERVER_SOFTWARE', '').startswith('Dev'):
# Enable ctypes for Jinja debugging
import sys
from google.appengine.tools.dev_appserver import HardenedModulesHook
assert isinstance(sys.meta_path[0], HardenedModulesHook)
sys.meta_path[0]._white_list_c_modules += ['_ctypes', 'gestalt']
在SDK 1.8.6和python 2.7的情况下,可以试试这个:
PRODUCTION_MODE = not os.environ.get(
'SERVER_SOFTWARE', 'Development').startswith('Development')
if not PRODUCTION_MODE:
from google.appengine.tools.devappserver2.python import sandbox
sandbox._WHITE_LIST_C_MODULES += ['_ctypes', 'gestalt']