File Coverage

blib/lib/Plack/Middleware/DebugLogging.pm
Criterion Covered Total %
statement 123 150 82.0
branch 50 96 52.0
condition 13 27 48.1
subroutine 26 29 89.6
pod 8 11 72.7
total 220 313 70.2


line stmt bran cond sub pod time code
1             package Plack::Middleware::DebugLogging;
2             $Plack::Middleware::DebugLogging::VERSION = '0.001005';
3             # ABSTRACT: Catalyst style console debugging for plack apps
4              
5 1     1   566 use strict;
  1         1  
  1         25  
6 1     1   3 use warnings;
  1         1  
  1         23  
7              
8 1     1   419 use Data::Dumper::Concise;
  1         5390  
  1         69  
9 1     1   375 use Data::Serializer::Raw;
  1         599  
  1         23  
10 1     1   396 use Module::Runtime qw(use_module);
  1         1166  
  1         5  
11 1     1   351 use Text::SimpleTable;
  1         1260  
  1         24  
12 1     1   385 use Plack::Request;
  1         22617  
  1         22  
13 1     1   329 use Plack::Response;
  1         782  
  1         18  
14 1     1   320 use Term::Size::Any;
  1         140  
  1         4  
15 1     1   1350 use Try::Tiny;
  1         1049  
  1         47  
16 1         5 use Plack::Util::Accessor qw(debug request response request_headers request_parameters
17             response_headers response_status_line keywords uploads
18             body_params query_params logger logger_override term_width
19 1     1   4 attempt_deserialize serializer);
  1         1  
20              
21 1     1   72 use parent qw/Plack::Middleware/;
  1         1  
  1         6  
