File Coverage

blib/lib/Log/Any/Adapter/Sentry/Raven.pm
Criterion Covered Total %
statement 63 63 100.0
branch 17 20 85.0
condition 9 12 75.0
subroutine 14 14 100.0
pod 0 2 0.0
total 103 111 92.7


line stmt bran cond sub pod time code
1             package Log::Any::Adapter::Sentry::Raven;
2              
3             # ABSTRACT: Log::Any::Adapter for Sentry::Raven
4 1     1   125631 use version;
  1         1993  
  1         6  
5             our $VERSION = 'v0.0.4'; # VERSION
6              
7             #pod =head1 SYNPOSIS
8             #pod
9             #pod use Log::Any::Adapter;
10             #pod Log::Any::Adapter->set('Sentry::Raven',
11             #pod sentry => Sentry::Raven->new(
12             #pod sentry_dsn => $dsn,
13             #pod environment => 'production',
14             #pod ...
15             #pod )
16             #pod );
17             #pod
18             #pod =head1 DESCRIPTION
19             #pod
20             #pod This is a backend to L<Log::Any> for L<Sentry::Raven>.
21             #pod
22             #pod When logging, it does its best to provide a L<Devel::StackTrace> to
23             #pod identify your message. To accomplish this, it uses L<Devel::StackTrace::Extract>
24             #pod to pull a trace from your message (if you pass multiple message arguments, it
25             #pod won't attempt this).
26             #pod Failing that, it will append a new C<Devel::StackTrace>.
27             #pod
28             #pod It takes two arguments:
29             #pod
30             #pod =over
31             #pod
32             #pod =item sentry (REQUIRED)
33             #pod
34             #pod An instantiated L<Sentry::Raven> object.
35             #pod Note that if you set any sentry-specific context directly through the sentry
36             #pod object, it will be picked up here eg.
37             #pod
38             #pod $sentry->add_context( Sentry::Raven->request_context($url, %p) )
39             #pod
40             #pod =item log_level (OPTIONAL)
41             #pod
42             #pod The minimum log_level to log. Defaults to C<trace> (everything).
43             #pod
44             #pod =back
45             #pod
46             #pod Any L<Log::Any/Log context data> will be sent to Sentry as tags.
47             #pod
48             #pod =head1 SEE ALSO
49             #pod
50             #pod L<Log::Any>, L<Sentry::Raven>
51             #pod
52             #pod =cut
53              
54 1     1   104 use strict;
  1         2  
  1         21  
55 1     1   5 use warnings;
  1         2  
  1         26  
56              
57 1     1   5 use Carp qw(carp croak);
  1         2  
  1         50  
58 1     1   511 use Devel::StackTrace;
  1         3396  
  1         34  
59 1     1   444 use Devel::StackTrace::Extract qw(extract_stack_trace);
  1         615  
  1         61  
60 1     1   7 use Log::Any::Adapter::Util qw(make_method numeric_level);
  1         2  
  1         51  
61 1     1   6 use Scalar::Util qw(blessed);
  1         2  
  1         37  
62 1     1   526 use Sentry::Raven;
  1         145385  
  1         41  
63              
64 1     1   24 use base qw(Log::Any::Adapter::Base);
  1         3  
  1         803  
