File Coverage

blib/lib/Plack/Middleware/TrafficLog.pm
Criterion Covered Total %
statement 85 86 98.8
branch 40 46 86.9
condition 7 8 87.5
subroutine 17 18 94.4
pod 2 2 100.0
total 151 160 94.3


line stmt bran cond sub pod time code
1             package Plack::Middleware::TrafficLog;
2              
3             =head1 NAME
4              
5             Plack::Middleware::TrafficLog - Log headers and body of HTTP traffic
6              
7             =head1 SYNOPSIS
8              
9             =for markdown ```perl
10              
11             # In app.psgi
12             use Plack::Builder;
13              
14             builder {
15             enable "TrafficLog", with_body => 1;
16             };
17              
18             =for markdown ```
19              
20             =head1 DESCRIPTION
21              
22             This middleware logs the request and response messages with detailed
23             information about headers and the body.
24              
25             The example log:
26              
27             =for markdown ```console
28              
29             [08/Aug/2012:16:59:47 +0200] [164836368] [127.0.0.1 -> 0:5000] [Request ]
30             |GET / HTTP/1.1|Connection: TE, close|Host: localhost:5000|TE: deflate,gzi
31             p;q=0.3|User-Agent: lwp-request/6.03 libwww-perl/6.03||
32             [08/Aug/2012:16:59:47 +0200] [164836368] [127.0.0.1 <- 0:5000] [Response]
33             |HTTP/1.0 200 OK|Content-Type: text/plain||Hello World
34              
35             =for markdown ```
36              
37             This module works also with applications that have delayed response. In that
38             case, each chunk is logged separately and shares the same unique ID number and
39             headers.
40              
41             The body of the request and response is not logged by default. For streaming
42             responses, only the first chunk is logged by default.
43              
44             =for readme stop
45              
46             =cut
47              
48 2     2   64212 use 5.008;
  2         6  
49              
50 2     2   11 use strict;
  2         4  
  2         37  
51 2     2   9 use warnings;
  2         2  
  2         98  
52              
53             our $VERSION = '0.0403';
54              
55 2     2   424 use parent 'Plack::Middleware';
  2         240  
  2         8  
56              
57 2         10 use Plack::Util::Accessor qw(
58             with_request with_response with_date with_body with_all_chunks eol body_eol logger
59             _counter _call_id _strftime
60 2     2   13158 );
  2         4  
61              
62 2     2   210 use Plack::Util;
  2         3  
  2         34  
63              
64 2     2   905 use Plack::Request;
  2         112616  
  2         63  
65 2     2   793 use Plack::Response;
  2         1905  
  2         64  
66              
67 2     2   875 use POSIX ();
  2         10496  
  2         51  
68 2     2   815 use POSIX::strftime::Compiler ();
  2         9516  
  2         46  
69 2     2   12 use Scalar::Util ();
  2         4  
  2         1506  