22              
23             sub prepare_app {
24 2     2 1 874 my ($self) = @_;
25              
26 2 50       4 $self->debug(1) unless defined $self->debug;
27 2 50       72 $self->request(1) unless defined $self->request;
28 2 50       13 $self->response(1) unless defined $self->response;
29 2 50       13 $self->keywords(1) unless defined $self->keywords;
30 2 50       14 $self->request_headers(1) unless defined $self->request_headers;
31 2 50       13 $self->request_parameters(1) unless defined $self->request_parameters;
32 2 50       13 $self->response_headers(1) unless defined $self->response_headers;
33 2 50       13 $self->response_status_line(1) unless defined $self->response_status_line;
34 2 50       14 $self->uploads(1) unless defined $self->uploads;
35 2 50       15 $self->body_params(1) unless defined $self->body_params;
36 2 50       17 $self->query_params(1) unless defined $self->query_params;
37 2 50       16 $self->attempt_deserialize(1) unless defined $self->attempt_deserialize;
38              
39 2 50       15 if ($self->attempt_deserialize) {
40 2         19 $self->serializer(Data::Serializer::Raw->new);
41             }
42              
43 2 50       792 $self->logger_override(1) if defined $self->logger;
44             }
45              
46             sub call {
47 4     4 1 17785 my($self, $env) = @_;
48              
49 4         21 my $request = Plack::Request->new($env);
50              
51             # take latest $request->logger unless it was explicitly provided at build time
52 4 50       33 if (!$self->logger_override) {
53 0 0       0 if ($request->logger) {
54 0         0 $self->logger($request->logger);
55             }
56             else {
57             $self->logger(sub {
58 0     0   0 my ($args) = @_;
59 0         0 print STDERR $args->{message};
60 0         0 });
61             }
62             }
63              
64 4 50       28 $self->log_request($request) if $self->request;
65              
66             $self->response_cb($self->app->($env), sub {
67 4     4   116 my $res = Plack::Response->new(@{shift()});
  4         22  
68 4 50       276 $self->log_response($res) if $self->response;
69 4         34 $res;
70 4         15 });
71             }
72              
73             sub log {
74 21     21 0 1870 my ($self, $msg) = @_;
75              
76 21 50       44 if (my $logger = $self->logger) {
77 21         158 $logger->({ level => 'debug', message => "$msg\n" });
78             }
79             else {
80 0         0 print STDERR $msg;
81             }
82             }
83              
84              
85             sub log_request {
86 4     4 1 21 my ($self, $request) = @_;
87              
88 4 50       10 return unless $self->debug;
89              
90 4         21 my ( $method, $path, $address ) = ( $request->method, $request->path, $request->address );
91 4   50     52 $method ||= '';
92 4 50       9 $path = '/' unless length $path;
93 4   50     7 $address ||= '';
94 4         16 $self->log(qq/"$method" request for "$path" from "$address"/);
95              
96 4 50       26 $self->log_headers('request', $request->headers)
97             if $self->request_headers;
98              
99 4 100       42 if ( index( $request->env->{QUERY_STRING}, '=' ) < 0 ) {
100 3         19 my $keywords = $self->unescape_uri($request->env->{QUERY_STRING});
101 3 100 66     13 $self->log("Query keywords are: $keywords\n")
102             if $keywords && $self->keywords;
103             }
104              
105 4 50       17 if ($self->request_parameters) {
106 4 50       20 $self->log_request_parameters(query => $request->query_parameters->mixed)
107             if $self->query_params;
108              
109 4 100 66     16 $self->log_request_parameters(body => $request->body_parameters->mixed)
110             if $request->content && $self->body_params;
111              
112 4 100 50     125 $self->log_request_parameters(encoded => $request)
      100        
113             if $request->content && ($request->content_type || '') !~ m/www-form-urlencoded/;
114             }
115              
116 4 50       200 $self->log_request_uploads($request) if $self->uploads;
117             }
118              
119              
120              
121             sub log_response {
122 4     4 1 29 my ($self, $response) = @_;
123              
124 4 50       8 return unless $self->debug;
125              
126 4 50       23 $self->log_response_status_line($response) if $self->response_status_line;
127 4 50       30 $self->log_headers('response', $response->headers) if $self->response_headers;
128             }
129              
130              
131             sub log_response_status_line {
132 4     4 1 20 my ($self, $response) = @_;
133              
134 4   50     23 $self->log(
      50        
      50        
135             sprintf(
136             'Response Code: %s; Content-Type: %s; Content-Length: %s',
137             $response->code || 'unknown',
138             $response->headers->header('Content-Type') || 'unknown',
139             $response->headers->header('Content-Length') || 'unknown'
140             )
141             );
142             }
143              
144              
145             our $module_map = {
146             'text/xml' => 'XML::Simple',
147             'text/x-yaml' => 'YAML',
148             'application/json' => 'JSON',
149             'text/x-json' => 'JSON',
150             'text/x-data-dumper' => 'Data::Dumper',
151             'text/x-data-denter' => 'Data::Denter',
152             'text/x-data-taxi' => 'Data::Taxi',
153             'application/x-storable' => 'Storable',
154             'application/x-freezethaw' => 'FreezeThaw',
155             'text/x-config-general' => 'Config::General',
156             'text/x-php-serialization' => 'PHP::Serialization'
157             };
158              
159             sub log_request_parameters {
160 7     7 1 2086 my $self = shift;
161 7         14 my %all_params = @_;
162              
163 7 50       16 return unless $self->debug;
164              
165 7         39 my $column_width = $self->_term_width() - 44;
166 7         54 foreach my $type (qw(query body)) {
167 14         28 my $params = $all_params{$type};
168 14 100       43 next if ! keys %$params;
169 3         14 my $t = Text::SimpleTable->new( [ 35, 'Parameter' ], [ $column_width, 'Value' ] );
170 3         136 for my $key ( sort keys %$params ) {
171 5         119 my @param = $params->{$key};
172 5 100       10 my $value = length($param[0]) ? $param[0] : '';
173 5 100       17 $t->row( $key, ref $value eq 'ARRAY' ? ( join ', ', @$value ) : $value );
174             }
175 3         155 $self->log( ucfirst($type) . " Parameters are:\n" . $t->draw );
176             }
177              
178 7 100       32 if (my $request = $all_params{encoded}) {
179 1 50       4 if (my $module = $module_map->{$request->content_type}) {
180             # if the module is not installed let Data::Serializer propogate the module load fail.
181             try {
182 1     1   35 $self->serializer->serializer($module);
183 1         1099 my $decoded = $self->serializer->deserialize($request->content);
184 1         85 $self->log($request->content_type . " encoded body parameters are:\n" . Dumper($decoded));
185             }
186             catch {
187 0     0   0 $self->log($request->content_type . " failed to deserialize: $_");
188 1         17 };
189             } else {
190 0         0 $self->log('Unrecognized Content-Type: ' .$request->content_type);
191             }
192             }
193             }
194              
195              
196             sub log_request_uploads {
197 4     4 1 25 my ($self, $request) = @_;
198              
199 4 50       8 return unless $self->debug;
200              
201 4         23 my $uploads = $request->uploads;
202 4 50       40 if ( keys %$uploads ) {
203 0         0 my $t = Text::SimpleTable->new(
204             [ 12, 'Parameter' ],
205             [ 26, 'Filename' ],
206             [ 18, 'Type' ],
207             [ 9, 'Size' ]
208             );
209 0         0 for my $key ( sort keys %$uploads ) {
210 0         0 my $upload = $uploads->{$key};
211 0 0       0 for my $u ( ref $upload eq 'ARRAY' ? @{$upload} : ($upload) ) {
  0         0  
212 0         0 $t->row( $key, $u->filename, $u->type, $u->size );
213             }
214             }
215 0         0 $self->log( "File Uploads are:\n" . $t->draw );
216             }
217             }
218              
219              
220             sub log_headers {
221 8     8 1 473 my ($self, $type, $headers) = @_;
222              
223 8 50       32 return unless $self->debug;
224              
225 8         75 my $column_width = $self->_term_width() - 28;
226 8         91 my $t = Text::SimpleTable->new( [ 15, 'Header Name' ], [ $column_width, 'Value' ] );
227             $headers->scan(
228             sub {
229 14     14   468 my ( $name, $value ) = @_;
230 14         31 $t->row( $name, $value );
231             }
232 8         393 );
233 8         404 $self->log( ucfirst($type) . " Headers:\n" . $t->draw );
234             }
235              
236             sub env_value {
237 0     0 0 0 my ( $class, $key ) = @_;
238              
239 0         0 $key = uc($key);
240 0         0 my @prefixes = ( class2env($class), 'PLACK' );
241              
242 0         0 for my $prefix (@prefixes) {
243 0 0       0 if ( defined( my $value = $ENV{"${prefix}_${key}"} ) ) {
244 0         0 return $value;
245             }
246             }
247              
248 0         0 return;
249             }
250              
251             sub _term_width {
252 15     15   12 my ($self) = @_;
253              
254 15 50       29 return $self->term_width if $self->term_width;
255              
256 0         0 my $width = eval '
257             my ($columns, $rows) = Term::Size::Any::chars;
258             return $columns;
259             ';
260              
261 0 0       0 if ($@) {
262 0 0 0     0 $width = $ENV{COLUMNS}
263             if exists($ENV{COLUMNS})
264             && $ENV{COLUMNS} =~ m/^\d+$/;
265             }
266              
267 0 0 0     0 $width = 80 unless ($width && $width >= 80);
268 0         0 return $width;
269             }
270              
271             sub unescape_uri {
272 3     3 0 12 my ( $self, $str ) = @_;
273              
274 3 50       8 $str =~ s/(?:%([0-9A-Fa-f]{2})|\+)/defined $1 ? chr(hex($1)) : ' '/eg;
  1         4  
275              
276 3         6 return $str;
277             }
278              
279             1;
280              
281             __END__