NAME

DBG - A collection of debugging functions

VERSION

version v0.4.1

SYNOPSIS

package Foo::Bar::Baz;
use DBG;

...
dbg "log this $message";
...
png;                  # do I ever get here?
...
trc;                  # how did I get here?
...
dmp $obj;             # what is this?
...
cyc $obj;             # does this have reference cycles?
...
my $ts = ts;          # get me the current time
...
rt $ts, ts;           # how long did that take?
...
prp "is it so", $val; # prints message plus "yes" or "no"
...
pkg $obj, 'doit';     # prints package providing obj's doit method
...

DESCRIPTION

This is just a collection of functions useful for debugging. Instead of adding

use Data::Dumper;
use B::Deparse;
use Devel::Size qw(total_size);

and so forth you can just type

use DBG;

at the top of the script. If you're using git, or another version control system with similar functionality, you can write a simple pre-commit hook to prevent yourself from committing debugging lines to the repository. Once you've deleted the use DBG; line you can find all the other stuff you may have left in by trying to compile the code and looking at the errors.

All functions have short names to make debugging quick(er).

All debugging messages are printed both to the screen and to a log. The log will be ~/DBG.log unless otherwise specified. See $ENV{DBG_LOG}. This facilitates examining debugging output at one's leisure without having to visually cull away any other output produced by the program.

All debugging functions are exported by default.

A timestamp will be printed before any debugging output to facilitate distinguising one debugging session from another.

FUNCTIONS

ts(;$) -- "get timestamp"

Returns a DateTime-based timestamp. The optional argument is a label for the timestamp. The label will be accessible via the timestamp's text method.

my $t = ts 'foo';
say $t->text;  # foo
say $t;        # 2014-05-31T22:31:52

rt($$) -- "report timestamp"

Report time difference. This function expects two objects generated by the ts function, the earlier first. It returns the second timestamp to facilitate the

my $ts = ts;
# some code
$ts = rt $ts, ts

pattern.

The report will vary according to whether the first timestamp holds a label.

my $t1 = ts 'foo';
my $t2 = rt $t1, ts 'bar';
sleep 1;
my $t3 = rt $t2, ts;
sleep 61;
rt $t3, ts;

# timestamp foo
# 	negligible time elapsed
# timestamp bar
# 	1 second
# 1 minute
# 1 second

trc() -- "trace"

Prints a stack trace, skipping its own frame. Each line of the trace is formatted as

frame number) code name (file:line)

This involves munging the frames as returned by caller so instead of saying "you got here when called from here" it says simply "you are here". The next line says how you got here. That is, the code name is the name of the code you're in, not the code that just called you. I simply find this easier to follow.

sub foo   { bar() }
sub bar   { baz() }
sub baz   { plugh() }
sub plugh { trc }
foo();

# TRACE
# 1) main::plugh (test.pl:11)
# 2) main::baz (test.pl:10)
# 3) main::bar (test.pl:9)
# 4) main::foo (test.pl:8)
# END TRACE

dmp($) -- "dump"

Prints a pretty data dump. This uses a combination of Perl::Tidy and Data::Dumper.

my $r = { a => [qw(1 2 3)], c => { d => undef, egg => [ {}, {} ] } };
dmp $r;

# {
#     a => [ '1', '2', '3' ],
#     c => {
#         d   => undef,
#         egg => [ {}, {} ]
#     }
# }

dbg($) -- "debug"

Prints a message to the debugging log.

dbg 'foo';  # foo

png(;$) -- "ping"

Prints a ping message to the debugging log. If optional argument is true, just prints "in code <code name> -- <optional arg>", where code name is the name of the function or method minus the package. If the optional argument is just "1", it is not suffixed to the ping message.

sub foo { png }
sub bar { png 1 }
sub baz { png 'la la la la la' }
foo();
bar();
baz();

# PING main::foo (test.pl:11)
# in code bar
# in code baz -- la la la la la

cyc($) -- "cycles"

Checks for cycles in a reference, teeing out the entire object graph. This is like a condensed dump concerning itself only with references.

my $a = {};
my $b = { b => $a };
my $c = { c => $b };
$a->{a} = $c;
cyc $a;

# HASH (140416464744656 <- base)
#    HASH (140416464745304 <- 140416464744656)
#       HASH (140416464744992 <- 140416464745304)
#          HASH (140416464744656 <- 140416464744992) -- ref count: 2

prp($$) -- "property"

Takes a message and a scalar to be evaluated as a boolean and submits this to dbg as "$message? yes/no".

prp 'true', 1;
prp 'true', 0;

# true? yes
# true? no

cnm($;$) -- "code name"

cnm $ua->can('request');  # LWP::UserAgent::request

Converts a code reference to the place in the source code it comes from. This uses B::svref_2object to do its magic. Sometimes it will provide the file and line number, sometimes not.

If the optional second parameter is provided, the information is only returned, not teed out.

pkg($$;$) -- "package"

Determines the package providing a method to an object. The first parameter is the object and the second the method name. Unless the optional third parameter is true, the file and line are also provided.

my $d = DateTime->now;
pkg $d, 'ymd';

