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, use p instead.

  • You must put the call to add_func() inside the import. That's because Debuggit::add_func doesn't even exist until after Debuggit::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 qualify add_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 the DUMP 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 the use statement. Since I'm debugging other people's code, there's no chance of leaving the debuggit 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. The open_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 using printflush (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