File Coverage

blib/lib/Log/Radis.pm
Criterion Covered Total %
statement 50 54 92.5
branch 20 24 83.3
condition 4 6 66.6
subroutine 10 11 90.9
pod 2 2 100.0
total 86 97 88.6


line stmt bran cond sub pod time code
1 1     1   76280 use strictures 2;
  1         6  
  1         35  
2              
3             package Log::Radis;
4              
5             # ABSTRACT: Radis is a graylog logging radio through a redis database
6              
7 1     1   957 use Moo 2;
  1         6146  
  1         5  
8 1     1   1598 use Redis 1.980;
  1         31871  
  1         36  
9 1     1   5 use JSON 2.90 qw(encode_json);
  1         17  
  1         5  
10 1     1   94 use Time::HiRes 1.9726;
  1         13  
  1         5  
11 1     1   439 use Sys::Hostname ;
  1         699  
  1         45  
12 1     1   4 use Carp qw(croak carp);
  1         1  
  1         36  
13 1     1   3 use Scalar::Util qw(blessed);
  1         1  
  1         511  
14              
15             our $GELF_SPEC_VERSION = '1.1';
16             our $HOSTNAME = hostname();
17              
18             our $VERSION = '0.003'; # VERSION
19              
20              
21              
22             has server => (
23             is => 'ro',
24             default => 'localhost:6379',
25             );
26              
27              
28             has reconnect => (
29             is => 'ro',
30             default => 5,
31             );
32              
33              
34             has every => (
35             is => 'ro',
36             default => 1,
37             );
38              
39              
40             has queue => (
41             is => 'ro',
42             default => 'graylog-radis:queue',
43             );
44              
45              
46             has redis => (
47             is => 'lazy',
48             isa => sub {
49             (blessed($_[0]) and ($_[0]->isa('Redis') or $_[0]->can('lpush'))) or
50             croak "Must be an instance of Redis".
51             " or a blessed reference implementing the 'lpush' method";
52             },
53             builder => sub {
54 0     0   0 my $self = shift;
55 0         0 return Redis->new(
56             server => $self->server,
57             reconnect => $self->reconnect,
58             every => $self->every,
59             );
60             }
61             );
62              
63              
64             my %levels = (
65             fatal => 1,
66             emerg => 1,
67             emergency => 1,
68              
69             alert => 2,
70              
71             crit => 2,
72             critical => 3,
73              
74             error => 4,
75             err => 4,
76              
77             warn => 5,
78             warning => 5,
79              
80             note => 6,
81             notice => 6,
82              
83             info => 7,
84              
85             debug => 8,
86              
87             trace => 9,
88             core => 9,
89             );
90              
91              
92             sub log {
93 26     26 1 25697 my $self = shift;
94 26         39 my ($level, $message, %gelf) = @_;
95              
96 26 100       59 croak "log message without level" unless defined $level;
97 25 100       41 croak "log message without message" unless defined $message;
98              
99             # replace level with numeric code, if needed
100 24 50       82 $level = $levels{lc($level)} unless $level =~ m{^\d$};
101              
102             # addiotional field are only allowed with a prefixed underscore
103             # and strip off all unallowed chars
104             %gelf = map {
105             m{^_[\w\.\-]+$}i
106             ?
107             (
108             lc($_)
109             ,
110             $gelf{$_}
111             )
112             :
113             (
114             '_'.s{[^\w\.\-]+}{}gr
115             ,
116 6 50       32 $gelf{$_}
117             )
118 24         41 } grep { defined $gelf{$_} } keys %gelf;
  6         12  
119              
120             # graylog omit the id field automatically
121 24 50       39 if (exists $gelf{_id}) {
122 0         0 carp "log message with id is not allowed";
123 0         0 delete $gelf{_id};
124             }
125              
126             # preserve params, which are allowed by client
127             # including some mispelled ones
128 24 100       36 $gelf{host} = delete $gelf{_hostname} if defined $gelf{_hostname};
129 24 100       33 $gelf{host} = delete $gelf{_host} if defined $gelf{_host};
130 24 100       30 $gelf{timestamp} = delete $gelf{_time} if defined $gelf{_time};
131 24 100       28 $gelf{timestamp} = delete $gelf{_timestamp} if defined $gelf{_timestamp};
132 24 50       28 $gelf{full_message} = delete $gelf{_message} if defined $gelf{_message};
133 24 100       38 $gelf{full_message} = delete $gelf{_full_message} if defined $gelf{_full_message};
134              
135             # hostname defaults to system hostname...
136 24   66     63 $gelf{host} //= $HOSTNAME;
137              
138             # ...and timestamp with milliseconds by default
139 24   66     69 $gelf{timestamp} //= Time::HiRes::time();
140              
141             # graylog seems to have problems with float values in json
142             # so force string, which works fine
143 24         191 $gelf{timestamp} = ''.$gelf{timestamp};
144              
145 24         25 $gelf{short_message} = $message;
146 24         21 $gelf{version} = $GELF_SPEC_VERSION;
147 24         23 $gelf{level} = $level;
148              
149 24         35 $self->push(\%gelf);
150             }
151              
152              
153             sub push {
154 25     25 1 1856 my ($self, $gelf) = @_;
155 25 100       52 if (ref $gelf eq 'HASH') {
156 24         101 $gelf = encode_json($gelf);
157             }
158 25         450 $self->redis->lpush($self->queue, $gelf);
159             }
160              
161             1;
162              
163             __END__