File Coverage

blib/lib/Net/Graylog/Client.pm
Criterion Covered Total %
statement 39 79 49.3
branch 0 16 0.0
condition 0 9 0.0
subroutine 13 18 72.2
pod 3 3 100.0
total 55 125 44.0


line stmt bran cond sub pod time code
1             # ABSTRACT: Client for Graylog2 analysis server
2              
3              
4             package Net::Graylog::Client;
5             {
6             $Net::Graylog::Client::VERSION = '0.7';
7             }
8              
9 1     1   64158 use strict;
  1         4  
  1         33  
10 1     1   7 use warnings;
  1         4  
  1         41  
11 1     1   7 use POSIX qw(strftime);
  1         9  
  1         20  
12 1     1   74 use Data::Printer;
  1         3  
  1         6  
13 1     1   670 use Furl;
  1         32122  
  1         32  
14 1     1   421 use JSON::Tiny qw(encode_json);
  1         14239  
  1         59  
15 1     1   373 use Sys::Hostname;
  1         827  
  1         43  
16 1     1   7 use Data::UUID;
  1         3  
  1         51  
17 1     1   5 use POSIX qw(strftime);
  1         2  
  1         7  
18              
19             # use Mo qw( default is required ); # not using (build builder coerce)
20 1     1   477 use Moo;
  1         7286  
  1         6  
21 1     1   1520 use namespace::clean;
  1         4652  
  1         6  
22              
23 1     1   312 use vars qw( @EXPORT @ISA);
  1         2  
  1         61  
24              
25             # -----------------------------------------------------------------------------
26              
27             @ISA = qw(Exporter);
28              
29             # this is the list of things that will get imported into the loading packages
30             # namespace
31             @EXPORT = qw(
32             valid_levels
33             valid_facilities
34             );
35              
36 1     1   6 use constant GELF_VERSION => "1.1";
  1         2  
  1         643  
37              
38             # -----------------------------------------------------------------------------
39              
40              
41              
42             has url => ( is => 'ro', required => 1 );
43             has _uuid => ( is => 'ro', init_arg => undef, default => sub { Data::UUID->new() }, );
44             has _hostname => ( is => 'ro', init_arg => undef, default => sub { hostname(); } );
45             has timeout => ( is => 'ro', default => sub { 0.01; } );
46              
47             # we need to set a timeout for the connection as Furl seems to wait
48             # for this time to elapse before giving us any response. If the default is used
49             # 180s then this will block for 3 minutes! crazy stuff, so I set it to 0.01
50             # which would allow me to send 100 messages/sec, which should be OK for my
51             # purposes especially as my graylog is on the local network
52             has _furl => (
53             is => 'lazy',
54             default => sub {
55             my $self = shift;
56             return Furl->new(
57             agent => __PACKAGE__,
58              
59             # headers => [
60             # 'Accept' => 'application/json',
61             # 'content-type' => 'application/json',
62             # ],
63             timeout => $self->timeout,
64             );
65             },
66             init_arg => undef,
67             );
68              
69             # -----------------------------------------------------------------------------
70              
71             # these are the syslog severity levels
72             my @msg_levels = qw( emerg alert crit error warning notice info debug);
73             my $_mc = 0;
74             my %msg_lvalues = map { $_ => $_mc++; } @msg_levels;
75              
76             # some levels have alternate names
77             my %msg_tx = ( panic => 'emerg', err => 'error', warn => 'warning' );
78              
79             my @msg_facilities = qw(
80             kern user mail daemon auth syslog lpr news
81             uucp clock authpriv ftp ntp audit alert cron
82             local0 local1 local2 local3 local4 local5 local6 local7
83             );
84             my $_mf = 0;
85             my %msg_fvalues = map { $_ => $_mf++; } @msg_facilities;
86              
87             # -----------------------------------------------------------------------------
88              
89              
90             sub send {
91 0     0 1   my $self = shift;
92 0           my (%data) = @_;
93              
94             # we add these fields so, we will report issues if they are passed
95             # for some reason graylog accepts a message with a count field
96             # but then silently discards it!
97 0 0         map { die "Field '$_' not allowed" if ( $data{$_} ) } qw( uuid timestamp timestr count);
  0            
98              
99 0 0         die "message field is required" if ( !$data{message} );
100              
101 0           $data{version} = GELF_VERSION;
102 0           $data{short_message} = $data{message};
103 0   0       $data{full_message} = $data{long} || $data{full_message};
104 0           $data{uuid} = $self->_uuid->create_str();
105 0           $data{timestamp} = time();
106 0           $data{timestr} = strftime( "%Y-%m-%d %H:%M:%S", gmtime( time() ) );
107 0   0       $data{host} = $data{server} || $data{host} || hostname();
108              
109             # convert the level to match a syslog level and stop graylog fretting
110 0 0 0       if ( defined $data{level} && $data{level} !~ /^\d+$/ ) {
111              
112             # convert the level into a number
113 0           my $l = $data{level};
114              
115             # get the alternate name if needed
116 0 0         $l = $msg_tx{ $data{level} } if ( $msg_tx{ $data{level} } );
117 0 0         if ( defined $msg_lvalues{$l} ) {
118 0           $data{level} = $msg_lvalues{$l};
119              
120             # also save as a string for user to reference
121 0           $data{levelstr} = $l;
122             }
123             }
124              
125             # remove some entries we dont want
126 0 0         map { delete $data{$_} if ( exists $data{$_} ); } qw( server message long);
  0            
127              
128             # rename things that are not allowed fields
129 0           my %allowed = map { $_ => 1 } qw(uuid timestamp host version timestr full_message short_message level facility file);
  0            
130 0           foreach my $k ( keys %data ) {
131 0 0         if ( !$allowed{$k} ) {
132              
133             # prefix with an underline and then remove original
134 0           $data{"_$k"} = $data{$k};
135 0           delete $data{$k};
136             }
137             }
138              
139             # convert any floats into strings
140             # foreach my $k ( keys %data) {
141             # # floating point numbers need to be made into strings
142             # if( $data{$k} =~ /^[0-9]{1,}(\.[0-9]{1,})$/) {
143             # $data{$k} = "" . $data{$k} ;
144             # }
145             # }
146              
147 0           my $status = $self->_furl->post( $self->url, [ 'Content-Type' => 'application/json' ], encode_json( \%data ) );
148              
149 0           return ( $status->is_success, $status->code );
150             }
151              
152             # -----------------------------------------------------------------------------
153              
154              
155             sub AUTOLOAD {
156              
157             # we use AUTOLOAD to handle some aliases for send
158              
159             # find out if this is a name we alias
160 0     0     my $level = our $AUTOLOAD;
161 0           $level =~ s/.*:://; # strip the package name
162 0 0         if ( !defined $msg_lvalues{$level} ) {
163 0           die qq(Can't locate object method $level via package "@{[__PACKAGE__]}");
  0            
164             }
165              
166 0           my $self = shift;
167 0           my %params = @_;
168              
169             # set the level field
170 0           $params{level} = $level;
171              
172             # and perform the actual send
173 0           return $self->send(%params);
174             }
175              
176             # -----------------------------------------------------------------------------
177              
178             sub DESTROY {
179 0     0     return 1;
180             }
181              
182             # -----------------------------------------------------------------------------
183              
184              
185             sub valid_levels {
186 0     0 1   return @msg_levels;
187             }
188              
189             # -----------------------------------------------------------------------------
190              
191              
192             sub valid_facilities {
193 0     0 1   return @msg_facilities;
194             }
195              
196              
197             # -----------------------------------------------------------------------------
198             1;
199              
200             __END__