File Coverage

blib/lib/LWP/ConsoleLogger.pm
Criterion Covered Total %
statement 231 264 87.5
branch 82 104 78.8
condition 34 48 70.8
subroutine 40 46 86.9
pod 2 3 66.6
total 389 465 83.6


line stmt bran cond sub pod time code
1             package LWP::ConsoleLogger;
2              
3 8     8   42826 use Moo;
  8         95902  
  8         37  
4 8     8   15797 use MooX::StrictConstructor;
  8         107899  
  8         44  
5              
6 8     8   194800 use 5.006;
  8         35  
7              
8             our $VERSION = '1.000001';
9              
10 8     8   4350 use Data::Printer { end_separator => 1, hash_separator => ' => ' };
  8         223131  
  8         86  
11 8     8   15087 use DateTime ();
  8         4351291  
  8         445  
12 8     8   5369 use HTML::Restrict ();
  8         958454  
  8         336  
13 8     8   4015 use HTTP::Body ();
  8         219613  
  8         290  
14 8     8   4097 use HTTP::CookieMonster ();
  8         218513  
  8         302  
15 8     8   3426 use JSON::MaybeXS qw( decode_json );
  8         41026  
  8         575  
16 8     8   5332 use List::AllUtils qw( any apply none );
  8         89124  
  8         800  
17 8     8   4198 use Log::Dispatch ();
  8         1719453  
  8         318  
18 8     8   3744 use Parse::MIME qw( parse_mime_type );
  8         9534  
  8         737  
19 8     8   72 use Ref::Util qw( is_blessed_ref );
  8         21  
  8         585  
20 8     8   3945 use Term::Size::Any ();
  8         2251  
  8         353  
21 8     8   3794 use Text::SimpleTable::AutoWidth 0.09 ();
  8         55948  
  8         257  
22 8     8   68 use Try::Tiny qw( catch try );
  8         27  
  8         586  
23 8     8   3744 use Types::Common::Numeric qw( PositiveInt );
  8         106047  
  8         79  
24 8     8   3611 use Types::Standard qw( ArrayRef Bool CodeRef InstanceOf );
  8         78  
  8         56  
25 8     8   11821 use URI::QueryParam qw();
  8         5833  
  8         351  
26 8     8   6865 use XML::Simple qw( XMLin );
  8         73653  
  8         68  