# package: DateTime; file: /Users/houghton/perl5/lib/perl5/darwin-thread-multi-2level/DateTime.pm; line: 820

pkg $d, 'ymd', 1;

# DateTime

sz($;$) -- "size"

Tees out the size of a scalar. If two arguments are given, the first is taken as a label and the second the scalar.

sz {};         # 128
sz 'foo', {};  # foo 128

This delegates to the total_size function in Devel::Size. If you do not have Devel::Size, the sz will only emit a warning that it requires Devel::Size.

mtd($;$) -- "method"

Dumps out a sorted list of the object's method names, fully qualified. If the optional parameter is provided, it also lists where the code for each method can be found.

my $d = DateTime->now;
mtd $d;

# Class: DateTime
# [
#     'DateTime::DefaultLanguage',
#     'DateTime::DefaultLocale',
#     'DateTime::INFINITY',
#     'DateTime::MAX_NANOSECONDS',
#     ...                           # many lines omitted
# ]

mtd $d, 1;

# Class: DateTime
# UNIVERSAL::DOES
# DefaultLanguage                 : /Users/houghton/perl5/lib/perl5/darwin-thread-multi-2level/DateTime.pm  106
# DefaultLocale                   : /Users/houghton/perl5/lib/perl5/darwin-thread-multi-2level/DateTime.pm  106
# INFINITY                        : /Users/houghton/perl5/lib/perl5/constant.pm  30
# MAX_NANOSECONDS                 : /Users/houghton/perl5/lib/perl5/darwin-thread-multi-2level/Class/MOP/Mixin/HasMethods.pm  131
# ...

inh($) -- "inheritance"

Takes an object or class and prints out a sorted list of all the classes in that object or class's inheritance tree.

package Plugh;
package Foo;
our @ISA = qw(Plugh);
package Bar;
package Baz;
our @ISA = qw(Foo Bar);
package main;

inh 'Baz';

# Classes in the inheritance hierarchy of Baz:
#   Bar
#   Baz
#   Foo
#   Plugh

dpr -- "deparse"

Takes a code reference and any optional parameters to pass to B::Deparse. Tees out the result of deparsing this reference.

my $foo = sub { print "foo\n" };
...
dpr $foo;    # what is this mystery code ref?

# {
#     use warnings;
#     use strict 'refs';
#     print "foo\n";
# }

flt($;$) -- "flatten"

Takes a parameter and flattens it. For an ordinary scalar this just means it returns it. For containers -- hash or array references -- it returns copies with flattened values. Anything blessed it stringifies.

flt { bar => 1, baz => DateTime->now };

# {
#     'bar' => 1,
#     'baz' => '2014-05-31T21:04:07'
# };

This is useful for dumping hashes containing huge objects whose innards you don't need to see.

If the optional second parameter is provided, the information is only returned, not also dumped out via dmp.

VARIABLES

$ENV{DBG_LOG}

If the DBG_LOG environment variable is set and is not equal to 0, this will be understood as the file into which debugging output should be dumped. If it is set to 0, the debugging output will only be sent to STDERR. If it is undefined, the log will be ~/DBG.log.

$ENV{DBG_ON}

If the DBG_ON environment variable is set, its boolean value will be used to determine the value of $DBG::ON.

$ENV{DBG_HEADER}

If the DBG_HEADER environment variable is set, its boolean value will be used to determine the value of $DBG::HEADER.

$DBG::ON

If $DBG::ON is true, which it is by default, all debugging code is executed. If it is false, debugging code is ignored (aside from the initial timestamp). The state of $ON can be manipulated programmatically or set by the $ENV{DBG_ON} environment variable. This can be used to constrain debugging output to a particular section of a program. For instance, one may set debugging to off and then locally set it to one within a particular method.

sub foo {
    local $DBG::ON = 1;
    my self = shift;
    ...
}

$DBG::HEADER

Unless $DBG::HEADER is false, a timestamp and process ID will be logged for a debugging process. The header is not printed until the first debugging line is logged, so this need not be set in a BEGIN block.

PRE-COMMIT HOOK

You probably don't want debugging code, at least not that associated with DBG, getting into your repository. Here's a sample git pre-commit hook script for screening it out:

my $rx = qr/
  ( (?&line){0,3} (?&dbg) (?&line){0,3} )
  (?(DEFINE)
    (?<line> ^.*?(?:\R|\z) )
    (?<dbg>  ^\+\s*use\s+DBG\b.*?(?:\R|\z) )
  )
/mx;
my $text = `git diff --staged`;
if ( my @matches = $text =~ /$rx/g ) {
    @matches = grep defined, @matches;
    exit 0 unless @matches;
    print STDERR "DBG lines: \n\n" . join "\n", @matches;
    print STDERR "\nRun with --no-verify if you want to skip the DBG check.\n";
    print STDERR "Aborting commit.\n";
    exit 1;
}
exit 0;

AUTHOR

Grant Street Group <developers@grantstreet.com>

COPYRIGHT AND LICENSE

This software is Copyright (c) 2014 - 2020 by Grant Street Group.

This is free software, licensed under:

The Artistic License 2.0 (GPL Compatible)