From 961e6ff825027765ee22d7cbec0edc9d122ad28f Mon Sep 17 00:00:00 2001 From: Vitaliy Filippov Date: Sat, 1 Nov 2014 00:18:57 +0300 Subject: [PATCH] Support logging DB queries --- Bugzilla.pm | 3 +- Bugzilla/Config/General.pm | 6 + Bugzilla/DB.pm | 129 ++++++++++++++++++ Bugzilla/DB/Oracle.pm | 3 +- .../en/default/admin/params/general.html.tmpl | 3 + 5 files changed, 141 insertions(+), 3 deletions(-) diff --git a/Bugzilla.pm b/Bugzilla.pm index 34e08cdd0..6bb0d5f00 100644 --- a/Bugzilla.pm +++ b/Bugzilla.pm @@ -200,8 +200,6 @@ sub _tt_provider_load_compiled # Global Code ##################################################################### -# $::SIG{__DIE__} = i_am_cgi() ? \&CGI::Carp::confess : \&Carp::confess; - # Note that this is a raw subroutine, not a method, so $class isn't available. sub init_page { @@ -1083,6 +1081,7 @@ sub _cleanup { next if !$dbh; $dbh->bz_rollback_transaction() if $dbh->bz_in_transaction; + $dbh->write_query_log; $dbh->disconnect; } undef $_request_cache; diff --git a/Bugzilla/Config/General.pm b/Bugzilla/Config/General.pm index a2c39709f..6d2b72695 100644 --- a/Bugzilla/Config/General.pm +++ b/Bugzilla/Config/General.pm @@ -95,6 +95,12 @@ use constant get_param_list => ( default => 0, }, + { + name => 'query_log', + type => 't', + default => '', + }, + { name => 'docs_urlbase', type => 't', diff --git a/Bugzilla/DB.pm b/Bugzilla/DB.pm index 7ba9725b4..f4327287c 100644 --- a/Bugzilla/DB.pm +++ b/Bugzilla/DB.pm @@ -44,6 +44,7 @@ use Bugzilla::Error; use Bugzilla::DB::Schema; use List::Util qw(max); +use POSIX qw(strftime); use Storable qw(dclone); ##################################################################### @@ -1474,6 +1475,134 @@ sub _check_references { } } +sub interpolate_params +{ + my $self = shift; + my ($sql, $bind) = @_; + my $r = ''; + # match unquoted part + any number of quoted strings/identifiers + while ($sql =~ s!^([^"`']*)((?:`(?:[^`]+|``)*`|"(?:[^"\\]+|\\.|"")*"|'(?:[^'\\]+|\\.|'')*)*)!!so) + { + my ($c, $q) = ($1, $2); + last if $c eq '' && $q eq ''; + $c =~ s/\?/$self->quote(shift @$bind)/geso; + $r .= $c.$q; + } + return $r; +} + +sub write_query_log +{ + my $self = shift; + if (my $logfile = Bugzilla->params->{query_log}) + { + # Log queries + $logfile = bz_locations()->{datadir} . '/' . $logfile if substr($logfile, 0, 1) ne '/'; + trick_taint($logfile); + my $fd; + my @q = @{ tied(%$self)->{_logged_queries} || [] }; + if (@q && open $fd, ">>", $logfile) + { + print $fd strftime("[%Y-%m-%d %H:%M:%S] ", localtime) . + "[pid=$$] $ENV{REQUEST_URI} DB queries:\n" . join("\n", map { $self->interpolate_params(@$_).';' } @q) . "\n\n"; + close $fd; + } + tied(%$self)->{_logged_queries} = []; + } +} + +sub log_query +{ + my $self = shift; + push @{tied(%$self)->{_logged_queries}}, [ @_ ] if Bugzilla->params->{query_log}; +} + +sub get_logged_queries +{ + my $self = shift; + return tied(%$self)->{_logged_queries} || []; +} + +sub do +{ + my $self = shift; + my ($sql, undef, @bind) = @_; + $self->log_query($sql, \@bind); + $self->SUPER::do(@_); +} + +sub selectrow_array +{ + my $self = shift; + my ($sql, undef, @bind) = @_; + $self->log_query($sql, \@bind); + return $self->SUPER::selectrow_array(@_); +} + +sub selectrow_arrayref +{ + my $self = shift; + my ($sql, undef, @bind) = @_; + $self->log_query($sql, \@bind); + return $self->SUPER::selectrow_array(@_); +} + +sub selectrow_hashref +{ + my $self = shift; + my ($sql, undef, @bind) = @_; + $self->log_query($sql, \@bind); + return $self->SUPER::selectrow_hashref(@_); +} + +sub selectall_hashref +{ + my $self = shift; + my ($sql, undef, @bind) = @_; + $self->log_query($sql, \@bind); + return $self->SUPER::selectall_hashref(@_); +} + +sub selectall_arrayref +{ + my $self = shift; + my ($sql, undef, @bind) = @_; + $self->log_query($sql, \@bind); + return $self->SUPER::selectall_arrayref(@_); +} + +sub selectcol_arrayref +{ + my $self = shift; + my ($sql, undef, @bind) = @_; + $self->log_query($sql, \@bind); + return $self->SUPER::selectcol_arrayref(@_); +} + +sub prepare +{ + my $self = shift; + return bless $self->SUPER::prepare(@_), 'Bugzilla::DB::st'; +} + +sub prepare_cached +{ + my $self = shift; + return bless $self->SUPER::prepare_cached(@_), 'Bugzilla::DB::st'; +} + +package Bugzilla::DB::st; + +use base qw(DBI::st); + +sub execute +{ + my $self = shift; + my $dbh = tied(%$self)->{Database}->{Driver}->{ChildHandles}; + $dbh->[$#$dbh]->log_query(tied(%$self)->{Statement}, [ @_ ]); + return $self->SUPER::execute(@_); +} + 1; __END__ diff --git a/Bugzilla/DB/Oracle.pm b/Bugzilla/DB/Oracle.pm index f60bef232..ac255da10 100644 --- a/Bugzilla/DB/Oracle.pm +++ b/Bugzilla/DB/Oracle.pm @@ -778,7 +778,8 @@ sub bz_setup_database { } package Bugzilla::DB::Oracle::st; -use base qw(DBI::st); + +use base qw(Bugzilla::DB::st); sub fetchrow_arrayref { diff --git a/template/en/default/admin/params/general.html.tmpl b/template/en/default/admin/params/general.html.tmpl index 7de7229c1..cafe70e4c 100644 --- a/template/en/default/admin/params/general.html.tmpl +++ b/template/en/default/admin/params/general.html.tmpl @@ -40,6 +40,9 @@ report_user_errors_to_maintainer => "Whether to send e-mail messages about each 'user' (invalid input, non-fatal) error to Bugzilla maintainer (not recommended).", + query_log => + "Use only for debug purposes! Path to the file to which Bugzilla should log all database queries with all parameters.", + docs_urlbase => "The URL that is the common initial leading part of all" _ " $terms.Bugzilla documentation URLs. It may be an absolute URL,"