File Coverage

lib/DBI/Log.pm
Criterion Covered Total %
statement 96 129 74.4
branch 19 32 59.3
condition 4 12 33.3
subroutine 12 16 75.0
pod 0 2 0.0
total 131 191 68.5


line stmt bran cond sub pod time code
1             package DBI::Log;
2              
3 1     1   88770 use 5.006;
  1         3  
4 1     1   6 no strict;
  1         2  
  1         20  
5 1     1   4 no warnings;
  1         2  
  1         31  
6 1     1   4 use DBI;
  1         3  
  1         30  
7 1     1   532 use Time::HiRes;
  1         1420  
  1         4  
8              
9             our $VERSION = "0.10";
10             our %opts = (
11             file => $file,
12             trace => 0,
13             timing => 0,
14             fh => undef,
15             exclude => undef,
16             );
17              
18             my $orig_execute = \&DBI::st::execute;
19             *DBI::st::execute = sub {
20 4     4   14797 my ($sth, @args) = @_;
21 4         63 my $log = pre_query("execute", $sth->{Database}, $sth, $sth->{Statement}, \@args);
22 4         32865 my $retval = $orig_execute->($sth, @args);
23 4         47 post_query($log);
24 4         25 return $retval;
25             };
26              
27             my $orig_selectall_arrayref = \&DBI::db::selectall_arrayref;
28             *DBI::db::selectall_arrayref = sub {
29 1     1   6484 my ($dbh, $query, $yup, @args) = @_;
30 1         20 my $log = pre_query("selectall_arrayref", $dbh, undef, $query, \@args);
31 1         53 my $retval = $orig_selectall_arrayref->($dbh, $query, $yup, @args);
32 1         216 post_query($log);
33 1         12 return $retval;
34             };
35              
36             my $orig_selectcol_arrayref = \&DBI::db::selectcol_arrayref;
37             *DBI::db::selectcol_arrayref = sub {
38 1     1   7356 my ($dbh, $query, $yup, @args) = @_;
39 1         36 my $log = pre_query("selectcol_arrayref", $dbh, undef, $query, \@args);
40 1         92 my $retval = $orig_selectcol_arrayref->($dbh, $query, $yup, @args);
41 1         151 post_query($log);
42 1         7 return $retval;
43             };
44              
45             my $orig_selectall_hashref = \&DBI::db::selectall_hashref;
46             *DBI::db::selectall_hashref = sub {
47 0     0   0 my ($dbh, $query, $yup, @args) = @_;
48 0         0 my $log = pre_query("selectall_hashref", $dbh, undef, $query, \@args);
49 0         0 my $retval = $orig_selectall_hashref->($dbh, $query, $yup, @args);
50 0         0 post_query($log);
51 0         0 return $retval;
52             };
53              
54             my $orig_selectrow_arrayref = \&DBI::db::selectrow_arrayref;
55             *DBI::db::selectrow_arrayref = sub {
56 0     0   0 my ($dbh, $query, $yup, @args) = @_;
57 0         0 my $log = pre_query("selectrow_arrayref", $dbh, $sth, $query, \@args);
58 0         0 my $retval = $orig_selectrow_arrayref->($dbh, $query, $yup, @args);
59 0         0 post_query($log);
60 0         0 return $retval;
61             };
62              
63             my $orig_selectrow_array = \&DBI::db::selectrow_array;
64             *DBI::db::selectrow_array = sub {
65 0     0   0 my ($dbh, $query, $yup, @args) = @_;
66 0         0 my $log = pre_query("selectrow_array", $dbh, undef, $query, \@args);
67 0         0 my $retval = $orig_selectrow_array->($dbh, $query, $yup, @args);
68 0         0 post_query($log);
69 0         0 return $retval;
70             };
71              
72             my $orig_selectrow_hashref = \&DBI::db::selectrow_hashref;
73             *DBI::db::selectrow_hashref = sub {
74 0     0   0 my ($dbh, $query, $yup, @args) = @_;
75 0         0 my $log = pre_query("selectrow_hashref", $dbh, undef, $query, \@args);
76 0         0 my $retval = $orig_selectrow_hashref->($dbh, $query, $yup, @args);
77 0         0 post_query($log);
78 0         0 return $retval;
79             };
80              
81             my $orig_do = \&DBI::db::do;
82             *DBI::db::do = sub {
83 2     2   13940 my ($dbh, $query, $yup, @args) = @_;
84 2         53 my $log = pre_query("do", $dbh, undef, $query, \@args);
85 2         88 my $retval = $orig_do->($dbh, $query, $yup, @args);
86 1         148 post_query($log);
87 1         6 return $retval;
88             };
89              
90              
91             sub import {
92 1     1   10 my ($package, %args) = @_;
93 1         3 for my $key (keys %args) {
94 1         3 $opts{$key} = $args{$key};
95             }
96 1 50       3 if (!$opts{file}) {
97 0         0 $opts{fh} = \*STDERR;
98             }
99             else {
100 1         1 my $file2 = $opts{file};
101 1 50       4 if ($file2 =~ m{^~/}) {
102 0   0     0 my $home = $ENV{HOME} || (getpwuid($<))[7];
103 0         0 $file2 =~ s{^~/}{$home/};
104             }
105 1 50       1873 open $opts{fh}, ">>", $file2 or die "Can't open $opts{file}: $!";
106             }
107             }
108              
109             sub pre_query {
110 8     8 0 112 my ($name, $dbh, $sth, $query, $args) = @_;
111 8         25 my $log = {};
112 8         15 my $mcount = 0;
113              
114             # Some DBI functions are composed of other DBI functions, so make sure we
115             # are only logging the top level one. For example $dbh->do() will call
116             # $dbh->execute() internally, so we need to make sure a DBI::Log function
117             # logs the $dbh->do() and not the internal $dbh->execute(). If multiple
118             # functions were called, we return and flag this log entry to be skipped in
119             # the post_query() part.
120 8         167 for (my $i = 0; my @caller = caller($i); $i++) {
121 20         80 my ($package, $file, $line, $sub) = @caller;
122 20 100       95 if ($package eq "DBI::Log") {
123 11         21 $mcount++;
124 11 100       163 if ($mcount > 1) {
125 3         41 $log->{skip} = 1;
126 3         34 return $log;
127             }
128             }
129             }
130 5         9 my @callers;
131 5         31 for (my $i = 0; my @caller = caller($i); $i++) {
132 11         90 push @callers, \@caller;
133             }
134              
135             # Order the call stack based on the highest level calls first, then the
136             # lower level calls. Once you reach a package that is excluded, do not show
137             # any more lines in the stack trace. By default, it will exclude anything
138             # past the DBI::Log package, but if user provides an exclude option, it will
139             # stop there.
140 5         7 my @filtered_callers;
141 5         11 CALLER: for my $caller (reverse @callers) {
142 11         51 my ($package, $file, $line, $sub) = @$caller;
143 11 100       28 if ($package eq "DBI::Log") {
144 5         15 last CALLER;
145             }
146 6 50       27 if ($opts{exclude}) {
147 0         0 for my $item (@{$opts{exclude}}) {
  0         0  
148 0 0       0 if ($package =~ /^$item(::|$)/) {
149 0         0 last CALLER;
150             }
151             }
152             }
153 6         15 push @filtered_callers, $caller;
154              
155             }
156 5 50       15 if (!$opts{trace}) {
157 5         13 @filtered_callers = ($filtered_callers[-1]);
158             }
159              
160 5         30 my $stack = "";
161 5         40 for my $caller (@filtered_callers) {
162 5         16 my ($package, $file, $line, $sub) = @$caller;
163 5         9 my $short_sub = $sub;
164 5         85 $short_sub =~ s/.*:://;
165 5 50       22 $short_sub = $name if $sub eq "DBI::Log::__ANON__";
166 5         24 $stack .= "-- $short_sub $file $line\n";
167             }
168              
169 5 100 66     27 if (ref($query) && ref($query) eq "DBI::st") {
170 1         3 $sth = $query;
171 1         18 $query = $query->{Statement};
172             }
173              
174 5 50       13 if ($dbh) {
175             # When you use $sth->bind_param(1, "value") the params can be found in
176             # $sth->{ParamValues} and they override arguments sent in to
177             # $sth->execute()
178              
179 5         13 my @args_copy = @$args;
180 5         20 my %values;
181 5 50 66     34 if ($sth && $sth->{ParamValues}) {
182 2         5 %values = %{$sth->{ParamValues}};
  2         16  
183             }
184 5         18 for my $key (keys %values) {
185 0 0 0     0 if (defined $key && $key =~ /^\d+$/) {
186 0         0 $args_copy[$key - 1] = $values{$key};
187             }
188             }
189              
190 5         23 for my $i (0 .. @args_copy - 1) {
191 4         25 my $value = $args_copy[$i];
192 4         102 $value = $dbh->quote($value);
193 4         104 $query =~ s{\?}{$value}e;
  4         28  
194             }
195             }
196              
197 5         74 $query =~ s/^\s*\n|\s*$//g;
198 5         234 $info = "-- " . scalar(localtime()) . "\n";
199 5         14 print {$opts{fh}} "$info$stack$query\n";
  5         48  
200 5         52 $log->{time1} = Time::HiRes::time();
201 5         53 return $log;
202             }
203              
204             sub post_query {
205 7     7 0 26 my ($log) = @_;
206 7 100       32 return if $log->{skip};
207 4 50       16 if ($opts{timing}) {
208 0         0 $log->{time2} = Time::HiRes::time();
209 0         0 my $diff = sprintf '%.3f', $log->{time2} - $log->{time1};
210 0         0 print {$opts{fh}} "-- ${diff}s\n";
  0         0  
211             }
212 4         7 print {$opts{fh}} "\n";
  4         22  
213             }
214              
215             1;
216              
217             __END__