File Coverage

blib/lib/Sentry/Client.pm
Criterion Covered Total %
statement 166 172 96.5
branch 12 16 75.0
condition 27 44 61.3
subroutine 31 32 96.8
pod 0 7 0.0
total 236 271 87.0


line stmt bran cond sub pod time code
1             package Sentry::Client;
2 4     4   350944 use Mojo::Base -base, -signatures;
  4         10  
  4         30  
3              
4 4     4   1870 use Mojo::Exception;
  4         2243  
  4         226  
5 4     4   1520 use Mojo::Home;
  4         2403  
  4         276  
6 4     4   30 use Mojo::Util 'dumper';
  4         8  
  4         223  
7 4     4   2186 use Sentry::DSN;
  4         15  
  4         30  
8 4     4   1440 use Sentry::Hub::Scope;
  4         14  
  4         37  
9 4     4   2533 use Sentry::Integration;
  4         14  
  4         31  
10 4     4   175 use Sentry::Logger 'logger';
  4         6  
  4         230  
11 4     4   1872 use Sentry::SourceFileRegistry;
  4         13  
  4         21  
12 4     4   1952 use Sentry::Stacktrace;
  4         13  
  4         22  
13 4     4   1962 use Sentry::Transport::Http;
  4         19  
  4         60  
14 4     4   246 use Sentry::Util qw(uuid4 truncate);
  4         8  
  4         267  
15 4     4   24 use Time::HiRes;
  4         8  
  4         41  
16 4     4   208 use Try::Tiny;
  4         11  
  4         9852  