70              
71             sub prepare_app {
72 13     13 1 30744 my ($self) = @_;
73              
74             # the default values
75 13 100       31 $self->with_request(Plack::Util::TRUE) unless defined $self->with_request;
76 13 100       161 $self->with_response(Plack::Util::TRUE) unless defined $self->with_response;
77 13 100       105 $self->with_date(Plack::Util::TRUE) unless defined $self->with_date;
78 13 100       87 $self->with_body(Plack::Util::FALSE) unless defined $self->with_body;
79 13 100       54 $self->with_all_chunks(Plack::Util::FALSE) unless defined $self->with_all_chunks;
80 13 100       104 $self->body_eol(defined $self->eol ? $self->eol : ' ') unless defined $self->body_eol;
    100          
81 13 100       123 $self->eol('|') unless defined $self->eol;
82              
83 13         123 $self->_strftime(POSIX::strftime::Compiler->new('%d/%b/%Y:%H:%M:%S %z'));
84              
85 13         3621 $self->_counter(0);
86             }
87              
88             sub _log_message { ## no critic(Subroutines::ProhibitManyArgs)
89 25     25   53 my ($self, $type, $env, $status, $headers, $body) = @_;
90              
91 25   50 0   49 my $logger = $self->logger || sub { $env->{'psgi.errors'}->print(@_) };
  0         0  
92              
93 25         143 my $server_addr = sprintf '%s:%s', $env->{SERVER_NAME}, $env->{SERVER_PORT};
94             my $remote_addr
95             = defined $env->{REMOTE_PORT}
96             ? sprintf '%s:%s', $env->{REMOTE_ADDR}, $env->{REMOTE_PORT}
97 25 50       72 : $env->{REMOTE_ADDR};
98              
99 25         55 my $eol = $self->eol;
100 25         104 my $body_eol = $self->body_eol;
101 25 50       203 $body =~ s/\015?\012/$body_eol/gs if defined $body_eol;
102              
103 25 100       54 my $date
104             = $self->with_date
105             ? ('[' . $self->_strftime->to_string(localtime) . '] ')
106             : '';
107              
108 25 50       1327 $logger->(
    100          
109             sprintf "%s[%s] [%s %s %s] [%s] %s%s%s%s%s%s\n",
110             $date,
111             $self->_call_id,
112              
113             $remote_addr,
114             $type eq 'Request ' ? '->' : $type eq 'Response' ? '<-' : '--',
115             $server_addr,
116              
117             $type,
118              
119             $eol,
120             $status,
121             $eol,
122             $headers->as_string($eol),
123             $eol,
124             $body,
125             );
126             }
127              
128             sub _log_request {
129 12     12   88 my ($self, $env) = @_;
130              
131 12         52 my $req = Plack::Request->new($env);
132              
133 12         88 my $status = sprintf '%s %s %s', $req->method, $req->request_uri, $req->protocol;
134 12         149 my $headers = $req->headers;
135 12 100       1479 my $body = $self->with_body ? $req->content : '';
136              
137 12         2337 $self->_log_message('Request ', $env, $status, $headers, $body);
138             }
139              
140             sub _log_response {
141 13     13   19 my ($self, $env, $ret) = @_;
142              
143 13         58 my $res = Plack::Response->new(@$ret);
144              
145 13         734 my $status_code = $res->status;
146 13         49 my $status_message = HTTP::Status::status_message($status_code);
147              
148 13 100       67 my $status = sprintf 'HTTP/1.0 %s %s', $status_code, defined $status_message ? $status_message : '';
149 13         20 my $headers = $res->headers;
150 13         67 my $body = '';
151 13 100       23 if ($self->with_body) {
152 12         46 $body = $res->content;
153 12 50       59 $body = '' unless defined $body;
154 12 50       38 $body = join '', grep { defined $_ } @$body if ref $body eq 'ARRAY';
  12         36  
155             }
156              
157 13         36 $self->_log_message('Response', $env, $status, $headers, $body);
158             }
159              
160             sub call {
161 13     13 1 22149 my ($self, $env) = @_;
162              
163             $self->_call_id(
164             sprintf '%015d',
165             time % 2**16
166             * 2**32
167 13 50       66 + (Scalar::Util::looks_like_number $env->{REMOTE_PORT} ? $env->{REMOTE_PORT} : int rand 2**16) % 2**16
168             * 2**16
169             + $self->_counter % 2**16
170             );
171 13         130 $self->_counter($self->_counter + 1);
172              
173             # Preprocessing
174 13 100       74 $self->_log_request($env) if $self->with_request;
175              
176             # $self->app is the original app
177 13         786 my $res = $self->app->($env);
178              
179             # Postprocessing
180             return $self->with_response
181             ? $self->response_cb(
182             $res,
183             sub {
184 12     12   318 my ($ret) = @_;
185 12         15 my $seen;
186             return sub {
187 25         521 my ($chunk) = @_;
188 25 100 100     82 return if $seen and not defined $chunk;
189 14 100 100     43 return $chunk if $seen and not $self->with_all_chunks;
190 13         44 $self->_log_response($env, [$ret->[0], $ret->[1], [$chunk]]);
191 13         501 $seen = Plack::Util::TRUE;
192 13         42 return $chunk;
193 12         32 };
194             }
195             )
196 13 100       102 : $res;
197             }
198              
199             1;
200              
201             __END__