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.001004';
3             # ABSTRACT: Catalyst style console debugging for plack apps
4              
5 1     1   781 use strict;
  1         2  
  1         32  
6 1     1   5 use warnings;
  1         3  
  1         30  
7              
8 1     1   897 use Data::Dumper::Concise;
  1         9403  
  1         78  
9 1     1   834 use Data::Serializer::Raw;
  1         878  
  1         31  
10 1     1   1026 use Module::Runtime qw(use_module);
  1         2058  
  1         7  
11 1     1   834 use Text::SimpleTable;
  1         2158  
  1         27  
12 1     1   954 use Plack::Request;
  1         49954  
  1         43  
13 1     1   841 use Plack::Response;
  1         1411  
  1         54  
14 1     1   813 use Term::Size::Any;
  1         225  
  1         6  
15 1     1   3309 use Try::Tiny;
  1         1550  
  1         72  
16 1         8 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   6 attempt_deserialize serializer);
  1         2  
20              
21 1     1   133 use parent qw/Plack::Middleware/;
  1         2  
  1         8  
22              
23             sub prepare_app {
24 2     2 1 2653 my ($self) = @_;
25              
26 2 50       8 $self->debug(1) unless defined $self->debug;
27 2 50       130 $self->request(1) unless defined $self->request;
28 2 50       27 $self->response(1) unless defined $self->response;
29 2 50       28 $self->keywords(1) unless defined $self->keywords;
30 2 50       27 $self->request_headers(1) unless defined $self->request_headers;
31 2 50       28 $self->request_parameters(1) unless defined $self->request_parameters;
32 2 50       26 $self->response_headers(1) unless defined $self->response_headers;
33 2 50       25 $self->response_status_line(1) unless defined $self->response_status_line;
34 2 50       27 $self->uploads(1) unless defined $self->uploads;
35 2 50       24 $self->body_params(1) unless defined $self->body_params;
36 2 50       25 $self->query_params(1) unless defined $self->query_params;
37 2 50       23 $self->attempt_deserialize(1) unless defined $self->attempt_deserialize;
38              
39 2 50       23 if ($self->attempt_deserialize) {
40 2         32 $self->serializer(Data::Serializer::Raw->new);
41             }
42              
43 2 50       1561 $self->logger_override(1) if defined $self->logger;
44             }
45              
46             sub call {
47 4     4 1 34491 my($self, $env) = @_;
48              
49 4         45 my $request = Plack::Request->new($env);
50              
51             # take latest $request->logger unless it was explicitly provided at build time
52 4 50       57 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       49 $self->log_request($request) if $self->request;
65              
66             $self->response_cb($self->app->($env), sub {
67 4     4   211 my $res = Plack::Response->new(@{shift()});
  4         51  
68 4 50       475 $self->log_response($res) if $self->response;
69 4         60 $res;
70 4         29 });
71             }
72              
73             sub log {
74 21     21 0 2677 my ($self, $msg) = @_;
75              
76 21 50       61 if (my $logger = $self->logger) {
77 21         238 $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 35 my ($self, $request) = @_;
87              
88 4 50       14 return unless $self->debug;
89              
90 4         38 my ( $method, $path, $address ) = ( $request->method, $request->path, $request->address );
91 4   50     83 $method ||= '';
92 4 50       14 $path = '/' unless length $path;
93 4   50     10 $address ||= '';
94 4         22 $self->log(qq/"$method" request for "$path" from "$address"/);
95              
96 4 50       37 $self->log_headers('request', $request->headers)
97             if $self->request_headers;
98              
99 4 100       53 if ( index( $request->env->{QUERY_STRING}, '=' ) < 0 ) {
100 3         29 my $keywords = $self->unescape_uri($request->env->{QUERY_STRING});
101 3 100 66     30 $self->log("Query keywords are: $keywords\n")
102             if $keywords && $self->keywords;
103             }
104              
105 4 50       28 if ($self->request_parameters) {
106 4 50       35 $self->log_request_parameters(query => $request->query_parameters->mixed)
107             if $self->query_params;
108              
109 4 100 66     19 $self->log_request_parameters(body => $request->body_parameters->mixed)
110             if $request->content && $self->body_params;
111              
112 4 100 50     167 $self->log_request_parameters(encoded => $request)
      100        
113             if $request->content && ($request->content_type || '') !~ m/www-form-urlencoded/;
114             }
115              
116 4 50       271 $self->log_request_uploads($request) if $self->uploads;
117             }
118              
119              
120              
121             sub log_response {
122 4     4 1 42 my ($self, $response) = @_;
123              
124 4 50       12 return unless $self->debug;
125              
126 4 50       37 $self->log_response_status_line($response) if $self->response_status_line;
127 4 50       46 $self->log_headers('response', $response->headers) if $self->response_headers;
128             }
129              
130              
131             sub log_response_status_line {
132 4     4 1 34 my ($self, $response) = @_;
133              
134 4   50     17 $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 4044 my $self = shift;
161 7         22 my %all_params = @_;
162              
163 7 50       21 return unless $self->debug;
164              
165 7         56 my $column_width = $self->_term_width() - 44;
166 7         81 foreach my $type (qw(query body)) {
167 14         54 my $params = $all_params{$type};
168 14 100       57 next if ! keys %$params;
169 3         47 my $t = Text::SimpleTable->new( [ 35, 'Parameter' ], [ $column_width, 'Value' ] );
170 3         234 for my $key ( sort keys %$params ) {
171 5         162 my @param = $params->{$key};
172 5 100       17 my $value = length($param[0]) ? $param[0] : '';
173 5 100       27 $t->row( $key, ref $value eq 'ARRAY' ? ( join ', ', @$value ) : $value );
174             }
175 3         222 $self->log( ucfirst($type) . " Parameters are:\n" . $t->draw );
176             }
177              
178 7 100       68 if (my $request = $all_params{encoded}) {
179 1 50       7 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   140 $self->serializer->serializer($module);
183 1         2988 my $decoded = $self->serializer->deserialize($request->content);
184 1         145 $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         23 };
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 42 my ($self, $request) = @_;
198              
199 4 50       16 return unless $self->debug;
200              
201 4         35 my $uploads = $request->uploads;
202 4 50       57 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 745 my ($self, $type, $headers) = @_;
222              
223 8 50       47 return unless $self->debug;
224              
225 8         175 my $column_width = $self->_term_width() - 28;
226 8         148 my $t = Text::SimpleTable->new( [ 15, 'Header Name' ], [ $column_width, 'Value' ] );
227             $headers->scan(
228             sub {
229 14     14   714 my ( $name, $value ) = @_;
230 14         60 $t->row( $name, $value );
231             }
232 8         594 );
233 8         619 $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   30 my ($self) = @_;
253              
254 15 50       43 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 19 my ( $self, $str ) = @_;
273              
274 3 50       13 $str =~ s/(?:%([0-9A-Fa-f]{2})|\+)/defined $1 ? chr(hex($1)) : ' '/eg;
  1         8  
275              
276 3         9 return $str;
277             }
278              
279             1;
280              
281             __END__