File Coverage

blib/lib/Sentry/Raven.pm
Criterion Covered Total %
statement 200 210 95.2
branch 28 34 82.3
condition 30 35 85.7
subroutine 55 59 93.2
pod 19 19 100.0
total 332 357 93.0


line stmt bran cond sub pod time code
1             package Sentry::Raven;
2              
3 8     8   492541 use 5.010;
  8         93  
4 8     8   57 use strict;
  8         16  
  8         172  
5 8     8   37 use warnings;
  8         16  
  8         214  
6 8     8   4256 use Moo;
  8         84860  
  8         35  
7 8     8   16055 use MooX::Types::MooseLike::Base qw/ ArrayRef HashRef Int Str /;
  8         56441  
  8         904  
8              
9             our $VERSION = '1.13';
10              
11 8     8   4465 use Data::Dump 'dump';
  8         52532  
  8         487  
12 8     8   4288 use Devel::StackTrace;
  8         27726  
  8         258  
13 8     8   3596 use English '-no_match_vars';
  8         18771  
  8         50  
14 8     8   3087 use File::Basename 'basename';
  8         21  
  8         535  
15 8     8   4490 use HTTP::Request::Common 'POST';
  8         150650  
  8         587  
16 8     8   3534 use HTTP::Status ':constants';
  8         28677  
  8         3664  
17 8     8   6079 use JSON::XS;
  8         37439  
  8         467  
18 8     8   6007 use LWP::UserAgent;
  8         167570  
  8         301  
19 8     8   4010 use Sys::Hostname;
  8         8787  
  8         479  
20 8     8   4647 use Time::Piece;
  8         63432  
  8         49  
21 8     8   675 use URI;
  8         29  
  8         239  
22 8     8   4530 use UUID::Tiny ':std';
  8         173855  
  8         1636  
23 8     8   5020 use File::Slurp;
  8         181835  
  8         585  
24 8     8   91 use List::Util qw(min max);
  8         22  
  8         733  
25              
26             # constants from server-side sentry code
27             use constant {
28 8         1409 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 8     8   57 };
  8         17  
49              
50             # self-imposed constants
51             use constant {
52 8         30203 MAX_HTTP_COOKIES => 1024,
53             MAX_HTTP_URL => 1024,
54              
55             MAX_STACKTRACE_VARS => 1024,
56 8     8   61 };
  8         129  
