File Coverage

blib/lib/Sentry/Raven.pm
Criterion Covered Total %
statement 203 213 95.3
branch 31 38 81.5
condition 30 35 85.7
subroutine 55 59 93.2
pod 19 19 100.0
total 338 364 92.8


line stmt bran cond sub pod time code
1             package Sentry::Raven;
2              
3 9     9   574272 use 5.010;
  9         95  
4 9     9   48 use strict;
  9         17  
  9         191  
5 9     9   47 use warnings;
  9         18  
  9         252  
6 9     9   4469 use Moo;
  9         93486  
  9         44  
7 9     9   17371 use MooX::Types::MooseLike::Base qw/ ArrayRef HashRef Int Str /;
  9         61114  
  9         903  
8              
9             our $VERSION = '1.14';
10              
11 9     9   4578 use Data::Dump 'dump';
  9         55370  
  9         561  
12 9     9   4466 use Devel::StackTrace;
  9         30351  
  9         299  
13 9     9   3515 use English '-no_match_vars';
  9         19737  
  9         59  
14 9     9   3333 use File::Basename 'basename';
  9         18  
  9         578  
15 9     9   4660 use HTTP::Request::Common 'POST';
  9         167324  
  9         678  
16 9     9   3292 use HTTP::Status ':constants';
  9         33679  
  9         3994  
17 9     9   6340 use JSON::XS;
  9         41026  
  9         539  
18 9     9   6147 use LWP::UserAgent;
  9         182874  
  9         372  
19 9     9   4478 use Sys::Hostname;
  9         10137  
  9         559  
20 9     9   4790 use Time::Piece;
  9         69696  
  9         47  
21 9     9   738 use URI;
  9         30  
  9         278  
22 9     9   4895 use UUID::Tiny ':std';
  9         182638  
  9         1931  
23 9     9   4609 use File::Slurp;
  9         176115  
  9         734  
24 9     9   79 use List::Util qw(min max);
  9         63  
  9         810  
25              
26             # constants from server-side sentry code
27             use constant {
28 9         1634 MAX_CULPRIT => 200,
29             MAX_MESSAGE => 2048,
30              
31             MAX_EXCEPTION_TYPE => 128,
32             MAX_EXCEPTION_VALUE => 4096,
33              
34             MAX_HTTP_QUERY_STRING => 1024,
35             MAX_HTTP_DATA => 2048,
36              
37             MAX_QUERY_ENGINE => 128,
38             MAX_QUERY_QUERY => 1024,
39              
40             MAX_STACKTRACE_FILENAME => 256,
41             MAX_STACKTRACE_PACKAGE => 256,
42             MAX_STACKTRACE_SUBROUTUNE => 256,
43              
44             MAX_USER_EMAIL => 128,
45             MAX_USER_ID => 128,
46             MAX_USER_USERNAME => 128,
47             MAX_USER_IP_ADDRESS => 45, # RFC 4291, section 2.2.3
48 9     9   75 };
  9         18  
49              
50             # self-imposed constants
51             use constant {
52 9         33145 MAX_HTTP_COOKIES => 1024,
53             MAX_HTTP_URL => 1024,
54              
55             MAX_STACKTRACE_VARS => 1024,
56 9     9   64 };
  9         125  
