diff options
-rw-r--r-- | Bugzilla/Config/Advanced.pm | 2 | ||||
-rw-r--r-- | Bugzilla/Error.pm | 14 | ||||
-rw-r--r-- | Bugzilla/Install/Filesystem.pm | 2 | ||||
-rw-r--r-- | Bugzilla/Sentry.pm (renamed from Bugzilla/Arecibo.pm) | 228 | ||||
-rw-r--r--[-rwxr-xr-x] | sentry.pl (renamed from arecibo.pl) | 48 | ||||
-rw-r--r-- | template/en/default/admin/params/advanced.html.tmpl | 6 | ||||
-rw-r--r-- | template/en/default/global/code-error.html.tmpl | 2 |
7 files changed, 161 insertions, 141 deletions
diff --git a/Bugzilla/Config/Advanced.pm b/Bugzilla/Config/Advanced.pm index a5ae3048a..5e51fbecc 100644 --- a/Bugzilla/Config/Advanced.pm +++ b/Bugzilla/Config/Advanced.pm @@ -71,7 +71,7 @@ use constant get_param_list => ( }, { - name => 'arecibo_server', + name => 'sentry_uri', type => 't', default => '', }, diff --git a/Bugzilla/Error.pm b/Bugzilla/Error.pm index e49f466d6..08978fa93 100644 --- a/Bugzilla/Error.pm +++ b/Bugzilla/Error.pm @@ -28,7 +28,7 @@ use base qw(Exporter); @Bugzilla::Error::EXPORT = qw(ThrowCodeError ThrowTemplateError ThrowUserError ThrowErrorPage); -use Bugzilla::Arecibo; +use Bugzilla::Sentry; use Bugzilla::Constants; use Bugzilla::WebService::Constants; use Bugzilla::Util; @@ -110,9 +110,8 @@ sub _throw_error { message => \$message }); if (Bugzilla->error_mode == ERROR_MODE_WEBPAGE) { - if (arecibo_should_notify($vars->{error})) { + if (sentry_should_notify($vars->{error})) { $vars->{maintainers_notified} = 1; - $vars->{uid} = arecibo_generate_id(); $vars->{processed} = {}; } else { $vars->{maintainers_notified} = 0; @@ -123,8 +122,7 @@ sub _throw_error { || ThrowTemplateError($template->error()); if ($vars->{maintainers_notified}) { - arecibo_handle_error( - $vars->{error}, $vars->{processed}->{error_message}, $vars->{uid}); + sentry_handle_error($vars->{error}, $vars->{processed}->{error_message}); } } elsif (Bugzilla->error_mode == ERROR_MODE_TEST) { @@ -206,8 +204,7 @@ sub ThrowTemplateError { $vars->{'template_error_msg'} = $template_err; $vars->{'error'} = "template_error"; - $vars->{'uid'} = arecibo_generate_id(); - arecibo_handle_error('error', $template_err, $vars->{'uid'}); + sentry_handle_error('error', $template_err); $vars->{'template_error_msg'} =~ s/ at \S+ line \d+\.\s*$//; my $template = Bugzilla->template; @@ -218,7 +215,6 @@ sub ThrowTemplateError { my $maintainer = html_quote(Bugzilla->params->{'maintainer'}); my $error = html_quote($vars->{'template_error_msg'}); my $error2 = html_quote($template->error()); - my $uid = html_quote($vars->{'uid'}); print <<END; <tt> <p> @@ -232,7 +228,7 @@ sub ThrowTemplateError { --> <p> The <a href="mailto:$maintainer">Bugzilla maintainers</a> have - been notified of this error [#$uid]. + been notified of this error. </p> </tt> END diff --git a/Bugzilla/Install/Filesystem.pm b/Bugzilla/Install/Filesystem.pm index 86978163b..1abac0154 100644 --- a/Bugzilla/Install/Filesystem.pm +++ b/Bugzilla/Install/Filesystem.pm @@ -159,7 +159,7 @@ sub FILESYSTEM { 'runtests.pl' => { perms => OWNER_EXECUTE }, 'jobqueue.pl' => { perms => OWNER_EXECUTE }, 'migrate.pl' => { perms => OWNER_EXECUTE }, - 'arecibo.pl' => { perms => OWNER_EXECUTE }, + 'sentry.pl' => { perms => OWNER_EXECUTE }, 'install-module.pl' => { perms => OWNER_EXECUTE }, 'Bugzilla.pm' => { perms => CGI_READ }, diff --git a/Bugzilla/Arecibo.pm b/Bugzilla/Sentry.pm index 08ff11c20..e07c2eb44 100644 --- a/Bugzilla/Arecibo.pm +++ b/Bugzilla/Sentry.pm @@ -5,66 +5,34 @@ # This Source Code Form is "Incompatible With Secondary Licenses", as # defined by the Mozilla Public License, v. 2.0. -package Bugzilla::Arecibo; +package Bugzilla::Sentry; use strict; use warnings; use base qw(Exporter); our @EXPORT = qw( - arecibo_handle_error - arecibo_generate_id - arecibo_should_notify + sentry_handle_error + sentry_should_notify ); use Apache2::Log; use Apache2::SubProcess; use Carp; use Data::Dumper; -use Email::Date::Format qw(email_gmdate); +use DateTime; use File::Temp; use LWP::UserAgent; use Sys::Hostname; +use URI; use Bugzilla::Constants; +use Bugzilla::RNG qw(irand); use Bugzilla::Util; use Bugzilla::WebService::Constants; use constant CONFIG => { - # 'types' maps from the error message to types and priorities - types => [ - { - type => 'the_schwartz', - boost => -10, - match => [ - qr/TheSchwartz\.pm/, - ], - }, - { - type => 'database_error', - boost => -10, - match => [ - qr/DBD::mysql/, - qr/Can't connect to the database/, - ], - }, - { - type => 'patch_reader', - boost => +5, - match => [ - qr#/PatchReader/#, - ], - }, - { - type => 'uninitialized_warning', - boost => 0, - match => [ - qr/Use of uninitialized value/, - ], - }, - ], - - # 'codes' lists the code-errors which are sent to arecibo + # 'codes' lists the code-errors which are sent to sentry codes => [qw( bug_error chart_datafile_corrupt @@ -78,32 +46,58 @@ use constant CONFIG => { token_generation_error )], - # any error messages matching these regex's will not be sent to arecibo + # any error messages matching these regex's will not be sent to sentry ignore => [ qr/Software caused connection abort/, qr/Could not check out .*\/cvsroot/, ], + + # (ab)use the logger to classify error/warning types + logger => [ + { + match => [ + qr/DBD::mysql/, + qr/Can't connect to the database/, + ], + logger => 'database_error', + }, + { + match => [ qr#/PatchReader/# ], + logger => 'patchreader', + }, + { + match => [ qr/Use of uninitialized value/ ], + logger => 'uninitialized_warning', + }, + ], }; -sub arecibo_generate_id { - return sprintf("%s.%s", (time), $$); +sub sentry_generate_id { + return sprintf('%04x%04x%04x%04x%04x%04x%04x%04x', + irand(0xffff), irand(0xffff), + irand(0xffff), + irand(0x0fff) | 0x4000, + irand(0x3fff) | 0x8000, + irand(0xffff), irand(0xffff), irand(0xffff) + ); } -sub arecibo_should_notify { +sub sentry_should_notify { my $code_error = shift; - return grep { $_ eq $code_error } @{CONFIG->{codes}}; + return grep { $_ eq $code_error } @{ CONFIG->{codes} }; } -sub arecibo_handle_error { - my $class = shift; +sub sentry_handle_error { + my $level = shift; my @message = split(/\n/, shift); - my $id = arecibo_generate_id(); + my $id = sentry_generate_id(); - my $is_error = $class eq 'error'; - if ($class ne 'error' && $class ne 'warning') { + my $is_error = $level eq 'error'; + if ($level ne 'error' && $level ne 'warning') { # it's a code-error - return 0 unless arecibo_should_notify($class); + return 0 unless sentry_should_notify($level); $is_error = 1; + $level = 'error'; } # build traceback @@ -115,9 +109,9 @@ sub arecibo_handle_error { #local $Carp::MaxArgNums = 0; local $Carp::MaxArgNums = -1; local $Carp::CarpInternal{'CGI::Carp'} = 1; - local $Carp::CarpInternal{'Bugzilla::Error'} = 1; - local $Carp::CarpInternal{'Bugzilla::Arecibo'} = 1; - $traceback = Carp::longmess(); + local $Carp::CarpInternal{'Bugzilla::Error'} = 1; + local $Carp::CarpInternal{'Bugzilla::Sentry'} = 1; + $traceback = trim(Carp::longmess()); } # strip timestamp @@ -126,85 +120,91 @@ sub arecibo_handle_error { } my $message = join(" ", map { trim($_) } grep { $_ ne '' } @message); - # don't send to arecibo unless configured - my $arecibo_server = Bugzilla->params->{arecibo_server} || ''; - my $send_to_arecibo = $arecibo_server ne ''; + # determine logger + my $logger; + foreach my $config (@{ CONFIG->{logger} }) { + foreach my $re (@{ $config->{match} }) { + if ($message =~ $re) { + $logger = $config->{logger}; + last; + } + } + last if $logger; + } + $logger ||= $level; + + # don't send to sentry unless configured + my $send_to_sentry = Bugzilla->params->{sentry_uri} ? 1 : 0; # web service filtering - if ($send_to_arecibo + if ($send_to_sentry && (Bugzilla->error_mode == ERROR_MODE_DIE_SOAP_FAULT || Bugzilla->error_mode == ERROR_MODE_JSON_RPC)) { my ($code) = $message =~ /^(-?\d+): /; if ($code && !($code == ERROR_UNKNOWN_FATAL || $code == ERROR_UNKNOWN_TRANSIENT)) { - $send_to_arecibo = 0; + $send_to_sentry = 0; } } # message content filtering - if ($send_to_arecibo) { - foreach my $re (@{CONFIG->{ignore}}) { + if ($send_to_sentry) { + foreach my $re (@{ CONFIG->{ignore} }) { if ($message =~ $re) { - $send_to_arecibo = 0; + $send_to_sentry = 0; last; } } } + # for now, don't send patchreader errors to sentry + $send_to_sentry = 0 + if $logger eq 'patchreader'; + # log to apache's error_log - if ($send_to_arecibo) { - $message .= " [#$id]"; + if ($send_to_sentry) { + _write_to_error_log("$message [#$id]", $is_error); } else { $traceback =~ s/\n/ /g; - $message .= " $traceback"; + _write_to_error_log("$message $traceback", $is_error); } - _write_to_error_log($message, $is_error); - return 0 unless $send_to_arecibo; + return 0 unless $send_to_sentry; - # set the error type and priority from the message content - $message = join("\n", grep { $_ ne '' } @message); - my $type = ''; - my $priority = $class eq 'error' ? 3 : 10; - foreach my $rh_type (@{CONFIG->{types}}) { - foreach my $re (@{$rh_type->{match}}) { - if ($message =~ $re) { - $type = $rh_type->{type}; - $priority += $rh_type->{boost}; - last; - } + my $user_data = undef; + eval { + my $user = Bugzilla->user; + if ($user->id) { + $user_data = { + id => $user->login, + name => $user->name, + }; } - last if $type ne ''; - } - $type ||= $class; - $priority = 1 if $priority < 1; - $priority = 10 if $priority > 10; - - my $username = ''; - eval { $username = Bugzilla->user->login }; + }; - my $request = ''; - foreach my $name (sort { lc($a) cmp lc($b) } keys %ENV) { - $request .= "$name=$ENV{$name}\n"; - } - chomp($request); - - my $data = [ - ip => remote_ip(), - msg => $message, - priority => $priority, - server => hostname(), - request => $request, - status => '500', - timestamp => email_gmdate(), - traceback => $traceback, - type => $type, - uid => $id, - url => Bugzilla->cgi->self_url, - user_agent => $ENV{HTTP_USER_AGENT}, - username => $username, - ]; + my $uri = URI->new(Bugzilla->cgi->self_url); + $uri->query(undef); + + my $data = { + event_id => $id, + message => $message, + timestamp => DateTime->now->iso8601(), + level => $level, + platform => 'Other', + logger => $logger, + server_name => hostname(), + 'sentry.interfaces.User' => $user_data, + 'sentry.interfaces.Http' => { + url => $uri->as_string, + method => $ENV{REQUEST_METHOD}, + query_string => $ENV{QUERY_STRING}, + env => \%ENV, + }, + extra => { + stacktrace => $traceback, + }, + }; my $fh = File::Temp->new( UNLINK => 0 ); if (!$fh) { @@ -215,7 +215,7 @@ sub arecibo_handle_error { close($fh) or die $!; my $filename = $fh->filename; - my $command = bz_locations()->{'cgi_path'} . "/arecibo.pl '$filename' &"; + my $command = bz_locations()->{'cgi_path'} . "/sentry.pl '$filename' &"; system($command); return 1; } @@ -244,14 +244,14 @@ sub _in_eval { return $in_eval; } -sub _arecibo_die_handler { +sub _sentry_die_handler { my $message = shift; $message =~ s/^undef error - //; # avoid recursion, and check for CGI::Carp::die failures my $in_cgi_carp_die = 0; for (my $stack = 1; my $sub = (caller($stack))[3]; $stack++) { - return if $sub =~ /:_arecibo_die_handler$/; + return if $sub =~ /:_sentry_die_handler$/; $in_cgi_carp_die = 1 if $sub =~ /CGI::Carp::die$/; } @@ -274,7 +274,7 @@ sub _arecibo_die_handler { $in_cgi_carp_die || ($nested_error && $nested_error !~ /\bModPerl::Util::exit\b/) ) { - arecibo_handle_error('error', $message); + sentry_handle_error('error', $message); # and call the normal error management # (ISE for web pages, error response for web services, etc) @@ -283,18 +283,18 @@ sub _arecibo_die_handler { exit; } -sub install_arecibo_handler { +sub install_sentry_handler { require CGI::Carp; - CGI::Carp::set_die_handler(\&_arecibo_die_handler); + CGI::Carp::set_die_handler(\&_sentry_die_handler); $main::SIG{__WARN__} = sub { return if _in_eval(); - arecibo_handle_error('warning', shift); + sentry_handle_error('warning', shift); }; } BEGIN { if ($ENV{SCRIPT_NAME} || $ENV{MOD_PERL}) { - install_arecibo_handler(); + install_sentry_handler(); } } diff --git a/arecibo.pl b/sentry.pl index 93c34c999..b5b9c3f31 100755..100644 --- a/arecibo.pl +++ b/sentry.pl @@ -8,9 +8,9 @@ # defined by the Mozilla Public License, v. 2.0. # -# report errors to arecibo +# report errors to sentry # expects a filename with a Data::Dumper serialised parameters -# called by Bugzilla::Arecibo +# called by Bugzilla::Sentry # use strict; @@ -22,10 +22,15 @@ use lib "$Bin/lib"; use Bugzilla; use Bugzilla::Constants; +use Bugzilla::RNG qw(irand); +use Fcntl qw(:flock); use File::Slurp; +use HTTP::Request::Common; +use JSON (); +use LWP::UserAgent; use POSIX qw(setsid nice); use Safe; -use Fcntl qw(:flock); +use URI; Bugzilla->usage_mode(USAGE_MODE_CMDLINE); nice(19); @@ -36,9 +41,9 @@ open(STDOUT, '>/dev/null'); open(STDERR, '>/dev/null'); setsid(); -# grab arecibo server url -my $arecibo_server = Bugzilla->params->{arecibo_server} || ''; -exit(1) unless $arecibo_server; +# grab sentry server url +my $sentry_uri = Bugzilla->params->{sentry_uri} || ''; +exit(1) unless $sentry_uri; # read data dump exit(1) unless my $filename = shift; @@ -50,15 +55,34 @@ my $cpt = new Safe; $cpt->reval($dump) || exit(1); my $data = ${$cpt->varglob('VAR1')}; +# split the sentry uri +my $uri = URI->new($sentry_uri); +my ($public_key, $secret_key) = split(/:/, $uri->userinfo); +$uri->userinfo(undef); +my $project_id = $uri->path; +$project_id =~ s/^\///; +$uri->path("/api/$project_id/store/"); + +# build the message +my $message = JSON->new->utf8(1)->pretty(0)->allow_nonref(1)->encode($data); +my %header = ( + 'X-Sentry-Auth' => sprintf( + "Sentry sentry_version=%s, sentry_timestamp=%s, sentry_key=%s, sentry_client=%s, sentry_secret=%s", + '2.0', + (time), + $public_key, + 'bugzilla/4.2', + $secret_key, + ), + 'Content-Type' => 'application/json' +); + # ensure we send warnings one at a time per webhead flock(DATA, LOCK_EX); -# and post to arecibo -my $agent = LWP::UserAgent->new( - agent => 'bugzilla.mozilla.org', - timeout => 10, # seconds -); -$agent->post($arecibo_server, $data); +# and post to sentry +my $request = POST $uri->canonical, %header, Content => $message; +my $response = LWP::UserAgent->new->request($request); __DATA__ this exists so the flock() code works. diff --git a/template/en/default/admin/params/advanced.html.tmpl b/template/en/default/admin/params/advanced.html.tmpl index 1cf0c344f..5301ff2cf 100644 --- a/template/en/default/admin/params/advanced.html.tmpl +++ b/template/en/default/admin/params/advanced.html.tmpl @@ -82,8 +82,8 @@ disable_bug_updates => "When enabled, all updates to $terms.bugs will be blocked.", - arecibo_server => + sentry_uri => "When set, important errors and warnings will be sent to the" - _ " specified Arecibo server. Enter the Arecibo server's full URL;" - _ " eg <code>https://arecibo.example.com/v/1/</code>.", + _ " specified Sentry server. Enter the full API KEY URL." + _ " eg <code>https://01234567890123456780123456780123:01234567890123456780123456780123@errormill.mozilla.org/10</code>.", } %] diff --git a/template/en/default/global/code-error.html.tmpl b/template/en/default/global/code-error.html.tmpl index 704d3ad16..d98f2578c 100644 --- a/template/en/default/global/code-error.html.tmpl +++ b/template/en/default/global/code-error.html.tmpl @@ -506,7 +506,7 @@ admindocslinks = admindocslinks %] -[%# return the generated error_message for arecibo %] +[%# return the generated error_message for sentry %] [% processed.error_message = error_message %] <p> |