Getting a Callstack When Your Puppet Plugin Fails
04-02-11
I have been well trained in the art of foo. I started at the innocent age of 8, learning some BASIC-foo. I first tinkered with some HTML-foo at 12. At 14 I stepped my BASIC-foo up a notch, and started to train extensively in TI-BASIC-foo. At 16 a new friend blew my mind with some TI-BASIC-foo of his own, and a year later I began learning the dark arts of Assembly-foo, in the arena of Z80. At 18, I applied my wisdom to develop a new set of C++-foo skills, and started fooing professionally. Since then, I’ve continued to expand my craft, and some of my foo skills are legendary.
But, there are no words foul enough to express the taste that Ruby-foo leaves in my mouth. As such, my Ruby skills have a whole lot of room for improvement. I look forward to continuing to avoid that improvement, just as soon as I finish the Puppet scripts I’m working on.
This, of course, brings us to the original topic of this article. In the course of my Puppet-fooing, I’ve found it necessary to develop and/or modify a handful of Puppet plugins written in Ruby. With repressed Ruby skills, I make more than the average number of mistakes, and take longer than the average Ruby-fooer to fix them.
Puppet is uniquely unhelpful with this. Not only does it fail to offer any helpful debugging tools, it actually gets in the way by preventing any useful debugging. When a plugin fails due to a stupid error, it will print a cryptic summary with exactly zero useful information. For example:
Could not evaluate: can't convert String into Integer
Rrriiight…
If you are dealing with a 20 line plugin, used only in one place, and just changed something small, you could probably riddle this out. If, on the other hand, this is a 1000 line plugin for managing iptables, and you just rewrote half of it to make it more compatible with “the real world”, this little blurb probably isn’t going to help you much. Seriously puppetlabs? would a file and line number be too much to ask for? And what about a call stack? This is 2011. Call stacks have been around for a while. Trust me, I know. How about getting me a call stack?
It turns out that programmers have stages of desperation which eventually lead up to “creative” solutions to their problems. Each step brings us further from denial, and closer to acceptance. When we realize that nobody else cares, and we have to fix our own problems, we eventually find solutions. Such is the case here.
The solution this time is actually pathetically simple:
require "yaml"
...
def that_thing_your_plugin_does
begin
...Put your code here...
rescue Exception => e
warning(e.inspect)
warning(YAML.dump(e.backtrace))
raise
end
end
This will cause any errors to print more complete error information, including a reasonably formatted stack trace. Horray for files and line numbers! I now know exactly where it died. Aaaannd… dangit, that variable got initialized as an array instead of an object. I guess that explains the error.
The solution above has to be added manually to any function you need to debug, but there’s no reason you can’t just leave it in there the whole time. If you are feeling more adventurous, Puppet itself is also doing something similar, except that it doesn’t give the backtrace. You could probably find the file(s) that normally catch(es) the error, and hack the core to add the missing backtrace. I’m not feeling that motivated at the moment (remember, Ruby makes me contemplate bad things) so I probably won’t. If I do, I’ll be sure to submit a patch to puppetlabs.