57              
58             =head1 NAME
59              
60             Sentry::Raven - A perl sentry client
61              
62             =head1 VERSION
63              
64             Version 1.13
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 170     170   3690 my ($string, $length) = @_;
217 170 100       754 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 232 my ($self, $subref, %context) = @_;
234              
235 2         6 my $wantarray = wantarray();
236              
237 2         6 my ($stacktrace, @retval);
238 2         4 eval {
239 2     2   14 local $SIG{__DIE__} = sub { $stacktrace = Devel::StackTrace->new(skip_frames => 1) };
  2         61  
240              
241 2 50       6 if ($wantarray) {
242 0         0 @retval = $subref->();
243             } else {
244 2         11 $retval[0] = $subref->();
245             }
246             };
247              
248 2         993 my $eval_error = $EVAL_ERROR;
249              
250 2 50       5 if ($eval_error) {
251 2         5 my $message = $eval_error;
252 2         7 chomp($message);
253              
254 2 50       10 my %stacktrace_context = $stacktrace
255             ? $self->stacktrace_context(
256             $self->_get_frames_from_devel_stacktrace($stacktrace),
257             )
258             : ();
259              
260 2         15 %context = (
261             culprit => $PROGRAM_NAME,
262             %context,
263             $self->exception_context($message),
264             %stacktrace_context,
265             );
266              
267 2         11 my $event_id = $self->capture_message($message, %context);
268              
269 2 100       41 if (!defined($event_id)) {
270 1         8 die "failed to submit event to sentry service:\n" . dump($self->_construct_message_event($message, %context));
271             }
272             }
273              
274 1 50       39 return $wantarray ? @retval : $retval[0];
275             };
276              
277             sub _get_frames_from_devel_stacktrace {
278 3     3   3058 my ($self, $stacktrace) = @_;
279              
280             my @frames = map {
281 3         13 my $frame = $_;
  21         296  
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 21         79 map { _trim(dump($_), MAX_STACKTRACE_VARS) } $frame->args(),
  16         115  
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 3         20 for my $i (0..$#frames) {
300 21         32 my $frame = $frames[$i];
301 21 100       46 my $parent = defined($frames[$i + 1])
302             ? $frames[$i + 1]
303             : {};
304 21         67 @$frame{'function', 'vars'} = @$parent{'function', 'vars'};
305             }
306              
307 3         22 return [ reverse(@frames) ];
308             }
309              
310             sub _get_lines_from_file {
311 21     21   257 my ($self, $abs_path, $lineno) = @_;
312              
313 21         63 my @lines = read_file($abs_path);
314 21         30637 chomp(@lines);
315            
316 21         54 my $context_lines = 5;
317 21         85 my $lower_bound = max(0, $lineno - $context_lines);
318 21         194 my $upper_bound = min($lineno + $context_lines, scalar @lines);
319              
320             return (
321 21         1837 context_line => $lines[ $lineno - 1 ],
322             pre_context => [ @lines[ $lower_bound - 1 .. $lineno - 2 ]],
323             post_context => [ @lines[ $lineno .. $upper_bound - 1 ]],
324             );
325             }
326              
327             =head1 METHODS
328              
329             These methods are for generating individual events.
330              
331             =head2 $raven->capture_message( $message, %context )
332              
333             Post a string message to the sentry service. Returns the event id.
334              
335             =cut
336              
337             sub capture_message {
338 7     7 1 488 my ($self, $message, %context) = @_;
339 7         35 return $self->_post_event($self->_construct_message_event($message, %context));
340             }
341              
342             sub _construct_message_event {
343 9     9   1483 my ($self, $message, %context) = @_;
344 9         71 return $self->_construct_event(message => $message, %context);
345             }
346              
347             =head2 $raven->capture_exception( $exception_value, %exception_context, %context )
348              
349             Post an exception type and value to the sentry service. Returns the event id.
350              
351             C<%exception_context> can contain:
352              
353             =over
354              
355             =item C<< type => $type >>
356              
357             =back
358              
359             =cut
360              
361             sub capture_exception {
362 0     0 1 0 my ($self, $value, %context) = @_;
363 0         0 return $self->_post_event($self->_construct_exception_event($value, %context));
364             };
365              
366             sub _construct_exception_event {
367 1     1   3579 my ($self, $value, %context) = @_;
368 1         7 return $self->_construct_event(
369             %context,
370             $self->exception_context($value, %context),
371             );
372             };
373              
374             =head2 $raven->capture_request( $url, %request_context, %context )
375              
376             Post a web url request to the sentry service. Returns the event id.
377              
378             C<%request_context> can contain:
379              
380             =over
381              
382             =item C<< method => 'GET' >>
383              
384             =item C<< data => 'foo=bar' >>
385              
386             =item C<< query_string => 'foo=bar' >>
387              
388             =item C<< cookies => 'foo=bar' >>
389              
390             =item C<< headers => { 'Content-Type' => 'text/html' } >>
391              
392             =item C<< env => { REMOTE_ADDR => '192.168.0.1' } >>
393              
394             =back
395              
396             =cut
397              
398             sub capture_request {
399 0     0 1 0 my ($self, $url, %context) = @_;
400 0         0 return $self->_post_event($self->_construct_request_event($url, %context));
401             };
402              
403             sub _construct_request_event {
404 1     1   3262 my ($self, $url, %context) = @_;
405              
406 1         6 return $self->_construct_event(
407             %context,
408             $self->request_context($url, %context),
409             );
410             };
411              
412             =head2 $raven->capture_stacktrace( $frames, %context )
413              
414             Post a stacktrace to the sentry service. Returns the event id.
415              
416             C<$frames> can be either a Devel::StackTrace object, or an arrayref of hashrefs with each hashref representing a single frame.
417              
418             my $frames = [
419             {
420             filename => 'my/file1.pl',
421             function => 'function1',
422             vars => { foo => 'bar' },
423             lineno => 10,
424             },
425             {
426             filename => 'my/file2.pl',
427             function => 'function2',
428             vars => { bar => 'baz' },
429             lineno => 20,
430             },
431             ];
432              
433             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:
434              
435             =over
436              
437             =item C<< filename => $file_name >>
438              
439             =item C<< function => $function_name >>
440              
441             =item C<< module => $module_name >>
442              
443             =item C<< lineno => $line_number >>
444              
445             =item C<< colno => $column_number >>
446              
447             =item C<< abs_path => $absolute_path_file_name >>
448              
449             =item C<< context_line => $line_of_code >>
450              
451             =item C<< pre_context => [ $previous_line1, $previous_line2 ] >>
452              
453             =item C<< post_context => [ $next_line1, $next_line2 ] >>
454              
455             =item C<< in_app => $one_if_not_external_library >>
456              
457             =item C<< vars => { $variable_name => $variable_value } >>
458              
459             =back
460              
461             =cut
462              
463             sub capture_stacktrace {
464 1     1 1 22 my ($self, $frames, %context) = @_;
465 1         5 return $self->_post_event($self->_construct_stacktrace_event($frames, %context));
466             };
467              
468             sub _construct_stacktrace_event {
469 3     3   5712 my ($self, $frames, %context) = @_;
470              
471 3         44 return $self->_construct_event(
472             %context,
473             $self->stacktrace_context($frames),
474             );
475             };
476              
477             =head2 $raven->capture_user( %user_context, %context )
478              
479             Post a user to the sentry service. Returns the event id.
480              
481             C<%user_context> can contain:
482              
483             =over
484              
485             =item C<< id => $unique_id >>
486              
487             =item C<< username => $username >>
488              
489             =item C<< email => $email >>
490              
491             =item C<< ip_address => $ip_address >>
492              
493             =back
494              
495             =cut
496              
497             sub capture_user {
498 0     0 1 0 my ($self, %context) = @_;
499 0         0 return $self->_post_event($self->_construct_user_event(%context));
500             };
501              
502             sub _construct_user_event {
503 1     1   4473 my ($self, %context) = @_;
504              
505             return $self->_construct_event(
506             %context,
507             $self->user_context(
508 1         12 map { $_ => $context{$_} } qw/email id username ip_address/
  4         13  
509             ),
510             );
511             };
512              
513             =head2 $raven->capture_query( $query, %query_context, %context )
514              
515             Post a query to the sentry service. Returns the event id.
516              
517             C<%query_context> can contain:
518              
519             =over
520              
521             =item C<< engine => $engine' >>
522              
523             =back
524              
525             =cut
526              
527             sub capture_query {
528 0     0 1 0 my ($self, $query, %context) = @_;
529 0         0 return $self->_post_event($self->_construct_query_event($query, %context));
530             };
531              
532             sub _construct_query_event {
533 1     1   2458 my ($self, $query, %context) = @_;
534              
535 1         8 return $self->_construct_event(
536             %context,
537             $self->query_context($query, %context),
538             );
539             };
540              
541             sub _post_event {
542 9     9   123 my ($self, $event) = @_;
543              
544 9         58 $event = $self->_process_event($event);
545              
546 9         22 my ($response, $response_code, $response_content);
547              
548 9         25 eval {
549 9         152 my $event_json = $self->json_obj()->encode( $event );
550              
551 8         235 $self->ua_obj()->timeout($self->timeout());
552              
553 8         261 my $request = POST(
554             $self->post_url(),
555             'X-Sentry-Auth' => $self->_generate_auth_header(),
556             Content => $event_json,
557             );
558 8         4009 $request->encode( $self->encoding() );
559 8         64880 $response = $self->ua_obj()->request($request);
560              
561 8         9791 $response_code = $response->code();
562 8         83 $response_content = $response->content();
563             };
564              
565 9 100       132 warn "$EVAL_ERROR\n" if $EVAL_ERROR;
566              
567 9 100 100     75 if (defined($response_code) && $response_code == HTTP_OK) {
568 6         174 return $self->json_obj()->decode($response_content)->{id};
569             } else {
570 3 100       12 if ($response) {
571 2         12 warn "Unsuccessful Response Posting Sentry Event:\n"._trim($response->as_string(), 1000)."\n";
572             }
573 3         112 return;
574             }
575             }
576              
577             sub _process_event {
578 9     9   21 my ($self, $event) = @_;
579              
580 9         18 foreach my $processor (@{$self->processors()}) {
  9         169  
581 2         37 my $processed_event = $processor->process($event);
582 2 50       14 if ($processed_event) {
583 2         5 $event = $processed_event;
584             } else {
585 0         0 die "processor $processor did not return an event";
586             }
587             }
588              
589 9         66 return $event;
590             }
591              
592             sub _generate_id {
593 20     20   274 (my $uuid = create_uuid_as_string(UUID_V4)) =~ s/-//g;
594 20         4268 return $uuid;
595             }
596              
597             sub _construct_event {
598 23     23   11447 my ($self, %context) = @_;
599              
600             my $event = {
601             event_id => $context{event_id} || $self->context()->{event_id} || _generate_id(),
602             timestamp => $context{timestamp} || $self->context()->{timestamp} || gmtime->datetime(),
603             logger => $context{logger} || $self->context()->{logger} || 'root',
604             server_name => $context{server_name} || $self->context()->{server_name} || hostname(),
605             platform => $context{platform} || $self->context()->{platform} || 'perl',
606              
607             release => $context{release} || $self->context()->{release},
608              
609             message => $context{message} || $self->context()->{message},
610             culprit => $context{culprit} || $self->context()->{culprit},
611              
612             extra => $self->_merge_hashrefs($self->context()->{extra}, $context{extra}),
613             tags => $self->_merge_hashrefs($self->context()->{tags}, $context{tags}),
614             fingerprint => $context{fingerprint} || $self->context()->{fingerprint} || ['{{ default }}'],
615              
616             level => $self->_validate_level($context{level}) || $self->context()->{level} || 'error',
617             environment => $context{environment} || $self->context()->{environment} || $ENV{SENTRY_ENVIRONMENT},
618 23   66     616 };
      66        
      100        
      66        
      100        
      66        
      100        
      100        
      100        
      100        
      66        
619              
620 23         822 $event->{message} = _trim($event->{message}, MAX_MESSAGE);
621 23         60 $event->{culprit} = _trim($event->{culprit}, MAX_CULPRIT);
622              
623 23         406 my $instance_ctx = $self->context();
624 23         138 foreach my $interface (@{ $self->valid_interfaces() }) {
  23         136  
625 115   100     312 my $interface_ctx = $context{$interface} || $instance_ctx->{$interface};
626 115 100       237 $event->{$interface} = $interface_ctx
627             if $interface_ctx;
628             }
629              
630 23         131 return $event;
631             }
632              
633             sub _merge_hashrefs {
634 48     48   6432 my ($self, $hash1, $hash2) = @_;
635              
636             return {
637 8         89 ($hash1 ? %{ $hash1 } : ()),
638 48 100       802 ($hash2 ? %{ $hash2 } : ()),
  6 100       90  
639             };
640             };
641              
642             sub _validate_level {
643 23     23   264 my ($self, $level) = @_;
644              
645 23 100       326 return unless defined($level);
646              
647 10         17 my %level_hash = map { $_ => 1 } @{ $self->valid_levels() };
  50         109  
  10         34  
648              
649 10 100       38 if (exists($level_hash{$level})) {
650 9         200 return $level;
651             } else {
652 1         11 warn "unknown level: $level\n";
653 1         25 return;
654             }
655             };
656              
657             sub _generate_auth_header {
658 8     8   20 my ($self) = @_;
659              
660 8         89 my %fields = (
661             sentry_version => $self->sentry_version(),
662             sentry_client => "raven-perl/$VERSION",
663             sentry_timestamp => time(),
664              
665             sentry_key => $self->public_key(),
666             sentry_secret => $self->secret_key(),
667             );
668              
669 8         60 return 'Sentry ' . join(', ', map { $_ . '=' . $fields{$_} } sort keys %fields);
  40         157  
670             }
671              
672 7     7   384 sub _build_json_obj { JSON::XS->new()->utf8(1)->pretty(1)->allow_nonref(1) }
673             sub _build_ua_obj {
674 1     1   116 return LWP::UserAgent->new(
675             keep_alive => 1,
676             );
677             }
678              
679             =head1 EVENT CONTEXT
680              
681             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.
682              
683             $raven->capture_message(
684             'The sky is falling',
685             Sentry::Raven->exception_context('falling', type => 'SkyException'),
686             );
687              
688             =head2 Sentry::Raven->exception_context( $value, %exception_context )
689              
690             =cut
691              
692             sub exception_context {
693 4     4 1 3148 my ($class, $value, %exception_context) = @_;
694              
695             return (
696             'sentry.interfaces.Exception' => {
697             value => _trim($value, MAX_EXCEPTION_VALUE),
698 4         14 type => _trim($exception_context{type}, MAX_EXCEPTION_TYPE),
699             }
700             );
701             };
702              
703             =head2 Sentry::Raven->request_context( $url, %request_context )
704              
705             =cut
706              
707             sub request_context {
708 1     1 1 6 my ($class, $url, %context) = @_;
709              
710             return (
711             'sentry.interfaces.Http' => {
712             url => _trim($url, MAX_HTTP_URL),
713             method => $context{method},
714             data => _trim($context{data}, MAX_HTTP_DATA),
715             query_string => _trim($context{query_string}, MAX_HTTP_QUERY_STRING),
716             cookies => _trim($context{cookies}, MAX_HTTP_COOKIES),
717             headers => $context{headers},
718             env => $context{env},
719             }
720 1         4 );
721             };
722              
723             =head2 Sentry::Raven->stacktrace_context( $frames )
724              
725             =cut
726              
727             sub stacktrace_context {
728 5     5 1 24 my ($class, $frames) = @_;
729              
730 5         12 eval {
731 5 50       58 $frames = $class->_get_frames_from_devel_stacktrace($frames)
732             if $frames->isa('Devel::StackTrace');
733             };
734              
735             return (
736 5         28 'sentry.interfaces.Stacktrace' => {
737             frames => $frames,
738             }
739             );
740             };
741              
742             =head2 Sentry::Raven->user_context( %user_context )
743              
744             =cut
745              
746             sub user_context {
747 2     2 1 3195 my ($class, %user_context) = @_;
748 2         10 my ($email, $id, $username, $ip_address) = delete @user_context{qw/email id username ip_address/};
749              
750             return (
751 2         7 'sentry.interfaces.User' => {
752             email => _trim($email, MAX_USER_EMAIL),
753             id => _trim($id, MAX_USER_ID),
754             username => _trim($username, MAX_USER_USERNAME),
755             ip_address => _trim($ip_address, MAX_USER_IP_ADDRESS),
756             %user_context,
757             }
758             );
759             };
760              
761             =head2 Sentry::Raven->query_context( $query, %query_context )
762              
763             =cut
764              
765             sub query_context {
766 1     1 1 6 my ($class, $query, %query_context) = @_;
767              
768             return (
769             'sentry.interfaces.Query' => {
770             query => _trim($query, MAX_QUERY_QUERY),
771 1         4 engine => _trim($query_context{engine}, MAX_QUERY_ENGINE),
772             }
773             );
774             };
775              
776             =pod
777              
778             The default context can be modified with the following accessors:
779              
780             =head2 my %context = $raven->get_context();
781              
782             =cut
783              
784             sub get_context {
785 2     2 1 1254 my ($self) = @_;
786 2         4 return %{ $self->context() };
  2         40  
787             };
788              
789             =head2 $raven->add_context( %context )
790              
791             =cut
792              
793             sub add_context {
794 3     3 1 9140 my ($self, %context) = @_;
795             $self->context()->{$_} = $context{$_}
796 3         83 for keys %context;
797             };
798              
799             =head2 $raven->merge_tags( %tags )
800              
801             Merge additional tags into any existing tags in the current context.
802              
803             =cut
804              
805             sub merge_tags {
806 1     1 1 16 my ($self, %tags) = @_;
807 1         33 $self->context()->{tags} = $self->_merge_hashrefs($self->context()->{tags}, \%tags);
808             };
809              
810             =head2 $raven->merge_extra( %tags )
811              
812             Merge additional extra into any existing extra in the current context.
813              
814             =cut
815              
816             sub merge_extra {
817 1     1 1 16 my ($self, %extra) = @_;
818 1         18 $self->context()->{extra} = $self->_merge_hashrefs($self->context()->{extra}, \%extra);
819             };
820              
821             =head2 $raven->clear_context()
822              
823             =cut
824              
825             sub clear_context {
826 1     1 1 1226 my ($self) = @_;
827 1         27 $self->context({});
828             };
829              
830             =head1 EVENT PROCESSORS
831              
832             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.
833              
834             See L<Sentry::Raven::Processor> for information on creating new processors.
835              
836             Available processors:
837              
838             =over
839              
840             =item L<Sentry::Raven::Processor::RemoveStackVariables>
841              
842             =back
843              
844             =head2 $raven->add_processors( [ Sentry::Raven::Processor::RemoveStackVariables, ... ] )
845              
846             =cut
847              
848             sub add_processors {
849 3     3 1 1607 my ($self, @processors) = @_;
850 3         5 push @{ $self->processors() }, @processors;
  3         58  
851             };
852              
853             =head2 $raven->clear_processors( [ Sentry::Raven::Processor::RemoveStackVariables, ... ] )
854              
855             =cut
856              
857             sub clear_processors {
858 3     3 1 6444 my ($self) = @_;
859 3         90 $self->processors([]);
860             };
861              
862             =head1 STANDARD OPTIONS
863              
864             These options can be passed to all methods accepting %context. Passing context to the constructor overrides defaults.
865              
866             =over
867              
868             =item C<< culprit => 'Some::Software' >>
869              
870             The source of the event. Defaults to C<undef>.
871              
872             =item C<< event_id => '534188f7c1ff4ff280c2e1206c9e0548' >>
873              
874             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.
875              
876             =item C<< extra => { key1 => 'val1', ... } >>
877              
878             Arbitrary key value pairs with extra information about an event. Defaults to C<{}>.
879              
880             =item C<< level => 'error' >>
881              
882             Event level of an event. Acceptable values are C<fatal>, C<error>, C<warning>, C<info>, and C<debug>. Defaults to C<error>.
883              
884             =item C<< logger => 'root' >>
885              
886             The creator of an event. Defaults to 'root'.
887              
888             =item C<< platform => 'perl' >>
889              
890             The platform (language) in which an event occurred. Defaults to C<perl>.
891              
892             =item C<< release => 'ec899ea' >>
893              
894             Track the release version of your application.
895              
896             =item C<< processors => [ Sentry::Raven::Processor::RemoveStackVariables, ... ] >>
897              
898             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.
899              
900             =item C<< server_name => 'localhost.example.com' >>
901              
902             The hostname on which an event occurred. Defaults to the system hostname.
903              
904             =item C<< tags => { key1 => 'val1, ... } >>
905              
906             Arbitrary key value pairs with tags for categorizing an event. Defaults to C<{}>.
907              
908             =item C<< fingerprint => [ 'val1', 'val2', ... } >>
909              
910             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 }}']>.
911              
912             =item C<< timestamp => '1970-01-01T00:00:00' >>
913              
914             Timestamp of an event. ISO 8601 format. Defaults to the current time. Invalid values may be discarded silently.
915              
916             =item C<< environment => 'production' >>
917              
918             Specify the environment (i.e. I<staging>, I<production>, etc.) that your project is deployed in. More information
919             can be found on the L<Sentry website|https://docs.sentry.io/enriching-error-data/environments/>.
920              
921             =back
922              
923             =head1 CONFIGURATION AND ENVIRONMENT
924              
925             =over
926              
927             =item SENTRY_DSN=C<< http://<publickey>:<secretkey>@sentry.io/<projectid> >>
928              
929             A default DSN to be used if sentry_dsn is not passed to c<new>.
930              
931             =back
932              
933             =head1 LICENSE
934              
935             Copyright (C) 2019 by Matt Harrington
936              
937             The full text of this license can be found in the LICENSE file included with this module.
938              
939             =cut
940              
941             1;