# $Id: db.pm 245 2006-07-25 14:20:59Z martin $ use strict; use warnings; use DBI; use Log::Log4perl; use Data::Dumper; package DBIx::Log4perl::db; @DBIx::Log4perl::db::ISA = qw(DBI::db DBIx::Log4perl); use DBIx::Log4perl::Constants qw (:masks $LogMask); # $_glogger is not relied upon - it is just a fallback my $_glogger; my $_counter; # to hold sub to count BEGIN { my $x = sub { my $start = shift; return sub {$start++}}; $_counter = &$x(0); # used to count dbh connections } sub STORE{ my $dbh = shift; my @args = @_; my $h = $dbh->{private_DBIx_Log4perl}; # as we don't set private_DBIx_Log4perl until the connect method sometimes # $h will not be set $dbh->_dbix_l4p_debug($h, 2, "STORE($h->{dbh_no})", @args) if ($h && ($h->{logmask} & DBIX_L4P_LOG_INPUT)); return $dbh->SUPER::STORE(@args); } sub get_info { my ($dbh, @args) = @_; my $h = $dbh->{private_DBIx_Log4perl}; my $value = $dbh->SUPER::get_info(@args); $dbh->_dbix_l4p_debug($h, 2, "get_info($h->{dbh_no})", @args, $value) if ($h->{logmask} & DBIX_L4P_LOG_INPUT); return $value; } sub prepare { my($dbh, @args) = @_; my $h = $dbh->{private_DBIx_Log4perl}; my $ctr = $h->{new_stmt_no}(); # get a new unique stmt counter in this dbh if (($h->{logmask} & (DBIX_L4P_LOG_INPUT|DBIX_L4P_LOG_SQL)) && (caller !~ /^DBIx::Log4perl/o) && (caller !~ /^DBD::/o)) { # e.g. from selectall_arrayref $dbh->_dbix_l4p_debug($h, 2, "prepare($h->{dbh_no}.$ctr)", $args[0]); } my $sth = $dbh->SUPER::prepare(@args); if ($sth) { $sth->{private_DBIx_Log4perl} = $h; $sth->{private_DBIx_st_no} = $ctr; } return $sth; } sub prepare_cached { my($dbh, @args) = @_; my $h = $dbh->{private_DBIx_Log4perl}; my $ctr = $h->{new_stmt_no}(); if (($h->{logmask} & (DBIX_L4P_LOG_INPUT|DBIX_L4P_LOG_SQL)) && (caller !~ /^DBIx::Log4perl/o) && (caller !~ /^DBD::/o)) { # e.g. from selectall_arrayref $dbh->_dbix_l4p_debug($h, 2, "prepare_cached($h->{dbh_no}.$ctr)", $args[0]); } my $sth = $dbh->SUPER::prepare_cached(@args); if ($sth) { $sth->{private_DBIx_Log4perl} = $h; $sth->{private_DBIx_st_no} = $ctr; } return $sth; } sub do { my ($dbh, @args) = @_; my $h = $dbh->{private_DBIx_Log4perl}; $h->{Statement} = $args[0]; $dbh->_dbix_l4p_debug($h, 2, "do($h->{dbh_no})", @args) if ($h->{logmask} & (DBIX_L4P_LOG_INPUT|DBIX_L4P_LOG_SQL)); my $affected = $dbh->SUPER::do(@args); if (!defined($affected)) { $dbh->_dbix_l4p_error(2, 'do error for ', @args) if (($h->{logmask} & DBIX_L4P_LOG_ERRCAPTURE) && !($h->{logmask} & DBIX_L4P_LOG_INPUT)); # not already logged } elsif (defined($affected) && $affected eq '0E0' && ($h->{logmask} & DBIX_L4P_LOG_WARNINGS)) { $dbh->_dbix_l4p_warning(2, 'no effect from ', @args); } elsif (($affected ne '0E0') && ($h->{logmask} & DBIX_L4P_LOG_INPUT)) { $dbh->_dbix_l4p_debug($h, 2, "affected($h->{dbh_no})", $affected); $dbh->_dbix_l4p_debug($h, 2, "\t" . $dbh->SUPER::errstr) if (!defined($affected)); } return $affected; } sub selectrow_array { my ($dbh, @args) = @_; my $h = $dbh->{private_DBIx_Log4perl}; if ($h->{logmask} & (DBIX_L4P_LOG_INPUT|DBIX_L4P_LOG_SQL)) { if ((scalar(@args) > 0) && (ref $args[0])) { $dbh->_dbix_l4p_debug($h, 2, "selectrow_array($h->{dbh_no}." . $args[0]->{private_DBIx_st_no} . ")", @args); } else { $dbh->_dbix_l4p_debug($h, 2, "selectrow_array($h->{dbh_no})", @args); } } if (wantarray) { my @ret = $dbh->SUPER::selectrow_array(@args); $dbh->_dbix_l4p_debug($h, 2, "result($h->{dbh_no})", @ret) if ($h->{logmask} & DBIX_L4P_LOG_OUTPUT); return @ret; } else { my $ret = $dbh->SUPER::selectrow_array(@args); $dbh->_dbix_l4p_debug($h, 2, "result($h->{dbh_no})", $ret) if ($h->{logmask} & DBIX_L4P_LOG_OUTPUT); return $ret; } } sub selectrow_arrayref { my ($dbh, @args) = @_; my $h = $dbh->{private_DBIx_Log4perl}; if ($h->{logmask} & (DBIX_L4P_LOG_INPUT|DBIX_L4P_LOG_SQL)) { if ((scalar(@args) > 0) && (ref $args[0])) { $dbh->_dbix_l4p_debug( $h, 2, "selectrow_arrayref($h->{dbh_no}." . $args[0]->{private_DBIx_st_no} . ")", @args); } else { $dbh->_dbix_l4p_debug( $h, 2, "selectrow_arrayref($h->{dbh_no})", @args); } } my $ref = $dbh->SUPER::selectrow_arrayref(@args); $dbh->_dbix_l4p_debug($h, 2, "result($h->{dbh_no})", $ref) if ($h->{logmask} & DBIX_L4P_LOG_OUTPUT); return $ref; } sub selectrow_hashref { my ($dbh, @args) = @_; my $h = $dbh->{private_DBIx_Log4perl}; if ($h->{logmask} & (DBIX_L4P_LOG_INPUT|DBIX_L4P_LOG_SQL)) { if ((scalar(@args) > 0) && (ref $args[0])){ $dbh->_dbix_l4p_debug( $h, 2, "selectrow_hashref($h->{dbh_no}." . $args[0]->{private_DBIx_st_no} . ")", @args) } else { $dbh->_dbix_l4p_debug($h, 2, "selectrow_hashref($h->{dbh_no})", @args); } } my $ref = $dbh->SUPER::selectrow_hashref(@args); # no need to show result - fetch will do this return $ref; } sub selectall_arrayref { my ($dbh, @args) = @_; my $h = $dbh->{private_DBIx_Log4perl}; if ($h->{logmask} & (DBIX_L4P_LOG_INPUT|DBIX_L4P_LOG_SQL)) { if ((scalar(@args) > 0) && (ref $args[0])) { $dbh->_dbix_l4p_debug( $h, 2, "selectall_arrayref($h->{dbh_no}." . $args[0]->{private_DBIx_st_no} . ")", @args); } else { $dbh->_dbix_l4p_debug( $h, 2, "selectall_arrayref($h->{dbh_no})", @args); } } my $ref = $dbh->SUPER::selectall_arrayref(@args); $dbh->_dbix_l4p_debug($h, 2, "result($h->{dbh_no})", $ref) if ($h->{logmask} & DBIX_L4P_LOG_OUTPUT); return $ref; } sub selectall_hashref { my ($dbh, @args) = @_; my $h = $dbh->{private_DBIx_Log4perl}; if ($h->{logmask} & (DBIX_L4P_LOG_INPUT|DBIX_L4P_LOG_SQL)) { if ((scalar(@args) > 0) && (ref $args[0])) { $dbh->_dbix_l4p_debug( $h, 2, "selectall_hashref($h->{dbh_no}." . $args[0]->{private_DBIx_st_no} . ")", @args); } else { $dbh->_dbix_l4p_debug($h, 2, "selectall_hashref($h->{dbh_no})", @args); } } my $ref = $dbh->SUPER::selectall_hashref(@args); # no need to show result - fetch will do this return $ref; } sub _make_counter { my $start = shift; return sub {$start++} }; sub connected { my ($dbh, $dsn, $user, $pass, $attr) = @_; my %h = (); $h{dbh_no} = &$_counter(); $h{new_stmt_no} = _make_counter(0); # get a new stmt count for this dbh if ($attr) { # check we have not got dbix_l4p_init without dbix_l4p_log or vice versa my ($a, $b) = (exists($attr->{dbix_l4p_init}), exists($attr->{dbix_l4p_class})); croak ('dbix_l4p_init specified without dbix_l4p_class or vice versa') if (($a xor $b)); # if passed a Log4perl log handle use that if (exists($attr->{dbix_l4p_logger})) { $h{logger} = $attr->{dbix_l4p_logger}; } elsif ($a && $b) { Log::Log4perl->init($attr->{dbix_l4p_init}); $h{logger} = Log::Log4perl->get_logger($attr->{dbix_l4p_class}); $h{init} = $attr->{dbix_l4p_init}; $h{class} = $attr->{dbix_l4p_class}; } else { $h{logger} = Log::Log4perl->get_logger('DBIx::Log4perl'); } # save log mask $h{logmask} = $attr->{dbix_l4p_logmask} if (exists($attr->{dbix_l4p_logmask})); # save error regexp $h{err_regexp} = $attr->{dbix_l4p_ignore_err_regexp} if (exists($attr->{dbix_l4p_ignore_err_regexp})); # remove our attrs from connection attrs #delete $attr->{dbix_l4p_init}; #delete $attr->{dbix_l4p_class}; #delete $attr->{dbix_l4p_logger}; #delete $attr->{dbix_l4p_logmask}; #delete $attr->{dbix_l4p_ignore_err_regexp}; } # take global log mask if non defined $h{logmask} = $LogMask unless (exists($h{logmask})); $h{logger} = Log::Log4perl->get_logger('DBIx::Log4perl') unless (exists($h{logger})); $_glogger = $h{logger}; # make sure you don't change the depth before calling get_logger: local $Log::Log4perl::caller_depth = $Log::Log4perl::caller_depth + 4; $h{dbd_specific} = 0; $h{driver} = $dbh->{Driver}->{Name}; $dbh->{private_DBIx_Log4perl} = \%h; if ($h{logmask} & DBIX_L4P_LOG_CONNECT) { local $Data::Dumper::Indent = 0; $h{logger}->debug( "connect($h{dbh_no}): " . (defined($dsn) ? $dsn : '') . ', ' . (defined($user) ? $user : '') . ', ' . Data::Dumper->Dump([$attr], [qw(attr)])); no strict 'refs'; my $v = "DBD::" . $dbh->{Driver}->{Name} . "::VERSION"; $h{logger}->info("DBI: " . $DBI::VERSION, ", DBIx::Log4perl: " . $DBIx::Log4perl::VERSION . ", Driver: " . $h{driver} . "(" . $$v . ")"); } # # If capturing errors then save any error handler and set_err Handler # passed to us and replace with our own. # if ($h{logmask} & DBIX_L4P_LOG_ERRCAPTURE) { $h{HandleError} = $attr->{HandleError} if (exists($attr->{HandleError})); $h{HandleSetErr} = $attr->{HandleSetErr} if (exists($attr->{HandleSetErr})); $dbh->{HandleError} = \&_error_handler; $dbh->{HandleSetErr} = \&_set_err_handler; } return; } sub clone { my ($dbh, @args) = @_; my $h = $dbh->{private_DBIx_Log4perl}; if ($h->{logmask} & DBIX_L4P_LOG_CONNECT) { $dbh->_dbix_l4p_debug($h, 2, "clone($h->{dbh_no})", @args); } return $dbh->SUPER::clone(@args); } sub disconnect { my $dbh = shift; if ($dbh) { my $h; eval { # Avoid # (in cleanup) Can't call method "FETCH" on an undefined value $h = $dbh->{private_DBIx_Log4perl}; }; if (!$@ && $h && defined($h->{logger})) { if ($h->{logmask} & DBIX_L4P_LOG_CONNECT) { local $Log::Log4perl::caller_depth = $Log::Log4perl::caller_depth + 2; $dbh->_dbix_l4p_debug($h, 2, "disconnect($h->{dbh_no})"); } } } return $dbh->SUPER::disconnect; } sub begin_work { my $dbh = shift; my $h = $dbh->{private_DBIx_Log4perl}; $dbh->_dbix_l4p_debug($h, 2, "start transaction($h->{dbh_no})") if ($h->{logmask} & DBIX_L4P_LOG_TXN); return $dbh->SUPER::begin_work; } sub rollback { my $dbh = shift; my $h = $dbh->{private_DBIx_Log4perl}; $dbh->_dbix_l4p_debug($h, 2, "roll back($h->{dbh_no})") if ($h->{logmask} & DBIX_L4P_LOG_TXN); return $dbh->SUPER::rollback; } sub commit { my $dbh = shift; my $h = $dbh->{private_DBIx_Log4perl}; $dbh->_dbix_l4p_debug($h, 2, "commit($h->{dbh_no})") if ($h->{logmask} & DBIX_L4P_LOG_TXN); return $dbh->SUPER::commit; } sub last_insert_id { my ($dbh, @args) = @_; my $h = $dbh->{private_DBIx_Log4perl}; $dbh->_dbix_l4p_debug($h, 2, sub {Data::Dumper->Dump([\@args], ["last_insert_id($h->{dbh_no})"])}) if ($h->{logmask} & DBIX_L4P_LOG_INPUT); my $ret = $dbh->SUPER::last_insert_id(@args); $dbh->_dbix_l4p_debug($h, 2, sub {"\t" . DBI::neat($ret)}) if ($h->{logmask} & DBIX_L4P_LOG_INPUT); return $ret; } # # Error handler to capture errors and log them # Whatever, errors are passed on. # if the user of DBIx::Log4perl passed in an error handler that is called # before returning. # sub _error_handler { my ($msg, $handle, $method_ret) = @_; local $Log::Log4perl::caller_depth = $Log::Log4perl::caller_depth + 1; my $dbh = $handle; my $lh; my $h = $handle->{private_DBIx_Log4perl}; my $out = ''; $lh = $_glogger; $lh = $h->{logger} if ($h && exists($h->{logger})); return 0 if (!$lh); if ($h && exists($h->{err_regexp})) { if ($dbh->err =~ $h->{err_regexp}) { goto FINISH; } } # start with error message, state and err $out .= ' ' . '=' x 60 . "\n $msg\n"; $out .= "err() = " . $handle->err . "\n"; $out .= "state() = " . $handle->state . "\n"; if ($DBI::lasth) { $out .= " lasth type: $DBI::lasth->{Type}\n" if ($DBI::lasth->{Type}); $out .= " lasth Statement ($DBI::lasth):\n " . "$DBI::lasth->{Statement}\n" if ($DBI::lasth->{Statement}); } # get db handle if we have an st my $type = $handle->{Type}; my $sql; if ($type eq 'st') { # given statement handle $dbh = $handle->{Database}; $sql = $handle->{Statement}; } else { # given db handle # We've got other stmts under this db but we'll deal with those later $sql = 'Possible SQL: '; $sql .= "/$h->{Statement}/" if (exists($h->{Statement})); $sql .= "/$dbh->{Statement}/" if ($dbh->{Statement} && (exists($h->{Statement}) && ($dbh->{Statement} ne $h->{Statement}))); } my $dbname = exists($dbh->{Name}) ? $dbh->{Name} : ""; my $username = exists($dbh->{Username}) ? $dbh->{Username} : ""; $out .= " DB: $dbname, Username: $username\n"; $out .= " handle type: $type\n SQL: " . DBI::neat($sql) . "\n"; $out .= ' db Kids=' . $dbh->{Kids} . ', ActiveKids=' . $dbh->{ActiveKids} . "\n"; $out .= " DB errstr: " . $handle->errstr . "\n" if ($handle->errstr && ($handle->errstr ne $msg)); if (exists($h->{ParamValues}) && $h->{ParamValues}) { $out .= " ParamValues captured in HandleSetErr:\n "; foreach (sort keys %{$h->{ParamValues}}) { $out .= "$_=" . DBI::neat($h->{ParamValues}->{$_}) . ","; } $out .= "\n"; } if ($type eq 'st') { my $str = ""; if ($handle->{ParamValues}) { foreach (sort keys %{$handle->{ParamValues}}) { $str .= "$_=" . DBI::neat($handle->{ParamValues}->{$_}) . ","; } } $out .= " ParamValues: $str\n"; $out .= " " . Data::Dumper->Dump([$handle->{ParamArrays}], ['ParamArrays']) if ($handle->{ParamArrays}); } my @substmts; # get list of statements under the db push @substmts, $_ for (grep { defined } @{$dbh->{ChildHandles}}); $out .= " " . scalar(@substmts) . " sub statements:\n"; if (scalar(@substmts)) { foreach my $stmt (@substmts) { $out .= " stmt($stmt):\n"; $out .= ' SQL(' . $stmt->{Statement} . ")\n " if ($stmt->{Statement} && (exists($h->{Statement}) && ($h->{Statement} ne $stmt->{Statement}))); if (exists($stmt->{ParamValues}) && $stmt->{ParamValues}) { $out .= ' Params('; foreach (sort keys %{$stmt->{ParamValues}}) { $out .= "$_=" . DBI::neat($stmt->{ParamValues}->{$_}) . ","; } $out .= ")\n"; } } } if (exists($dbh->{Callbacks})) { $out .= " Callbacks exist for " . join(",", keys(%{$dbh->{Callbacks}})) . "\n"; } local $Carp::MaxArgLen = 256; $out .= " " .Carp::longmess("DBI error trap"); $out .= " " . "=" x 60 . "\n"; $lh->fatal($out); FINISH: if ($h && exists($h->{ErrorHandler})) { return $h->{ErrorHandler}($msg, $handle, $method_ret); } else { return 0; # pass error on } } # # set_err handler so we can capture ParamValues before a statement # is destroyed. # If the use of DBIx::Log4perl passed in an error handler that is # called before returning. # sub _set_err_handler { my ($handle, $err, $errstr, $state, $method) = @_; # Capture ParamValues if ($handle) { my $h = $handle->{private_DBIx_Log4perl}; $h->{ParamValues} = $handle->{ParamValues} if (exists($handle->{ParamValues})); return $h->{HandleSetErr}(@_) if (exists($h->{HandleSetErr})); } return 0; } 1;