File Coverage

blib/lib/Log/Log4perl/Appender/Elasticsearch.pm
Criterion Covered Total %
statement 85 115 73.9
branch 12 24 50.0
condition 1 5 20.0
subroutine 17 22 77.2
pod 1 4 25.0
total 116 170 68.2


line stmt bran cond sub pod time code
1             package Log::Log4perl::Appender::Elasticsearch;
2 3     3   35510 use version();
  3         1247  
  3         99  
3             $Log::Log4perl::Appender::Elasticsearch::VERSION = version->parse("0.09");
4              
5 3     3   11 use strict;
  3         3  
  3         44  
6 3     3   8 use warnings;
  3         9  
  3         100  
7             our @ISA = qw(Log::Log4perl::Appender);
8              
9 3     3   35 use Carp;
  3         4  
  3         155  
10 3     3   1347 use HTTP::Headers();
  3         15899  
  3         62  
11 3     3   1110 use HTTP::Request();
  3         25874  
  3         58  
12 3     3   17 use JSON;
  3         3  
  3         22  
13 3     3   1986 use LWP::UserAgent();
  3         43375  
  3         81  
14 3     3   750 use Log::Log4perl;
  3         35303  
  3         22  
15 3     3   1333 use MIME::Base64;
  3         1564  
  3         161  
16 3     3   13 use URI;
  3         5  
  3         2430  
