NAME
Debuggit::Cookbook - Debuggit example recipes
DESCRIPTION
Herein are provided a number of (mostly) short examples on how to use Debuggit to do clever things. More examples from users are welcomed.
Wrapping the debugging output
You can take advantage of the fact that the default formatter is stored as Debuggit::default_formatter
to do some clever things.
Show timestamp
For instance, it's pretty trivial to add a timestamp to debugging output:
# add timestamp to debugging (at least for this function/module/whatever)
local $Debuggit::formatter = sub
{
return scalar(localtime) . ': ' . Debuggit::default_formatter(@_);
};
Note how the local
restricts the change in debuggit
's behavior to the current scope.
Show caller info
Similar to the last recipe. This is only trickier because you have to figure out the right argument to caller
.
# all debugging statements in the current scope will show function name
local $Debuggit::formatter = sub
{
# note that caller(0) would be this formatter sub, and
# caller(1) would be debuggit(), so caller(2) is what we want
# element 3 is the subroutine name (which includes the package name)
return (caller(2))[3] . ': ' . Debuggit::default_formatter(@_);
};
Note that this example only handles the simple cases--if your debuggit() calls get stuck inside eval's or coderef's or anything like that, this breaks down. But often the simple case is close enough.
Controlling where debugging goes
Output to a log file
Perhaps you want a log file:
my $log = '/tmp/debug.log';
$Debuggit::output = sub
{
open(LOG, ">>$log") or return;
print LOG @_;
close(LOG);
};
Notice how you have to append to the file, else multiple debuggit
calls will just overwrite each other.
Debug to a string
Instead of printing debugging immediately, perhaps you want to save them up and print them out at the end. This could be useful e.g. when debugging web pages.
our $log_msg;
local $Debuggit::output = sub { $log_msg .= join('', @_) };
Again, we're appending. We join all the args together (although most formatters will return only one value, probably best not to assume), but use no separator. This example uses our
instead of my
for the string; this way, the variable is accessible from outside the current scope, which might be necessary for later printing (depending on where the current scope is).
Interesting debugging functions
A separator function
Remember that functions don't have to take any arguments, or return any. For instance, you could replace this:
debuggit('=' x 40);
debuggit("new code section starts here");
with this:
debuggit(SEPARATOR => "new code section starts here");
by defining your function thus:
Debuggit::add_func(SEPARATOR => 0, sub
{
$Debuggit::output->('=' x 40);
return ();
});
Since we're replacing two calls with one, we use a call to $Debuggit::output
to make sure that the separator line goes where it should, even if someone wants to change where that is. To make sure we don't insert an undef
into the debugging output stream, we return an empty list.
Fun with policy modules
Test suite debugging
Although a policy module will typically pass a debug value through to Debuggit, it doesn't have to. For instance, if you're writing a module for all your test scripts to include, you might wish to force DEBUG
to 1. That's easy:
package MyTestDebuggit;
use Debuggit ();
use Test::More;
$Debuggit::output = sub { diag @_ }; # nicer with TAP
sub import
{
Debuggit->import(PolicyModule => 1, DEBUG => 1);
}
Using Data::Printer instead of Data::Dumper
If you prefer Data::Printer over Data::Dumper, you almost certainly prefer it everywhere. This is the perfect sort of thing to put in a policy module:
package MyPrettyDebuggit;
use Debuggit ();
sub import
{
Debuggit->import(PolicyModule => 1, DataPrinter => 1);
}
If you'd like to fiddle with the default parameters to Data::Printer, do it this way instead:
package MyPrettyDebuggit;
use Debuggit ();
sub import
{
Debuggit->import(PolicyModule => 1);
Debuggit::add_func(DUMP => 1, sub
{
require Data::Printer;
shift;
return &Data::Printer::np(shift, colored => 1, quote_keys => 1);
});
}
Note the following things about the latter example:
We've used Data::Printer's
np
function, which is the proper way to do it for the later versions of Data::Printer. For versions prior to 0.36, usep
instead.You must put the call to
add_func()
inside theimport
. That's becauseDebuggit::add_func
doesn't even exist until afterDebuggit::import
has been called. Which is, in turn, so that it won't hang around taking up memory when debugging is turned off. Also, don't forget you have to qualifyadd_func
with the full package name, as it isn't exported (and we aren't importing anyway).We didn't bother passing
DataPrinter
to Debuggit this time. All that does is set up the initial definition of theDUMP
function, and we're just going to override that anyway, so why bother?Since we're completely replacing Debuggit's idea of what Data::Printer should output, anything that would normally be set by passing
DataPrinter
but isn't mentioned by our replacement function will have normal Data::Printer default values. For instance, in the example above,hash_separator
would go back to being spaces.Remember that Debuggit is dealing only with strings until the very end, and then it uses its own output function. Thus, setting Data::Printer's
output
parameter wouldn't have any effect.The seemingly extraneous
&
before the function call disables any potential prototype Data::Printer is using. It might not be necessary (depending on your DP version), but it won't hurt anything, and better safe than sorry.
Putting it all together
This section contains slightly longer recipes showcasing multiple features of Debuggit.
Debugging when STDERR is redirected
Recently, I was trying to debug some CPAN modules that were failing in some of my test files. The CPAN modules were being called from a script, and the script was being called with its STDERR (and STDOUT, for that matter) redirected so it could be captured by the test script. This can make it pretty tough to debug, but I came up with a pretty quick solution based on Debuggit. I've extended it and tweaked it a bit since I originally wrote it; here's what it looks like today:
package Debuggit::TermDirect;
use Carp;
use IO::Handle;
use Method::Signatures;
use Debuggit ();
our $count = 0;
open_direct();
$Debuggit::formatter = sub { return '#>>> ' . ++$count . '. ' . Debuggit::default_formatter(@_) };
$Debuggit::output = sub { open_direct(); DIRECT->printflush(@_); };
sub import
{
my $class = shift;
Debuggit->import(PolicyModule => 1, DEBUG => 1);
Debuggit::add_func(CMD => 1, method ($cmd)
{
my @lines = `$cmd`;
chomp @lines;
return @lines;
});
Debuggit::add_func(ENV => 1, method ($varname)
{
return ("\$$varname =", $ENV{$varname});
});
}
sub open_direct
{
if (tell(DIRECT) == -1)
{
open(DIRECT, '>/dev/tty') or croak("couldn't open channel to terminal");
}
}
Let's look at a few of the features:
The overall structure is basically that shown in "Policy Modules" in Debuggit::Manual.
However, I'm setting
DEBUG
to 1 directly instead of requiring the value to be passed in in theuse
statement. Since I'm debugging other people's code, there's no chance of leaving thedebuggit
calls in permanently, so I may as well make this a debug-mode-only package.$Debuggit::output
is set to print to/dev/tty
. (This only works on Linux, of course ... maybe on a Mac if it's using OSX). In this way, it doesn't matter if STDERR is redirected; my debugging still gets to the screen. Theopen_direct
function opens the file if the handle is not already opened:tell()
returning -1 is one easy way to check for that (thanks, PerlMonks!). I'm usingprintflush
(from IO::Handle) to make sure my output isn't buffered.I was putting a lot of different calls into a lot of different modules, and, in some cases, I wasn't sure what happened first. I thought it might be nice to have a running counter for the debugging output. I also decided to make the debugging distinct: since it was getting intermingled with TAP, I started it with the
#
, but added some>
's to make it stand out a little. Then I call the default formatter.Note how I'm using Method::Signatures to give me the
method
keyword, which I use for my debugging functions. That gives me$self
automatically, and I can put any remaining arguments in my signature, making my deugging function code more concise.My first debugging function is one which calls an external command for me. I was trying to figure out what was going on in a temporary directory, which was getting cleaned up at the end of the test. This function allowed me to do things like:
debuggit("temp dir contents now:", CMD => "ls $tempdir");
(Again, this only works on Linux or OSX.) Note that it takes multiple lines and jams them together into a single line, but that was okay for what I was doing.
My second debugging function is just a quick shortcut for debugging environment variables. So this:
debuggit("after bmoogling the frobnitz", ENV => 'PERL5LIB');
would produce something like:
#>>> 4. after bmoogling the frobnitz $PERL5LIB = blib/lib:/home/me/common/perl
1 POD Error
The following errors were encountered while parsing the POD:
- Around line 283:
=over without closing =back