57              
58             =head1 NAME
59              
60             Sentry::Raven - A perl sentry client
61              
62             =head1 VERSION
63              
64             Version 1.14
65              
66             =head1 SYNOPSIS
67              
68             my $raven = Sentry::Raven->new( sentry_dsn => 'https://<publickey>:<secretkey>@sentry.io/<projectid>' );
69              
70             # capture all errors
71             $raven->capture_errors( sub {
72             ..do something here..
73             } );
74              
75             # capture an individual event
76             $raven->capture_message('The sky is falling');
77              
78             # annotate an event with context
79             $raven->capture_message(
80             'The sky is falling',
81             Sentry::Raven->exception_context('SkyException', 'falling'),
82             );
83              
84             =head1 DESCRIPTION
85              
86             This module implements the recommended raven interface for posting events to a sentry service.
87              
88             =head1 CONSTRUCTOR
89              
90             =head2 my $raven = Sentry::Raven->new( %options, %context )
91              
92             Create a new sentry interface object. It accepts the following named options:
93              
94             =over
95              
96             =item C<< sentry_dsn => 'http://<publickey>:<secretkey>@sentry.io/<projectid>' >>
97              
98             The DSN for your sentry service. Get this from the client configuration page for your project.
99              
100             =item C<< timeout => 5 >>
101              
102             Do not wait longer than this number of seconds when attempting to send an event.
103              
104             =back
105              
106             =cut
107              
108             has [qw/ post_url public_key secret_key /] => (
109             is => 'ro',
110             isa => Str,
111             required => 1,
112             );
113              
114             has sentry_version => (
115             is => 'ro',
116             isa => Int,
117             default => 7,
118             );
119              
120             has timeout => (
121             is => 'ro',
122             isa => Int,
123             default => 5,
124             );
125              
126             has json_obj => (
127             is => 'ro',
128             builder => '_build_json_obj',
129             lazy => 1,
130             );
131              
132             has ua_obj => (
133             is => 'ro',
134             builder => '_build_ua_obj',
135             lazy => 1,
136             );
137              
138             has valid_levels => (
139             is => 'ro',
140             isa => ArrayRef[Str],
141             default => sub { [qw/ fatal error warning info debug /] },
142             );
143              
144             has valid_interfaces => (
145             is => 'ro',
146             isa => ArrayRef[Str],
147             default => sub { [qw/
148             sentry.interfaces.Exception sentry.interfaces.Http
149             sentry.interfaces.Stacktrace sentry.interfaces.User
150             sentry.interfaces.Query
151             /] },
152             );
153              
154             has context => (
155             is => 'rw',
156             isa => HashRef[],
157             default => sub { { } },
158             );
159              
160             has processors => (
161             is => 'rw',
162             isa => ArrayRef[],
163             default => sub { [] },
164             );
165              
166             has encoding => (
167             is => 'rw',
168             isa => Str,
169             default => 'gzip',
170             );
171              
172             around BUILDARGS => sub {
173             my ($orig, $class, %args) = @_;
174              
175             my $sentry_dsn = $args{sentry_dsn} || $ENV{SENTRY_DSN}
176             or die "must pass sentry_dsn or set SENTRY_DSN envirionment variable\n";
177              
178             delete($args{sentry_dsn});
179              
180             my $uri = URI->new($sentry_dsn);
181              
182             die "unable to parse sentry dsn: $sentry_dsn\n"
183             unless defined($uri) && $uri->can('userinfo');
184              
185             die "unable to parse public and secret keys from: $sentry_dsn\n"
186             unless defined($uri->userinfo()) && $uri->userinfo() =~ m/:/;
187              
188             my @path = split(m{/}, $uri->path());
189             my ($public_key, $secret_key) = $uri->userinfo() =~ m/(.*):(.*)/;
190             my $project_id = pop(@path);
191              
192             my $post_url =
193             $uri->scheme().'://'.$uri->host().':'.$uri->port() .
194             join('/', @path).'/api/'.$project_id.'/store/'
195             ;
196              
197             my $timeout = delete($args{timeout});
198             my $ua_obj = delete($args{ua_obj});
199             my $processors = delete($args{processors}) || [];
200             my $encoding = delete($args{encoding});
201              
202             return $class->$orig(
203             post_url => $post_url,
204             public_key => $public_key,
205             secret_key => $secret_key,
206             context => \%args,
207             processors => $processors,
208              
209             (defined($encoding) ? (encoding => $encoding) : ()),
210             (defined($timeout) ? (timeout => $timeout) : ()),
211             (defined($ua_obj) ? (ua_obj => $ua_obj) : ()),
212             );
213             };
214              
215             sub _trim {
216 183     183   4014 my ($string, $length) = @_;
217 183 100       815 return defined($string)
218             ? substr($string, 0, $length)
219             : undef;
220             }
221              
222             =head1 ERROR HANDLERS
223              
224             These methods are designed to capture events and handle them automatically.
225              
226             =head2 $raven->capture_errors( $subref, %context )
227              
228             Execute the $subref and report any exceptions (die) back to the sentry service. If it is unable to submit an event (capture_message return undef), it will die and include the event details in the die message. This automatically includes a stacktrace unless C<$SIG{__DIE__}> has been overridden in subsequent code.
229              
230             =cut
231              
232             sub capture_errors {
233 2     2 1 233 my ($self, $subref, %context) = @_;
234              
235 2         5 my $wantarray = wantarray();
236              
237 2         4 my ($stacktrace, @retval);
238 2         3 eval {
239 2     2   12 local $SIG{__DIE__} = sub { $stacktrace = Devel::StackTrace->new(skip_frames => 1) };
  2         53  
240              
241 2 50       8 if ($wantarray) {
242 0         0 @retval = $subref->();
243             } else {
244 2         10 $retval[0] = $subref->();
245             }
246             };
247              
248 2         973 my $eval_error = $EVAL_ERROR;
249              
250 2 50       8 if ($eval_error) {
251 2         3 my $message = $eval_error;
252 2         5 chomp($message);
253              
254 2 50       23 my %stacktrace_context = $stacktrace
255             ? $self->stacktrace_context(
256             $self->_get_frames_from_devel_stacktrace($stacktrace),
257             )
258             : ();
259              
260 2         12 %context = (
261             culprit => $PROGRAM_NAME,
262             %context,
263             $self->exception_context($message),
264             %stacktrace_context,
265             );
266              
267 2         12 my $event_id = $self->capture_message($message, %context);
268              
269 2 100       34 if (!defined($event_id)) {
270 1         6 die "failed to submit event to sentry service:\n" . dump($self->_construct_message_event($message, %context));
271             }
272             }
273              
274 1 50       40 return $wantarray ? @retval : $retval[0];
275             };
276              
277             sub _get_frames_from_devel_stacktrace {
278 4     4   2748 my ($self, $stacktrace) = @_;
279              
280             my @frames = map {
281 4         20 my $frame = $_;
  23         497  
282             {
283             abs_path => _trim($frame->filename(), MAX_STACKTRACE_FILENAME),
284             filename => _trim(basename($frame->filename()), MAX_STACKTRACE_FILENAME),
285             function => _trim($frame->subroutine(), MAX_STACKTRACE_SUBROUTUNE),
286             lineno => $frame->line(),
287             module => _trim($frame->package(), MAX_STACKTRACE_PACKAGE),
288             vars => {
289             '@_' => [
290 23         84 map { _trim(dump($_), MAX_STACKTRACE_VARS) } $frame->args(),
  19         112  
291             ],
292             },
293             $self->_get_lines_from_file($frame->filename(), $frame->line()),
294             }
295             } $stacktrace->frames();
296              
297             # Devel::Stacktrace::Frame's subroutine() and args() describe what's being called by the current frame,
298             # whereas Sentry expects function and vars to describe the current frame.
299 4         26 for my $i (0..$#frames) {
300 23         32 my $frame = $frames[$i];
301 23 100       56 my $parent = defined($frames[$i + 1])
302             ? $frames[$i + 1]
303             : {};
304 23         70 @$frame{'function', 'vars'} = @$parent{'function', 'vars'};
305             }
306              
307 4         23 return [ reverse(@frames) ];
308             }
309              
310             sub _get_lines_from_file {
311 23     23   265 my ($self, $abs_path, $lineno) = @_;
312              
313 23         50 my @lines = eval { read_file($abs_path) };
  23         72  
314 23         35424 chomp(@lines);
315 23 100       100 return () unless @lines;
316 22 50       65 return () unless @lines >= $lineno;
317            
318 22         48 my $context_lines = 5;
319 22         85 my $lower_bound = max(0, $lineno - $context_lines);
320 22         55 my $upper_bound = min($lineno + $context_lines, scalar @lines);
321              
322             return (
323 22         1967 context_line => $lines[ $lineno - 1 ],
324             pre_context => [ @lines[ $lower_bound - 1 .. $lineno - 2 ]],
325             post_context => [ @lines[ $lineno .. $upper_bound - 1 ]],
326             );
327             }
328              
329             =head1 METHODS
330              
331             These methods are for generating individual events.
332              
333             =head2 $raven->capture_message( $message, %context )
334              
335             Post a string message to the sentry service. Returns the event id.
336              
337             =cut
338              
339             sub capture_message {
340 7     7 1 505 my ($self, $message, %context) = @_;
341 7         35 return $self->_post_event($self->_construct_message_event($message, %context));
342             }
343              
344             sub _construct_message_event {
345 9     9   1393 my ($self, $message, %context) = @_;
346 9         65 return $self->_construct_event(message => $message, %context);
347             }
348              
349             =head2 $raven->capture_exception( $exception_value, %exception_context, %context )
350              
351             Post an exception type and value to the sentry service. Returns the event id.
352              
353             C<%exception_context> can contain:
354              
355             =over
356              
357             =item C<< type => $type >>
358              
359             =back
360              
361             =cut
362              
363             sub capture_exception {
364 0     0 1 0 my ($self, $value, %context) = @_;
365 0         0 return $self->_post_event($self->_construct_exception_event($value, %context));
366             };
367              
368             sub _construct_exception_event {
369 1     1   3560 my ($self, $value, %context) = @_;
370 1         6 return $self->_construct_event(
371             %context,
372             $self->exception_context($value, %context),
373             );
374             };
375              
376             =head2 $raven->capture_request( $url, %request_context, %context )
377              
378             Post a web url request to the sentry service. Returns the event id.
379              
380             C<%request_context> can contain:
381              
382             =over
383              
384             =item C<< method => 'GET' >>
385              
386             =item C<< data => 'foo=bar' >>
387              
388             =item C<< query_string => 'foo=bar' >>
389              
390             =item C<< cookies => 'foo=bar' >>
391              
392             =item C<< headers => { 'Content-Type' => 'text/html' } >>
393              
394             =item C<< env => { REMOTE_ADDR => '192.168.0.1' } >>
395              
396             =back
397              
398             =cut
399              
400             sub capture_request {
401 0     0 1 0 my ($self, $url, %context) = @_;
402 0         0 return $self->_post_event($self->_construct_request_event($url, %context));
403             };
404              
405             sub _construct_request_event {
406 1     1   3207 my ($self, $url, %context) = @_;
407              
408 1         7 return $self->_construct_event(
409             %context,
410             $self->request_context($url, %context),
411             );
412             };
413              
414             =head2 $raven->capture_stacktrace( $frames, %context )
415              
416             Post a stacktrace to the sentry service. Returns the event id.
417              
418             C<$frames> can be either a Devel::StackTrace object, or an arrayref of hashrefs with each hashref representing a single frame.
419              
420             my $frames = [
421             {
422             filename => 'my/file1.pl',
423             function => 'function1',
424             vars => { foo => 'bar' },
425             lineno => 10,
426             },
427             {
428             filename => 'my/file2.pl',
429             function => 'function2',
430             vars => { bar => 'baz' },
431             lineno => 20,
432             },
433             ];
434              
435             The first frame should be the oldest frame. Frames must contain at least one of C<filename>, C<function>, or C<module>. These additional attributes are also supported:
436              
437             =over
438              
439             =item C<< filename => $file_name >>
440              
441             =item C<< function => $function_name >>
442              
443             =item C<< module => $module_name >>
444              
445             =item C<< lineno => $line_number >>
446              
447             =item C<< colno => $column_number >>
448              
449             =item C<< abs_path => $absolute_path_file_name >>
450              
451             =item C<< context_line => $line_of_code >>
452              
453             =item C<< pre_context => [ $previous_line1, $previous_line2 ] >>
454              
455             =item C<< post_context => [ $next_line1, $next_line2 ] >>
456              
457             =item C<< in_app => $one_if_not_external_library >>
458              
459             =item C<< vars => { $variable_name => $variable_value } >>
460              
461             =back
462              
463             =cut
464              
465             sub capture_stacktrace {
466 1     1 1 25 my ($self, $frames, %context) = @_;
467 1         6 return $self->_post_event($self->_construct_stacktrace_event($frames, %context));
468             };
469              
470             sub _construct_stacktrace_event {
471 4     4   6268 my ($self, $frames, %context) = @_;
472              
473 4         50 return $self->_construct_event(
474             %context,
475             $self->stacktrace_context($frames),
476             );
477             };
478              
479             =head2 $raven->capture_user( %user_context, %context )
480              
481             Post a user to the sentry service. Returns the event id.
482              
483             C<%user_context> can contain:
484              
485             =over
486              
487             =item C<< id => $unique_id >>
488              
489             =item C<< username => $username >>
490              
491             =item C<< email => $email >>
492              
493             =item C<< ip_address => $ip_address >>
494              
495             =back
496              
497             =cut
498              
499             sub capture_user {
500 0     0 1 0 my ($self, %context) = @_;
501 0         0 return $self->_post_event($self->_construct_user_event(%context));
502             };
503              
504             sub _construct_user_event {
505 1     1   4427 my ($self, %context) = @_;
506              
507             return $self->_construct_event(
508             %context,
509             $self->user_context(
510 1         6 map { $_ => $context{$_} } qw/email id username ip_address/
  4         13  
511             ),
512             );
513             };
514              
515             =head2 $raven->capture_query( $query, %query_context, %context )
516              
517             Post a query to the sentry service. Returns the event id.
518              
519             C<%query_context> can contain:
520              
521             =over
522              
523             =item C<< engine => $engine' >>
524              
525             =back
526              
527             =cut
528              
529             sub capture_query {
530 0     0 1 0 my ($self, $query, %context) = @_;
531 0         0 return $self->_post_event($self->_construct_query_event($query, %context));
532             };
533              
534             sub _construct_query_event {
535 1     1   2476 my ($self, $query, %context) = @_;
536              
537 1         26 return $self->_construct_event(
538             %context,
539             $self->query_context($query, %context),
540             );
541             };
542              
543             sub _post_event {
544 9     9   116 my ($self, $event) = @_;
545              
546 9         36 $event = $self->_process_event($event);
547              
548 9         20 my ($response, $response_code, $response_content);
549              
550 9         18 eval {
551 9         157 my $event_json = $self->json_obj()->encode( $event );
552              
553 8         179 $self->ua_obj()->timeout($self->timeout());
554              
555 8         252 my $request = POST(
556             $self->post_url(),
557             'X-Sentry-Auth' => $self->_generate_auth_header(),
558             Content => $event_json,
559             );
560 8         3697 $request->encode( $self->encoding() );
561 8         59348 $response = $self->ua_obj()->request($request);
562              
563 8         9137 $response_code = $response->code();
564 8         87 $response_content = $response->content();
565             };
566              
567 9 100       132 warn "$EVAL_ERROR\n" if $EVAL_ERROR;
568              
569 9 100 100     67 if (defined($response_code) && $response_code == HTTP_OK) {
570 6         156 return $self->json_obj()->decode($response_content)->{id};
571             } else {
572 3 100       11 if ($response) {
573 2         10 warn "Unsuccessful Response Posting Sentry Event:\n"._trim($response->as_string(), 1000)."\n";
574             }
575 3         98 return;
576             }
577             }
578              
579             sub _process_event {
580 9     9   21 my ($self, $event) = @_;
581              
582 9         18 foreach my $processor (@{$self->processors()}) {
  9         180  
583 2         35 my $processed_event = $processor->process($event);
584 2 50       17 if ($processed_event) {
585 2         5 $event = $processed_event;
586             } else {
587 0         0 die "processor $processor did not return an event";
588             }
589             }
590              
591 9         70 return $event;
592             }
593              
594             sub _generate_id {
595 21     21   304 (my $uuid = create_uuid_as_string(UUID_V4)) =~ s/-//g;
596 21         4432 return $uuid;
597             }
598              
599             sub _construct_event {
600 24     24   11038 my ($self, %context) = @_;
601              
602             my $event = {
603             event_id => $context{event_id} || $self->context()->{event_id} || _generate_id(),
604             timestamp => $context{timestamp} || $self->context()->{timestamp} || gmtime->datetime(),
605             logger => $context{logger} || $self->context()->{logger} || 'root',
606             server_name => $context{server_name} || $self->context()->{server_name} || hostname(),
607             platform => $context{platform} || $self->context()->{platform} || 'perl',
608              
609             release => $context{release} || $self->context()->{release},
610              
611             message => $context{message} || $self->context()->{message},
612             culprit => $context{culprit} || $self->context()->{culprit},
613              
614             extra => $self->_merge_hashrefs($self->context()->{extra}, $context{extra}),
615             tags => $self->_merge_hashrefs($self->context()->{tags}, $context{tags}),
616             fingerprint => $context{fingerprint} || $self->context()->{fingerprint} || ['{{ default }}'],
617              
618             level => $self->_validate_level($context{level}) || $self->context()->{level} || 'error',
619             environment => $context{environment} || $self->context()->{environment} || $ENV{SENTRY_ENVIRONMENT},
620 24   66     658 };
      66        
      100        
      66        
      100        
      66        
      100        
      100        
      100        
      100        
      66        
621              
622 24         852 $event->{message} = _trim($event->{message}, MAX_MESSAGE);
623 24         64 $event->{culprit} = _trim($event->{culprit}, MAX_CULPRIT);
624              
625 24         425 my $instance_ctx = $self->context();
626 24         133 foreach my $interface (@{ $self->valid_interfaces() }) {
  24         119  
627 120   100     319 my $interface_ctx = $context{$interface} || $instance_ctx->{$interface};
628 120 100       282 $event->{$interface} = $interface_ctx
629             if $interface_ctx;
630             }
631              
632 24         165 return $event;
633             }
634              
635             sub _merge_hashrefs {
636 50     50   6458 my ($self, $hash1, $hash2) = @_;
637              
638             return {
639 8         90 ($hash1 ? %{ $hash1 } : ()),
640 50 100       802 ($hash2 ? %{ $hash2 } : ()),
  6 100       84  
641             };
642             };
643              
644             sub _validate_level {
645 24     24   286 my ($self, $level) = @_;
646              
647 24 100       378 return unless defined($level);
648              
649 10         19 my %level_hash = map { $_ => 1 } @{ $self->valid_levels() };
  50         107  
  10         37  
650              
651 10 100       33 if (exists($level_hash{$level})) {
652 9         192 return $level;
653             } else {
654 1         12 warn "unknown level: $level\n";
655 1         25 return;
656             }
657             };
658              
659             sub _generate_auth_header {
660 8     8   22 my ($self) = @_;
661              
662 8         89 my %fields = (
663             sentry_version => $self->sentry_version(),
664             sentry_client => "raven-perl/$VERSION",
665             sentry_timestamp => time(),
666              
667             sentry_key => $self->public_key(),
668             sentry_secret => $self->secret_key(),
669             );
670              
671 8         57 return 'Sentry ' . join(', ', map { $_ . '=' . $fields{$_} } sort keys %fields);
  40         171  
672             }
673              
674 7     7   364 sub _build_json_obj { JSON::XS->new()->utf8(1)->pretty(1)->allow_nonref(1) }
675             sub _build_ua_obj {
676 1     1   113 return LWP::UserAgent->new(
677             keep_alive => 1,
678             );
679             }
680              
681             =head1 EVENT CONTEXT
682              
683             These methods are for annotating events with additional context, such as stack traces or HTTP requests. Simply pass their output to any other method accepting C<%context>. They accept all of the same arguments as their C<capture_*> counterparts.
684              
685             $raven->capture_message(
686             'The sky is falling',
687             Sentry::Raven->exception_context('falling', type => 'SkyException'),
688             );
689              
690             =head2 Sentry::Raven->exception_context( $value, %exception_context )
691              
692             =cut
693              
694             sub exception_context {
695 4     4 1 3161 my ($class, $value, %exception_context) = @_;
696              
697             return (
698             'sentry.interfaces.Exception' => {
699             value => _trim($value, MAX_EXCEPTION_VALUE),
700 4         13 type => _trim($exception_context{type}, MAX_EXCEPTION_TYPE),
701             }
702             );
703             };
704              
705             =head2 Sentry::Raven->request_context( $url, %request_context )
706              
707             =cut
708              
709             sub request_context {
710 1     1 1 6 my ($class, $url, %context) = @_;
711              
712             return (
713             'sentry.interfaces.Http' => {
714             url => _trim($url, MAX_HTTP_URL),
715             method => $context{method},
716             data => _trim($context{data}, MAX_HTTP_DATA),
717             query_string => _trim($context{query_string}, MAX_HTTP_QUERY_STRING),
718             cookies => _trim($context{cookies}, MAX_HTTP_COOKIES),
719             headers => $context{headers},
720             env => $context{env},
721             }
722 1         4 );
723             };
724              
725             =head2 Sentry::Raven->stacktrace_context( $frames )
726              
727             =cut
728              
729             sub stacktrace_context {
730 6     6 1 19 my ($class, $frames) = @_;
731              
732 6         13 eval {
733 6 50       72 $frames = $class->_get_frames_from_devel_stacktrace($frames)
734             if $frames->isa('Devel::StackTrace');
735             };
736              
737             return (
738 6         44 'sentry.interfaces.Stacktrace' => {
739             frames => $frames,
740             }
741             );
742             };
743              
744             =head2 Sentry::Raven->user_context( %user_context )
745              
746             =cut
747              
748             sub user_context {
749 2     2 1 3147 my ($class, %user_context) = @_;
750 2         10 my ($email, $id, $username, $ip_address) = delete @user_context{qw/email id username ip_address/};
751              
752             return (
753 2         8 'sentry.interfaces.User' => {
754             email => _trim($email, MAX_USER_EMAIL),
755             id => _trim($id, MAX_USER_ID),
756             username => _trim($username, MAX_USER_USERNAME),
757             ip_address => _trim($ip_address, MAX_USER_IP_ADDRESS),
758             %user_context,
759             }
760             );
761             };
762              
763             =head2 Sentry::Raven->query_context( $query, %query_context )
764              
765             =cut
766              
767             sub query_context {
768 1     1 1 5 my ($class, $query, %query_context) = @_;
769              
770             return (
771             'sentry.interfaces.Query' => {
772             query => _trim($query, MAX_QUERY_QUERY),
773 1         3 engine => _trim($query_context{engine}, MAX_QUERY_ENGINE),
774             }
775             );
776             };
777              
778             =pod
779              
780             The default context can be modified with the following accessors:
781              
782             =head2 my %context = $raven->get_context();
783              
784             =cut
785              
786             sub get_context {
787 2     2 1 1215 my ($self) = @_;
788 2         5 return %{ $self->context() };
  2         40  
789             };
790              
791             =head2 $raven->add_context( %context )
792              
793             =cut
794              
795             sub add_context {
796 3     3 1 9042 my ($self, %context) = @_;
797             $self->context()->{$_} = $context{$_}
798 3         80 for keys %context;
799             };
800              
801             =head2 $raven->merge_tags( %tags )
802              
803             Merge additional tags into any existing tags in the current context.
804              
805             =cut
806              
807             sub merge_tags {
808 1     1 1 14 my ($self, %tags) = @_;
809 1         17 $self->context()->{tags} = $self->_merge_hashrefs($self->context()->{tags}, \%tags);
810             };
811              
812             =head2 $raven->merge_extra( %tags )
813              
814             Merge additional extra into any existing extra in the current context.
815              
816             =cut
817              
818             sub merge_extra {
819 1     1 1 17 my ($self, %extra) = @_;
820 1         20 $self->context()->{extra} = $self->_merge_hashrefs($self->context()->{extra}, \%extra);
821             };
822              
823             =head2 $raven->clear_context()
824              
825             =cut
826              
827             sub clear_context {
828 1     1 1 1199 my ($self) = @_;
829 1         27 $self->context({});
830             };
831              
832             =head1 EVENT PROCESSORS
833              
834             Processors are a mechanism for modifying events after they are generated but before they are posted to the sentry service. They are useful for scrubbing sensitive data, such as passwords, as well as adding additional context. If the processor fails (dies or returns undef), the failure will be passed to the caller.
835              
836             See L<Sentry::Raven::Processor> for information on creating new processors.
837              
838             Available processors:
839              
840             =over
841              
842             =item L<Sentry::Raven::Processor::RemoveStackVariables>
843              
844             =back
845              
846             =head2 $raven->add_processors( [ Sentry::Raven::Processor::RemoveStackVariables, ... ] )
847              
848             =cut
849              
850             sub add_processors {
851 3     3 1 1582 my ($self, @processors) = @_;
852 3         5 push @{ $self->processors() }, @processors;
  3         58  
853             };
854              
855             =head2 $raven->clear_processors( [ Sentry::Raven::Processor::RemoveStackVariables, ... ] )
856              
857             =cut
858              
859             sub clear_processors {
860 3     3 1 6527 my ($self) = @_;
861 3         78 $self->processors([]);
862             };
863              
864             =head1 STANDARD OPTIONS
865              
866             These options can be passed to all methods accepting %context. Passing context to the constructor overrides defaults.
867              
868             =over
869              
870             =item C<< culprit => 'Some::Software' >>
871              
872             The source of the event. Defaults to C<undef>.
873              
874             =item C<< event_id => '534188f7c1ff4ff280c2e1206c9e0548' >>
875              
876             The unique identifier string for an event, usually UUID v4. Max 32 characters. Defaults to a new unique UUID for each event. Invalid ids may be discarded silently.
877              
878             =item C<< extra => { key1 => 'val1', ... } >>
879              
880             Arbitrary key value pairs with extra information about an event. Defaults to C<{}>.
881              
882             =item C<< level => 'error' >>
883              
884             Event level of an event. Acceptable values are C<fatal>, C<error>, C<warning>, C<info>, and C<debug>. Defaults to C<error>.
885              
886             =item C<< logger => 'root' >>
887              
888             The creator of an event. Defaults to 'root'.
889              
890             =item C<< platform => 'perl' >>
891              
892             The platform (language) in which an event occurred. Defaults to C<perl>.
893              
894             =item C<< release => 'ec899ea' >>
895              
896             Track the release version of your application.
897              
898             =item C<< processors => [ Sentry::Raven::Processor::RemoveStackVariables, ... ] >>
899              
900             A set or processors to be applied to events before they are posted. See L<Sentry::Raven::Processor> for more information. This can only be set during construction and not on other methods accepting %context.
901              
902             =item C<< server_name => 'localhost.example.com' >>
903              
904             The hostname on which an event occurred. Defaults to the system hostname.
905              
906             =item C<< tags => { key1 => 'val1, ... } >>
907              
908             Arbitrary key value pairs with tags for categorizing an event. Defaults to C<{}>.
909              
910             =item C<< fingerprint => [ 'val1', 'val2', ... } >>
911              
912             Array of strings used to control how events aggregate in the sentry web interface. The string C<'{{ default }}'> has special meaning when used as the first value; it indicates that sentry should use the default aggregation method in addition to any others specified (useful for fine-grained aggregation). Defaults to C<['{{ default }}']>.
913              
914             =item C<< timestamp => '1970-01-01T00:00:00' >>
915              
916             Timestamp of an event. ISO 8601 format. Defaults to the current time. Invalid values may be discarded silently.
917              
918             =item C<< environment => 'production' >>
919              
920             Specify the environment (i.e. I<staging>, I<production>, etc.) that your project is deployed in. More information
921             can be found on the L<Sentry website|https://docs.sentry.io/enriching-error-data/environments/>.
922              
923             =back
924              
925             =head1 CONFIGURATION AND ENVIRONMENT
926              
927             =over
928              
929             =item SENTRY_DSN=C<< http://<publickey>:<secretkey>@sentry.io/<projectid> >>
930              
931             A default DSN to be used if sentry_dsn is not passed to c<new>.
932              
933             =back
934              
935             =head1 LICENSE
936              
937             Copyright (C) 2019 by Matt Harrington
938              
939             The full text of this license can be found in the LICENSE file included with this module.
940              
941             =cut
942              
943             1;