Debugging Chef Runs with CHEF_LOG

When you have experience with troubleshooting Vagrant, you probably know that you can enable detailed logging by setting the VAGRANT_LOG environment variable to a specific log level. For example, the info level will make Vagrant much more chatty:

$ VAGRANT_LOG=info vagrant up
 INFO global: Vagrant version: 1.3.4
 INFO manager: Registered plugin: box command
 INFO manager: Registered plugin: destroy command
 INFO manager: Registered plugin: halt command
 INFO manager: Registered plugin: help command
...

While this is helpful for debugging Vagrant itself, I’ve always wanted to have something similar for Vagrant’s provisioners–in particular the Chef Solo provisioner, which I’m using most of the time. In order to get more detailed information about a Chef run, here is how you can enable debug-level logging in your Vagrantfile:

Vagrant.configure("2") do |config|
  # ...
  config.vm.provision :chef_solo do |chef|
    # ...
    chef.log_level = :debug
  end
end

Since the very verbose debug output makes it hard to spot events during normal Chef runs, I used to reduce the log level from debug to info (the default) after each debugging session by commenting out the line. Of course, I don’t like toggling log levels in source code, and neither does Git. As a result, I came up with this VAGRANT_LOG-like solution:

Vagrant.configure("2") do |config|
  # ...
  config.vm.provision :chef_solo do |chef|
    # ...
    chef.log_level = ENV.fetch("CHEF_LOG", "info").downcase.to_sym
  end
end

This bit of Ruby code allows you to configure Chef’s log level via the environment variable CHEF_LOG, which must be set to debug, info, warn, error, or fatal.

Now, debugging Chef runs boils down to:

$ CHEF_LOG=debug vagrant provision
...
[default] Running provisioner: chef_solo...
Generating chef JSON and uploading...
Running chef-solo...
stdin: is not a tty
[2013-10-07T09:40:15+00:00] INFO: Forking chef instance to converge...
[2013-10-07T09:40:15+00:00] DEBUG: Fork successful. Waiting for new chef pid: 1653
[2013-10-07T09:40:15+00:00] DEBUG: Forked instance now converging
[2013-10-07T09:40:15+00:00] INFO: *** Chef 11.6.0 ***
[2013-10-07T09:40:16+00:00] DEBUG: Building node object for cats
[2013-10-07T09:40:16+00:00] DEBUG: Extracting run list from JSON attributes provided on command line
[2013-10-07T09:40:16+00:00] INFO: Setting the run_list to ["recipe[cats::default]"] from JSON
[2013-10-07T09:40:16+00:00] DEBUG: Applying attributes from json file
[2013-10-07T09:40:16+00:00] DEBUG: Platform is ubuntu version 12.04
...

This works with both Chef Solo and Chef Client. It should be trivial to implement the same mechanism for other provisioners like Puppet (using --debug or --test) or Shell (with set -x).

CHEF_FORMAT (added on 2013-12-13)

Besides the log level discussed above, there’s another configuration option you can set in your Vagrantfiles to influence the output of Chef: formatter. This one, as its name implies, changes the output format of Chef. Valid formats are doc, min (or minimal), and null. Chef uses doc by default whenever you run it directly from the command line. Vagrant, on the other hand, applies null by default, which effectively reduces output to log messages.

Similar to CHEF_LOG, it’s straightforward to make the output format configurable via an environment variable–say hello to CHEF_FORMAT. The final code looks like the following:

Vagrant.configure("2") do |config|
  config.vm.provision :chef_solo do |chef|
    # Configure Chef output
    chef.formatter = ENV.fetch("CHEF_FORMAT", "null").downcase.to_sym
    chef.log_level = ENV.fetch("CHEF_LOG", "info").downcase.to_sym
  end
end

As an example, this combination of CHEF_FORMAT and CHEF_LOG will cause Chef to only print out the most basic information:

$ CHEF_FORMAT=min CHEF_LOG=warn vagrant provision

Happy logging!

Tagged under: Vagrant, Chef, Logging