在Google App Engine中调试Jinja2

28 投票
6 回答
3771 浏览
提问于 2025-04-16 00:15

当我在Google App Engine上运行Jinja2时,得到的调试信息没什么用。我了解到这是因为常见问题解答中的一项内容:

我的错误追踪信息看起来很奇怪,发生了什么事?

如果没有编译加速模块,并且你使用的Python版本没有ctypes(比如没有ctypes的Python 2.4、Jython或Google的AppEngine),那么Jinja2就无法提供正确的调试信息,错误追踪信息可能会不完整。目前对于Jython或AppEngine没有好的解决办法,因为那里没有ctypes,也无法使用加速扩展。

虽然目前没有“好的”解决办法,但有没有其他方法可以让出现异常时打印的信息更有帮助呢?

谢谢你的阅读。

布莱恩

6 个回答

2

我使用了以下的猴子补丁(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变量。

这不是一个完美的解决方案,但我发现它非常有帮助。

2

也许你可以直接使用PyCharm的交互式调试器,逐步查看代码的执行过程:

http://www.jetbrains.com/pycharm/quickstart/#RunAndDebug

29

你可以通过在开发服务器的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']

撰写回答