| line | stmt | bran | cond | sub | pod | time | code | 
| 1 |  |  |  |  |  |  | package Log::Log4perl::Appender::Raven; | 
| 2 |  |  |  |  |  |  | $Log::Log4perl::Appender::Raven::VERSION = '0.005'; | 
| 3 | 9 |  |  | 9 |  | 40884 | use Moose; | 
|  | 9 |  |  |  |  | 3321672 |  | 
|  | 9 |  |  |  |  | 57 |  | 
| 4 |  |  |  |  |  |  |  | 
| 5 | 9 |  |  | 9 |  | 52371 | use Carp; | 
|  | 9 |  |  |  |  | 15 |  | 
|  | 9 |  |  |  |  | 643 |  | 
| 6 | 9 |  |  | 9 |  | 5608 | use Data::Dumper; | 
|  | 9 |  |  |  |  | 42238 |  | 
|  | 9 |  |  |  |  | 609 |  | 
| 7 | 9 |  |  | 9 |  | 68 | use Digest::MD5; | 
|  | 9 |  |  |  |  | 16 |  | 
|  | 9 |  |  |  |  | 293 |  | 
| 8 | 9 |  |  | 9 |  | 5520 | use Sentry::Raven; | 
|  | 9 |  |  |  |  | 2302350 |  | 
|  | 9 |  |  |  |  | 313 |  | 
| 9 | 9 |  |  | 9 |  | 889 | use Log::Log4perl; | 
|  | 9 |  |  |  |  | 35699 |  | 
|  | 9 |  |  |  |  | 81 |  | 
| 10 | 9 |  |  | 9 |  | 391 | use Devel::StackTrace; | 
|  | 9 |  |  |  |  | 23 |  | 
|  | 9 |  |  |  |  | 198 |  | 
| 11 | 9 |  |  | 9 |  | 11843 | use Safe; | 
|  | 9 |  |  |  |  | 285453 |  | 
|  | 9 |  |  |  |  | 522 |  | 
| 12 | 9 |  |  | 9 |  | 6594 | use Text::Template; | 
|  | 9 |  |  |  |  | 25834 |  | 
|  | 9 |  |  |  |  | 10940 |  | 
| 13 |  |  |  |  |  |  |  | 
| 14 |  |  |  |  |  |  | ## Configuration | 
| 15 |  |  |  |  |  |  | has 'sentry_dsn' => ( is => 'ro', isa => 'Maybe[Str]' ); | 
| 16 |  |  |  |  |  |  | has 'sentry_timeout' => ( is => 'ro' , isa => 'Int' ,required => 1 , default => 1 ); | 
| 17 |  |  |  |  |  |  | has 'sentry_culprit_template' => ( is => 'ro', isa => 'Str', required => 1 , default => '{$function}'); | 
| 18 |  |  |  |  |  |  | has 'infect_die' => ( is => 'ro' , isa => 'Bool', default => 0 ); | 
| 19 |  |  |  |  |  |  | # STATIC CONTEXT | 
| 20 |  |  |  |  |  |  | has 'context' => ( is => 'ro' , isa => 'HashRef', default => sub{ {}; }); | 
| 21 |  |  |  |  |  |  | # STATIC TAGS. They will go in the global context. | 
| 22 |  |  |  |  |  |  | has 'tags' => ( is => 'ro' ,isa => 'HashRef', default => sub{ {}; }); | 
| 23 |  |  |  |  |  |  | # Log4Perl MDC key to look for tags | 
| 24 |  |  |  |  |  |  | has 'mdc_tags' => ( is => 'ro' , isa => 'Maybe[Str]' , default => 'sentry_tags' ); | 
| 25 |  |  |  |  |  |  | # Log4perl MDC key to look for extra | 
| 26 |  |  |  |  |  |  | has 'mdc_extra' => ( is => 'ro', isa => 'Maybe[Str]' , default => 'sentry_extra' ); | 
| 27 |  |  |  |  |  |  | # Log4perl MDC key to look for user data. | 
| 28 |  |  |  |  |  |  | has 'mdc_user'  => ( is => 'ro' ,isa => 'Maybe[Str]' , default => 'sentry_user' ); | 
| 29 |  |  |  |  |  |  | # Log4perl MDC key to look for http data. | 
| 30 |  |  |  |  |  |  | has 'mdc_http' => ( is => 'ro' , isa => 'Maybe[Str]' , default => 'sentry_http' ); | 
| 31 |  |  |  |  |  |  |  | 
| 32 |  |  |  |  |  |  | ## End of configuration | 
| 33 |  |  |  |  |  |  |  | 
| 34 |  |  |  |  |  |  | # Operation objects | 
| 35 |  |  |  |  |  |  | has 'raven' => ( is => 'ro', isa => 'Sentry::Raven', lazy_build => 1); | 
| 36 |  |  |  |  |  |  | has 'culprit_text_template' => ( is => 'ro', isa => 'Text::Template' , lazy_build => 1); | 
| 37 |  |  |  |  |  |  | has 'safe' => ( is => 'ro' , isa => 'Safe', lazy_build => 1); | 
| 38 |  |  |  |  |  |  |  | 
| 39 |  |  |  |  |  |  |  | 
| 40 |  |  |  |  |  |  | my %L4P2SENTRY = ('ALL' => 'info', | 
| 41 |  |  |  |  |  |  | 'TRACE' => 'debug', | 
| 42 |  |  |  |  |  |  | 'DEBUG' => 'debug', | 
| 43 |  |  |  |  |  |  | 'INFO' => 'info', | 
| 44 |  |  |  |  |  |  | 'WARN' => 'warning', | 
| 45 |  |  |  |  |  |  | 'ERROR' => 'error', | 
| 46 |  |  |  |  |  |  | 'FATAL' => 'fatal'); | 
| 47 |  |  |  |  |  |  |  | 
| 48 |  |  |  |  |  |  | sub BUILD{ | 
| 49 | 10 |  |  | 10 | 0 | 21 | my ($self) = @_; | 
| 50 | 10 | 100 |  |  |  | 470 | if( $self->infect_die() ){ | 
| 51 | 1 |  |  |  |  | 146 | warn q|INFECTING SIG __DIE__ with Log4perl trickery. Ideally you should not count on that. | 
| 52 |  |  |  |  |  |  |  | 
| 53 |  |  |  |  |  |  | See perldoc Log::Log4perl::Appender::Raven, section 'CODE WIHTOUT LOG4PERL' | 
| 54 |  |  |  |  |  |  |  | 
| 55 |  |  |  |  |  |  | |; | 
| 56 |  |  |  |  |  |  |  | 
| 57 |  |  |  |  |  |  | # Infect die. This is based on http://log4perl.sourceforge.net/releases/Log-Log4perl/docs/html/Log/Log4perl/FAQ.html#73200 | 
| 58 |  |  |  |  |  |  | $SIG{__DIE__} = sub{ | 
| 59 |  |  |  |  |  |  |  | 
| 60 |  |  |  |  |  |  | ## Are we called from within log4perl at all. | 
| 61 |  |  |  |  |  |  | { | 
| 62 | 11 |  |  | 11 |  | 11476 | my $frame_up = 0; | 
|  | 11 |  |  |  |  | 21 |  | 
| 63 | 11 |  |  |  |  | 101 | while( my @caller = caller($frame_up++) ){ | 
| 64 | 45 | 100 |  |  |  | 319 | if( $caller[0] =~ /^Log::Log4perl/ ){ | 
| 65 | 7 |  |  |  |  | 62 | return; | 
| 66 |  |  |  |  |  |  | } | 
| 67 |  |  |  |  |  |  | } | 
| 68 |  |  |  |  |  |  | } | 
| 69 |  |  |  |  |  |  |  | 
| 70 |  |  |  |  |  |  |  | 
| 71 |  |  |  |  |  |  | ## warn "CALLING die Handler"; | 
| 72 | 4 |  |  |  |  | 8 | my $method = 'fatal'; | 
| 73 |  |  |  |  |  |  |  | 
| 74 | 4 |  |  |  |  | 6 | my $level_up = 1; | 
| 75 |  |  |  |  |  |  |  | 
| 76 |  |  |  |  |  |  | # In an eval, nothing is fatal: | 
| 77 | 4 | 50 |  |  |  | 20 | if( $^S ){ | 
| 78 | 4 |  |  |  |  | 10 | $method = 'error'; | 
| 79 |  |  |  |  |  |  | } | 
| 80 |  |  |  |  |  |  |  | 
| 81 | 4 |  |  |  |  | 27 | my ($package, $filename, $line, | 
| 82 |  |  |  |  |  |  | $subroutine, @discard )  = caller(0); | 
| 83 |  |  |  |  |  |  | # warn "CALLER PACKAGE IS $package\n"; | 
| 84 |  |  |  |  |  |  | # warn "CALLER SUBROUTINE IS $subroutine"; | 
| 85 | 4 | 100 |  |  |  | 21 | if( $package =~ /^Carp/ ){ | 
| 86 |  |  |  |  |  |  | # One level up please. We dont want to make Carp the culprit. | 
| 87 |  |  |  |  |  |  | # and we want to know which is the calling package (to get the logger). | 
| 88 | 1 |  |  |  |  | 6 | ($package, @discard )  = caller(1); | 
| 89 | 1 |  |  |  |  | 1 | $level_up++  ; | 
| 90 |  |  |  |  |  |  | } | 
| 91 |  |  |  |  |  |  |  | 
| 92 | 4 |  | 50 |  |  | 45 | my $logger = Log::Log4perl->get_logger($package || ''); | 
| 93 |  |  |  |  |  |  |  | 
| 94 |  |  |  |  |  |  | ## This will make sure the following error or | 
| 95 |  |  |  |  |  |  | ## fatal level work as usual. | 
| 96 | 4 |  |  |  |  | 494 | local $Log::Log4perl::caller_depth = | 
| 97 |  |  |  |  |  |  | $Log::Log4perl::caller_depth + $level_up ; | 
| 98 |  |  |  |  |  |  |  | 
| 99 | 4 |  |  |  |  | 24 | $logger->$method(@_); | 
| 100 |  |  |  |  |  |  |  | 
| 101 | 4 | 50 |  |  |  | 174 | if( $method eq 'error' ){ | 
| 102 |  |  |  |  |  |  | # Do not die. This will be catched by the enclosing eval. | 
| 103 | 4 |  |  |  |  | 49 | return undef; | 
| 104 |  |  |  |  |  |  | } | 
| 105 |  |  |  |  |  |  |  | 
| 106 |  |  |  |  |  |  | # Not in an eval, die for good. | 
| 107 | 0 |  |  |  |  | 0 | die @_; | 
| 108 | 1 |  |  |  |  | 50 | }; | 
| 109 |  |  |  |  |  |  | } | 
| 110 |  |  |  |  |  |  | } | 
| 111 |  |  |  |  |  |  |  | 
| 112 |  |  |  |  |  |  | sub _build_safe{ | 
| 113 |  |  |  |  |  |  | # We do not authorize anything. | 
| 114 | 6 |  |  | 6 |  | 64 | return Safe->new(); | 
| 115 |  |  |  |  |  |  | } | 
| 116 |  |  |  |  |  |  |  | 
| 117 |  |  |  |  |  |  | { | 
| 118 |  |  |  |  |  |  | # The fallback culprint template will signal itself as such in sentry. | 
| 119 |  |  |  |  |  |  | my $FALLBACK_CULPRIT_TEMPLATE = 'FALLBACK {$function}'; | 
| 120 |  |  |  |  |  |  | sub _build_culprit_text_template{ | 
| 121 | 8 |  |  | 8 |  | 19 | my ($self) = @_; | 
| 122 | 8 |  |  |  |  | 368 | my $tmpl = Text::Template->new( TYPE => 'STRING', | 
| 123 |  |  |  |  |  |  | SOURCE => $self->sentry_culprit_template(), | 
| 124 |  |  |  |  |  |  | ); | 
| 125 | 8 | 100 |  |  |  | 1109 | unless( $tmpl->compile() ){ | 
| 126 | 1 |  |  |  |  | 131 | warn "Cannot compile template from '".$self->sentry_culprit_template()."' ERROR:".$Text::Template::ERROR. | 
| 127 |  |  |  |  |  |  | " - Will fallback to hardcoded '".$FALLBACK_CULPRIT_TEMPLATE."'"; | 
| 128 | 1 |  |  |  |  | 7 | $tmpl = Text::Template->new( TYPE => 'STRING', SOURCE => $FALLBACK_CULPRIT_TEMPLATE); | 
| 129 | 1 | 50 |  |  |  | 92 | $tmpl->compile() or die "Invalid fallback template ".$FALLBACK_CULPRIT_TEMPLATE; | 
| 130 |  |  |  |  |  |  | } | 
| 131 | 8 |  |  |  |  | 1099 | return $tmpl; | 
| 132 |  |  |  |  |  |  | } | 
| 133 |  |  |  |  |  |  | } | 
| 134 |  |  |  |  |  |  |  | 
| 135 |  |  |  |  |  |  | sub _build_raven{ | 
| 136 | 9 |  |  | 9 |  | 17 | my ($self) = @_; | 
| 137 |  |  |  |  |  |  |  | 
| 138 | 9 |  | 33 |  |  | 345 | my $dsn = $self->sentry_dsn || $ENV{SENTRY_DSN} || confess("No sentry_dsn config or SENTRY_DSN in ENV"); | 
| 139 |  |  |  |  |  |  |  | 
| 140 |  |  |  |  |  |  |  | 
| 141 | 8 |  |  |  |  | 17 | my %raven_context = %{$self->context()}; | 
|  | 8 |  |  |  |  | 291 |  | 
| 142 | 8 |  |  |  |  | 324 | $raven_context{tags} = $self->tags(); | 
| 143 |  |  |  |  |  |  |  | 
| 144 | 8 |  |  |  |  | 325 | return Sentry::Raven->new( sentry_dsn => $dsn, | 
| 145 |  |  |  |  |  |  | timeout => $self->sentry_timeout, | 
| 146 |  |  |  |  |  |  | %raven_context | 
| 147 |  |  |  |  |  |  | ); | 
| 148 |  |  |  |  |  |  | } | 
| 149 |  |  |  |  |  |  |  | 
| 150 |  |  |  |  |  |  | sub log{ | 
| 151 | 19 |  |  | 19 | 0 | 27017 | my ($self, %params) = @_; | 
| 152 |  |  |  |  |  |  |  | 
| 153 |  |  |  |  |  |  | ## Any logging within this method will be discarded. | 
| 154 | 19 | 50 |  |  |  | 122 | if( Log::Log4perl::MDC->get(__PACKAGE__.'-reentrance') ){ | 
| 155 | 0 |  |  |  |  | 0 | return; | 
| 156 |  |  |  |  |  |  | } | 
| 157 | 19 |  |  |  |  | 288 | Log::Log4perl::MDC->put(__PACKAGE__.'-reentrance', 1); | 
| 158 |  |  |  |  |  |  |  | 
| 159 |  |  |  |  |  |  | # use Data::Dumper; | 
| 160 |  |  |  |  |  |  | # warn Dumper(\%params); | 
| 161 |  |  |  |  |  |  |  | 
| 162 |  |  |  |  |  |  | # Look there to see what sentry expects: | 
| 163 |  |  |  |  |  |  | # http://sentry.readthedocs.org/en/latest/developer/client/index.html#building-the-json-packet | 
| 164 |  |  |  |  |  |  |  | 
| 165 | 19 | 50 |  |  |  | 186 | my $sentry_message = length($params{message}) > 1000 ? substr($params{message}, 0 , 1000) : $params{message}; | 
| 166 | 19 |  |  |  |  | 36 | my $sentry_logger  = $params{log4p_category}; | 
| 167 | 19 |  | 50 |  |  | 94 | my $sentry_level = $L4P2SENTRY{$params{log4p_level}} || 'info'; | 
| 168 |  |  |  |  |  |  |  | 
| 169 |  |  |  |  |  |  | # We are 4 levels down after the standard Log4perl caller_depth | 
| 170 | 19 |  |  |  |  | 74 | my $caller_offset = Log::Log4perl::caller_depth_offset( $Log::Log4perl::caller_depth + 4 ); | 
| 171 |  |  |  |  |  |  |  | 
| 172 |  |  |  |  |  |  | ## Stringify arguments NOW (no_refs => 1). This avoids sending huuuuuge objects when | 
| 173 |  |  |  |  |  |  | ## Serializing this stack trace inside Sentry::Raven | 
| 174 | 19 |  |  |  |  | 352 | my $caller_frames = Devel::StackTrace->new( no_refs => 1); | 
| 175 |  |  |  |  |  |  | { | 
| 176 |  |  |  |  |  |  | ## Remove the frames from the Log4Perl layer. | 
| 177 | 19 |  |  |  |  | 4959 | my @frames = $caller_frames->frames(); | 
|  | 19 |  |  |  |  | 71 |  | 
| 178 | 19 |  |  |  |  | 6112 | splice(@frames, 0, $caller_offset); | 
| 179 | 19 |  |  |  |  | 59 | $caller_frames->frames(@frames); | 
| 180 |  |  |  |  |  |  | } | 
| 181 |  |  |  |  |  |  |  | 
| 182 | 19 |  |  |  |  | 470 | my $call_depth = $caller_offset; | 
| 183 |  |  |  |  |  |  |  | 
| 184 |  |  |  |  |  |  | # Defaults caller properties | 
| 185 | 19 |  |  |  |  | 52 | my $caller_properties = { | 
| 186 |  |  |  |  |  |  | function => 'main', | 
| 187 |  |  |  |  |  |  | }; | 
| 188 | 19 |  |  |  |  | 143 | my @log_call_info = caller($call_depth - 1); | 
| 189 | 19 |  | 50 |  |  | 111 | $caller_properties->{line} = $log_call_info[2] || 'NOTOPLINE'; | 
| 190 |  |  |  |  |  |  |  | 
| 191 |  |  |  |  |  |  | { | 
| 192 |  |  |  |  |  |  | # Go up the caller ladder until the first non eval | 
| 193 | 19 |  |  |  |  | 28 | while( my @caller_info = caller($call_depth) ){ | 
|  | 19 |  |  |  |  | 129 |  | 
| 194 |  |  |  |  |  |  |  | 
| 195 |  |  |  |  |  |  | # Skip evals and __ANON__ methods. | 
| 196 |  |  |  |  |  |  | # The anon method will make that compatible with the new Log::Any (>0.15) | 
| 197 | 19 |  | 50 |  |  | 109 | my $caller_string = $caller_info[3] || ''; | 
| 198 |  |  |  |  |  |  |  | 
| 199 | 19 | 100 | 100 |  |  | 199 | unless( ( $caller_string eq '(eval)' ) | 
| 200 |  |  |  |  |  |  | || ( scalar(reverse($caller_string)) =~ /^__NONA__/ ) | 
| 201 |  |  |  |  |  |  | #  ^ This test for the caller string to end with __ANON__ , but faster. | 
| 202 |  |  |  |  |  |  | ){ | 
| 203 |  |  |  |  |  |  | # This is good. | 
| 204 |  |  |  |  |  |  | # Subroutine name, or filename, or just main | 
| 205 | 14 |  | 0 |  |  | 49 | $caller_properties->{function} = $caller_info[3] || $caller_info[1] || 'main'; | 
| 206 |  |  |  |  |  |  | # For other properties, we are interested in the place where $log->something | 
| 207 |  |  |  |  |  |  | # was called, not were the caller of $log->something was called from | 
| 208 | 14 |  |  |  |  | 79 | my @log_call_info = caller($call_depth - 1); | 
| 209 | 14 |  | 50 |  |  | 47 | $caller_properties->{line} = $log_call_info[2] || 'NOLINE'; | 
| 210 | 14 |  |  |  |  | 50 | last; | 
| 211 |  |  |  |  |  |  | } | 
| 212 | 5 |  |  |  |  | 32 | $call_depth++; | 
| 213 |  |  |  |  |  |  | } | 
| 214 |  |  |  |  |  |  | } | 
| 215 |  |  |  |  |  |  |  | 
| 216 | 19 |  |  |  |  | 38 | my $tags = {}; | 
| 217 | 19 | 50 |  |  |  | 908 | if( my $mdc_tags = $self->mdc_tags() ){ | 
| 218 | 19 |  | 100 |  |  | 71 | $tags = Log::Log4perl::MDC->get($mdc_tags) || {}; | 
| 219 |  |  |  |  |  |  | } | 
| 220 |  |  |  |  |  |  |  | 
| 221 | 19 |  |  |  |  | 210 | my $extra = {}; | 
| 222 | 19 | 50 |  |  |  | 775 | if( my $mdc_extra = $self->mdc_extra() ){ | 
| 223 | 19 |  | 100 |  |  | 71 | $extra = Log::Log4perl::MDC->get($mdc_extra) || {}; | 
| 224 |  |  |  |  |  |  | } | 
| 225 |  |  |  |  |  |  |  | 
| 226 | 19 |  |  |  |  | 190 | my $user; | 
| 227 | 19 | 50 |  |  |  | 736 | if( my $mdc_user = $self->mdc_user() ){ | 
| 228 | 19 |  |  |  |  | 59 | $user = Log::Log4perl::MDC->get($mdc_user); | 
| 229 |  |  |  |  |  |  | } | 
| 230 |  |  |  |  |  |  |  | 
| 231 | 19 |  |  |  |  | 118 | my $http; | 
| 232 | 19 | 50 |  |  |  | 732 | if( my $mdc_http = $self->mdc_http() ){ | 
| 233 | 19 |  |  |  |  | 68 | $http = Log::Log4perl::MDC->get($mdc_http); | 
| 234 |  |  |  |  |  |  | } | 
| 235 |  |  |  |  |  |  |  | 
| 236 |  |  |  |  |  |  | # Calculate the culprit from the template | 
| 237 |  |  |  |  |  |  | my $sentry_culprit = $self->culprit_text_template->fill_in( | 
| 238 |  |  |  |  |  |  | SAFE => $self->safe(), | 
| 239 |  |  |  |  |  |  | HASH => { | 
| 240 | 19 |  |  |  |  | 235 | %{$caller_properties}, | 
| 241 |  |  |  |  |  |  | message => $sentry_message, | 
| 242 |  |  |  |  |  |  | sign => sub{ | 
| 243 | 0 |  |  | 0 |  | 0 | my ($string, $offset, $length) = @_; | 
| 244 | 0 | 0 |  |  |  | 0 | defined( $string ) || ( $string = '' ); | 
| 245 | 0 | 0 |  |  |  | 0 | defined( $offset ) || ( $offset = 0 ); | 
| 246 | 0 | 0 |  |  |  | 0 | defined( $length ) || ( $length = 4 ); | 
| 247 | 0 |  |  |  |  | 0 | return substr(Digest::MD5::md5_hex(substr($string, $offset, $length)), 0, 4); | 
| 248 |  |  |  |  |  |  | } | 
| 249 | 19 |  |  |  |  | 893 | }); | 
| 250 |  |  |  |  |  |  |  | 
| 251 |  |  |  |  |  |  | # OK WE HAVE THE BASIC Sentry options. | 
| 252 |  |  |  |  |  |  | $self->raven->capture_message($sentry_message, | 
| 253 |  |  |  |  |  |  | logger => $sentry_logger, | 
| 254 |  |  |  |  |  |  | level => $sentry_level, | 
| 255 |  |  |  |  |  |  | culprit => $sentry_culprit, | 
| 256 |  |  |  |  |  |  | tags => $tags, | 
| 257 |  |  |  |  |  |  | extra => $extra, | 
| 258 |  |  |  |  |  |  | Sentry::Raven->stacktrace_context( $caller_frames ), | 
| 259 |  |  |  |  |  |  | ( $user ? Sentry::Raven->user_context(%$user) : () ), | 
| 260 | 19 | 100 |  |  |  | 44380 | ( $http ? Sentry::Raven->request_context( ( delete $http->{url} ) , %$http ) : () ) | 
|  |  | 100 |  |  |  |  |  | 
| 261 |  |  |  |  |  |  | ); | 
| 262 |  |  |  |  |  |  |  | 
| 263 | 19 |  |  |  |  | 9561 | Log::Log4perl::MDC->put(__PACKAGE__.'-reentrance', undef); | 
| 264 |  |  |  |  |  |  | } | 
| 265 |  |  |  |  |  |  |  | 
| 266 |  |  |  |  |  |  |  | 
| 267 |  |  |  |  |  |  | __PACKAGE__->meta->make_immutable(); | 
| 268 |  |  |  |  |  |  |  | 
| 269 |  |  |  |  |  |  |  | 
| 270 |  |  |  |  |  |  | =head1 NAME | 
| 271 |  |  |  |  |  |  |  | 
| 272 |  |  |  |  |  |  | Log::Log4perl::Appender::Raven - Append log events to your Sentry account. | 
| 273 |  |  |  |  |  |  |  | 
| 274 |  |  |  |  |  |  | =head1 BUILD STATUS | 
| 275 |  |  |  |  |  |  |  | 
| 276 |  |  |  |  |  |  | =begin html | 
| 277 |  |  |  |  |  |  |  | 
| 278 |  |  |  |  |  |  | <a href="https://travis-ci.org/jeteve/l4p-appender-raven"><img src="https://travis-ci.org/jeteve/l4p-appender-raven.svg?branch=master"></a> | 
| 279 |  |  |  |  |  |  |  | 
| 280 |  |  |  |  |  |  | =end html | 
| 281 |  |  |  |  |  |  |  | 
| 282 |  |  |  |  |  |  | =head1 WARNING(s) | 
| 283 |  |  |  |  |  |  |  | 
| 284 |  |  |  |  |  |  | This appender will send ALL the log events it receives to your | 
| 285 |  |  |  |  |  |  | Sentry DSN synchronously. If you generate a lot of logging, that can make your sentry account | 
| 286 |  |  |  |  |  |  | saturate quite quickly and your application come to a severe slowdown. | 
| 287 |  |  |  |  |  |  |  | 
| 288 |  |  |  |  |  |  | Using Log4perl appender's Threshold or L<Log::Log4perl::Filter> in your log4perl config, and | 
| 289 |  |  |  |  |  |  | experimenting a little bit is Highly Recommended. | 
| 290 |  |  |  |  |  |  |  | 
| 291 |  |  |  |  |  |  | Remember sentry is designed to record errors, so hopefully your application will | 
| 292 |  |  |  |  |  |  | not generate too many of them. | 
| 293 |  |  |  |  |  |  |  | 
| 294 |  |  |  |  |  |  | You have been warned. | 
| 295 |  |  |  |  |  |  |  | 
| 296 |  |  |  |  |  |  | =head1 SYNOPSIS | 
| 297 |  |  |  |  |  |  |  | 
| 298 |  |  |  |  |  |  | Read the L<CONFIGURATION> section, then use Log4perl just as usual. | 
| 299 |  |  |  |  |  |  |  | 
| 300 |  |  |  |  |  |  | If you are not familiar with Log::Log4perl, please check L<Log::Log4perl> | 
| 301 |  |  |  |  |  |  |  | 
| 302 |  |  |  |  |  |  | In a nutshell, here's the minimul l4p config to output anything from ERROR to Sentry: | 
| 303 |  |  |  |  |  |  |  | 
| 304 |  |  |  |  |  |  | log4perl.rootLogger=DEBUG, Raven | 
| 305 |  |  |  |  |  |  |  | 
| 306 |  |  |  |  |  |  | log4perl.appender.Raven=Log::Log4perl::Appender::Raven | 
| 307 |  |  |  |  |  |  | log4perl.appender.Raven.Threshold=ERROR | 
| 308 |  |  |  |  |  |  | log4perl.appender.Raven.sentry_dsn="https://user:key@sentry-host.com/project_id" | 
| 309 |  |  |  |  |  |  | log4perl.appender.Raven.layout=Log::Log4perl::Layout::PatternLayout | 
| 310 |  |  |  |  |  |  | log4perl.appender.Raven.layout.ConversionPattern=%X{chunk} %d %F{1} %L> %m %n | 
| 311 |  |  |  |  |  |  |  | 
| 312 |  |  |  |  |  |  |  | 
| 313 |  |  |  |  |  |  | =head1 CONFIGURATION | 
| 314 |  |  |  |  |  |  |  | 
| 315 |  |  |  |  |  |  | This is just another L<Log::Log4perl::Appender>. | 
| 316 |  |  |  |  |  |  |  | 
| 317 |  |  |  |  |  |  | =head2 Simple Configuration | 
| 318 |  |  |  |  |  |  |  | 
| 319 |  |  |  |  |  |  | The only mandatory configuration key | 
| 320 |  |  |  |  |  |  | is *sentry_dsn* which is your sentry dsn string obtained from your sentry account. | 
| 321 |  |  |  |  |  |  | See http://www.getsentry.com/ and https://github.com/getsentry/sentry for more details. | 
| 322 |  |  |  |  |  |  |  | 
| 323 |  |  |  |  |  |  | Alternatively to setting this configuration key, you can set an environment variable SENTRY_DSN | 
| 324 |  |  |  |  |  |  | with the same setting. - Not recommended - | 
| 325 |  |  |  |  |  |  |  | 
| 326 |  |  |  |  |  |  | Example: | 
| 327 |  |  |  |  |  |  |  | 
| 328 |  |  |  |  |  |  | log4perl.rootLogger=ERROR, Raven | 
| 329 |  |  |  |  |  |  |  | 
| 330 |  |  |  |  |  |  | layout_class=Log::Log4perl::Layout::PatternLayout | 
| 331 |  |  |  |  |  |  | layout_pattern=%X{chunk} %d %F{1} %L> %m %n | 
| 332 |  |  |  |  |  |  |  | 
| 333 |  |  |  |  |  |  | log4perl.appender.Raven=Log::Log4perl::Appender::Raven | 
| 334 |  |  |  |  |  |  | log4perl.appender.Raven.sentry_dsn="http://user:key@host.com/project_id" | 
| 335 |  |  |  |  |  |  | log4perl.appender.Raven.sentry_timeout=1 | 
| 336 |  |  |  |  |  |  | log4perl.appender.Raven.layout=${layout_class} | 
| 337 |  |  |  |  |  |  | log4perl.appender.Raven.layout.ConversionPattern=${layout_pattern} | 
| 338 |  |  |  |  |  |  |  | 
| 339 |  |  |  |  |  |  | =head2 Configuring the culprit string | 
| 340 |  |  |  |  |  |  |  | 
| 341 |  |  |  |  |  |  | By default, this appender will calculate the Sentry culprit to be | 
| 342 |  |  |  |  |  |  | the fully qualified name of the function that called the log method, as Sentry | 
| 343 |  |  |  |  |  |  | recommends. | 
| 344 |  |  |  |  |  |  |  | 
| 345 |  |  |  |  |  |  | If you require more flexibility and precision in your culprit, you can | 
| 346 |  |  |  |  |  |  | configure it as a template. For instance: | 
| 347 |  |  |  |  |  |  |  | 
| 348 |  |  |  |  |  |  | log4perl.appender.Raven.sentry_culprit_template={$function}-{$line} | 
| 349 |  |  |  |  |  |  |  | 
| 350 |  |  |  |  |  |  | The default is '{$function}', as Sentry prescribes. But most people will probably | 
| 351 |  |  |  |  |  |  | be more happy with the added {$line} element, as it makes discriminating between culprits easier. | 
| 352 |  |  |  |  |  |  |  | 
| 353 |  |  |  |  |  |  | The template format follows L<Text::Template> and the available variables and functions are as follow: | 
| 354 |  |  |  |  |  |  |  | 
| 355 |  |  |  |  |  |  | =over | 
| 356 |  |  |  |  |  |  |  | 
| 357 |  |  |  |  |  |  | =item function | 
| 358 |  |  |  |  |  |  |  | 
| 359 |  |  |  |  |  |  | The fully qualified name of the function that called the log method. | 
| 360 |  |  |  |  |  |  |  | 
| 361 |  |  |  |  |  |  | =item line | 
| 362 |  |  |  |  |  |  |  | 
| 363 |  |  |  |  |  |  | The line at which the log method was called | 
| 364 |  |  |  |  |  |  |  | 
| 365 |  |  |  |  |  |  | =item message | 
| 366 |  |  |  |  |  |  |  | 
| 367 |  |  |  |  |  |  | The Log4perl generated message. Keep in mind that this is the message AFTER it has been calculated by | 
| 368 |  |  |  |  |  |  | the layout pattern. | 
| 369 |  |  |  |  |  |  |  | 
| 370 |  |  |  |  |  |  | =item sign($string, $offset, $length) | 
| 371 |  |  |  |  |  |  |  | 
| 372 |  |  |  |  |  |  | A function that calculates a small (4 chars) signature of the given string. $string, $offset | 
| 373 |  |  |  |  |  |  | and $length are optional. | 
| 374 |  |  |  |  |  |  |  | 
| 375 |  |  |  |  |  |  | This is useful for instance if some part of your code manage errors in a centralized way, or in other | 
| 376 |  |  |  |  |  |  | terms if the place at which you call '$log->error()' can output various messages. | 
| 377 |  |  |  |  |  |  | To help discriminating between culprit, you can for instance configure your culprit template: | 
| 378 |  |  |  |  |  |  |  | 
| 379 |  |  |  |  |  |  |  | 
| 380 |  |  |  |  |  |  | log4perl.appender.Raven.sentry_culprit_template={$function}-{$line}-{sign($message, 30, 4)} | 
| 381 |  |  |  |  |  |  |  | 
| 382 |  |  |  |  |  |  | Note that in the example, we look at a part of the message after the 30th character, which | 
| 383 |  |  |  |  |  |  | helps skipping the common message parts defined by your message layout. Adjust this number (30) | 
| 384 |  |  |  |  |  |  | to make sure you pick a substring of your message in a meaningful area. | 
| 385 |  |  |  |  |  |  |  | 
| 386 |  |  |  |  |  |  | =back | 
| 387 |  |  |  |  |  |  |  | 
| 388 |  |  |  |  |  |  | =head2 Timeout | 
| 389 |  |  |  |  |  |  |  | 
| 390 |  |  |  |  |  |  | The default timeout is 1 second. Feel free to bump it up. If sending an event | 
| 391 |  |  |  |  |  |  | timesout (or if the sentry host is down or doesn't exist), a plain Perl | 
| 392 |  |  |  |  |  |  | warning will be output. | 
| 393 |  |  |  |  |  |  |  | 
| 394 |  |  |  |  |  |  | =head2 Configuration with Static Tags | 
| 395 |  |  |  |  |  |  |  | 
| 396 |  |  |  |  |  |  | You have the option of predefining a set of tags that will be send to | 
| 397 |  |  |  |  |  |  | your Sentry installation with every event. Remember Sentry tags have a name | 
| 398 |  |  |  |  |  |  | and a value (they are not just 'labels'). | 
| 399 |  |  |  |  |  |  |  | 
| 400 |  |  |  |  |  |  | Example: | 
| 401 |  |  |  |  |  |  |  | 
| 402 |  |  |  |  |  |  | ... | 
| 403 |  |  |  |  |  |  | log4perl.appender.Raven.tags.application=myproduct | 
| 404 |  |  |  |  |  |  | log4perl.appender.Raven.tags.installation=live | 
| 405 |  |  |  |  |  |  | ... | 
| 406 |  |  |  |  |  |  |  | 
| 407 |  |  |  |  |  |  | =head2 Configure and use Dynamic Tagging | 
| 408 |  |  |  |  |  |  |  | 
| 409 |  |  |  |  |  |  | Dynamic tagging is performed using the Log4Perl MDC mechanism. | 
| 410 |  |  |  |  |  |  | See L<Log::Log4perl::MDC> if you are not familiar with it. | 
| 411 |  |  |  |  |  |  |  | 
| 412 |  |  |  |  |  |  | Anywhere in your code. | 
| 413 |  |  |  |  |  |  |  | 
| 414 |  |  |  |  |  |  | ... | 
| 415 |  |  |  |  |  |  | Log::Log4perl::MDC->set('sentry_tags' , { subsystem => 'my_subsystem', ... }); | 
| 416 |  |  |  |  |  |  | $log->error("Something very wrong"); | 
| 417 |  |  |  |  |  |  | ... | 
| 418 |  |  |  |  |  |  |  | 
| 419 |  |  |  |  |  |  | Or specify which key to capture in config: | 
| 420 |  |  |  |  |  |  |  | 
| 421 |  |  |  |  |  |  | ... | 
| 422 |  |  |  |  |  |  | log4perl.appender.Raven.mdc_tags=my_sentry_tags | 
| 423 |  |  |  |  |  |  | ... | 
| 424 |  |  |  |  |  |  |  | 
| 425 |  |  |  |  |  |  |  | 
| 426 |  |  |  |  |  |  | Note that tags added this way will be added to the statically define ones, or override them in case | 
| 427 |  |  |  |  |  |  | of conflict. | 
| 428 |  |  |  |  |  |  |  | 
| 429 |  |  |  |  |  |  | Note: Tags are meant to categorize your Sentry events and will be displayed | 
| 430 |  |  |  |  |  |  | in the Sentry GUI like any other category. | 
| 431 |  |  |  |  |  |  |  | 
| 432 |  |  |  |  |  |  | =head2 Configure and use User Data | 
| 433 |  |  |  |  |  |  |  | 
| 434 |  |  |  |  |  |  | Sentry supports structured user data that can be added to your event. | 
| 435 |  |  |  |  |  |  | User data works a bit like the tags, except only three keys are supported: | 
| 436 |  |  |  |  |  |  |  | 
| 437 |  |  |  |  |  |  | id, username and email. See L<Sentry::Raven> (capture_user) for a description of those keys. | 
| 438 |  |  |  |  |  |  |  | 
| 439 |  |  |  |  |  |  |  | 
| 440 |  |  |  |  |  |  | In your code: | 
| 441 |  |  |  |  |  |  |  | 
| 442 |  |  |  |  |  |  | ... | 
| 443 |  |  |  |  |  |  | Log::Log4perl::MDC->set('sentry_user' , { id => '123' , email => 'jeteve@cpan.org', username => 'jeteve' }); | 
| 444 |  |  |  |  |  |  | $log->error("Something very wrong"); | 
| 445 |  |  |  |  |  |  | ... | 
| 446 |  |  |  |  |  |  |  | 
| 447 |  |  |  |  |  |  |  | 
| 448 |  |  |  |  |  |  | Or specify the MDC key to capture in Config: | 
| 449 |  |  |  |  |  |  |  | 
| 450 |  |  |  |  |  |  | ... | 
| 451 |  |  |  |  |  |  | log4perl.appender.Raven.mdc_user=my_sentry_user | 
| 452 |  |  |  |  |  |  | ... | 
| 453 |  |  |  |  |  |  |  | 
| 454 |  |  |  |  |  |  |  | 
| 455 |  |  |  |  |  |  | =head2 Configure and use HTTP Request data. | 
| 456 |  |  |  |  |  |  |  | 
| 457 |  |  |  |  |  |  | Sentry support HTTP Request structured data that can be added to your event. | 
| 458 |  |  |  |  |  |  | HTTP Data work a bit like tags, except only a number of keys are supported: | 
| 459 |  |  |  |  |  |  |  | 
| 460 |  |  |  |  |  |  | url, method, data, query_string, cookies, headers, env | 
| 461 |  |  |  |  |  |  |  | 
| 462 |  |  |  |  |  |  | See L<Sentry::Raven> (capture_request) or interface 'Http' in L<http://sentry.readthedocs.org/en/latest/developer/interfaces/index.html> | 
| 463 |  |  |  |  |  |  | for a full description of those keys. | 
| 464 |  |  |  |  |  |  |  | 
| 465 |  |  |  |  |  |  | In your code: | 
| 466 |  |  |  |  |  |  |  | 
| 467 |  |  |  |  |  |  | ... | 
| 468 |  |  |  |  |  |  | Log::Log4perl::MDC->set('sentry_http' , { url => 'http://www.example.com' , method => 'GET' , ... }); | 
| 469 |  |  |  |  |  |  | $log->error("Something very wrong"); | 
| 470 |  |  |  |  |  |  | ... | 
| 471 |  |  |  |  |  |  |  | 
| 472 |  |  |  |  |  |  | Or specify the MDC key to capture in Config: | 
| 473 |  |  |  |  |  |  |  | 
| 474 |  |  |  |  |  |  | ... | 
| 475 |  |  |  |  |  |  | log4perl.appender.Raven.mdc_http=my_sentry_http | 
| 476 |  |  |  |  |  |  | ... | 
| 477 |  |  |  |  |  |  |  | 
| 478 |  |  |  |  |  |  | =head2 Configure and use Dynamic Extra | 
| 479 |  |  |  |  |  |  |  | 
| 480 |  |  |  |  |  |  | Sentry allows you to specify any data (as a Single level HashRef) that will be stored with the Event. | 
| 481 |  |  |  |  |  |  |  | 
| 482 |  |  |  |  |  |  | It's very similar to dynamic tags, except its not tags. | 
| 483 |  |  |  |  |  |  |  | 
| 484 |  |  |  |  |  |  | Then anywere in your code: | 
| 485 |  |  |  |  |  |  |  | 
| 486 |  |  |  |  |  |  | ... | 
| 487 |  |  |  |  |  |  | Log::Log4perl::MDC->set('my_sentry_extra' , { session_id => ... , ...  }); | 
| 488 |  |  |  |  |  |  | $log->error("Something very wrong"); | 
| 489 |  |  |  |  |  |  | ... | 
| 490 |  |  |  |  |  |  |  | 
| 491 |  |  |  |  |  |  |  | 
| 492 |  |  |  |  |  |  | Or specify MDC key to capture in config: | 
| 493 |  |  |  |  |  |  |  | 
| 494 |  |  |  |  |  |  | ... | 
| 495 |  |  |  |  |  |  | log4perl.appender.Raven.mdc_extra=my_sentry_extra | 
| 496 |  |  |  |  |  |  | ... | 
| 497 |  |  |  |  |  |  |  | 
| 498 |  |  |  |  |  |  | =head2 Configuration with a Static Context. | 
| 499 |  |  |  |  |  |  |  | 
| 500 |  |  |  |  |  |  | You can use lines like: | 
| 501 |  |  |  |  |  |  |  | 
| 502 |  |  |  |  |  |  | log4perl.appender.Raven.context.platform=myproduct | 
| 503 |  |  |  |  |  |  |  | 
| 504 |  |  |  |  |  |  | To define static L<Sentry::Raven> context. The list of context keys supported is not very | 
| 505 |  |  |  |  |  |  | long, and most of them are defined dynamically when you use this package anyway. | 
| 506 |  |  |  |  |  |  |  | 
| 507 |  |  |  |  |  |  | See L<Sentry::Raven> for more details. | 
| 508 |  |  |  |  |  |  |  | 
| 509 |  |  |  |  |  |  | =head1 USING Log::Any | 
| 510 |  |  |  |  |  |  |  | 
| 511 |  |  |  |  |  |  | This is tested to work with Log::Any just the same way it works when you use Log4perl directly. | 
| 512 |  |  |  |  |  |  |  | 
| 513 |  |  |  |  |  |  | =head1 CODE WITHOUT LOG4PERL | 
| 514 |  |  |  |  |  |  |  | 
| 515 |  |  |  |  |  |  | Warning: Experimental feature. | 
| 516 |  |  |  |  |  |  |  | 
| 517 |  |  |  |  |  |  | If your code, or some of its dependencies is not using Log4perl, you might want | 
| 518 |  |  |  |  |  |  | to consider infecting the __DIE__ pseudo signal with some amount of trickery to have die (and Carp::confess/croak) | 
| 519 |  |  |  |  |  |  | calls go through log4perl. | 
| 520 |  |  |  |  |  |  |  | 
| 521 |  |  |  |  |  |  | This appender makes that easy for you, and provides the 'infect_die' configuration property | 
| 522 |  |  |  |  |  |  | to do so: | 
| 523 |  |  |  |  |  |  |  | 
| 524 |  |  |  |  |  |  | ... | 
| 525 |  |  |  |  |  |  | log4perl.appender.Raven.infect_die=1 | 
| 526 |  |  |  |  |  |  | ... | 
| 527 |  |  |  |  |  |  |  | 
| 528 |  |  |  |  |  |  | This is heavily inspired by L<https://metacpan.org/pod/Log::Log4perl::FAQ#My-program-already-uses-warn-and-die-.-How-can-I-switch-to-Log4perl> | 
| 529 |  |  |  |  |  |  |  | 
| 530 |  |  |  |  |  |  | While this can be convenient to quickly implement this in a non-log4perl aware piece of software, you | 
| 531 |  |  |  |  |  |  | are strongly encourage not to use this feature and pepper your call with appropriate Log4perl calls. | 
| 532 |  |  |  |  |  |  |  | 
| 533 |  |  |  |  |  |  | =head1 SEE ALSO | 
| 534 |  |  |  |  |  |  |  | 
| 535 |  |  |  |  |  |  | L<Sentry::Raven> , L<Log::Log4perl>, L<Log::Any> , L<Log::Any::Adapter::Log4perl> | 
| 536 |  |  |  |  |  |  |  | 
| 537 |  |  |  |  |  |  | =head1 AUTHOR | 
| 538 |  |  |  |  |  |  |  | 
| 539 |  |  |  |  |  |  | Jerome Eteve jeteve@cpan.com | 
| 540 |  |  |  |  |  |  |  | 
| 541 |  |  |  |  |  |  | =cut |