File Coverage

blib/lib/Sentry/Log/Raven.pm
Criterion Covered Total %
statement 13 15 86.6
branch n/a
condition n/a
subroutine 5 5 100.0
pod n/a
total 18 20 90.0


line stmt bran cond sub pod time code
1             package Sentry::Log::Raven;
2              
3             =head1 NAME
4              
5             Sentry::Log::Raven - sending exception log messages to Sentry.
6              
7             =cut
8              
9             our $VERSION = '1.01';
10              
11              
12             =head1 SYNOPSIS
13              
14              
15             my $raven = Sentry::Log::Raven->new(
16             sentry_public_key => "public",
17             sentry_secret_key => "secret",
18             domain_url => "http(s)://sentry domain",
19             project_id => "sentry project id",
20             sentry_version => 4 # can be omitted
21             ssl_verify => 0 # can be omitted
22              
23             );
24              
25              
26             $raven->message({ message => "Alert!" });
27              
28             =head1 EXPORT
29              
30              
31             =cut
32              
33 1     1   15007 use strict;
  1         1  
  1         43  
34 1     1   4 use warnings;
  1         1  
  1         24  
35              
36 1     1   516 use HTTP::Request::Common;
  1         21899  
  1         74  
37 1     1   3734 use LWP::UserAgent;
  1         20804  
  1         32  
38 1     1   217 use JSON;
  0            
  0            
39             use MIME::Base64 'encode_base64';
40             use Time::HiRes (qw(gettimeofday));
41             use DateTime;
42             use Sys::Hostname;
43             use Mozilla::CA;
44             use IO::Socket::SSL;
45              
46             =head4 new
47              
48             Constructor. Use like:
49              
50             my $raven = Sentry::Log::Raven->new(
51             sentry_public_key => "public",
52             sentry_secret_key => "secret",
53             domain_url => "http(s)://sentry domain",
54             project_id => "sentry project id",
55             sentry_version => 4 # can be omitted
56             ssl_verify => 0 # can be omitted
57             );
58              
59             =cut
60             sub new {
61             my ( $class, %options ) = @_;
62              
63             foreach (qw(sentry_public_key sentry_secret_key domain_url project_id)) {
64             if (!exists $options{$_}) {
65             die "Mandatory paramter '$_' not defined";
66             }
67             }
68              
69             my $self = {
70             ua => LWP::UserAgent->new(),
71             %options,
72             };
73              
74             $self->{'ssl_verify'} ||= 0;
75              
76             if ($self->{domain_url} !~ m/^http/) {
77             die "Domain url not defined correctly";
78             }
79              
80             if ($self->{domain_url} =~ m/^https/) {
81             if ($self->{'ssl_verify'} == 1) {
82             $self->{ua}->ssl_opts( SSL_ca_file => Mozilla::CA::SSL_ca_file() );
83             } else {
84             $self->{ua}->ssl_opts( verify_hostname => 0 );
85             }
86             }
87              
88             $self->{'sentry_version'} ||= 4;
89              
90             bless $self, $class;
91             }
92              
93             =head4 message
94              
95             Send message to Sentry server.
96              
97             $raven->message( {
98             'message' => "Message",
99             'logger' => "Name of the logger", # defult "root"
100             'level' => "Error level", # default 'error'
101             'platform' => "Platform name", # default 'perl',
102             'culprit' => "Module or/and function raised error", # default ""
103             'tags' => "Hashref of tags", # default {}
104             'server_name' => "Server name where error occured", # current host name is default
105             'modules' => "list of relevant modules",
106             'extra' => "extra params described below"
107             } );
108              
109             The structure of 'modules' list is:
110              
111             [
112             {
113             "my.module.name": "1.0"
114             }
115             ]
116              
117             The structure of 'extra' field is:
118              
119             {
120             "my_key" => 1,
121             "some_other_value" => "foo bar"
122             }
123              
124              
125             =cut
126             sub message {
127             my ( $self, $params ) = @_;
128            
129             my $message = $self->buildMessage( $params );
130             my $stamp = gettimeofday();
131             $stamp = sprintf ( "%.12g", $stamp );
132              
133             my $header_format = sprintf (
134             "Sentry sentry_version=%s, sentry_timestamp=%s, sentry_key=%s, sentry_client=%s, sentry_secret=%s",
135             $self->{sentry_version},
136             time(),
137             $self->{'sentry_public_key'},
138             "perl_client/0.01",
139             $self->{'sentry_secret_key'},
140             );
141             my %header = ( 'X-Sentry-Auth' => $header_format );
142              
143             my $sentry_url;
144            
145             if ($self->{'sentry_version'} > 3) {
146             $sentry_url = $self->{domain_url} . '/api/' . $self->{project_id} . '/store/';
147             } else {
148             $sentry_url = $self->{domain_url};
149             }
150              
151             my $request = POST($sentry_url, %header, Content => $message);
152             my $response = $self->{'ua'}->request( $request );
153            
154             return $response;
155             }
156              
157              
158             sub buildMessage {
159             my ( $self, $params ) = @_;
160            
161             my $data = {
162             'event_id' => sprintf("%x%x%x", time(), time() + int(rand()), time() + int(rand())),
163             'message' => $params->{'message'},
164             'timestamp' => time(),
165             'level' => $params->{'level'} || 'error',
166             'logger' => $params->{'logger'} || 'root',
167             'platform' => $params->{'platform'} || 'perl',
168             'culprit' => $params->{'culprit'} || "",
169             'tags' => $params->{'tags'} || {},
170             'server_name' => $params->{server_name} || hostname,
171             'modules' => $params->{'modules'},
172             'extra' => $params->{'extra'} || {}
173             };
174              
175             my $json = JSON->new->utf8(1)->pretty(1)->allow_nonref(1);
176             return $json->encode( $data );
177             }
178              
179             1;
180              
181             =head1 LICENSE AND COPYRIGHT
182              
183             Copyright (C) 2014 by Enginuity Search Media
184              
185             daniel@theenginuity.com
186              
187             This program is free software; you can redistribute it and/or modify it
188             under the terms of the the Artistic License (2.0). You may obtain a
189             copy of the full license at:
190              
191             L