File Coverage

blib/lib/Mojolicious/Plugin/ErrorTracking/Sentry.pm
Criterion Covered Total %
statement 51 60 85.0
branch 5 12 41.6
condition 2 7 28.5
subroutine 12 13 92.3
pod 1 1 100.0
total 71 93 76.3


line stmt bran cond sub pod time code
1             package Mojolicious::Plugin::ErrorTracking::Sentry;
2 2     2   69551 use 5.008001;
  2         10  
3 2     2   10 use strict;
  2         3  
  2         34  
4 2     2   8 use warnings;
  2         4  
  2         70  
5              
6             our $VERSION = '1.1.0';
7              
8 2     2   437 use Mojo::Base 'Mojolicious::Plugin';
  2         298164  
  2         15  
9              
10 2     2   2797 use English '-no_match_vars';
  2         9991  
  2         22  
11 2     2   2046 use Data::Dump 'dump';
  2         6107  
  2         265  
12 2     2   757 use Devel::StackTrace;
  2         6603  
  2         68  
13 2     2   1013 use Sentry::Raven;
  2         227109  
  2         1518  
14              
15             sub register {
16 1     1 1 42 my ( $self, $app, $conf ) = @_;
17             my $raven = Sentry::Raven->new(
18             environment => $conf->{environment},
19             release => $conf->{release},
20             sentry_dsn => $conf->{sentry_dsn},
21             timeout => $conf->{timeout},
22 1         10 );
23              
24             $app->helper(
25             capture_message => sub {
26 0     0   0 my ( $self, $data, %p ) = @_;
27              
28 0   0     0 $p{level} ||= 'info';
29 0         0 my $stacktrace = Devel::StackTrace->new( skip_frames => 2 );
30 0         0 my $context = _make_context( $raven, $self->req, $stacktrace, \%p );
31 0         0 my $event_id = $raven->capture_message( $data, %$context );
32 0 0       0 if ( !defined($event_id) ) {
33 0         0 die "failed to submit event to sentry service:\n"
34             . dump( $raven->_construct_message_event( $data, %$context ) );
35             }
36             }
37 1         25293 );
38              
39             $app->hook(
40             around_dispatch => sub {
41 2     2   244508 my ( $next, $c ) = @_;
42              
43 2         4 my $stacktrace;
44 2 100       6 return if eval {
45 2         17 local $SIG{__DIE__} = sub { $stacktrace = Devel::StackTrace->new( skip_frames => 1 ) };
  3         30467  
46 2         7 $next->();
47 1         2914 1;
48             };
49              
50 1         2515 my $message = my $eval_error = $EVAL_ERROR;
51 1         4 chomp($message);
52              
53 1         4 my $custom_context = _build_custom_context( $conf, $c );
54 1         154 my $context = _make_context( $raven, $c->req, $stacktrace, $custom_context );
55 1   50     10 my %exception_context = ( type => ( ref $eval_error ) || 'Mojo::Exception' );
56 1         7 my $event_id = $raven->capture_exception( $message, %exception_context, %$context );
57 1 50       318104 if ( !defined($event_id) ) {
58 1         9 die "failed to submit event to sentry service:\n"
59             . dump( $raven->_construct_exception_event( $message, %exception_context, %$context ) );
60             }
61              
62             # Raise error for Mojolicious
63 0 0       0 return ref $eval_error ? CORE::die($eval_error) : Mojo::Exception->throw($eval_error);
64             }
65 1         149 );
66              
67 1         22 return 1;
68             } ## end sub register
69              
70             sub _build_custom_context {
71 1     1   3 my ( $conf, $c ) = @_;
72 1 50 33     12 return $conf->{on_error}->($c) if ( $conf->{on_error} && ref( $conf->{on_error} ) eq 'CODE' );
73 0         0 return {};
74             }
75              
76             sub _make_context {
77 1     1   17 my ( $raven, $req, $stacktrace, $custom_context ) = @_;
78             my %request_context = $raven->request_context(
79             $req->url->to_string,
80             method => $req->method,
81             data => $req->params->to_hash,
82 1         6 headers => { map { $_ => ~~ $req->headers->header($_) } @{ $req->headers->names } },
  4         70  
  1         579  
83             );
84 1 50       53 my %stacktrace_context
85             = $stacktrace ? $raven->stacktrace_context( $raven->_get_frames_from_devel_stacktrace($stacktrace) ) : ();
86              
87 1         20248 return { culprit => $PROGRAM_NAME, %$custom_context, %request_context, %stacktrace_context, };
88             } ## end sub _make_context
89              
90             1;
91             __END__
92              
93             =encoding utf-8
94              
95             =head1 NAME
96              
97             Mojolicious::Plugin::ErrorTracking::Sentry - error tracking plugin for Mojolicious with Sentry
98              
99             =head1 SYNOPSIS
100              
101             # Mojolicious
102             $self->plugin('ErrorTracking::Sentry', sentry_dsn => 'http://<publickey>:<secretkey>@app.getsentry.com/<projectid>');
103              
104             # Custom error context handling
105             use Sentry::Raven;
106              
107             $self->plugin('ErrorTracking::Sentry',
108             sentry_dsn => 'http://<publickey>:<secretkey>@app.getsentry.com/<projectid>',
109             on_error => sub {
110             my $c = shift;
111             # Make context you want.
112             my %user_context = Sentry::Raven->user_context(
113             id => $c->stash->{user}->{id},
114             );
115             return \%user_context; # Must return HashRef.
116             },
117             );
118              
119             =head1 DESCRIPTION
120              
121             Mojolicious::Plugin::ErrorTracking::Sentry is a Mojolicious plugin to send error reports to Sentry.
122              
123             =head1 CONFIG
124              
125             =head2 C<< sentry_dsn => 'http://<publickey>:<secretkey>@app.getsentry.com/<projectid>' >>
126              
127             The DSN for your sentry service. Get this from the client configuration page for your project.
128              
129             =head2 C<< timeout => 5 >>
130              
131             Do not wait longer than this number of seconds when attempting to send an event.
132              
133             =head2 C<on_error>
134              
135             You can pass custom error context. For example
136              
137             $self->plugin('ErrorTracking::Sentry', on_error => sub {
138             my $c = shift;
139             return +{
140             Sentry::Raven->user_context(id => $c->stash->{id}) ,
141             };
142             });
143              
144             =head1 SEE ALSO
145              
146             =over 4
147              
148             =item L<Sentry::Raven>
149              
150             This plugin uses Sentry::Raven.
151              
152             =back
153              
154             =head1 AUTHORS
155              
156             Akira Osada E<lt>osd.akira@gmail.comE<gt>
157              
158             Andrew Pam E<lt>apam@infoxchange.orgE<gt>
159              
160             =head1 COPYRIGHT AND LICENSE
161              
162             Copyright 2017, 2019 Akira Osada and Andrew Pam
163              
164             Released under the MIT license
165             http://opensource.org/licenses/mit-license.php
166              
167             =cut