TRACING ^

The DBI has a powerful tracing mechanism built in. It enables you to see what's going on 'behind the scenes', both within the DBI and the drivers you're using.

Trace Settings

Which details are written to the trace output is controlled by a combination of a trace level, an integer from 0 to 15, and a set of trace flags that are either on or off. Together these are known as the trace settings and are stored together in a single integer. For normal use you only need to set the trace level, and generally only to a value between 1 and 4.

Each handle has it's own trace settings, and so does the DBI. When you call a method the DBI merges the handles settings into its own for the duration of the call: the trace flags of the handle are OR'd into the trace flags of the DBI, and if the handle has a higher trace level then the DBI trace level is raised to match it. The previous DBI trace setings are restored when the called method returns.

Trace Levels

Trace levels are as follows:

  0 - Trace disabled.
  1 - Trace top-level DBI method calls returning with results or errors.
  2 - As above, adding tracing of top-level method entry with parameters.
  3 - As above, adding some high-level information from the driver
      and some internal information from the DBI.
  4 - As above, adding more detailed information from the driver.
      This is the first level to trace all the rows being fetched.
  5 to 15 - As above but with more and more internal information.

Trace level 1 is best for a simple overview of what's happening. Trace levels 2 thru 4 a good choice for general purpose tracing. Levels 5 and above are best reserved for investigating a specific problem, when you need to see "inside" the driver and DBI.

The trace output is detailed and typically very useful. Much of the trace output is formatted using the "neat" function, so strings in the trace output may be edited and truncated by that function.

Trace Flags

Trace flags are used to enable tracing of specific activities within the DBI and drivers. The DBI defines some trace flags and drivers can define others. DBI trace flag names begin with a capital letter and driver specific names begin with a lowercase letter, as usual.

Curently the DBI only defines two trace flags:

  ALL - turn on all DBI and driver flags (not recommended)
  SQL - trace SQL statements executed (not yet implemented)

The "parse_trace_flags" and "parse_trace_flag" methods are used to convert trace flag names into the coresponding integer bit flags.

Enabling Trace

The $h->trace method sets the trace settings for a handle and DBI->trace does the same for the DBI.

In addition to the "trace" method, you can enable the same trace information, and direct the output to a file, by setting the DBI_TRACE environment variable before starting Perl. See "DBI_TRACE" for more information.

Finally, you can set, or get, the trace settings for a handle using the TraceLevel attribute.

All of those methods use parse_trace_flags() and so allow you set both the trace level and multiple trace flags by using a string containing the trace level and/or flag names separated by vertical bar ("|") or comma (",") characters. For example:

  local $h->{TraceLevel} = "3|SQL|foo";

Trace Output

Initially trace output is written to STDERR. Both the $h->trace and DBI->trace methods take an optional $trace_file parameter, which may be either the name of a file to be openned by DBI in append mode, or a reference to an existing writable (possibly layered) filehandle. If $trace_file is a filename, and can be opened in append mode, or $trace_file is a writable filehandle, then all trace output (currently including that from other handles) is redirected to that file. A warning is generated if $trace_file can't be opened or is not writable.

Further calls to trace() without $trace_file do not alter where the trace output is sent. If $trace_file is undefined, then trace output is sent to STDERR and, if the prior trace was openned with $trace_file as a filename, the previous trace file is closed; if $trace_file was a filehandle, the filehandle is not closed.

NOTE: If $trace_file is specified as a filehandle, the filehandle should not be closed until all DBI operations are completed, or the application has reset the trace file via another call to trace() that changes the trace file.

Tracing to Layered Filehandles

NOTE:

As of version 5.8, Perl provides the ability to layer various "disciplines" on an open filehandle via the PerlIO module.

A simple example of using PerlIO layers is to use a scalar as the output:

    my $scalar = '';
    open( my $fh, "+>:scalar", \$scalar );
    $dbh->trace( 2, $fh );

Now all trace output is simply appended to $scalar.

A more complex application of tracing to a layered filehandle is the use of a custom layer (Refer to Perlio::via for details on creating custom PerlIO layers.). Consider an application with the following logger module:

    package MyFancyLogger;

    sub new
    {
        my $self = {};
        my $fh;
        open $fh, '>', 'fancylog.log';
        $self->{_fh} = $fh;
        $self->{_buf} = '';
        return bless $self, shift;
    }

    sub log
    {
        my $self = shift;
        return unless exists $self->{_fh};
        my $fh = $self->{_fh};
        $self->{_buf} .= shift;
    #
    # DBI feeds us pieces at a time, so accumulate a complete line
    # before outputing
    #
        print $fh "At ", scalar localtime(), ':', $self->{_buf}, "\n" and
        $self->{_buf} = ''
            if $self->{_buf}=~tr/\n//;
    }

    sub close {
        my $self = shift;
        return unless exists $self->{_fh};
        my $fh = $self->{_fh};
        print $fh "At ", scalar localtime(), ':', $self->{_buf}, "\n" and
        $self->{_buf} = ''
            if $self->{_buf};
        close $fh;
        delete $self->{_fh};
    }

    1;

To redirect DBI traces to this logger requires creating a package for the layer:

    package PerlIO::via::MyFancyLogLayer;

    sub PUSHED
    {
        my ($class,$mode,$fh) = @_;
        my $logger;
        return bless \$logger,$class;
    }

    sub OPEN {
        my ($self, $path, $mode, $fh) = @_;
        #
        # $path is actually our logger object
        #
        $$self = $path;
        return 1;
    }

    sub WRITE
    {
        my ($self, $buf, $fh) = @_;
        $$self->log($buf);
        return length($buf);
    }

    sub CLOSE {
        my $self = shift;
        $$self->close();
        return 0;
    }

    1;

The application can then cause DBI traces to be routed to the logger using

    use PerlIO::via::MyFancyLogLayer;

    open my $fh, '>:via(MyFancyLogLayer)', MyFancyLogger->new();

    $dbh->trace('SQL', $fh);

Now all trace output will be processed by MyFancyLogger's log() method.

Trace Content

Many of the values embeded in trace output are formatted using the neat() utility function. This means they may be quoted, sanitized, and possibly truncated if longer than $DBI::neat_maxlen. See "neat" for more details.

Tracing Tips

You can add tracing to your own application code using the "trace_msg" method.

It can sometimes be handy to compare trace files from two different runs of the same script. However using a tool like diff on the original log output doesn't work well because the trace file is full of object addresses that may differ on each run.

The DBI includes a handy utility called dbilogstrip that can be used to 'normalize' the log content. It can be used as a filter like this:

    DBI_TRACE=2 perl yourscript.pl ...args1... 2>&1 | dbilogstrip > dbitrace1.log
    DBI_TRACE=2 perl yourscript.pl ...args2... 2>&1 | dbilogstrip > dbitrace2.log
    diff -u dbitrace1.log dbitrace2.log

See dbilogstrip for more information.