17              
18             has _dsn => sub ($self) { Sentry::DSN->parse($self->_options->{dsn}) };
19             has _options => sub { {} };
20             has _transport =>
21             sub ($self) { Sentry::Transport::Http->new(dsn => $self->_dsn) };
22             has scope => sub { Sentry::Hub::Scope->new };
23             has integrations => sub ($self) { $self->_options->{integrations} // [] };
24              
25 21     21 0 31 sub setup_integrations ($self) {
  21         34  
  21         40  
26 21         78 Sentry::Integration->setup($self->integrations);
27             }
28              
29             # (alternatively normal constructor) This takes typically an object with options + dsn.
30             sub from_config ($package, $config) { }
31              
32             sub event_from_message (
33 14         18 $self, $message,
  14         17  
34 14         20 $level = Sentry::Severity->Info,
35             $hint = undef
36 14     14 0 1313 ) {
  14         20  
  14         16  
37             my %event = (
38             event_id => $hint && $hint->{event_id},
39 14   66     93 level => $level,
40             message => $message,
41             );
42              
43 14         31 return \%event;
44             }
45              
46             sub capture_message (
47 12         16 $self, $message,
  12         17  
48 12         17 $level = undef,
49 12         15 $hint = undef,
50             $scope = undef
51 12     12 0 3535 ) {
  12         15  
  12         16  
52 12         36 my $event = $self->event_from_message($message, $level, $hint);
53              
54 12         35 return $self->_capture_event($event, $hint, $scope);
55             }
56              
57 13     13 0 5801 sub capture_event ($self, $event, $hint = undef, $scope = undef) {
  13         22  
  13         18  
  13         18  
  13         19  
  13         18  
58 13   100     54 my $event_id = ($hint // {})->{event_id};
59              
60 13         42 return $self->_capture_event($event, $hint, $scope);
61             }
62              
63 2     2 0 2 sub event_from_exception ($self, $exception, $hint = undef, $scope = undef) {
  2         3  
  2         3  
  2         2  
  2         4  
  2         2  
64 2 50       13 if (!ref($exception)) {
65 0         0 $exception = Mojo::Exception->new($exception)->trace;
66             }
67              
68 0         0 my $stacktrace = Sentry::Stacktrace->new({
69             exception => $exception,
70 0     0   0 frame_filter => sub ($frame) {
  0         0  
71 0         0 $frame->module !~ m{^(Sentry::.*|Class::MOP|CGI::Carp|Try::Tiny)$};
72             },
73 2         33 });
74              
75             return {
76             event_id => $hint && $hint->{event_id},
77 2 50 33     52 level => ($hint && $hint->{level}) || Sentry::Severity->Error,
      33        
78             exception => {
79             values => [{
80             type => ref($exception),
81             value => $exception->can('to_string')
82             ? $exception->to_string
83             : $exception,
84             module => ref($exception),
85             stacktrace => $stacktrace,
86             }]
87             }
88             };
89             }
90              
91 2     2 0 2 sub capture_exception ($self, $exception, $hint = undef, $scope = undef) {
  2         3  
  2         3  
  2         3  
  2         2  
  2         3  
92 2         8 my $event = $self->event_from_exception($exception, $hint);
93              
94 2         95 return $self->_capture_event($event, $hint, $scope);
95             }
96              
97 27     27   57 sub _capture_event ($self, $event, $hint = undef, $scope = undef) {
  27         51  
  27         33  
  27         36  
  27         30  
  27         37  
98 27         35 my $event_id;
99              
100             try {
101 27     27   1217 $event_id = $self->_process_event($event, $hint, $scope)->{event_id};
102             } catch {
103 1     1   27 logger->error($_);
104 27         187 };
105              
106 27         553 return $event_id;
107             }
108              
109             # Captures the event by merging it with other data with defaults from the
110             # client. In addition, if a scope is passed to this system, the data from the
111             # scope passes it to the internal transport.
112             # sub capture_event ($self, $event, $scope) { }
113              
114             # Flushes out the queue for up to timeout seconds. If the client can guarantee
115             # delivery of events only up to the current point in time this is preferred.
116             # This might block for timeout seconds. The client should be disabled or
117             # disposed after close is called
118             sub close ($self, $timeout) { }
119              
120             # Same as close difference is that the client is NOT disposed after calling flush
121             sub flush ($self, $timeout) { }
122              
123             # Applies `normalize` function on necessary `Event` attributes to make them safe for serialization.
124             # Normalized keys:
125             # - `breadcrumbs.data`
126             # - `user`
127             # - `contexts`
128             # - `extra`
129 27     27   36 sub _normalize_event ($self, $event) {
  27         37  
  27         31  
  27         29  
130 27         243 my %normalized = ($event->%*,);
131 27         129 return \%normalized;
132             }
133              
134 27     27   35 sub _apply_client_options ($self, $event) {
  27         39  
  27         59  
  27         30  
135 27         61 my $options = $self->_options;
136 27   50     152 my $max_value_length = $options->{max_value_length} // 250;
137              
138 27   50     161 $event->{environment} //= $options->{environment} // 'production';
      33        
139 27   33     90 $event->{dist} //= $options->{dist};
140 27 50 0     60 $event->{release} //= $options->{release} if $options->{release};
141              
142             $event->{message} = truncate($event->{message}, $max_value_length)
143 27 100       101 if $event->{message};
144              
145 27         50 return;
146             }
147              
148 13     13 0 181 sub get_options ($self) {
  13         18  
  13         18  
149 13         32 return $self->_options;
150             }
151              
152 27     27   36 sub _apply_integrations_metadata ($self, $event) {
  27         31  
  27         34  
  27         31  
153 27   100     78 $event->{sdk} //= {};
154              
155 27         67 my @integrations = $self->integrations->@*;
156 27 50       155 $event->{sdk}->{integrations} = [map { ref($_) } @integrations]
  0         0  
157             if @integrations;
158             }
159              
160             # Adds common information to events.
161             #
162             # The information includes release and environment from `options`,
163             # breadcrumbs and context (extra, tags and user) from the scope.
164             #
165             # Information that is already present in the event is never overwritten. For
166             # nested objects, such as the context, keys are merged.
167             #
168             # @param event The original event.
169             # @param hint May contain additional information about the original exception.
170             # @param scope A scope containing event metadata.
171             # @returns A new event with more information.
172 27     27   35 sub _prepare_event ($self, $event, $scope, $hint = undef) {
  27         37  
  27         34  
  27         35  
  27         38  
  27         30  
173             my %prepared = (
174             $event->%*,
175             sdk => $self->_options->{_metadata}{sdk},
176             platform => 'perl',
177             event_id => $event->{event_id} // ($hint // {})->{event_id} // uuid4(),
178             timestamp => $event->{timestamp} // time,
179 27   100     120 );
      100        
      66        
      66        
180              
181 27         437 $self->_apply_client_options(\%prepared);
182 27         77 $self->_apply_integrations_metadata(\%prepared);
183              
184             # If we have scope given to us, use it as the base for further modifications.
185             # This allows us to prevent unnecessary copying of data if `capture_context`
186             # is not provided.
187 27         34 my $final_scope = $scope;
188 27 100 100     76 if (($hint // {})->{capture_context}) {
189 1         5 $final_scope = $scope->clone()->update($hint->{capture_context});
190             }
191              
192             # We prepare the result here with a resolved Event.
193 27         42 my $result = \%prepared;
194             # This should be the last thing called, since we want that
195             # {@link Hub.addEventProcessor} gets the finished prepared event.
196 27 100       63 if ($final_scope) {
197              
198             # In case we have a hub we reassign it.
199 23         91 $result = $final_scope->apply_to_event(\%prepared, $hint);
200             }
201              
202 27         75 return $self->_normalize_event($result);
203             }
204              
205 27     27   37 sub _process_event ($self, $event, $hint, $scope) {
  27         44  
  27         60  
  27         33  
  27         37  
  27         36  
206 27         89 my $prepared = $self->_prepare_event($event, $scope, $hint);
207              
208             my $before_send = $self->_options->{before_send}
209 27   66 25   67 // sub ($event, $hint) {$event};
  25         39  
  25         30  
  25         28  
  25         30  
  25         28  
210              
211 27   100     261 my $processed_event = $before_send->($prepared, $hint // {});
212              
213 27 100       100 die 'An event processor returned undef, will not send event.'
214             unless $processed_event;
215              
216 26         68 $self->_send_event($processed_event);
217              
218 26         174 return $processed_event;
219             }
220              
221 26     26   29 sub _send_event ($self, $event) {
  26         33  
  26         31  
  26         31  
222 26         54 $self->_transport->send($event);
223 26         412 return;
224             }
225              
226             1;
227