From 667035453b0a61c1d48281f67e6f9da3c5df412e Mon Sep 17 00:00:00 2001 From: "Chris St. Pierre" Date: Thu, 5 Jul 2012 17:00:33 -0400 Subject: try to get details about a genshi error --- .../Bcfg2/Server/Plugins/Cfg/CfgGenshiGenerator.py | 65 ++++++++++++++++++++-- 1 file changed, 61 insertions(+), 4 deletions(-) (limited to 'src/lib/Bcfg2/Server/Plugins/Cfg') diff --git a/src/lib/Bcfg2/Server/Plugins/Cfg/CfgGenshiGenerator.py b/src/lib/Bcfg2/Server/Plugins/Cfg/CfgGenshiGenerator.py index 5447717d8..277a26f97 100644 --- a/src/lib/Bcfg2/Server/Plugins/Cfg/CfgGenshiGenerator.py +++ b/src/lib/Bcfg2/Server/Plugins/Cfg/CfgGenshiGenerator.py @@ -1,5 +1,7 @@ +import re import sys import logging +import traceback import Bcfg2.Server.Plugin from Bcfg2.Server.Plugins.Cfg import CfgGenerator @@ -8,6 +10,7 @@ logger = logging.getLogger(__name__) try: import genshi.core from genshi.template import TemplateLoader, NewTextTemplate + from genshi.template.eval import UndefinedError have_genshi = True except ImportError: TemplateLoader = None @@ -25,6 +28,7 @@ def removecomment(stream): class CfgGenshiGenerator(CfgGenerator): __extensions__ = ['genshi'] __loader_cls__ = TemplateLoader + pyerror_re = re.compile('<\w+ u?[\'"](.*?)\s*\.\.\.[\'"]>') def __init__(self, fname, spec, encoding): CfgGenerator.__init__(self, fname, spec, encoding) @@ -47,10 +51,63 @@ class CfgGenshiGenerator(CfgGenerator): metadata=metadata, path=self.name).filter(removecomment) try: - return stream.render('text', encoding=self.encoding, - strip_whitespace=False) - except TypeError: - return stream.render('text', encoding=self.encoding) + try: + return stream.render('text', encoding=self.encoding, + strip_whitespace=False) + except TypeError: + return stream.render('text', encoding=self.encoding) + except UndefinedError: + # a failure in a genshi expression _other_ than %{ python ... %} + err = sys.exc_info()[1] + stack = traceback.extract_tb(sys.exc_info()[2]) + for quad in stack: + if quad[0] == self.name: + logger.error("Cfg: Error rendering %s at %s: %s" % + (fname, quad[2], err)) + break + raise + except: + # a failure in a %{ python ... %} block -- the snippet in + # the traceback is just the beginning of the block. + err = sys.exc_info()[1] + stack = traceback.extract_tb(sys.exc_info()[2]) + (filename, lineno, func, text) = stack[-1] + # this is horrible, and I deeply apologize to whoever gets + # to maintain this after I go to the Great Beer Garden in + # the Sky. genshi is incredibly opaque about what's being + # executed, so the only way I can find to determine which + # {% python %} block is being executed -- if there are + # multiples -- is to iterate through them and match the + # snippet of the first line that's in the traceback with + # the first non-empty line of the block. + execs = [contents + for etype, contents, loc in self.template.stream + if etype == self.template.EXEC] + contents = None + if len(execs) == 1: + contents = execs[0] + elif len(execs) > 1: + match = pyerror_re.match(func) + if match: + firstline = match.group(0) + for pyblock in execs: + if pyblock.startswith(firstline): + contents = pyblock + break + # else, no EXEC blocks -- WTF? + if contents: + # we now have the bogus block, but we need to get the + # offending line. To get there, we do (line number + # given in the exception) - (firstlineno from the + # internal genshi code object of the snippet) + 1 = + # (line number of the line with an error within the + # block, with all multiple line breaks elided to a + # single line break) + real_lineno = lineno - contents.code.co_firstlineno + src = re.sub(r'\n\n+', '\n', contents.source).splitlines() + logger.error("Cfg: Error rendering %s at %s: %s" % + (fname, src[real_lineno], err)) + raise def handle_event(self, event): if event.code2str() == 'deleted': -- cgit v1.2.3-1-g7c22