File Coverage

blib/lib/Mojolicious/Plugin/Airbrake.pm
Criterion Covered Total %
statement 10 12 83.3
branch n/a
condition n/a
subroutine 4 4 100.0
pod n/a
total 14 16 87.5


line stmt bran cond sub pod time code
1             package Mojolicious::Plugin::Airbrake;
2              
3 1     1   40190 use 5.010001;
  1         4  
  1         40  
4 1     1   6 use strict;
  1         2  
  1         43  
5 1     1   5 use warnings;
  1         12  
  1         115  
6 1     1   417 use Mojo::Base 'Mojolicious::Plugin';
  0            
  0            
7             use Mojo::UserAgent;
8             use Data::Dumper;
9              
10             our $VERSION = '0.01';
11              
12             has 'api_key';
13             has 'airbrake_base_url' => 'https://airbrake.io/api/v3/projects/';
14             has 'ua' => sub { Mojo::UserAgent->new() };
15             has 'project_id';
16             has 'pending' => sub { {} };
17             has 'include_session' => 1;
18             has 'debug' => 0;
19              
20             has url => sub {
21             my $self = shift;
22              
23             return $self->airbrake_base_url . $self->project_id . '/notices?key=' . $self->api_key;
24             };
25              
26             has user_id_sub_ref => sub {
27             return sub {
28             return 'n/a';
29             }
30             };
31              
32             sub register {
33             my ($self, $app, $conf) = (@_);
34             $conf ||= {};
35             $self->{$_} = $conf->{$_} for keys %$conf;
36              
37             $self->_hook_after_dispatch($app);
38             $self->_hook_on_message($app);
39              
40             }
41              
42             sub _hook_after_dispatch {
43             my $self = shift;
44             my $app = shift;
45              
46             $app->hook(after_dispatch => sub {
47             my $c = shift;
48              
49             if (my $ex = $c->stash('exception')) {
50             # Mark this exception as handled. We don't delete it from $pending
51             # because if the same exception is logged several times within a
52             # 2-second period, we want the logger to ignore it.
53             $self->pending->{$ex} = 0 if defined $self->pending->{$ex};
54             $self->notify($ex, $app, $c);
55             }
56              
57             });
58              
59             }
60              
61             sub _hook_on_message {
62             my $self = shift;
63             my $app = shift;
64              
65             $app->log->on(message => sub {
66             my ($log, $level, $ex) = @_;
67             if ($level eq 'error') {
68             $ex = Mojo::Exception->new($ex) unless ref $ex;
69            
70             # This exception is already pending
71             return if defined $self->pending->{$ex};
72            
73             $self->pending->{$ex} = 1;
74            
75             # Wait 2 seconds before we handle it; if the exception happened in
76             # a request we want the after_dispatch-hook to handle it instead.
77             Mojo::IOLoop->timer(2 => sub {
78             $self->notify($ex, $app) if delete $self->pending->{$ex};
79             });
80             }
81              
82             });
83             }
84              
85             sub notify {
86             my ($self, $ex, $app, $c) = @_;
87              
88             my $call_back = sub { };
89              
90             if($self->debug) {
91             $call_back = sub {
92             print STDERR "Debug airbrake callback: " . Dumper(\@_);
93             };
94             }
95              
96              
97             my $tx = $self->ua->post($self->url => json => $self->_json_content($ex, $app, $c), $call_back );
98              
99              
100              
101             }
102              
103             sub _json_content {
104             my $self = shift;
105             my $ex = shift;
106             my $app = shift;
107             my $c = shift;
108              
109             my $json = {
110             notifier => {
111             name => 'Mojolicious::Plugin::Airbrake',
112             version => $VERSION,
113             url => 'https://github.com/jontaylor/Mojolicious-Plugin-Airbrake'
114             }
115             };
116              
117             $json->{errors} = [{
118             type => ref $ex,
119             message => $ex->message,
120             backtrace => [],
121             }];
122              
123             foreach my $frame (@{$ex->frames}) {
124             my ($package, $file, $line, $subroutine) = @$frame;
125             push @{$json->{errors}->[0]->{backtrace}}, {
126             file => $file, line => $line, function => $subroutine
127             };
128             }
129              
130             $json->{context} = {
131             environment => $app->mode,
132             rootDirectory => $app->home
133             };
134              
135             if($c) {
136              
137             $json->{url} = $c->req->url->to_abs;
138             $json->{component} = ref $c;
139             $json->{action} = $c->stash('action');
140             $json->{userId} = $self->user_id_sub_ref->($c);
141              
142             $json->{environment} = { map { $_ => "".$c->req->headers->header($_) } (@{$c->req->headers->names}) };
143             $json->{params} = { map { $_ => string_dump($c->param($_)) } ($c->param) };
144             $json->{session} = { map { $_ => string_dump($c->session($_)) } (keys %{$c->session}) } if $self->include_session;
145             }
146              
147             return $json;
148              
149             }
150              
151             sub string_dump {
152             my $obj = shift;
153             ref $obj ? Dumper($obj) : $obj;
154             }
155              
156             1;
157             __END__