27              
28             my $json_regex = qr{vnd.*\+json};
29              
30             sub BUILD {
31 24     24 0 101288 my $self = shift;
32 24         552 $Text::SimpleTable::AutoWidth::WIDTH_LIMIT = $self->term_width();
33             }
34              
35             has content_pre_filter => (
36             is => 'rw',
37             isa => CodeRef,
38             );
39              
40             has dump_content => (
41             is => 'rw',
42             isa => Bool,
43             default => 0,
44             );
45              
46             has dump_cookies => (
47             is => 'rw',
48             isa => Bool,
49             default => 0,
50             );
51              
52             has dump_headers => (
53             is => 'rw',
54             isa => Bool,
55             default => 1,
56             );
57              
58             has dump_params => (
59             is => 'rw',
60             isa => Bool,
61             default => 1,
62             );
63              
64             has dump_status => (
65             is => 'rw',
66             isa => Bool,
67             default => 1,
68             );
69              
70             has dump_text => (
71             is => 'rw',
72             isa => Bool,
73             default => 1,
74             );
75              
76             has dump_title => (
77             is => 'rw',
78             isa => Bool,
79             default => 1,
80             );
81              
82             has dump_uri => (
83             is => 'rw',
84             isa => Bool,
85             default => 1,
86             );
87              
88             has headers_to_redact => (
89             is => 'rw',
90             isa => ArrayRef,
91             lazy => 1,
92             builder => '_build_headers_to_redact',
93             );
94              
95             has html_restrict => (
96             is => 'rw',
97             isa => InstanceOf ['HTML::Restrict'],
98             lazy => 1,
99             default => sub { HTML::Restrict->new },
100             );
101              
102             has logger => (
103             is => 'rw',
104             isa => InstanceOf ['Log::Dispatch'],
105             lazy => 1,
106             handles => { _debug => 'debug' },
107             default => sub {
108             return Log::Dispatch->new(
109             outputs => [
110             [ 'Screen', min_level => 'debug', newline => 1, utf8 => 1, ],
111             ],
112             );
113             },
114             );
115              
116             has params_to_redact => (
117             is => 'rw',
118             isa => ArrayRef,
119             lazy => 1,
120             builder => '_build_params_to_redact',
121             );
122              
123             has pretty => (
124             is => 'rw',
125             isa => Bool,
126             default => 1,
127             );
128              
129             has term_width => (
130             is => 'rw',
131             isa => PositiveInt,
132             required => 0,
133             lazy => 1,
134             trigger => \&_term_set,
135             builder => '_build_term_width',
136             );
137              
138             has text_pre_filter => (
139             is => 'rw',
140             isa => CodeRef,
141             );
142              
143             sub _build_headers_to_redact {
144 20     20   286 my $self = shift;
145             return $ENV{LWPCL_REDACT_HEADERS}
146 20 50       378 ? [ split m{,}, $ENV{LWPCL_REDACT_HEADERS} ]
147             : [];
148             }
149              
150             sub _build_params_to_redact {
151 2     2   38 my $self = shift;
152             return $ENV{LWPCL_REDACT_PARAMS}
153 2 50       70 ? [ split m{,}, $ENV{LWPCL_REDACT_PARAMS} ]
154             : [];
155             }
156              
157             sub _term_set {
158 0     0   0 my $self = shift;
159 0         0 my $width = shift;
160 0         0 $Text::SimpleTable::AutoWidth::WIDTH_LIMIT = $width;
161             }
162              
163             sub request_callback {
164 28     28 1 49941 my $self = shift;
165 28         84 my $req = shift;
166 28         70 my $ua = shift;
167              
168 28 100       1059 if ( $self->dump_uri ) {
169 26         381 my $uri_without_query = $req->uri->clone;
170 26         710 $uri_without_query->query(undef);
171              
172 26         637 $self->_debug( $req->method . q{ } . $uri_without_query . "\n" );
173             }
174              
175 28 100       1502675 if ( $req->method eq 'GET' ) {
176 23         502 $self->_log_params( $req, 'GET' );
177             }
178             else {
179 5         131 $self->_log_params( $req, $_ ) for ( 'GET', $req->method );
180             }
181              
182 28         2383 $self->_log_headers( 'request (before sending)', $req->headers );
183              
184             # This request might have a body.
185 28 100       21905 return unless $req->content;
186              
187 5         116 $self->_log_content($req);
188 5         2046 $self->_log_text($req);
189 5         2232 return;
190             }
191              
192             sub response_callback {
193 23     23 1 30392 my $self = shift;
194 23         52 my $res = shift;
195 23         49 my $ua = shift;
196              
197 23         82 $self->_log_headers( 'request (after sending)', $res->request->headers );
198              
199 23 100       17301 if ( $self->dump_status ) {
200 21         353 $self->_debug( '==> ' . $res->status_line . "\n" );
201             }
202 23 100 100     5059 if ( $self->dump_title && $ua->can('title') && $ua->title ) {
      100        
203 1         507 $self->_debug( 'Title: ' . $ua->title . "\n" );
204             }
205              
206 23         1375 $self->_log_headers( 'response', $res->headers );
207 23         21707 $self->_log_cookies( 'response', $ua->cookie_jar, $res->request->uri );
208              
209 23         231 $self->_log_content($res);
210 23         26757 $self->_log_text($res);
211 23         5115 return;
212             }
213              
214             sub _log_headers {
215 74     74   1014 my ( $self, $type, $headers ) = @_;
216              
217 74 100       1947 return if !$self->dump_headers;
218              
219 68 100       1713 unless ( $self->pretty ) {
220 3         39 $self->_debug( $headers->as_string );
221 3         942 return;
222             }
223              
224 65         1638 my $t = Text::SimpleTable::AutoWidth->new();
225 65         9215 $t->captions( [ ucfirst $type . ' Header', 'Value' ] );
226              
227 65         291 foreach my $name ( sort $headers->header_field_names ) {
228             my $val
229 183 50   0   3552 = ( any { $name eq $_ } @{ $self->headers_to_redact } )
  0         0  
  183         3363  
230             ? '[REDACTED]'
231             : $headers->header($name);
232 183         8578 $t->row( $name, $val );
233             }
234              
235 65         956 $self->_draw($t);
236             }
237              
238             sub _log_params {
239 33     33   247 my ( $self, $req, $type ) = @_;
240              
241 33 100       939 return if !$self->dump_params;
242              
243 30         360 my %params;
244 30         152 my $uri = $req->uri;
245              
246 30 100       334 if ( $type eq 'GET' ) {
    100          
247 26         442 my @params = $uri->query_param;
248 26 50       1114 return unless @params;
249              
250 0         0 $params{$_} = [ $uri->query_param($_) ] for @params;
251             }
252              
253             elsif ( $req->header('Content-Length') ) {
254 3         317 my $content_type = $req->header('Content-Type');
255 3         199 my $body = HTTP::Body->new(
256             $content_type,
257             $req->header('Content-Length')
258             );
259 3         1501 $body->add( $req->content );
260 3         2558 %params = %{ $body->param };
  3         38  
261              
262 3 100       84 unless ( keys %params ) {
263             {
264 1         35 my $t = Text::SimpleTable::AutoWidth->new;
265 1         2386 $t->captions( [ $type . ' Raw Body' ] );
266 1         5 $t->row( $req->content );
267 1         57 $self->_draw($t);
268             }
269              
270             {
271 1         10 my $t = Text::SimpleTable::AutoWidth->new;
  1         831  
  1         23  
272 1         17 $t->captions( [ $type . ' Parsed Body' ] );
273 1         16 $self->_parse_body( $req->content, $content_type, $t );
274 1         12 $self->_draw($t);
275             }
276             }
277             }
278              
279 4         980 my $t = Text::SimpleTable::AutoWidth->new();
280 4         3159 $t->captions( [ 'Key', 'Value' ] );
281 4         54 foreach my $name ( sort keys %params ) {
282             my @values
283 0     0   0 = ( any { $name eq $_ } @{ $self->params_to_redact } )
  4         150  
284             ? '[REDACTED]'
285 0         0 : ref $params{$name} ? @{ $params{$name} }
286 4 50       75 : $params{$name};
    50          
287              
288 4         190 $t->row( $name, $_ ) for sort @values;
289             }
290              
291 4         82 $self->_draw( $t, "$type Params:\n" );
292             }
293              
294             sub _log_cookies {
295 23     23   768 my $self = shift;
296 23         59 my $type = shift;
297 23         51 my $jar = shift;
298 23         45 my $uri = shift;
299              
300 23 100 100     626 return if !$self->dump_cookies || !$jar || !is_blessed_ref($jar);
      66        
301              
302 19 100       718 if ( $jar->isa('HTTP::Cookies') ) {
    100          
303 17         474 my $monster = HTTP::CookieMonster->new($jar);
304 17         15155 my @cookies = $monster->all_cookies;
305              
306 17         723 my @methods = (
307             'key', 'val', 'path', 'domain',
308             'path_spec', 'secure', 'expires'
309             );
310              
311 17         157 foreach my $cookie (@cookies) {
312              
313 0         0 my $t = Text::SimpleTable::AutoWidth->new;
314 0         0 $t->captions( [ 'Key', 'Value' ] );
315              
316 0         0 foreach my $method (@methods) {
317 0         0 my $val = $cookie->$method;
318 0 0       0 if ($val) {
319 0 0       0 $val = DateTime->from_epoch( epoch => $val )
320             if $method eq 'expires';
321 0         0 $t->row( $method, $val );
322             }
323             }
324              
325 0         0 $self->_draw( $t, ucfirst $type . " Cookie:\n" );
326             }
327             }
328             elsif ( $jar->isa('HTTP::CookieJar') ) {
329 1         12 my @cookies = $jar->cookies_for($uri);
330 1         97 for my $cookie (@cookies) {
331              
332 0         0 my $t = Text::SimpleTable::AutoWidth->new;
333 0         0 $t->captions( [ 'Key', 'Value' ] );
334              
335 0         0 for my $key ( sort keys %{$cookie} ) {
  0         0  
336 0         0 my $val = $cookie->{$key};
337 0 0 0     0 if ( $val && $key =~ m{expires|_time} ) {
338 0         0 $val = DateTime->from_epoch( epoch => $val );
339             }
340 0         0 $t->row( $key, $val );
341             }
342              
343             $self->_draw(
344             $t,
345             sprintf( '%s Cookie (%s)', ucfirst($type), $cookie->{name} )
346 0         0 );
347             }
348             }
349             }
350              
351             sub _get_content {
352 50     50   106 my $self = shift;
353 50         86 my $r = shift;
354              
355 50 50       206 my $content
356             = $r->can('decoded_content') ? $r->decoded_content : $r->content;
357 50 100       40232 return unless $content;
358              
359 48         147 my $content_type = $r->header('Content-Type');
360 48 50       2033 return $content unless $content_type;
361              
362 48     144   481 my ( $type, $subtype ) = apply { lc $_ } parse_mime_type($content_type);
  144         2066  
363 48 100 100     1142 if (
    100 66        
364             ( $type ne 'text' )
365             && (
366 52     52   505 none { $_ eq $subtype } (
367             'javascript', 'html', 'json', 'xml', 'soap+xml',
368             'x-www-form-urlencoded',
369             )
370             )
371             && $subtype !~ m{$json_regex}
372             ) {
373 2         17 $content = $self->_redaction_message($content_type);
374             }
375             elsif ( $self->content_pre_filter ) {
376 2         44 $content = $self->content_pre_filter->( $content, $content_type );
377             }
378              
379 48         1801 return $content;
380             }
381              
382             sub _log_content {
383 28     28   66 my $self = shift;
384 28         59 my $r = shift;
385              
386 28 100       626 return unless $self->dump_content;
387              
388 25         356 my $content = $self->_get_content($r);
389              
390 25 100       96 return unless $content;
391              
392 24 100       436 unless ( $self->pretty ) {
393 1         25 $self->_debug("Content\n\n$content\n\n");
394 1         203 return;
395             }
396              
397 23         572 my $t = Text::SimpleTable::AutoWidth->new();
398 23         540 $t->captions( ['Content'] );
399              
400 23         98 $t->row($content);
401 23         240 $self->_draw($t);
402             }
403              
404             sub _log_text {
405 28     28   98 my $self = shift;
406 28         55 my $r = shift; # HTTP::Request or HTTP::Response
407              
408 28 100       711 return unless $self->dump_text;
409 25         289 my $content = $self->_get_content($r);
410 25 100       118 return unless $content;
411              
412 24         92 my $content_type = $r->header('Content-Type');
413              
414             # If a pre_filter converts HTML to text, for example, we don't want to
415             # reprocess the text as HTML.
416              
417 24 100 100     1594 if ( $self->text_pre_filter && $r->isa('HTTP::Response') ) {
418 16         392 ( $content, my $type )
419             = $self->text_pre_filter->( $content, $content_type, $r->base );
420 16 100       39493 $content_type = $type if $type;
421             }
422              
423 24 100       243 return unless $content;
424              
425 14 100       284 unless ( $self->pretty ) {
426 1         27 $self->_debug("Text\n\n$content\n\n");
427 1         200 return;
428             }
429              
430 13         329 my $t = Text::SimpleTable::AutoWidth->new();
431 13         352 $t->captions( ['Text'] );
432              
433 13         75 $self->_parse_body( $content, $content_type, $t );
434              
435 13         166 $self->_draw($t);
436             }
437              
438             sub _parse_body {
439 14     14   64 my $self = shift;
440 14         26 my $content = shift;
441 14         28 my $content_type = shift;
442 14         27 my $t = shift;
443              
444             # Is this maybe JSON?
445             try {
446 14     14   746 decode_json($content);
447              
448             # If we get this far, it's valid JSON.
449 4         11 $content_type = 'application/json';
450 14         170 };
451              
452             # nothing to do here
453 14 50       242 unless ($content_type) {
454 0         0 $t->row($content);
455 0         0 return;
456             }
457              
458 14     42   143 my ( $type, $subtype ) = apply { lc $_ } parse_mime_type($content_type);
  42         516  
459 14 50       68 unless ($subtype) {
460 0         0 $t->row($content);
461 0         0 return;
462             }
463              
464 14 100 66     448 if ( $subtype eq 'html' ) {
    50 66        
    100 100        
    100 66        
    100 66        
    100 66        
465 4         101 $content = $self->html_restrict->process($content);
466 4         17738 $content =~ s{\s+}{ }g;
467 4         14 $content =~ s{\n{2,}}{\n\n}g;
468              
469 4 50       12 return if !$content;
470             }
471             elsif ( $subtype eq 'xml' ) {
472             try {
473 0     0   0 my $pretty = XMLin( $content, KeepRoot => 1 );
474 0         0 $content = np( $pretty, return_value => 'dump' );
475             }
476 0     0   0 catch { $t->row("Error parsing XML: $_") };
  0         0  
477             }
478             elsif ( $subtype eq 'json' || $subtype =~ m{$json_regex} ) {
479             try {
480 4     4   149 $content = decode_json($content);
481 4         35 $content = np( $content, return_value => 'dump' );
482             }
483 4     0   36 catch { $t->row("Error parsing JSON: $_") };
  0         0  
484             }
485             elsif ( $type && $type eq 'application' && $subtype eq 'javascript' ) {
486              
487             # clean it up a bit, and print some of it
488 2         54 $content =~ s{^\s*}{}mg;
489 2 50       28 if ( length $content > 253 ) {
490 2         8 $content = substr( $content, 0, 252 ) . '...';
491             }
492             }
493             elsif ($type
494             && $type eq 'application'
495             && $subtype eq 'x-www-form-urlencoded' ) {
496              
497             # Pretend we have query params.
498 1         13 my $uri = URI->new( '?' . $content );
499 1         112 $content = np( $uri->query_form_hash );
500             }
501             elsif ( !$type || $type ne 'text' ) {
502              
503             # Avoid things like dumping gzipped content to the screen
504 1         13 $content = $self->_redaction_message($content_type);
505             }
506              
507 14         27633 $content =~ s{^\\ }{}; # don't prefix HashRef with Data::Printer's slash
508 14         67 $t->row($content);
509             }
510              
511             sub _redaction_message {
512 3     3   7 my $self = shift;
513 3         12 my $content_type = shift;
514 3         25 return sprintf '[ REDACTED by %s. Do not know how to display %s. ]',
515             __PACKAGE__, $content_type;
516             }
517              
518             sub _build_term_width {
519 24     24   342 my ($self) = @_;
520              
521             # cargo culted from Plack::Middleware::DebugLogging
522 24         1653 my $width = eval '
523             my ($columns, $rows) = Term::Size::Any::chars;
524             return $columns;
525             ';
526              
527 24 50       180 if ($@) {
528             $width = $ENV{COLUMNS}
529             if exists( $ENV{COLUMNS} )
530 24 50 33     125 && $ENV{COLUMNS} =~ m/^\d+$/;
531             }
532              
533 24 50 33     118 $width = 80 unless ( $width && $width >= 80 );
534 24         526 return $width;
535             }
536              
537             sub _draw {
538 107     107   188 my $self = shift;
539 107         152 my $t = shift;
540 107         213 my $preamble = shift;
541              
542 107 100       330 return if !$t->rows;
543 105 100       365 $self->_debug($preamble) if $preamble;
544 105         789 $self->_debug( $t->draw );
545             }
546              
547             1;
548              
549             =pod
550              
551             =encoding UTF-8
552              
553             =head1 NAME
554              
555             LWP::ConsoleLogger - LWP tracing and debugging
556              
557             =head1 VERSION
558              
559             version 1.000001
560              
561             =head1 SYNOPSIS
562              
563             The simplest way to get started is by adding L<LWP::ConsoleLogger::Everywhere>
564             to your code and then just watching your output.
565              
566             use LWP::ConsoleLogger::Everywhere ();
567              
568             If you need more control, look at L<LWP::ConsoleLogger::Easy>.
569              
570             use LWP::ConsoleLogger::Easy qw( debug_ua );
571             use WWW::Mechanize;
572              
573             my $mech = WWW::Mechanize->new; # or LWP::UserAgent->new() etc
574             my $console_logger = debug_ua( $mech );
575             $mech->get( 'https://metacpan.org' );
576              
577             # now watch the console for debugging output
578             # turn off header dumps
579             $console_logger->dump_headers( 0 );
580              
581             $mech->get( $some_other_url );
582              
583             To get down to the lowest level, use LWP::ConsoleLogger directly.
584              
585             my $ua = LWP::UserAgent->new( cookie_jar => {} );
586             my $console_logger = LWP::ConsoleLogger->new(
587             dump_content => 1,
588             dump_text => 1,
589             content_pre_filter => sub {
590             my $content = shift;
591             my $content_type = shift;
592              
593             # mangle content here
594             # ...
595              
596             return $content;
597             },
598             );
599              
600             $ua->default_header(
601             'Accept-Encoding' => scalar HTTP::Message::decodable() );
602              
603             $ua->add_handler( 'response_done',
604             sub { $console_logger->response_callback( @_ ) } );
605             $ua->add_handler( 'request_send',
606             sub { $console_logger->request_callback( @_ ) } );
607              
608             # now watch debugging output to your screen
609             $ua->get( 'http://nytimes.com/' );
610              
611             Sample output might look like this.
612              
613             GET http://www.nytimes.com/2014/04/24/technology/fcc-new-net-neutrality-rules.html
614              
615             GET params:
616             .-----+-------.
617             | Key | Value |
618             +-----+-------+
619             | _r | 1 |
620             | hp | |
621             '-----+-------'
622              
623             .-----------------+--------------------------------.
624             | Request Header | Value |
625             +-----------------+--------------------------------+
626             | Accept-Encoding | gzip |
627             | Cookie2 | $Version="1" |
628             | Referer | http://www.nytimes.com?foo=bar |
629             | User-Agent | WWW-Mechanize/1.73 |
630             '-----------------+--------------------------------'
631              
632             ==> 200 OK
633              
634             Title: The New York Times - Breaking News, World News & Multimedia
635              
636             .--------------------------+-------------------------------.
637             | Response Header | Value |
638             +--------------------------+-------------------------------+
639             | Accept-Ranges | bytes |
640             | Age | 176 |
641             | Cache-Control | no-cache |
642             | Channels | NytNow |
643             | Client-Date | Fri, 30 May 2014 22:37:42 GMT |
644             | Client-Peer | 170.149.172.130:80 |
645             | Client-Response-Num | 1 |
646             | Client-Transfer-Encoding | chunked |
647             | Connection | keep-alive |
648             | Content-Encoding | gzip |
649             | Content-Type | text/html; charset=utf-8 |
650             | Date | Fri, 30 May 2014 22:37:41 GMT |
651             | NtCoent-Length | 65951 |
652             | Server | Apache |
653             | Via | 1.1 varnish |
654             | X-Cache | HIT |
655             | X-Varnish | 1142859770 1142854917 |
656             '--------------------------+-------------------------------'
657              
658             .--------------------------+-------------------------------.
659             | Text |
660             +--------------------------+-------------------------------+
661             | F.C.C., in a Shift, Backs Fast Lanes for Web Traffic... |
662             '--------------------------+-------------------------------'
663              
664             =head1 DESCRIPTION
665              
666             It can be hard (or at least tedious) to debug mechanize scripts. LWP::Debug is
667             deprecated. It suggests you write your own debugging handlers, set up a proxy
668             or install Wireshark. Those are all workable solutions, but this module exists
669             to save you some of that work. The guts of this module are stolen from
670             L<Plack::Middleware::DebugLogging>, which in turn stole most of its internals
671             from L<Catalyst>. If you're new to LWP::ConsoleLogger, I suggest getting
672             started with the L<LWP::ConsoleLogger::Easy> wrapper. This will get you up and
673             running in minutes. If you need to tweak the settings that
674             L<LWP::ConsoleLogger::Easy> chooses for you (or if you just want to be fancy),
675             please read on.
676              
677             Since this is a debugging library, I've left as much mutable state as possible,
678             so that you can easily toggle output on and off and otherwise adjust how you
679             deal with the output.
680              
681             =head1 CONSTRUCTOR
682              
683             =head2 new()
684              
685             The following arguments can be passed to new(), although none are required.
686             They can also be called as methods on an instantiated object. I'll list them
687             here and discuss them in detail below.
688              
689             =over 4
690              
691             =item * C<< dump_content => 0|1 >>
692              
693             =item * C<< dump_cookies => 0|1 >>
694              
695             =item * C<< dump_headers => 0|1 >>
696              
697             =item * C<< dump_params => 0|1 >>
698              
699             =item * C<< dump_status => 0|1 >>
700              
701             =item * C<< dump_text => 0|1 >>
702              
703             =item * C<< dump_title => 0|1 >>
704              
705             =item * C<< dump_text => 0|1 >>
706              
707             =item * C<< dump_uri => 0|1 >>
708              
709             =item * C<< content_pre_filter => sub { ... } >>
710              
711             =item * C<< headers_to_redact => ['Authentication', 'Foo'] >>
712              
713             =item * C<< params_to_redact => ['token', 'password'] >>
714              
715             =item * C<< text_pre_filter => sub { ... } >>
716              
717             =item * C<< html_restrict => HTML::Restrict->new( ... ) >>
718              
719             =item * C<< logger => Log::Dispatch->new( ... ) >>
720              
721             =item * C<< pretty => 0|1 >>
722              
723             =item * C<< term_width => $integer >>
724              
725             =back
726              
727             =head1 SUBROUTINES/METHODS
728              
729             =head2 dump_content( 0|1 )
730              
731             Boolean value. If true, the actual content of your response (HTML, JSON, etc)
732             will be dumped to your screen. Defaults to false.
733              
734             =head2 dump_cookies( 0|1 )
735              
736             Boolean value. If true, the content of your cookies will be dumped to your
737             screen. Defaults to false.
738              
739             =head2 dump_headers( 0|1 )
740              
741             Boolean value. If true, both request and response headers will be dumped to
742             your screen. Defaults to true.
743              
744             Headers are dumped in alphabetical order.
745              
746             =head2 dump_params( 0|1 )
747              
748             Boolean value. If true, both GET and POST params will be dumped to your screen.
749             Defaults to true.
750              
751             Params are dumped in alphabetical order.
752              
753             =head2 dump_status( 0|1 )
754              
755             Boolean value. If true, dumps the HTTP response code for each page being
756             visited. Defaults to true.
757              
758             =head2 dump_text( 0|1 )
759              
760             Boolean value. If true, dumps the text of your page after both the
761             content_pre_filter and text_pre_filters have been applied. Defaults to true.
762              
763             =head2 dump_title( 0|1 )
764              
765             Boolean value. If true, dumps the titles of HTML pages if your UserAgent has
766             a C<title> method and if it returns something useful. Defaults to true.
767              
768             =head2 dump_uri( 0|1 )
769              
770             Boolean value. If true, dumps the URI of each page being visited. Defaults to
771             true.
772              
773             =head2 pretty ( 0|1 )
774              
775             Boolean value. If disabled, request headers, response headers, content and text
776             sections will be dumped without using tables. Handy for copy/pasting JSON etc
777             for faking responses later. Defaults to true.
778              
779             =head2 content_pre_filter( sub { ... } )
780              
781             Subroutine reference. This allows you to manipulate content before it is
782             dumped. A common use case might be stripping headers and footers away from
783             HTML content to make it easier to detect changes in the body of the page.
784              
785             $easy_logger->content_pre_filter(
786             sub {
787             my $content = shift;
788             my $content_type = shift; # the value of the Content-Type header
789             if ( $content_type =~ m{html}i
790             && $content =~ m{<!--\scontent\s-->(.*)<!--\sfooter}msx ) {
791             return $1;
792             }
793             return $content;
794             }
795             );
796              
797             Try to make sure that your content mangling doesn't return broken HTML as that
798             may not play well with L<HTML::Restrict>.
799              
800             =head2 request_callback
801              
802             Use this handler to set up console logging on your requests.
803              
804             my $ua = LWP::UserAgent->new;
805             $ua->add_handler(
806             'request_send',
807             sub { $console_logger->request_callback(@_) }
808             );
809              
810             This is done for you by default if you set up your logging via
811             L<LWP::ConsoleLogger::Easy>.
812              
813             =head2 response_callback
814              
815             Use this handler to set up console logging on your responses.
816              
817             my $ua = LWP::UserAgent->new;
818             $ua->add_handler(
819             'response_done',
820             sub { $console_logger->response_callback(@_) }
821             );
822              
823             This is done for you by default if you set up your logging via
824             L<LWP::ConsoleLogger::Easy>.
825              
826             =head2 text_pre_filter( sub { ... } )
827              
828             Subroutine reference. This allows you to manipulate text before it is dumped.
829             A common use case might be stripping away duplicate whitespace and/or newlines
830             in order to improve formatting. Keep in mind that the C<content_pre_filter>
831             will have been applied to the content which is passed to the text_pre_filter.
832             The idea is that you can strip away an HTML you don't care about in the
833             content_pre_filter phase and then process the remainder of the content in the
834             text_pre_filter.
835              
836             $easy_logger->text_pre_filter(
837             sub {
838             my $content = shift;
839             my $content_type = shift; # the value of the Content-Type header
840             my $base_url = shift;
841              
842             # do something with the content
843             # ...
844              
845             return ( $content, $new_content_type );
846             }
847             );
848              
849             If your C<text_pre_filter()> converts from HTML to plain text, be sure to
850             return the new content type (text/plain) when you exit the sub. If you do not
851             do this, HTML formatting will then be applied to your plain text as is
852             explained below.
853              
854             If this is HTML content, L<HTML::Restrict> will be applied after the
855             text_pre_filter has been run. LWP::ConsoleLogger will then strip away some
856             whitespace and newlines from processed HTML in its own opinionated way, in
857             order to present you with more readable text.
858              
859             =head2 html_restrict( HTML::Restrict->new( ... ) )
860              
861             If the content_type indicates HTML then HTML::Restrict will be used to strip
862             tags from your content in the text rendering process. You may pass your own
863             HTML::Restrict object, if you like. This would be helpful in situations where
864             you still do want to have some tags in your text.
865              
866             =head2 logger( Log::Dispatch->new( ... ) )
867              
868             By default all data will be dumped to your console (as the name of this module
869             implies) using Log::Dispatch. However, you may use your own Log::Dispatch
870             module in order to facilitate logging to files or any other output which
871             Log::Dispatch supports.
872              
873             =head2 term_width( $integer )
874              
875             By default this module will try to find the maximum width of your terminal and
876             use all available space when displaying tabular data. You may use this
877             parameter to constrain the tables to an arbitrary width.
878              
879             =head1 CAVEATS
880              
881             I've written this to suit my needs and there are a lot of things I haven't
882             considered. For example, I'm mostly assuming that the content will be text,
883             HTML, JSON or XML.
884              
885             The test suite is not very robust either. If you'd like to contribute to this
886             module and you can't find an appropriate test, do add something to the example
887             folder (either a new script or alter an existing one), so that I can see what
888             your patch does.
889              
890             =for Pod::Coverage BUILD
891              
892             =head1 AUTHOR
893              
894             Olaf Alders <olaf@wundercounter.com>
895              
896             =head1 COPYRIGHT AND LICENSE
897              
898             This software is Copyright (c) 2014 by MaxMind, Inc.
899              
900             This is free software, licensed under:
901              
902             The Artistic License 2.0 (GPL Compatible)
903              
904             =cut
905              
906             __END__
907              
908             # ABSTRACT: LWP tracing and debugging
909