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   80007 use 5.008;
  2         8  
49              
50 2     2   12 use strict;
  2         4  
  2         46  
51 2     2   9 use warnings;
  2         4  
  2         129  
52              
53             our $VERSION = '0.0404';
54              
55 2     2   535 use parent 'Plack::Middleware';
  2         387  
  2         11  
56              
57 2         19 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   16460 );
  2         5  
61              
62 2     2   246 use Plack::Util;
  2         4  
  2         53  
63              
64 2     2   1132 use Plack::Request;
  2         142627  
  2         81  
65 2     2   1022 use Plack::Response;
  2         2613  
  2         79  
66              
67 2     2   1050 use POSIX ();
  2         13774  
  2         62  
68 2     2   1102 use POSIX::strftime::Compiler ();
  2         12002  
  2         58  
69 2     2   15 use Scalar::Util ();
  2         7  
  2         1979  
70              
71             sub prepare_app {
72 13     13 1 38161 my ($self) = @_;
73              
74             # the default values
75 13 100       43 $self->with_request(Plack::Util::TRUE) unless defined $self->with_request;
76 13 100       193 $self->with_response(Plack::Util::TRUE) unless defined $self->with_response;
77 13 100       119 $self->with_date(Plack::Util::TRUE) unless defined $self->with_date;
78 13 100       94 $self->with_body(Plack::Util::FALSE) unless defined $self->with_body;
79 13 100       79 $self->with_all_chunks(Plack::Util::FALSE) unless defined $self->with_all_chunks;
80 13 100       123 $self->body_eol(defined $self->eol ? $self->eol : ' ') unless defined $self->body_eol;
    100          
81 13 100       153 $self->eol('|') unless defined $self->eol;
82              
83 13         143 $self->_strftime(POSIX::strftime::Compiler->new('%d/%b/%Y:%H:%M:%S %z'));
84              
85 13         4502 $self->_counter(0);
86             }
87              
88             sub _log_message { ## no critic(Subroutines::ProhibitManyArgs)
89 25     25   70 my ($self, $type, $env, $status, $headers, $body) = @_;
90              
91 25   50 0   61 my $logger = $self->logger || sub { $env->{'psgi.errors'}->print(@_) };
  0         0  
92              
93 25         175 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       83 : $env->{REMOTE_ADDR};
98              
99 25         63 my $eol = $self->eol;
100 25         107 my $body_eol = $self->body_eol;
101 25 50       237 $body =~ s/\015?\012/$body_eol/gs if defined $body_eol;
102              
103 25 100       68 my $date
104             = $self->with_date
105             ? ('[' . $self->_strftime->to_string(localtime) . '] ')
106             : '';
107              
108 25 50       1690 $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   79 my ($self, $env) = @_;
130              
131 12         58 my $req = Plack::Request->new($env);
132              
133 12         110 my $status = sprintf '%s %s %s', $req->method, $req->request_uri, $req->protocol;
134 12         175 my $headers = $req->headers;
135 12 100       1859 my $body = $self->with_body ? $req->content : '';
136              
137 12         2854 $self->_log_message('Request ', $env, $status, $headers, $body);
138             }
139              
140             sub _log_response {
141 13     13   27 my ($self, $env, $ret) = @_;
142              
143 13         69 my $res = Plack::Response->new(@$ret);
144              
145 13         874 my $status_code = $res->status;
146 13         63 my $status_message = HTTP::Status::status_message($status_code);
147              
148 13 100       80 my $status = sprintf 'HTTP/1.0 %s %s', $status_code, defined $status_message ? $status_message : '';
149 13         58 my $headers = $res->headers;
150 13         86 my $body = '';
151 13 100       29 if ($self->with_body) {
152 12         60 $body = $res->content;
153 12 50       71 $body = '' unless defined $body;
154 12 50       50 $body = join '', grep { defined $_ } @$body if ref $body eq 'ARRAY';
  12         46  
155             }
156              
157 13         37 $self->_log_message('Response', $env, $status, $headers, $body);
158             }
159              
160             sub call {
161 13     13 1 27170 my ($self, $env) = @_;
162              
163             $self->_call_id(
164             sprintf '%015s',
165             time % 2**16
166             * 2**32
167 13 50       87 + (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         156 $self->_counter($self->_counter + 1);
172              
173             # Preprocessing
174 13 100       94 $self->_log_request($env) if $self->with_request;
175              
176             # $self->app is the original app
177 13         998 my $res = $self->app->($env);
178              
179             # Postprocessing
180             return $self->with_response
181             ? $self->response_cb(
182             $res,
183             sub {
184 12     12   430 my ($ret) = @_;
185 12         16 my $seen;
186             return sub {
187 25         637 my ($chunk) = @_;
188 25 100 100     100 return if $seen and not defined $chunk;
189 14 100 100     34 return $chunk if $seen and not $self->with_all_chunks;
190 13         60 $self->_log_response($env, [$ret->[0], $ret->[1], [$chunk]]);
191 13         606 $seen = Plack::Util::TRUE;
192 13         50 return $chunk;
193 12         45 };
194             }
195             )
196 13 100       155 : $res;
197             }
198              
199             1;
200              
201             __END__