File Coverage

blib/lib/Plack/Middleware/AccessLog/Structured.pm
Criterion Covered Total %
statement 61 62 98.3
branch 12 12 100.0
condition 9 14 64.2
subroutine 15 15 100.0
pod 2 2 100.0
total 99 105 94.2


line stmt bran cond sub pod time code
1             package Plack::Middleware::AccessLog::Structured;
2             {
3             $Plack::Middleware::AccessLog::Structured::VERSION = '0.001000';
4             }
5 1     1   101108 use parent qw(Plack::Middleware);
  1         2  
  1         8  
6              
7             # ABSTRACT: Access log middleware which creates structured log messages
8              
9 1     1   18259 use strict;
  1         4  
  1         35  
10 1     1   6 use warnings;
  1         2  
  1         25  
11              
12 1     1   11 use Carp;
  1         4  
  1         77  
13 1     1   22 use MRO::Compat;
  1         2  
  1         29  
14 1     1   3144 use Time::HiRes;
  1         2160  
  1         6  
15 1     1   144 use Plack::Util::Accessor qw(logger callback extra_field);
  1         3  
  1         10  
16 1     1   1133 use Net::Domain qw(hostname hostfqdn);
  1         12646  
  1         96  
17 1     1   1698 use DateTime;
  1         233104  
  1         48  
18 1     1   37 use Time::HiRes;
  1         1  
  1         9  
19 1     1   1631 use JSON;
  1         16044  
  1         8  
20              
21              
22             sub new {
23 5     5 1 29759 my ($class, $arg_ref) = @_;
24              
25 5         37 my $self = $class->next::method($arg_ref);
26 5 100 100     169 if (defined $self->callback() && ref $self->callback() ne 'CODE') {
27 1         154 croak("Passed 'callback' parameter must be a code reference");
28             }
29 4 100 100     571 if (defined $self->extra_field() && ref $self->extra_field() ne 'HASH') {
30 1         22 croak("Passed 'extra_field' parameter must be a hash reference");
31             }
32              
33 3         44 return $self;
34             }
35              
36              
37              
38             sub call {
39 3     3 1 60375 my ($self, $env) = @_;
40              
41 3         12 my $t_before = Time::HiRes::time();
42 3         25 my $res = $self->app->($env);
43 3         38 my $t_after = Time::HiRes::time();
44              
45             return $self->response_cb($res, sub {
46 3     3   54 my ($cb_res) = @_;
47              
48 3         17 my $h = Plack::Util::headers($res->[1]);
49 3         117 my $content_type = $h->get('Content-Type');
50 3 100 33     117 my $log_entry = {
      33        
51             class => ref($self),
52             # Request data
53             remote_addr => $env->{REMOTE_ADDR},
54             request_method => _safe($env->{REQUEST_METHOD}),
55             request_uri => _safe($env->{REQUEST_URI}),
56             server_protocol => $env->{SERVER_PROTOCOL},
57             http_user_agent => _safe($env->{HTTP_USER_AGENT}),
58             http_host => $env->{HTTP_HOST} || $env->{SERVER_NAME},
59             http_referer => $env->{HTTP_REFERER},
60             remote_user => $env->{REMOTE_USER},
61             # Server information
62             pid => $$,
63             hostfqdn => hostfqdn(),
64             hostname => hostname(),
65             # Response data
66             response_status => $cb_res->[0],
67             content_length => Plack::Util::content_length($res->[2]) || $h->get('Content-Length'),
68             content_type => defined $content_type ? "$content_type" : undef,
69             # Timing
70             request_duration => ( $t_after - $t_before ) * 1000,
71             date => DateTime->from_epoch(epoch => $t_before)->strftime('%Y-%m-%dT%H:%M:%S.%3NZ'),
72             epochtime => $t_before,
73             };
74              
75 3 100       2800 if ($self->extra_field()) {
76 1         29 for my $env_field (keys %{$self->extra_field}) {
  1         4  
77 1         10 $log_entry->{$self->extra_field()->{$env_field}}
78             = $env->{$env_field};
79             }
80             }
81              
82 3 100       29 if ($self->callback()) {
83 1         8 $log_entry = $self->callback()->($env, $log_entry);
84             }
85              
86 3   50     30 my $logger = $self->logger() || sub { $env->{'psgi.errors'}->print($_[0] . "\n") };
87 3         89 $logger->(encode_json($log_entry));
88              
89 3         61 return;
90 3         39 });
91             }
92              
93              
94             # Taken from Plack::Middleware::AccessLog
95             sub _safe {
96 9     9   16 my ($string) = @_;
97 9 100       30 $string =~ s/([^[:print:]])/"\\x" . unpack("H*", $1)/xeg
  0         0  
98             if defined $string;
99 9         51 return $string;
100             }
101              
102              
103              
104             1;
105              
106             __END__