65              
66             sub init {
67 7     7 0 9100 my $self = shift;
68              
69 7         24 my $sentry = $self->{sentry};
70 7 100 100     320 croak "An initialized Sentry::Raven object must be passed as the 'sentry' arg"
71             unless blessed($sentry) && $sentry->isa('Sentry::Raven');
72              
73             # copied from Log::Any::Adapter::Stderr
74 5 100 100     123 if ( exists $self->{log_level} && $self->{log_level} =~ /\D/ ) {
75 3         20 my $numeric_level = numeric_level( $self->{log_level} );
76 3 100       31 if ( !defined($numeric_level) ) {
77 1         210 carp( sprintf 'Invalid log level "%s". Defaulting to "%s"', $self->{log_level}, 'trace' );
78             }
79 3         146 $self->{log_level} = $numeric_level;
80             }
81 5 100       18 if ( !defined $self->{log_level} ) {
82 2         19 $self->{log_level} = numeric_level('trace');
83             }
84             }
85              
86             sub structured {
87 5     5 0 165 my ($self, $level, $category, @log_args) = @_;
88              
89 5         13 my $is_level = "is_$level";
90 5 50       13 return unless $self->$is_level;
91              
92 5         11 my $log_any_context = {};
93 5 100       17 if ((ref $log_args[-1]) eq 'HASH') {
94 1         5 $log_any_context = pop @log_args;
95             }
96              
97 5         12 my $stack_trace = _get_stack_trace(@log_args);
98 5         17 my $log_message = join "\n" => @log_args;
99              
100 5         20 my $sentry_severity = $level;
101 5         11 for ($sentry_severity) {
102 5 50 33     44 s/trace/debug/ or
103             s/notice/info/ or
104             s/critical|alert|emergency/fatal/
105             }
106              
107 5         16 my @message_args = (
108             $log_message,
109             level => $sentry_severity,
110             tags => $log_any_context,
111             );
112              
113 5 50       33 if ($stack_trace) {
114 5         5986 push @message_args, Sentry::Raven->stacktrace_context($stack_trace);
115             }
116              
117             # https://docs.sentry.io/data-management/event-grouping/
118 5         8179 $self->{sentry}->capture_message( @message_args );
119             }
120              
121             for my $method ( Log::Any->detection_methods() ) {
122             my $method_base = substr($method, 3); # chop of is_
123             my $method_level = numeric_level($method_base);
124             make_method(
125             $method,
126 18     18   9855 sub { return $method_level <= $_[0]->{log_level} },
127             );
128             }
129              
130             sub _get_stack_trace {
131 5     5   12 my @message_parts = @_;
132              
133 5         8 my $trace;
134 5 100       12 if (@message_parts == 1) {
135 4         15 $trace = extract_stack_trace($message_parts[0]);
136             }
137 5 100 66     692 unless (blessed($trace) && $trace->isa('Devel::StackTrace')) {
138 4         18 $trace = Devel::StackTrace->new;
139             }
140              
141 5         1621 return $trace;
142             }
143              
144             1;
145              
146             __END__
147              
148             =pod
149              
150             =encoding UTF-8
151              
152             =head1 NAME
153              
154             Log::Any::Adapter::Sentry::Raven - Log::Any::Adapter for Sentry::Raven
155              
156             =head1 VERSION
157              
158             version v0.0.4
159              
160             =head1 DESCRIPTION
161              
162             This is a backend to L<Log::Any> for L<Sentry::Raven>.
163              
164             When logging, it does its best to provide a L<Devel::StackTrace> to
165             identify your message. To accomplish this, it uses L<Devel::StackTrace::Extract>
166             to pull a trace from your message (if you pass multiple message arguments, it
167             won't attempt this).
168             Failing that, it will append a new C<Devel::StackTrace>.
169              
170             It takes two arguments:
171              
172             =over
173              
174             =item sentry (REQUIRED)
175              
176             An instantiated L<Sentry::Raven> object.
177             Note that if you set any sentry-specific context directly through the sentry
178             object, it will be picked up here eg.
179              
180             $sentry->add_context( Sentry::Raven->request_context($url, %p) )
181              
182             =item log_level (OPTIONAL)
183              
184             The minimum log_level to log. Defaults to C<trace> (everything).
185              
186             =back
187              
188             Any L<Log::Any/Log context data> will be sent to Sentry as tags.
189              
190             =head1 SYNPOSIS
191              
192             use Log::Any::Adapter;
193             Log::Any::Adapter->set('Sentry::Raven',
194             sentry => Sentry::Raven->new(
195             sentry_dsn => $dsn,
196             environment => 'production',
197             ...
198             )
199             );
200              
201             =head1 SEE ALSO
202              
203             L<Log::Any>, L<Sentry::Raven>
204              
205             =head1 AUTHOR
206              
207             Grant Street Group <developers@grantstreet.com>
208              
209             =head1 COPYRIGHT AND LICENSE
210              
211             This software is Copyright (c) 2019 - 2020 by Grant Street Group.
212              
213             This is free software, licensed under:
214              
215             The Artistic License 2.0 (GPL Compatible)
216              
217             =cut