17              
18             =head1 NAME
19              
20             Log::Log4perl::Appender::Elasticsearch - implements appending to Elasticsearch
21              
22             =head1 DESCRIPTION
23              
24             This is a simple appender for writing log entries to Elasticsearch via L.
25              
26             L does the same task by using L.
27              
28             =head1 VERSION
29              
30             Version 0.09
31              
32             =cut
33              
34             =head1 SYNOPSIS
35              
36             use Log::Log4perl;
37              
38             Log::Log4perl->init(\<<'HERE');
39             log4perl.logger=DEBUG, ES
40              
41             log4perl.appender.ES = Log::Log4perl::Appender::Elasticsearch
42             log4perl.appender.ES.layout = Log::Log4perl::Layout::NoopLayout
43              
44             log4perl.appender.ES.body.level = %p
45             log4perl.appender.ES.body.module = %M
46             log4perl.appender.ES.body.line = %L
47              
48             log4perl.appender.ES.nodes = localhost:9200
49             log4perl.appender.ES.index = log4perl
50             log4perl.appender.ES.type = entry
51              
52             log4perl.appender.ES.use_https = 0
53             log4perl.appender.ES.user_agent.timeout = 5
54              
55             log4perl.appender.ES.headers.User-Agent = foo
56             HERE
57              
58             Log::Log4perl::get_logger()->info("OK");
59              
60             # look up:
61             # curl -XPOST 'http://localhost:9200/log4perl/_search' -d \
62             # '{"query": {"query_string": {"query": "level:INFO AND message:OK"}}}'
63             # ...
64             # "_source": {
65             # "module": "main::__ANON__",
66             # "line": "60",
67             # "level": "INFO",
68             # "message": "OK"
69             # }
70              
71              
72             =head1 OPTIONS
73              
74             =over 4
75              
76             =item
77              
78             nodes
79              
80             a comma separeted list of nodes. The message will be sent to the next node only if previous request failed
81              
82             =item
83              
84             index
85              
86             The name of the elasticsearch index the message will be stored in.
87              
88             =item
89              
90             type
91              
92             The name of the type in given index the message belongs to.
93              
94             =item
95              
96             use_https
97              
98             0|1 global https setting for all nodes
99              
100             the individual https setting possible too:
101              
102             C
103              
104             =item
105              
106             user_agent
107              
108             LWP::UserAgent parameters
109              
110             C
111              
112             =item
113              
114             headers
115              
116             HTTP::Headers parameters
117              
118             C
119              
120             =back
121              
122             =cut
123              
124             sub new {
125 2     2 1 672 my ($proto, %p) = @_;
126 2   33     13 my $class = ref $proto || $proto;
127 2         4 my $self = bless {}, $class;
128              
129 2         10 $self->_init(%p);
130 2         8 return $self;
131             } ## end sub new
132              
133             sub index {
134 3     3 0 4 my ($self, $index) = @_;
135 3 100       7 if (defined $index) {
136 2         4 $self->{_index} = $index;
137             }
138              
139 3         9 return $self->{_index};
140             } ## end sub index
141              
142             sub type {
143 3     3 0 4 my ($self, $type) = @_;
144 3 100       20 if (defined $type) {
145 2         3 $self->{_type} = $type;
146             }
147              
148 3         8 return $self->{_type};
149             } ## end sub type
150              
151             sub log {
152 0     0 0 0 my ($self, %p) = @_;
153 0         0 $self->_send_request($self->_to_json($self->_prepare_body(%p)));
154             }
155              
156             sub _init {
157 2     2   8 my ($self, %p) = @_;
158              
159             defined($p{nodes})
160 2 50       6 || Carp::croak('Log4perl: nodes not set in ', __PACKAGE__);
161              
162 2         2 my $use_https = delete($p{use_https});
163 2         6 foreach (split ',', delete($p{nodes})) {
164 2         9 (my $node = $_) =~ s/^\s+|\s+$//g;
165 2 50       8 unless ($node =~ m{^http(s)?://}) {
166 2 50       7 $node = ($use_https ? 'https://' : 'http://') . $node;
167             }
168              
169 2         11 my $uri = URI->new($node);
170 2         11232 push @{ $self->{_nodes} }, $uri;
  2         21  
171             } ## end foreach (split ',', delete(...))
172              
173 2         3 foreach my $k (qw/index type/) {
174 4         6 my $v = delete($p{$k});
175 4 50       10 $v || Carp::croak("Log4perl: $k not set in ", __PACKAGE__);
176 4         14 $self->$k($v);
177             }
178              
179 2         3 my $b = delete($p{body});
180 2 50       3 scalar(keys %{$b})
  2         7  
181             || Carp::croak('Log4perl: body not set in ', __PACKAGE__);
182              
183 2         2 foreach my $k (keys %{$b}) {
  2         4  
184 6 50       321 $k eq 'message' && Carp::croak(
185             "Log4perl: choose an other key name instead $k. The key $k is used to store the logging message ",
186             __PACKAGE__
187             );
188             $self->{_body}{$k} = Log::Log4perl::Layout::PatternLayout->new(
189 6         40 { ConversionPattern => { value => $b->{$k} } });
190             } ## end foreach my $k (keys %{$b})
191              
192 2         93 my $h = delete($p{headers});
193 2         3 $self->{_headers} = HTTP::Headers->new(%{$h});
  2         13  
194              
195 2         104 my $up = delete($p{user_agent});
196 2         2 $self->{_user_agent} = LWP::UserAgent->new(%{$up});
  2         13  
197              
198 2         3747 foreach my $k (keys %p) {
199 1         3 $self->{$k} = $p{$k};
200             }
201             } ## end sub _init
202              
203             sub _send_request {
204 0     0   0 my ($self, $data, $suffix) = @_;
205 0         0 my @nodes = @{ $self->{_nodes} };
  0         0  
206 0         0 my (@errors, $ok);
207 0   0     0 do {
208 0         0 my $node = shift @nodes;
209 0         0 my $uri = $self->_uri($node, $suffix);
210 0         0 my $req = $self->_request($uri, $data);
211              
212 0         0 my $resp = $self->{_user_agent}->request($req);
213 0         0 $ok = $resp->is_success;
214 0 0       0 if (!$ok) {
215 0         0 push @errors, join ': ', $uri, $resp->status_line;
216             }
217             } while (!$ok && scalar(@nodes));
218              
219 0 0       0 $ok || Carp::croak('coud not send log the message to any node: ',
220             join '; ', @errors);
221             } ## end sub _send_request
222              
223             sub _uri {
224 1     1   539 my ($self, $node, $suffix) = @_;
225 1         11 my $uri = $node->clone;
226             $uri->path(join '', $uri->path,
227 1 50       52 join('/', $self->{_index}, $self->{_type}, $suffix ? $suffix : ''));
228              
229 1         58 return $uri;
230             } ## end sub _uri
231              
232             sub _headers {
233 1     1   2 my ($self, $uri) = @_;
234 1         84 my $h = $self->{_headers}->clone;
235 1         4 $h->header('Content-Type' => 'application/json');
236              
237 1         34 my $ui = $uri->userinfo;
238 1 50       44 if ($ui) {
239 0         0 my $auth = MIME::Base64::encode_base64($ui);
240 0         0 chomp $auth;
241 0         0 $h->header(Authorization => "Basic $auth");
242             }
243              
244 1         4 return $h;
245             } ## end sub _headers
246              
247             sub _request {
248 0     0     my ($self, $uri, $data) = @_;
249              
250 0           return HTTP::Request->new(
251             POST => $uri,
252             $self->_headers($uri),
253             $data
254             );
255             } ## end sub _request
256              
257             sub _prepare_body {
258 0     0     my ($self, %p) = @_;
259              
260 0           my $b = {};
261 0           foreach my $k (keys %{ $self->{_body} }) {
  0            
262             my $v
263             = $self->{_body}{$k}
264             ->render($p{message}, $p{log4p_category}, $p{log4p_level},
265 0           5 + $Log::Log4perl::caller_depth,
266             );
267              
268 0           $b->{$k} = $v;
269             } ## end foreach my $k (keys %{ $self...})
270              
271 0           $b->{message} = $p{message};
272              
273 0           return $b;
274             } ## end sub _prepare_body
275              
276             sub _to_json {
277 0     0     my ($self, $o) = @_;
278 0           return JSON::encode_json($o);
279             }
280              
281             1; # End of Log::Log4perl::Appender::Elasticsearch
282             __END__