File Coverage

blib/lib/Mojolicious/Plugin/Log/Elasticsearch.pm
Criterion Covered Total %
statement 43 58 74.1
branch 11 22 50.0
condition 5 14 35.7
subroutine 6 6 100.0
pod 1 1 100.0
total 66 101 65.3


line stmt bran cond sub pod time code
1             package Mojolicious::Plugin::Log::Elasticsearch;
2             $Mojolicious::Plugin::Log::Elasticsearch::VERSION = '1.162530';
3             # ABSTRACT: Mojolicious Plugin to log requests to an Elasticsearch instance
4              
5 2     2   1047 use Mojo::Base 'Mojolicious::Plugin';
  2         2  
  2         9  
6              
7 2     2   278 use Time::HiRes qw/time/;
  2         2  
  2         8  
8 2     2   178 use Mojo::JSON qw/encode_json/;
  2         3  
  2         1299  
9              
10             sub register {
11 2     2 1 58 my ($self, $app, $conf) = @_;
12              
13 2   50     7 my $index = $conf->{index} || die "no elasticsearch index provided";
14 2   50     4 my $type = $conf->{type} || die "no elasticsearch type provided";
15 2         3 my $ts_name = $conf->{timestamp_field};
16 2   50     5 my $es_url = $conf->{elasticsearch_url} || die "no elasticsearch url provided";
17 2   100     6 my $log_stash_keys = $conf->{log_stash_keys} || [];
18 2         3 my $extra_keys_sub = $conf->{extra_keys_hook};
19              
20 2         2 my $geoip;
21 2 50       4 if ($conf->{geo_ip_citydb}) {
22 0         0 require Geo::IP;
23 0         0 $geoip = Geo::IP->open($conf->{geo_ip_citydb});
24             }
25              
26             # We should be smarter and only create this index if it isn't already
27             # in existence. There's no harm here, it's just poor form.
28 2         10 my $tx_c = $app->ua->put("${es_url}/${index}");
29              
30 2 100       5664 my $index_meta = {
31             $type => {
32             # let ES generate timestamps if they haven't specified a ts field name
33             ! $ts_name ? ("_timestamp" => { enabled => 1, store => 1 }) : (),
34             "properties" => {
35             'ip' => { 'type' => 'ip', 'store' => 1 },
36             'path' => { 'type' => 'string', index => 'not_analyzed' },
37             'location' => { 'type' => 'geo_point' },
38             }
39             }
40             };
41              
42 2         6 my $url = "${es_url}/${index}/${type}/_mapping";
43 2         6 my $tx = $app->ua->post($url, json => $index_meta);
44              
45             $app->hook(before_dispatch => sub {
46 5     5   26321 my $c = shift;
47 5         25 $c->stash->{'mojolicious-plugin-log-elasticsearch.start'} = time();
48 2         3367 });
49              
50             $app->hook(after_dispatch => sub {
51 5     5   42715 my $c = shift;
52 5         23 my @n = gmtime();
53 5         24 my $t = sprintf("%04d-%02d-%02dT%02d:%02d:%02dZ", $n[5]+1900, $n[4]+1, $n[3],
54             @n[2,1,0]);
55 5         18 my $dur = time() - $c->stash->{'mojolicious-plugin-log-elasticsearch.start'};
56              
57             # perhaps look up Geo::IP stuff
58 5         28 my %geo_ip_data;
59 5         5 my ($lat, $long, $country_code);
60             eval {
61 5 50       18 return 1 if (! $geoip);
62 0 0       0 return 1 if (! $c->tx->remote_address);
63              
64 0         0 my $rec = $geoip->record_by_addr($c->tx->remote_address);
65 0 0       0 return 1 if (! $rec);
66              
67 0         0 $lat = $rec->latitude;
68 0         0 $long = $rec->longitude;
69 0         0 $country_code = $rec->country_code;
70              
71 0         0 %geo_ip_data = ( location => "$lat, $long", country_code => $country_code );
72              
73 0         0 1;
74 5 50       5 } or do {
75 0         0 $c->app->log->warn("could not lookup lat/long for ip: $@");
76             };
77            
78 5 100       15 my $data = { ip => $c->tx->remote_address,
79             path => $c->req->url->to_abs->path,
80             code => $c->res->code,
81             method => $c->req->method,
82             time => $dur,
83             $ts_name ? ( $ts_name => int(time() * 1000) ) : (),
84             %geo_ip_data,
85             };
86 5         851 foreach (@{ $log_stash_keys}) {
  5         8  
87 4 100       7 $data->{$_} = $c->stash->{$_} if (exists $c->stash->{$_});
88             }
89              
90 5 100       55 if ($extra_keys_sub) {
91 4         8 my %new_values = $extra_keys_sub->($c);
92 4         41 $data = { %$data, %new_values };
93             }
94              
95 5         17 my $url = "${es_url}/${index}/${type}/?timestamp=${t}";
96             $c->app->ua->post($url, json => $data, sub {
97 0           my ($ua, $tx) = @_;
98 0 0 0       if (! $tx) {
    0 0        
99 0           $c->app->log->warn("could not log to elasticsearch");
100             }
101             elsif ($tx->res && $tx->res->code && $tx->res->code !~ /^20./) {
102 0           $c->app->log->warn("could not log to elasticsearch - " . $tx->res->body);
103             }
104 5         11 });
105 2         32 });
106             }
107              
108             1;
109              
110             __END__