Bump copyright year.
[privoxy.git] / tools / privoxy-log-parser.pl
1 #!/usr/bin/perl
2
3 ################################################################################
4 # privoxy-log-parser
5 #
6 # A parser for Privoxy log messages. For incomplete documentation run
7 # perldoc privoxy-log-parser(.pl), for fancy screenshots see:
8 #
9 # http://www.fabiankeil.de/sourcecode/privoxy-log-parser/
10 #
11 # $Id: privoxy-log-parser.pl,v 1.83 2010/07/22 14:51:59 fabiankeil Exp $
12 #
13 # TODO:
14 #       - LOG_LEVEL_CGI, LOG_LEVEL_ERROR, LOG_LEVEL_WRITE content highlighting
15 #       - create fancy statistics
16 #       - grep through Privoxy sources to find unsupported log messages
17 #       - hunt down substitutions that match content from variables which
18 #         can contain stuff like ()?'[]
19 #       - replace $h{'foo'} with h('foo') where possible
20 #       - hunt down XXX comments instead of just creating them
21 #       - add example log lines for every regex and mark them up for
22 #         regression testing
23 #       - Handle incomplete input without Perl warning about undefined variables.
24 #       - Use generic highlighting function that takes a regex and the
25 #         hash key as input.
26 #
27 # Copyright (c) 2007-2010 Fabian Keil <fk@fabiankeil.de>
28 #
29 # Permission to use, copy, modify, and distribute this software for any
30 # purpose with or without fee is hereby granted, provided that the above
31 # copyright notice and this permission notice appear in all copies.
32 #
33 # THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
34 # WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
35 # MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
36 # ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
37 # WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
38 # ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
39 # OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
40 ################################################################################
41
42 use strict;
43 use warnings;
44 use Getopt::Long;
45
46 use constant {
47     PRIVOXY_LOG_PARSER_VERSION => '0.6',
48     # Feel free to mess with these ...
49     DEFAULT_BACKGROUND => 'black',  # Choose registered colour (like 'black')
50     DEFAULT_TEXT_COLOUR => 'white', # Choose registered colour (like 'black')
51     HEADER_DEFAULT_COLOUR => 'yellow',
52     REGISTER_HEADERS_WITH_THE_SAME_COLOUR => 1,
53
54     CLI_OPTION_DEFAULT_TO_HTML_OUTPUT => 0,
55     CLI_OPTION_TITLE => 'Privoxy-Log-Parser in da house',
56     CLI_OPTION_NO_EMBEDDED_CSS => 0,
57     CLI_OPTION_NO_MSECS => 0,
58     CLI_OPTION_NO_SYNTAX_HIGHLIGHTING => 0,
59     CLI_OPTION_SHORTEN_THREAD_IDS => 0,
60     CLI_OPTION_SHOW_INEFFECTIVE_FILTERS => 0,
61     CLI_OPTION_ACCEPT_UNKNOWN_MESSAGES => 0,
62     CLI_OPTION_STATISTICS => 0,
63
64     SUPPRESS_SUCCEEDED_FILTER_ADDITIONS => 1,
65     SHOW_SCAN_INTRO => 0,
66     SHOW_FILTER_READIN_IN => 0,
67     SUPPRESS_EMPTY_LINES => 1,
68     SUPPRESS_SUCCESSFUL_CONNECTIONS => 1,
69     SUPPRESS_ACCEPTED_CONNECTIONS => 1,
70     SUPPRESS_GIF_NOT_CHANGED => 1,
71     SUPPRESS_NEED_TO_DE_CHUNK_FIRST => 1,
72
73     DEBUG_HEADER_REGISTERING => 0,
74     DEBUG_HEADER_HIGHLIGHTING => 0,
75     DEBUG_TICKS => 0,
76     DEBUG_PAINT_IT => 0,
77     DEBUG_SUPPRESS_LOG_MESSAGES => 0,
78
79     PUNISH_MISSING_LOG_KNOWLEDGE_WITH_DEATH => 0,
80     PUNISH_MISSING_HIGHLIGHT_KNOWLEDGE_WITH_DEATH => 1,
81
82     LOG_UNPARSED_LINES_TO_EXTRA_FILE => 0,
83     ERROR_LOG_FILE => '/var/log/privoxy-log-parser',
84
85     # You better leave these alone unless you know what you're doing.
86     COLOUR_RESET      => "\033[0;0m",
87     ESCAPE => "\033[",
88 };
89
90 # For performance reasons, these are global.
91
92 my $t;
93 my %req; # request data from previous lines
94 my %h;
95 my %thread_colours;
96 my @all_colours;
97 my @time_colours;
98 my $thread_colour_index = 0;
99 my $header_colour_index = 0;
100 my $time_colour_index = 0;
101 my %header_colours;
102 my $no_special_header_highlighting;
103 my %reason_colours;
104 my %h_colours;
105 my $header_highlight_regex = '';
106
107 my $html_output_mode;
108 my $no_msecs_mode; # XXX: should probably be removed
109 my $shorten_thread_ids;
110 my $line_end;
111
112 sub prepare_our_stuff () {
113
114     # Syntax Higlight hash
115     @all_colours = (
116         'red', 'green', 'brown', 'blue', 'purple', 'cyan',
117         'light_gray', 'light_red', 'light_green', 'yellow',
118         'light_blue', 'pink', 'light_cyan', 'white'
119     );
120
121     %h = (
122         # LOG_LEVEL
123         Info            => 'blue',
124         Header          => 'green',
125         Filter          => 'purple', # XXX: Used?
126         'Re-Filter'     => 'purple',
127         Connect         => 'brown',
128         Request         => 'light_cyan',
129         CGI             => 'light_green',
130         Redirect        => 'cyan',
131         Error           => 'light_red',
132         Crunch          => 'cyan',
133         'Fatal error'   => 'light_red',
134         'Gif-Deanimate' => 'blue',
135         Force           => 'red',
136         Writing         => 'light_green',
137         # ----------------------
138         URL                  => 'yellow',
139         path                 => 'brown',
140         request_             => 'brown', # host+path but no protocol
141         'ip-address'         => 'yellow',
142         Number               => 'yellow',
143         Standard             => 'reset',
144         Truncation           => 'light_red',
145         Status               => 'brown',
146         Timestamp            => 'brown',
147         Crunching            => 'light_red',
148         crunched             => 'light_red',
149         'Request-Line'       => 'pink',
150         method               => 'purple',
151         destination          => 'yellow',
152         'http-version'       => 'pink',
153         'crunch-pattern'     => 'pink',
154         not                  => 'brown',
155         file                 => 'brown',
156         signal               => 'yellow',
157         version              => 'green',
158         'program-name'       => 'cyan',
159         port                 => 'red',
160         host                 => 'red',
161         warning              => 'light_red',
162         debug                => 'light_red',
163         filter               => 'green',
164         tag                  => 'green',
165         tagger               => 'green',
166         'status-message'     => 'light_cyan',
167         'status-code'        => 'yellow',
168         'invalid-request'    => 'light_red',
169         'hits'               => 'yellow',
170         error                => 'light_red',
171         'rewritten-URL'      => 'light_red',
172         'pcrs-delimiter'     => 'light_red',
173         'ignored'            => 'light_red',
174         'action-bits-update' => 'light_red',
175         'configuration-line' => 'red',
176         'content-type'       => 'yellow',
177     );
178
179     %h_colours = %h;
180
181     # Header colours need their own hash so the keys can be accessed properly
182     %header_colours = (
183         # Prefilled with headers that should not appear with default header colours
184         Cookie => 'light_red',
185         'Set-Cookie' => 'light_red',
186         Warning => 'light_red',
187         Default => HEADER_DEFAULT_COLOUR,
188     );
189
190     # Crunch reasons need their own hash as well
191     %reason_colours = (
192         'Unsupported HTTP feature'               => 'light_red',
193         Blocked                                  => 'light_red',
194         Untrusted                                => 'light_red',
195         Redirected                               => 'green',
196         'CGI Call'                               => 'white',
197         'DNS failure'                            => 'red',
198         'Forwarding failed'                      => 'light_red',
199         'Connection failure'                     => 'light_red',
200         'Out of memory (may mask other reasons)' => 'light_red',
201         'No reason recorded'                     => 'light_red',
202     );
203
204     @time_colours = ('white', 'light_gray');
205
206     # Translate highlight strings into highlight code
207     prepare_highlight_hash(\%header_colours);
208     prepare_highlight_hash(\%reason_colours);
209     prepare_highlight_hash(\%h);
210     prepare_colour_array(\@all_colours);
211     prepare_colour_array(\@time_colours);
212     init_css_colours();
213
214     init_stats();
215 }
216
217 sub paint_it ($) {
218 ###############################################################
219 # Takes a colour string and returns an ANSI escape sequence
220 # (unless --no-syntax-highlighting is used).
221 # XXX: The Rolling Stones reference has to go.
222 ###############################################################
223
224     my $colour = shift @_;
225
226     return "" if cli_option_is_set('no-syntax-highlighting');
227
228     my %light = (
229         black       => 0,
230         red         => 0,
231         green       => 0,
232         brown       => 0,
233         blue        => 0,
234         purple      => 0,
235         cyan        => 0,
236         light_gray  => 0,
237         gray        => 0,
238         dark_gray   => 1,
239         light_red   => 1,
240         light_green => 1,
241         yellow      => 1,
242         light_blue  => 1,
243         pink        => 1,
244         light_cyan  => 1,
245         white       => 1,
246     );
247
248     my %text = (
249         black       => 30,
250         red         => 31,
251         green       => 32,
252         brown       => 33,
253         blue        => 34,
254         purple      => 35,
255         cyan        => 36,
256         gray        => 37,
257         light_gray  => 37,
258         dark_gray   => 30,
259         light_red   => 31,
260         light_green => 32,
261         yellow      => 33,
262         light_blue  => 34,
263         pink        => 35,
264         light_cyan  => 36,
265         white       => 37,
266     );
267
268     my $bg_code = get_background();
269     my $colour_code;
270     our $default = default_colours();
271
272     if (defined($text{$colour})) {
273         $colour_code  = ESCAPE;
274         $colour_code .= $text{$colour};
275         $colour_code .= ";";
276         $colour_code .= $light{$colour} ? "1" : "2";
277         $colour_code .= ";";
278         $colour_code .= $bg_code;
279         $colour_code .= "m";
280         debug_message $colour . " is \'" . $colour_code . $colour . $default . "\'" if DEBUG_PAINT_IT;
281
282     } elsif ($colour =~ /reset/) {
283
284         $colour_code = default_colours();
285
286     } else {
287
288         die "What's $colour supposed to mean?\n";
289     }
290
291     return $colour_code;
292 }
293
294 sub get_semantic_html_markup ($) {
295 ###############################################################
296 # Takes a string and returns a span element
297 ###############################################################
298
299     my $type = shift @_;
300     my $code;
301
302     if ($type =~ /Standard/) {
303         $code = '</span>';
304     } else {
305         $type = lc($type);
306         $code = '<span title="' . $type . '" class="' . $type . '">';
307     }
308
309     return $code;
310 }
311
312 sub cli_option_is_set ($) {
313
314     our %cli_options;
315     my $cli_option = shift;
316
317     die "Unknown CLI option: $cli_option" unless defined $cli_options{$cli_option};
318
319     return $cli_options{$cli_option};
320 }
321
322 sub get_html_title () {
323
324     our %cli_options;
325     return $cli_options{'title'};
326
327 }
328
329 sub init_css_colours() {
330
331     our %css_colours = (
332         black       => "000",
333         red         => "F00",
334         green       => "0F0",
335         brown       => "C90",
336         blue        => "0F0",
337         purple      => "F06", # XXX: wrong
338         cyan        => "F09", # XXX: wrong
339         light_gray  => "999",
340         gray        => "333",
341         dark_gray   => "222",
342         light_red   => "F33",
343         light_green => "33F",
344         yellow      => "FF0",
345         light_blue  => "30F",
346         pink        => "F0F",
347         light_cyan  => "66F",
348         white       => "FFF",
349     );
350 }
351
352 sub get_css_colour ($) {
353
354    our %css_colours;
355    my $colour = shift;
356
357    die "What's $colour supposed to mean?\n" unless defined($css_colours{$colour});
358
359    return '#' . $css_colours{$colour};
360 }
361
362 sub get_css_line ($) {
363
364     my $class = shift;
365     my $css_line;
366
367     $css_line .= '.' . lc($class) . ' {'; # XXX: lc() shouldn't be necessary
368     die "What's $class supposed to mean?\n" unless defined($h_colours{$class});
369     $css_line .= 'color:' . get_css_colour($h_colours{$class}) . ';';
370     $css_line .= 'background-color:' . get_css_colour(DEFAULT_BACKGROUND) . ';';
371     $css_line .= '}' . "\n";
372
373     return $css_line;
374 }
375
376 sub get_css_line_for_colour ($) {
377
378     my $colour = shift;
379     my $css_line;
380
381     $css_line .= '.' . lc($colour) . ' {'; # XXX: lc() shouldn't be necessary
382     $css_line .= 'color:' . get_css_colour($colour) . ';';
383     $css_line .= 'background-color:' . get_css_colour(DEFAULT_BACKGROUND) . ';';
384     $css_line .= '}' . "\n";
385
386     return $css_line;
387 }
388
389 # XXX: Wrong solution
390 sub get_missing_css_lines () {
391
392     my $css_line;
393
394     $css_line .= '.' . 'default' . ' {';
395     $css_line .= 'color:' . HEADER_DEFAULT_COLOUR . ';';
396     $css_line .= 'background-color:' . get_css_colour(DEFAULT_BACKGROUND) . ';';
397     $css_line .= '}' . "\n";
398
399     return $css_line;
400 }
401
402 sub get_css () {
403
404     our %css_colours; #XXX: Wrong solution
405
406     my $css = '';
407
408     $css .= '.privoxy-log {';
409     $css .= 'color:' . get_css_colour(DEFAULT_TEXT_COLOUR) . ';';
410     $css .= 'background-color:' . get_css_colour(DEFAULT_BACKGROUND) . ';';
411     $css .= '}' . "\n";
412
413     foreach my $key (keys %h_colours) {
414
415         next if ($h_colours{$key} =~ m/reset/); #XXX: Wrong solution.
416         $css .= get_css_line($key);
417
418     }
419
420     foreach my $colour (keys %css_colours) {
421
422         $css .= get_css_line_for_colour($colour);
423
424     }
425
426     $css .= get_missing_css_lines(); #XXX: Wrong solution
427
428     return $css;
429 }
430
431 sub print_intro () {
432
433     my $intro = '';
434
435     if (cli_option_is_set('html-output')) {
436
437         my $title = get_html_title();
438
439         $intro .= '<html><head>';
440         $intro .= '<title>' . $title . '</title>';
441         $intro .= '<style>' . get_css() . '</style>' unless cli_option_is_set('no-embedded-css');
442         $intro .= '</head><body>';
443         $intro .= '<h1>' . $title . '</h1><p class="privoxy-log">';
444
445         print $intro;
446     }
447 }
448
449 sub print_outro () {
450
451     my $outro = '';
452
453     if (cli_option_is_set('html-output')) {
454
455         $outro = '</p></body></html>';
456         print $outro;
457
458     }
459 }
460
461 sub get_line_end () {
462     return cli_option_is_set('html-output') ? "<br>\n" : "\n";
463 }
464
465 sub get_colour_html_markup ($) {
466 ###############################################################
467 # Takes a colour string a span element. XXX: WHAT?
468 # XXX: This function shouldn't be necessary, the
469 # markup should always be semantically correct.
470 ###############################################################
471
472     my $type = shift @_;
473     my $code;
474
475     if ($type =~ /Standard/) {
476         $code = '</span>';
477     } else {
478         $code = '<span class="' . lc($type) . '">';
479     }
480
481     return $code;
482 }
483
484 sub default_colours () {
485     # XXX: Properly
486     our $bg_code;
487     return reset_colours();
488 }
489
490 sub show_colours () {
491     # XXX: Implement
492 }
493
494 sub reset_colours () {
495     return ESCAPE . "0m";
496 }
497
498 sub set_background ($){
499
500     my $colour = shift;
501     our $bg_code;
502     my %backgrounds = (
503               black       => "40",
504               red         => "41",
505               green       => "42",
506               brown       => "43",
507               blue        => "44",
508               magenta     => "45",
509               cyan        => "46",
510               white       => "47",
511               default     => "49",
512     );
513
514     if (defined($backgrounds{$colour})) {
515         $bg_code = $backgrounds{$colour};
516     } else {
517         die "Invalid background colour: " . $colour;
518     }
519 }
520
521 sub get_background (){
522     return our $bg_code;
523 }
524
525 sub prepare_highlight_hash ($) {
526     my $ref = shift;
527
528     foreach my $key (keys %$ref) {
529         $$ref{$key} = $html_output_mode ?
530             get_semantic_html_markup($key) :
531             paint_it($$ref{$key});
532     }
533 }
534
535 sub prepare_colour_array ($) {
536     my $ref = shift;
537
538     foreach my $i (0 ... @$ref - 1) {
539         $$ref[$i] = $html_output_mode ?
540             get_colour_html_markup($$ref[$i]) :
541             paint_it($$ref[$i]);
542     }
543 }
544
545 sub found_unknown_content ($) {
546
547     my $unknown = shift;
548     my $message;
549
550     return if cli_option_is_set('accept-unknown-messages');
551
552     return if ($unknown =~ /\[too long, truncated\]$/);
553
554     $message = "found_unknown_content: Don't know how to highlight: ";
555     # Break line so the log file can later be parsed as Privoxy log file again
556     $message .= '"' . $unknown . '"' . " in:\n";
557     $message .= $req{$t}{'log-message'};
558     debug_message($message);
559     log_parse_error($req{$t}{'log-message'});
560
561     die "Unworthy content parser" if PUNISH_MISSING_LOG_KNOWLEDGE_WITH_DEATH;
562 }
563
564 sub log_parse_error ($) {
565
566     my $message = shift;
567
568     if (LOG_UNPARSED_LINES_TO_EXTRA_FILE) {
569         open(ERRORLOG, ">>" . ERROR_LOG_FILE) || die "Writing " . ERROR_LOG_FILE . " failed";
570         print ERRORLOG $message;
571         close(ERRORLOG);
572     }
573 }
574
575 sub debug_message (@) {
576     my @message = @_;
577
578     print $h{'debug'} . "@message" . $h{'Standard'} . "\n";
579 }
580
581 ################################################################################
582 # highlighter functions that aren't loglevel-specific
583 ################################################################################
584
585 sub h ($) {
586
587     # Get highlight marker
588     my $highlight = shift; # XXX: Stupid name;
589     my $result = '';
590     my $message;
591
592     if (defined($highlight)) {
593
594         $result = $h{$highlight};
595
596     } else {
597
598         $message = "h: Don't recognize highlighter $highlight.";
599         debug_message($message);
600         log_parser_error($message);
601         die "Unworthy highlighter function" if PUNISH_MISSING_HIGHLIGHT_KNOWLEDGE_WITH_DEATH;
602     }
603
604     return $result;
605 }
606
607 sub highlight_known_headers ($) {
608
609     my $content = shift;
610
611     debug_message("Searching $content for things to highlight.") if DEBUG_HEADER_HIGHLIGHTING;
612
613     if ($content =~ m/(?<=\s)($header_highlight_regex):/) {
614         my $header = $1;
615         $content =~ s@(?<=[\s|'])($header)(?=:)@$header_colours{$header}$1$h{'Standard'}@ig;
616         debug_message("Highlighted '$header' in '$content'") if DEBUG_HEADER_HIGHLIGHTING;
617     }
618
619     return $content;
620 }
621
622 sub highlight_matched_request_line ($$) {
623
624     my $result = shift; # XXX: Stupid name;
625     my $regex = shift;
626     if ($result =~ m@(.*)($regex)(.*)@) {
627         $result = $1 . highlight_request_line($2) . $3
628     }
629     return $result;
630 }
631
632 sub highlight_request_line ($) {
633
634     my $rl = shift;
635     my ($method, $url, $http_version);
636
637     #GET http://images.sourceforge.net/sfx/icon_warning.gif HTTP/1.1
638     if ($rl =~ m/Invalid request/) {
639
640         $rl = h('invalid-request') . $rl . h('Standard');
641
642     } elsif ($rl =~ m/^([-\w]+) (.*) (HTTP\/\d\.\d)/) {
643
644         # XXX: might not match in case of HTTP method fuzzing.
645         # XXX: save these: ($method, $path, $http_version) = ($1, $2, $3);
646         $rl =~ s@^(\w+)@$h{'method'}$1$h{'Standard'}@;
647         if ($rl =~ /http:\/\//) {
648             $rl = highlight_matched_url($rl, '[^\s]*(?=\sHTTP)');
649         } else {
650             $rl = highlight_matched_pattern($rl, 'request_', '[^\s]*(?=\sHTTP)');
651         }
652
653         $rl =~ s@(HTTP\/\d\.\d)$@$h{'http-version'}$1$h{'Standard'}@;
654
655     } elsif ($rl =~ m/\.\.\. \[too long, truncated\]$/) {
656
657         $rl =~ s@^(\w+)@$h{'method'}$1$h{'Standard'}@;
658         $rl = highlight_matched_url($rl, '[^\s]*(?=\.\.\.)');
659
660     } elsif ($rl =~ m/^ $/) {
661
662         $rl = h('error') . "No request line specified!" . h('Standard');
663
664     } else {
665
666         debug_message ("Can't parse request line: $rl");
667
668     }
669
670     return $rl;
671 }
672
673 sub highlight_response_line ($) {
674
675     my $rl = shift;
676     my ($http_version, $status_code, $status_message);
677
678     #HTTP/1.1 200 OK
679     #ICY 200 OK
680
681     # TODO: Mark different status codes differently
682
683     if ($rl =~ m/((?:HTTP\/\d\.\d|ICY)) (\d+) (.*)/) {
684         ($http_version, $status_code, $status_message) = ($1, $2, $3);
685     } else {
686         debug_message ("Can't parse response line: $rl") and die 'Fix this';
687     }
688
689     # Rebuild highlighted
690     $rl= "";
691     $rl .= h('http-version') . $http_version . h('Standard');
692     $rl .= " ";
693     $rl .= h('status-code') . $status_code . h('Standard');
694     $rl .= " ";
695     $rl .= h('status-message') . $status_message . h('Standard');
696
697     return $rl;
698 }
699
700 sub highlight_matched_url ($$) {
701
702     my $result = shift; # XXX: Stupid name;
703     my $regex = shift;
704
705     #print "Got $result, regex ($regex)\n";
706
707     if ($result =~ m@(.*?)($regex)(.*)@) {
708         $result = $1 . highlight_url($2) . $3;
709         #print "Now the result is $result\n";
710     }
711
712     return $result;
713 }
714
715 sub highlight_matched_host ($$) {
716
717     my ($result, $regex) = @_; # XXX: result ist stupid name;
718
719     if ($result =~ m@(.*?)($regex)(.*)@) {
720         $result = $1 . $h{host} . $2 . $h{Standard} . $3;
721     }
722
723     return $result;
724 }
725
726 sub highlight_matched_pattern ($$$) {
727
728     my $result = shift; # XXX: Stupid name;
729     my $key = shift;
730     my $regex = shift;
731
732     die "Unknown key $key" unless defined $h{$key};
733
734     if ($result =~ m@(.*?)($regex)(.*)@) {
735         $result = $1 . h($key) . $2 . h('Standard') . $3;
736     }
737
738     return $result;
739 }
740
741 sub highlight_matched_path ($$) {
742
743     my $result = shift; # XXX: Stupid name;
744     my $regex = shift;
745
746     if ($result =~ m@(.*?)($regex)(.*)@) {
747         $result = $1 . h('path') . $2 . h('Standard') . $3;
748     }
749
750     return $result;
751 }
752
753 sub highlight_url ($) {
754
755     my $url = shift;
756
757     if ($html_output_mode) {
758
759         $url = '<a href="' . $url . '">' . $url . '</a>';
760
761     } else {
762
763         $url = h('URL') . $url . h('Standard');
764
765     }
766
767     return $url;
768 }
769
770 sub update_header_highlight_regex ($) {
771
772     my $header = shift;
773     my $headers = join ('|', keys %header_colours);
774
775     $header_highlight_regex = qr/$headers/;
776     print "Registering '$header'\n" if DEBUG_HEADER_HIGHLIGHTING;
777 }
778
779 ################################################################################
780 # loglevel-specific highlighter functions
781 ################################################################################
782
783 sub handle_loglevel_header ($) {
784
785     my $c = shift;
786
787     if ($c =~ /^scan:/) {
788
789         if ($c =~ m/^scan: ([^: ]+):/) {
790
791             # Register new headers
792             # scan: Accept: image/png,image/*;q=0.8,*/*;q=0.5
793             my $header = $1;
794             if (!defined($header_colours{$header}) and $header =~ /^[\d\w-]*$/) {
795                 debug_message "Registering previously unknown header $1" if DEBUG_HEADER_REGISTERING;
796
797                 if (REGISTER_HEADERS_WITH_THE_SAME_COLOUR) {
798                     $header_colours{$header} =  $header_colours{'Default'};
799                 } else {
800                     $header_colours{$header} = $all_colours[$header_colour_index % @all_colours];
801                     $header_colour_index++;
802                 }
803                 update_header_highlight_regex($header);
804             }
805
806         } elsif ($c =~ m/^scan: ((\w+) (.+) (HTTP\/\d\.\d))/) {
807
808             # Client request line
809             # Save for statistics (XXX: Not implemented yet)
810             $req{$t}{'method'} = $2;
811             $req{$t}{'destination'} = $3;
812             $req{$t}{'http-version'} = $4;
813
814             $c = highlight_request_line($1);
815
816         } elsif ($c =~ m/^(scan: )((?:HTTP\/\d\.\d|ICY) (\d+) (.*))/) {
817
818             # Server response line
819             $req{$t}{'response_line'} = $2;
820             $req{$t}{'status_code'} = $3;
821             $req{$t}{'status_message'} = $4;
822             $c = $1 . highlight_response_line($req{$t}{'response_line'});
823         }
824
825     } elsif ($c =~ m/^Crunching (?:server|client) header: .* \(contains: ([^\)]*)\)/) {
826
827         # Crunching server header: Set-Cookie: trac_form_token=d5308c34e16d15e9e301a456; (contains: Cookie:)
828         $c =~ s@(?<=contains: )($1)@$h{'crunch-pattern'}$1$h{'Standard'}@;
829         $c =~ s@(Crunching)@$h{$1}$1$h{'Standard'}@;
830
831     } elsif ($c =~ m/^New host is: ([^\s]*)\./) {
832
833         # New host is: trac.vidalia-project.net. Crunching Referer: http://www.vidalia-project.net/!
834         $c = highlight_matched_host($c, '(?<=New host is: )[^\s]+(?=\.)');
835         $c = highlight_matched_url($c, '(?<=Crunching Referer: )[^\s!]+');
836
837     } elsif ($c =~ m/^Text mode enabled by force. (Take cover)!/) {
838
839         # Text mode enabled by force. Take cover!
840         $c =~ s@($1)@$h{'warning'}$1$h{'Standard'}@;
841
842     } elsif ($c =~ m/^(New HTTP Request-Line: )(.*)/) {
843
844         # New HTTP Request-Line: GET http://www.privoxy.org/ HTTP/1.1
845         $c = $1 . highlight_request_line($2);
846
847     } elsif ($c =~ m/^Adjust(ed)? Content-Length to \d+/) {
848
849         # Adjusted Content-Length to 2132
850         # Adjust Content-Length to 33533
851         $c =~ s@(?<=Content-Length to )(\d+)@$h{'Number'}$1$h{'Standard'}@;
852         $c = highlight_known_headers($c);
853
854     } elsif ($c =~ m/^Destination extracted from "Host:" header. New request URL:/) {
855
856         # Destination extracted from "Host:" header. New request URL: http://www.cccmz.de/~ridcully/blog/
857         $c = highlight_matched_url($c, '(?<=New request URL: ).*');
858
859     } elsif ($c =~ m/^Couldn\'t parse:/) {
860
861         # XXX: These should probable be logged with LOG_LEVEL_ERROR
862         # Couldn't parse: If-Modified-Since: Wed, 21 Mar 2007 16:34:50 GMT (crunching!)
863         # Couldn't parse: at, 24 Mar 2007 13:46:21 GMT in If-Modified-Since: Sat, 24 Mar 2007 13:46:21 GMT (crunching!)
864         $c =~ s@^(Couldn\'t parse)@$h{'error'}$1$h{'Standard'}@;
865
866     } elsif ($c =~ /^Tagger \'([^\']*)\' added tag \'([^\']*)\'/ or
867              $c =~ m/^Adding tag \'([^\']*)\' created by header tagger \'([^\']*)\'/) {
868
869         # Adding tag 'GET request' created by header tagger 'method-man' (XXX: no longer used)
870         # Tagger 'revalidation' added tag 'REVALIDATION-REQUEST'. No action bit update necessary.
871         # Tagger 'revalidation' added tag 'REVALIDATION-REQUEST'. Action bits updated accordingly.
872
873         # XXX: Save tag and tagger
874
875         $c =~ s@(?<=^Tagger \')([^\']*)@$h{'tagger'}$1$h{'Standard'}@;
876         $c =~ s@(?<=added tag \')([^\']*)@$h{'tag'}$1$h{'Standard'}@;
877         $c =~ s@(?<=Action bits )(updated)@$h{'action-bits-update'}$1$h{'Standard'}@;
878         $no_special_header_highlighting = 1;
879
880     } elsif ($c =~ /^Tagger \'([^\']*)\' didn['']t add tag \'([^\']*)\'/) {
881
882         # Tagger 'revalidation' didn't add tag 'REVALIDATION-REQUEST'. Tag already present
883         # XXX: Save tag and tagger
884
885         $c =~ s@(?<=^Tagger \')([^\']*)@$h{'tag'}$1$h{'Standard'}@;
886         $c =~ s@(?<=didn['']t add tag \')([^\']*)@$h{'tagger'}$1$h{'Standard'}@;
887
888     } elsif ($c =~ m/^(?:scan:|Randomiz|addh:|Adding:|Removing:|Referer:|Modified:|Accept-Language header|[Cc]ookie)/
889           or $c =~ m/^(Text mode is already enabled|Denied request with NULL byte|Replaced:|add-unique:)/
890           or $c =~ m/^(Crunched (incoming|outgoing) cookie|Suppressed offer|Accepted the client)/
891           or $c =~ m/^(addh-unique|Referer forged to)/
892           or $c =~ m/^Downgraded answer to HTTP\/1.0/
893           or $c =~ m/^Parameter: \+hide-referrer\{[^\}]*\} is a bad idea, but I don\'t care./
894           or $c =~ m/^Referer (?:overwritten|replaced) with: Referer: / #XXX: should this be highlighted?
895           or $c =~ m/^Referer crunched!/
896           or $c =~ m/^crunched x-forwarded-for!/
897           or $c =~ m/^crunched From!/
898           or $c =~ m/^ modified$/
899           or $c =~ m/^Content filtering is enabled. Crunching:/
900           or $c =~ m/^force-text-mode overruled the client/
901           or $c =~ m/^Server time in the future\./
902           or $c =~ m/^content-disposition header crunched and replaced with:/i
903           or $c =~ m/^Reducing white space in /
904           or $c =~ m/^Ignoring single quote in /
905           or $c =~ m/^Converting tab to space in /
906           or $c =~ m/A HTTP\/1\.1 response without/
907           or $c =~ m/Disabled filter mode on behalf of the client/
908           or $c =~ m/Keeping the (?:server|client) header /
909           or $c =~ m/Content modified with no Content-Length header set/
910           or $c =~ m/^Appended client IP address to/
911           or $c =~ m/^Removing 'Connection: close' to imply keep-alive./
912           or $c =~ m/^keep-alive support is disabled/
913           or $c =~ m/^Continue hack in da house/
914             )
915     {
916         # XXX: Some of these may need highlighting
917
918         # Modified: User-Agent: Mozilla/5.0 (X11; U; SunOS i86pc; pl-PL; rv:1.8.1.1) Gecko/20070214 Firefox/2.0.0.1
919         # Accept-Language header crunched and replaced with: Accept-Language: pl-pl
920         # cookie 'Set-Cookie: eZSessionCookie=07bfec287c197440d299f81580593c3d; \
921         #  expires=Thursday, 12-Apr-07 15:16:18 GMT; path=/' send by \
922         #  http://wirres.net/article/articleview/4265/1/6/ appears to be using time format 1 (XXX: gone with the wind)
923         # Cookie rewritten to a temporary one: Set-Cookie: NSC_gffe-iuuq-mc-wtfswfs=8efb33a53660;path=/
924         # Text mode is already enabled
925         # Denied request with NULL byte(s) turned into line break(s)
926         # Replaced: 'Connection: Yo, home to Bel Air' with 'Connection: close'
927         # addh-unique: Host: people.freebsd.org
928         # Suppressed offer to compress content
929         # Crunched incoming cookie -- yum!
930         # Accepted the client's request to fetch without filtering.
931         # Crunched outgoing cookie: Cookie: PREF=ID=6cf0abd347b30262:TM=1173357617:LM=1173357617:S=jZypyyJ7LPiwFi1_
932         # addh-unique: Host: subkeys.pgp.net:11371
933         # Referer forged to: Referer: http://10.0.0.1/
934         # Downgraded answer to HTTP/1.0
935         # Parameter: +hide-referrer{pille-palle} is a bad idea, but I don't care.
936         # Referer overwritten with: Referer: pille-palle
937         # Referer replaced with: Referer: pille-palle
938         # crunched x-forwarded-for!
939         # crunched From!
940         #  modified # XXX: pretty stupid log message
941         # Content filtering is enabled. Crunching: 'Range: 1234-5678' to prevent range-mismatch problems
942         # force-text-mode overruled the client's request to fetch without filtering!
943         # Server time in the future.
944         # content-disposition header crunched and replaced with: content-disposition: filename=baz
945         # Content-Disposition header crunched and replaced with: content-disposition: filename=baz
946         # Reducing white space in 'X-LWS-Test: "This  is  quoted" this is not "this  is  " but " this again   is  not'
947         # Ignoring single quote in 'X-LWS-Test: "This  is  quoted" this is not "this  is  " but "  this again   is  not'
948         # Converting tab to space in 'X-LWS-Test:   "This  is  quoted" this   is  not "this  is  "  but  "\
949         #  this again   is  not'
950         # A HTTP/1.1 response without Connection header implies keep-alive.
951         # Disabled filter mode on behalf of the client.
952         # Keeping the server header 'Connection: keep-alive' around.
953         # Keeping the client header 'Connection: close' around. The connection will not be kept alive.
954         # Keeping the client header 'Connection: keep-alive' around. The connection will be kept alive if possible.
955         # Content modified with no Content-Length header set. Creating a fake one for adjustment later on.
956         # Appended client IP address to X-Forwarded-For: 10.0.0.2, 10.0.0.1
957         # Removing 'Connection: close' to imply keep-alive.
958         # keep-alive support is disabled. Crunching: Keep-Alive: 300.
959         # Continue hack in da house.
960
961     } elsif ($c =~ m/^scanning headers for:/) {
962
963         return '' unless SHOW_SCAN_INTRO;
964
965     } elsif ($c =~ m/^[Cc]runch(ing|ed)|crumble crunched:/) {
966         # crunched User-Agent!
967         # Crunching: Content-Encoding: gzip
968
969         $c =~ s@(Crunching|crunched)@$h{$1}$1$h{'Standard'}@;
970
971     } elsif ($c =~ m/^Offending request data with NULL bytes turned into \'°\' characters:/) {
972
973         # Offending request data with NULL bytes turned into '°' characters: Â°Â°n°°(°°°
974
975         $c = h('warning') . $c . h('Standard');
976
977     } elsif ($c =~ m/^(Transforming \")(.*?)(\" to \")(.*?)(\")/) {
978
979         # Transforming "Proxy-Authenticate: Basic realm="Correos Proxy Server"" to\
980         #  "Proxy-Authenticate: Basic realm="Correos Proxy Server""
981
982        $c =~ s@(?<=^Transforming \")(.*)(?=\" to)@$h{'Header'}$1$h{'Standard'}@;
983        $c =~ s@(?<=to \")(.*)(?=\")@$h{'Header'}$1$h{'Standard'}@;
984
985     } elsif ($c =~ m/^Removing empty header/) {
986
987         # Removing empty header
988         # Ignore for now
989
990     } elsif ($c =~ m/^Content-Type: .* not replaced/) {
991
992         # Content-Type: application/octet-stream not replaced. It doesn't look like text.\
993         #  Enable force-text-mode if you know what you're doing.
994         # XXX: Could highlight more here.
995         $c =~ s@(?<=^Content-Type: )(.*)(?= not replaced)@$h{'content-type'}$1$h{'Standard'}@;
996
997     } elsif ($c =~ m/^(Server|Client) keep-alive timeout is/) {
998
999        # Server keep-alive timeout is 5. Sticking with 10.
1000        # Client keep-alive timeout is 20. Sticking with 10.
1001
1002        $c =~ s@(?<=timeout is )(\d+)@$h{'Number'}$1$h{'Standard'}@;
1003        $c =~ s@(?<=Sticking with )(\d+)@$h{'Number'}$1$h{'Standard'}@;
1004
1005     } elsif ($c =~ m/^Reducing keep-alive timeout/) {
1006
1007        # Reducing keep-alive timeout from 60 to 10.
1008
1009        $c =~ s@(?<= from )(\d+)@$h{'Number'}$1$h{'Standard'}@;
1010        $c =~ s@(?<= to )(\d+)@$h{'Number'}$1$h{'Standard'}@;
1011
1012     } else {
1013
1014         found_unknown_content($c);
1015     }
1016
1017     # Highlight headers
1018     unless ($c =~ m/^Transforming/) {
1019         $c = highlight_known_headers($c) unless $no_special_header_highlighting;
1020     }
1021
1022     return $c;
1023 }
1024
1025 sub handle_loglevel_re_filter ($) {
1026
1027     my $content = shift;
1028     my $c = $content;
1029     my $key;
1030
1031     if ($c =~ m/^(?:re_)?filtering ([^\s]+) \(size (\d+)\) with (?:filter )?\'?([^\s]+?)\'? produced (\d+) hits \(new size (\d+)\)/) {
1032
1033         # XXX: only the second version gets highlighted properly.
1034         # re_filtering www.lfk.de/favicon.ico (size 209) with filter untrackable-hulk produced 0 hits (new size 209).
1035         # filtering aci.blogg.de/ (size 37988) with 'blogg.de' produced 3 hits (new size 38057)
1036         $req{$t}{'content_source'} = $1;
1037         $req{$t}{'content_size'}   = $2;
1038         $req{$t}{'content_filter'} = $3;
1039         $req{$t}{'content_hits'}   = $4;
1040         $req{$t}{'new_content_size'} = $5;
1041         $req{$t}{'content_size_change'} = $req{$t}{'new_content_size'} - $req{$t}{'content_size'};
1042         #return '' if ($req{$t}{'content_hits'} == 0 && !cli_option_is_set('show-ineffective-filters'));
1043         if ($req{$t}{'content_hits'} == 0 and
1044             not (cli_option_is_set('show-ineffective-filters')
1045                  or ($req{$t}{'content_filter'} =~ m/^privoxy-filter-test$/))) {
1046                 return '';
1047         }
1048
1049         $c =~ s@(?<=\(size )(\d+)\)(?= with)@$h{'Number'}$1$h{'Standard'}@;
1050         $c =~ s@(?<=\(new size )(\d+)@$h{'Number'}$1$h{'Standard'}@;
1051         $c =~ s@(?<=produced )(\d+)(?= hits)@$h{'Number'}$1$h{'Standard'}@;
1052
1053         $c =~ s@([^\s]+?)(\'? produced)@$h{'filter'}$1$h{'Standard'}$2@;
1054         $c = highlight_matched_host($c, '(?<=filtering )[^\s]+');
1055
1056         $c =~ s@\.$@ @;
1057         $c .= "(" . $h{'Number'};
1058         $c .= "+" if ($req{$t}{'content_size_change'} >= 0);
1059         $c .= $req{$t}{'content_size_change'} . $h{'Standard'} . ")";
1060         $content = $c;
1061
1062   } elsif ($c =~ /\.{3}$/
1063         and $c =~ m/^(?:re_)?filtering \'?(.*?)\'? \(size (\d*)\) with (?:filter )?\'?([^\s]*?)\'? ?\.{3}$/) {
1064
1065         # Used by Privoxy 3.0.5 and 3.0.6:
1066         # XXX: Fill in ...
1067         # Used by Privoxy 3.0.7:
1068         # filtering 'Connection: close' (size 17) with 'generic-content-ads' ...
1069
1070         $req{$t}{'filtered_header'} = $1;
1071         $req{$t}{'old_header_size'} = $2;
1072         $req{$t}{'header_filter_name'} = $3;
1073
1074         unless (cli_option_is_set('show-ineffective-filters') or
1075                 $req{$t}{'header_filter_name'} =~ m/^privoxy-filter-test$/) {
1076             return '';
1077         }
1078         $content =~ s@(?<=\(size )(\d+)@$h{'Number'}$1$h{'Standard'}@;
1079         $content =~ s@($req{$t}{'header_filter_name'})@$h{'filter'}$1$h{'Standard'}@;
1080
1081     } elsif ($c =~ m/^ ?\.\.\. ?produced (\d*) hits \(new size (\d*)\)\./) {
1082
1083         # ...produced 0 hits (new size 23).
1084         #... produced 1 hits (new size 54).
1085
1086         $req{$t}{'header_filter_hits'} = $1;
1087         $req{$t}{'new_header_size'} = $2;
1088
1089         unless (cli_option_is_set('show-ineffective-filters') or
1090                 (defined($req{$t}{'header_filter_name'}) and
1091                  $req{$t}{'header_filter_name'} =~ m/^privoxy-filter-test$/)) {
1092
1093             if ($req{$t}{'header_filter_hits'} == 0 and
1094                 not (defined($req{$t}{'header_filter_name'}) and
1095                  $req{$t}{'header_filter_name'} =~ m/^privoxy-filter-test$/)) {
1096                 return '';
1097             }
1098             # Reformat including information from the intro
1099             $c = "'" . h('filter') . $req{$t}{'header_filter_name'} . h('Standard') . "'";
1100             $c .= " hit ";
1101             # XXX: Hide behind constant, it may be interesting if LOG_LEVEL_HEADER isn't enabled as well.
1102             # $c .= $req{$t}{'filtered_header'} . " ";
1103             $c .= h('Number') . $req{$t}{'header_filter_hits'}. h('Standard');
1104             $c .= ($req{$t}{'header_filter_hits'} == 1) ? " time, " : " times, ";
1105
1106             if ($req{$t}{'old_header_size'} !=  $req{$t}{'new_header_size'}) {
1107
1108                 $c .= "changing size from ";
1109                 $c .=  h('Number') . $req{$t}{'old_header_size'} . h('Standard');
1110                 $c .= " to ";
1111                 $c .= h('Number') . $req{$t}{'new_header_size'} . h('Standard');
1112                 $c .= ".";
1113
1114             } else {
1115
1116                 $c .= "keeping the size at " . $req{$t}{'old_header_size'};
1117
1118             }
1119
1120             # Highlight from last line (XXX: What?)
1121             # $c =~ s@(?<=produced )(\d+)@$h{'Number'}$1$h{'Standard'}@;
1122             # $c =~ s@($req{$t}{'header_filter_name'})@$h{'filter'}$1$h{'Standard'}@;
1123
1124         } else {
1125
1126            # XXX: Untested
1127            $c =~ s@(?<=produced )(\d+)@$h{'Number'}$1$h{'Standard'}@;
1128            $c =~ s@(?<=new size )(\d+)@$h{'Number'}$1$h{'Standard'}@;
1129
1130         }
1131         $content = $c;
1132
1133     } elsif ($c =~ m/^(Tagger|Filter) ([^\s]*) has empty joblist. Nothing to do./) {
1134
1135         # Filter privoxy-filter-test has empty joblist. Nothing to do.
1136         # Tagger variable-test has empty joblist. Nothing to do.
1137
1138         $content =~ s@(?<=$1 )([^\s]*)@$h{'filter'}$1$h{'Standard'}@;
1139
1140     } elsif ($c =~ m/^De-chunking successful. Shrunk from (\d+) to (\d+)/) {
1141
1142         $req{$t}{'chunked-size'} = $1;
1143         $req{$t}{'dechunked-size'} = $2;
1144         $req{$t}{'dechunk-change'} = $req{$t}{'dechunked-size'} - $req{$t}{'chunked-size'};
1145
1146         $content .= " (" . h('Number') . $req{$t}{'dechunk-change'} . h('Standard') . ")";
1147
1148         $content =~ s@(?<=from )($req{$t}{'chunked-size'})@$h{'Number'}$1$h{'Standard'}@;
1149         $content =~ s@(?<=to )($req{$t}{'dechunked-size'})@$h{'Number'}$1$h{'Standard'}@;
1150
1151     } elsif ($c =~ m/^Decompression successful. Old size: (\d+), new size: (\d+)./) {
1152
1153         # Decompression successful. Old size: 670, new size: 1166.
1154
1155         $req{$t}{'size-compressed'} = $1;
1156         $req{$t}{'size-decompressed'} = $2;
1157         $req{$t}{'decompression-gain'} = $req{$t}{'size-decompressed'} - $req{$t}{'size-compressed'};
1158
1159         $content =~ s@(?<=Old size: )($req{$t}{'size-compressed'})@$h{'Number'}$1$h{'Standard'}@;
1160         $content =~ s@(?<=new size: )($req{$t}{'size-decompressed'})@$h{'Number'}$1$h{'Standard'}@;
1161
1162         # XXX: Create sub get_percentage()
1163         if ($req{$t}{'size-decompressed'}) {
1164             $req{$t}{'decompression-gain-percent'} =
1165                 $req{$t}{'decompression-gain'} / $req{$t}{'size-decompressed'} * 100;
1166
1167             $content .= " (saved: ";
1168             #$content .= h('Number') . $req{$t}{'decompression-gain'} . h('Standard');
1169             #$content .= "/";
1170             $content .= h('Number') . sprintf("%.2f%%", $req{$t}{'decompression-gain-percent'}) . h('Standard');
1171             $content .= ")";
1172         }
1173
1174     } elsif ($c =~ m/^(Need to de-chunk first)/) {
1175
1176         # Need to de-chunk first
1177         return '' if SUPPRESS_NEED_TO_DE_CHUNK_FIRST;
1178
1179     } elsif ($c =~ m/^(Adding (?:dynamic )?re_filter job)/) {
1180
1181         return ''  if (SUPPRESS_SUCCEEDED_FILTER_ADDITIONS && m/succeeded/);
1182
1183         # Adding re_filter job ...
1184         # Adding dynamic re_filter job s@^(?:\w*)\s+.*\s+HTTP/\d\.\d\s*@IP-ADDRESS: $origin@D\
1185         #  to filter client-ip-address succeeded.
1186
1187     } elsif ($c =~ m/^Reading in filter/) {
1188
1189         return '' unless SHOW_FILTER_READIN_IN;
1190
1191     } else {
1192
1193         found_unknown_content($content);
1194
1195     }
1196
1197     return $content;
1198 }
1199
1200 sub handle_loglevel_redirect ($) {
1201
1202     my $c = shift;
1203
1204     if ($c =~ m/^Decoding "([^""]*)"/) {
1205
1206          $req{$t}{'original-destination'} = $1;
1207          $c = highlight_matched_path($c, '(?<=Decoding ")[^"]*');
1208          $c =~ s@\"@@g;
1209
1210     } elsif ($c =~ m/^Checking/) {
1211
1212          # Checking /_ylt=A0geu.Z76BRGR9k/**http://search.yahoo.com/search?p=view+odb+presentation+on+freebsd\
1213          #  &ei=UTF-8&xargs=0&pstart=1&fr=moz2&b=11 for redirects.
1214
1215          # TODO: Change colour if really url-decoded
1216          $req{$t}{'decoded-original-destination'} = $1;
1217          $c = highlight_matched_path($c, '(?<=Checking ")[^"]*');
1218          $c =~ s@\"@@g;
1219
1220     } elsif ($c =~ m/^pcrs command "([^""]*)" changed /) {
1221
1222         # pcrs command "s@&from=rss@@" changed \
1223         #  "http://it.slashdot.org/article.pl?sid=07/03/02/1657247&from=rss"\
1224         #  to "http://it.slashdot.org/article.pl?sid=07/03/02/1657247" (1 hit).
1225         $c =~ s@(?<=pcrs command )"([^""]*)"@$h{'filter'}$1$h{'Standard'}@;
1226         $c = highlight_matched_url($c, '(?<=changed ")[^""]*');
1227         $c =~ s@(?<=changed )"([^""]*)"@$1@; # Remove quotes
1228         $c = highlight_matched_url($c, '(?<=to ")[^""]*');
1229         $c =~ s@(?<=to )"([^""]*)"@$1@; # Remove quotes
1230         $c =~ s@(\d+)(?= hits?)@$h{'hits'}$1$h{'Standard'}@;
1231
1232     } elsif ($c =~ m/^pcrs command "([^""]*)" didn\'t change/) {
1233
1234         # pcrs command "s@^http://([^.]+?)/?$@http://www.bing.com/search?q=$1@" didn't \
1235         #  change "http://www.example.org/".
1236         $c =~ s@(?<=pcrs command )"([^""]*)"@$h{'filter'}$1$h{'Standard'}@;
1237         $c = highlight_matched_url($c, '(?<=change ")[^""]*');
1238
1239     } elsif ($c =~ m/(^New URL is: )(.*)/) {
1240
1241         # New URL is: http://it.slashdot.org/article.pl?sid=07/03/04/1511210
1242         # XXX: Use URL highlighter
1243         # XXX: Save?
1244         $c = $1 . h('rewritten-URL') . $2 . h('Standard');
1245
1246     } elsif ($c =~ m/No pcrs command recognized, assuming that/) {
1247         # No pcrs command recognized, assuming that "http://config.privoxy.org/user-manual/favicon.png"\
1248         #  is already properly formatted.
1249         # XXX: assume the same?
1250         $c = highlight_matched_url($c, '(?<=assuming that \")[^"]*');
1251
1252     } else {
1253
1254         found_unknown_content($c);
1255
1256     }
1257
1258     return $c;
1259 }
1260
1261 sub handle_loglevel_gif_deanimate ($) {
1262
1263     my $content = shift;
1264
1265     if ($content =~ m/Success! GIF shrunk from (\d+) bytes to (\d+)\./) {
1266
1267         my $bytes_from = $1;
1268         my $bytes_to = $2;
1269         # Gif-Deanimate: Success! GIF shrunk from 205 bytes to 133.
1270         $content =~ s@$bytes_from@$h{'Number'}$bytes_from$h{'Standard'}@;
1271         # XXX: Do we need g in case of ($1 == $2)?
1272         $content =~ s@$bytes_to@$h{'Number'}$bytes_to$h{'Standard'}@;
1273
1274     } elsif ($content =~ m/GIF (not) changed/) {
1275
1276         # Gif-Deanimate: GIF not changed.
1277         return '' if SUPPRESS_GIF_NOT_CHANGED;
1278         $content =~ s@($1)@$h{'not'}$1$h{'Standard'}@;
1279
1280     } elsif ($content =~ m/^failed! \(gif parsing\)/) {
1281
1282         # failed! (gif parsing)
1283         # XXX: Replace this error message with something less stupid
1284         $content =~ s@(failed!)@$h{'error'}$1$h{'Standard'}@;
1285
1286     } elsif ($content =~ m/^Need to de-chunk first/) {
1287
1288         # Need to de-chunk first
1289         return '' if SUPPRESS_NEED_TO_DE_CHUNK_FIRST;
1290
1291     } elsif ($content =~ m/^(?:No GIF header found|failed while parsing)/) {
1292
1293         # No GIF header found (XXX: Did I ever commit this?)
1294         # failed while parsing 195 134747048 (XXX: never commited)
1295
1296         # Ignore these for now
1297
1298     } else {
1299
1300         found_unknown_content($content);
1301
1302     }
1303
1304     return $content;
1305 }
1306
1307 sub handle_loglevel_request ($) {
1308
1309     my $content = shift;
1310
1311     if ($content =~ m/crunch! /) {
1312
1313         # config.privoxy.org/send-stylesheet crunch! (CGI Call)
1314
1315         # Highlight crunch reasons
1316         foreach my $reason (keys %reason_colours) {
1317             $content =~ s@\(($reason)\)@$reason_colours{$reason}($1)$h{'Standard'}@g;
1318         }
1319         # Highlight request URL domain and ditch 'crunch!'
1320         $content = highlight_matched_pattern($content, 'request_', '[^ ]*(?= crunch!)');
1321         $content =~ s@ crunch!@@;
1322
1323     } elsif ($content =~ m/\[too long, truncated\]$/) {
1324
1325         # config.privoxy.org/edit-actions-submit?f=3&v=1176116716&s=7&Submit=Submit[...]&filter... [too long, truncated]
1326         $content = highlight_matched_pattern($content, 'request_', '^.*(?=\.\.\. \[too long, truncated\]$)');
1327
1328     } elsif ($content =~ m/(.*)/) { # XXX: Pretty stupid
1329
1330         # trac.vidalia-project.net/wiki/Volunteer?format=txt
1331         $content = h('request_') . $content . h('Standard');
1332
1333     } else {  # XXX: Nop
1334
1335         found_unknown_content($content);
1336
1337     }
1338
1339     return $content;
1340 }
1341
1342 sub handle_loglevel_crunch ($) {
1343
1344     my $content = shift;
1345
1346     # Highlight crunch reason
1347     foreach my $reason (keys %reason_colours) {
1348         $content =~ s@($reason)@$reason_colours{$reason}$1$h{'Standard'}@g;
1349     }
1350
1351     if ($content =~ m/\[too long, truncated\]$/) {
1352
1353         # Blocked: config.privoxy.org/edit-actions-submit?f=3&v=1176116716&s=7&Submit=Submit\
1354         #  [...]&filter... [too long, truncated]
1355         $content = highlight_matched_pattern($content, 'request_', '^.*(?=\.\.\. \[too long, truncated\]$)');
1356
1357     } else {
1358
1359         # Blocked: http://ads.example.org/
1360         $content = highlight_matched_pattern($content, 'request_', '(?<=: ).*');
1361     }
1362
1363     return $content;
1364 }
1365
1366 sub handle_loglevel_connect ($) {
1367
1368     my $c = shift;
1369
1370     if ($c =~ m/^via [^\s]+ to: [^\s]+/) {
1371
1372         # Connect: via 10.0.0.1:8123 to: www.example.org.noconnect
1373
1374         $c = highlight_matched_host($c, '(?<=via )[^\s]+');
1375         $c = highlight_matched_host($c, '(?<=to: )[^\s]+');
1376
1377     } elsif ($c =~ m/^connect to: .* failed: .*/) {
1378
1379         # connect to: www.example.org.noconnect failed: Operation not permitted
1380
1381         $c = highlight_matched_host($c, '(?<=connect to: )[^\s]+');
1382
1383         $c =~ s@(?<=failed: )(.*)@$h{'error'}$1$h{'Standard'}@;
1384
1385     } elsif ($c =~ m/^to ([^\s]*)( successful)?$/) {
1386
1387         # Connect: to www.nzherald.co.nz successful
1388         # Connect: to archiv.radiotux.de
1389
1390         return '' if SUPPRESS_SUCCESSFUL_CONNECTIONS;
1391         $c = highlight_matched_host($c, '(?<=to )[^\s]+');
1392
1393     } elsif ($c =~ m/^to ([^\s]*)$/) {
1394
1395         # Connect: to lists.sourceforge.net:443
1396
1397         $c = highlight_matched_host($c, '(?<=to )[^\s]+');
1398
1399     } elsif ($c =~ m/^accepted connection from .*/ or
1400              $c =~ m/^OK/) {
1401
1402         # accepted connection from 10.0.0.1
1403         # Privoxy 3.0.6 and earlier just say:
1404         # OK
1405         return '' if SUPPRESS_ACCEPTED_CONNECTIONS;
1406         $c = highlight_matched_host($c, '(?<=connection from ).*');
1407
1408     } elsif ($c =~ m/^write header to: .* failed:/) {
1409
1410         # write header to: 10.0.0.1 failed: Broken pipe
1411
1412         $c = highlight_matched_host($c, '(?<=write header to: )[^\s]*');
1413         $c =~ s@(?<=failed: )(.*)@$h{'Error'}$1$h{'Standard'}@;
1414
1415     } elsif ($c =~ m/^write header to client failed:/) {
1416
1417         # write header to client failed: Broken pipe
1418         # XXX: Stil in use?
1419         $c =~ s@(?<=failed: )(.*)@$h{'Error'}$1$h{'Standard'}@;
1420
1421     } elsif ($c =~ m/^socks4_connect:/) {
1422
1423         # socks4_connect: SOCKS request rejected or failed.
1424         $c =~ s@(?<=socks4_connect: )(.*)@$h{'Error'}$1$h{'Standard'}@;
1425
1426     } elsif ($c =~ m/^Listening for new connections/ or
1427              $c =~ m/^accept connection/) {
1428         # XXX: Highlight?
1429         # Privoxy versions above 3.0.6 say:
1430         # Listening for new connections ...
1431         # earlier versions say:
1432         # accept connection ...
1433         return '';
1434
1435     } elsif ($c =~ m/^accept failed:/) {
1436
1437         $c =~ s@(?<=accept failed: )(.*)@$h{'Error'}$1$h{'Standard'}@;
1438
1439     } elsif ($c =~ m/^Overriding forwarding settings/) {
1440
1441         # Overriding forwarding settings based on 'forward 10.0.0.1:8123'
1442         $c =~ s@(?<=based on \')(.*)(?=\')@$h{'configuration-line'}$1$h{'Standard'}@;
1443
1444     } elsif ($c =~ m/^Denying suspicious CONNECT request from/) {
1445
1446         # Denying suspicious CONNECT request from 10.0.0.1
1447         $c = highlight_matched_host($c, '(?<=from )[^\s]+'); # XXX: not an URL
1448
1449     } elsif ($c =~ m/^socks5_connect:/) {
1450
1451         $c =~ s@(?<=socks5_connect: )(.*)@$h{'error'}$1$h{'Standard'}@;
1452
1453     } elsif ($c =~ m/^Created new connection to/) {
1454
1455         # Created new connection to www.privoxy.org:80 on socket 11.
1456         $c = highlight_matched_host($c, '(?<=connection to )[^\s]+');
1457         $c =~ s@(?<=on socket )(\d+)@$h{'Number'}$1$h{'Standard'}@;
1458
1459     } elsif ($c =~ m/^Found reusable socket/) {
1460
1461         # Found reusable socket 9 for www.privoxy.org:80 in slot 0.
1462         # 3.0.15 and later:
1463         # Found reusable socket 8 for www.privoxy.org:80 in slot 2.\
1464         #  Timestamp made 0 seconds ago. Timeout: 1. Latency: 0.
1465         $c =~ s@(?<=Found reusable socket )(\d+)@$h{'Number'}$1$h{'Standard'}@;
1466         $c = highlight_matched_host($c, '(?<=for )[^\s]+');
1467         $c =~ s@(?<=in slot )(\d+)@$h{'Number'}$1$h{'Standard'}@;
1468         $c =~ s@(?<=made )(\d+)@$h{'Number'}$1$h{'Standard'}@;
1469         $c =~ s@(?<=Timeout: )(\d+)@$h{'Number'}$1$h{'Standard'}@;
1470         $c =~ s@(?<=Latency: )(\d+)@$h{'Number'}$1$h{'Standard'}@;
1471
1472     } elsif ($c =~ m/^Marking open socket/) {
1473
1474         # Marking open socket 9 for www.privoxy.org:80 in slot 0 as unused.
1475         $c =~ s@(?<=Marking open socket )(\d+)@$h{'Number'}$1$h{'Standard'}@;
1476         $c = highlight_matched_host($c, '(?<=for )[^\s]+');
1477         $c =~ s@(?<=in slot )(\d+)@$h{'Number'}$1$h{'Standard'}@;
1478
1479     } elsif ($c =~ m/^No reusable/) {
1480
1481         # No reusable socket for addons.mozilla.org:443 found. Opening a new one.
1482         $c = highlight_matched_host($c, '(?<=for )[^\s]+');
1483
1484     } elsif ($c =~ m/^(Remembering|Forgetting) socket/) {
1485
1486         # Remembering socket 13 for www.privoxy.org:80 in slot 0.
1487         # Forgetting socket 38 for www.privoxy.org:80 in slot 5.
1488
1489         $c =~ s@(?<=socket )(\d+)@$h{'Number'}$1$h{'Standard'}@;
1490         $c = highlight_matched_host($c, '(?<=for )[^\s]+');
1491         $c =~ s@(?<=in slot )(\d+)@$h{'Number'}$1$h{'Standard'}@;
1492
1493     } elsif ($c =~ m/^Socket/) {
1494
1495         # Socket 16 already forgotten or never remembered.
1496         $c =~ s@(?<=Socket )(\d+)@$h{'Number'}$1$h{'Standard'}@;
1497
1498     } elsif ($c =~ m/^The connection to/) {
1499
1500         # The connection to www.privoxy.org:80 in slot 6 timed out. Closing socket 19. Timeout is: 61.
1501         # 3.0.15 and later:
1502         # The connection to 1.bp.blogspot.com:80 in slot 0 timed out. Closing socket 5.\
1503         #  Timeout is: 1. Assumed latency: 4.
1504         # The connection to 10.0.0.1:80 in slot 0 is no longer usable. Closing socket 4.
1505         $c = highlight_matched_host($c, '(?<=connection to )[^\s]+');
1506         $c =~ s@(?<=in slot )(\d+)@$h{'Number'}$1$h{'Standard'}@;
1507         $c =~ s@(?<=Closing socket )(\d+)@$h{'Number'}$1$h{'Standard'}@;
1508         $c =~ s@(?<=Timeout is: )(\d+)@$h{'Number'}$1$h{'Standard'}@;
1509         $c =~ s@(?<=Assumed latency: )(\d+)@$h{'Number'}$1$h{'Standard'}@;
1510
1511     } elsif ($c =~ m/^Stopped waiting for the request line./) {
1512
1513         # Stopped waiting for the request line. Timeout: 121.
1514         $c =~ s@(?<=Timeout: )(\d+)@$h{'Number'}$1$h{'Standard'}@;
1515
1516     } elsif ($c =~ m/^Waiting for \d/) {
1517
1518         # Waiting for 1 connections to timeout.
1519         $c =~ s@(?<=^Waiting for )(\d+)@$h{'Number'}$1$h{'Standard'}@;
1520
1521     } elsif ($c =~ m/^Initialized/) {
1522
1523         # Initialized 20 socket slots.
1524         $c =~ s@(?<=Initialized )(\d+)@$h{'Number'}$1$h{'Standard'}@;
1525
1526     } elsif ($c =~ m/^Done reading from server/) {
1527
1528         # Done reading from server. Expected content length: 24892. \
1529         #  Actual content length: 24892. Most recently received: 4412.
1530         # 3.0.15 and later:
1531         # Done reading from server. Expected content length: 24892. \
1532         #  Actual content length: 24892. Bytes most recently read: 4412.
1533         # Done reading from server. Content length: 6018 as expected. \
1534         #  Bytes most recently read: 294.
1535         $c =~ s@(?<=ontent length: )(\d+)@$h{'Number'}$1$h{'Standard'}@g;
1536         $c =~ s@(?<=received: )(\d+)@$h{'Number'}$1$h{'Standard'}@;
1537         $c =~ s@(?<=read: )(\d+)@$h{'Number'}$1$h{'Standard'}@;
1538
1539     } elsif ($c =~ m/^Continuing buffering headers/) {
1540
1541         # Continuing buffering headers. byte_count: 19. header_offset: 517. len: 536.
1542         $c =~ s@(?<=byte_count: )(\d+)@$h{'Number'}$1$h{'Standard'}@;
1543         $c =~ s@(?<=header_offset: )(\d+)@$h{'Number'}$1$h{'Standard'}@;
1544         $c =~ s@(?<=len: )(\d+)@$h{'Number'}$1$h{'Standard'}@;
1545         # 3.0.15 and later:
1546         # Continuing buffering headers. Bytes most recently read: %d.
1547         $c =~ s@(?<=read: )(\d+)@$h{'Number'}$1$h{'Standard'}@;
1548
1549     } elsif ($c =~ m/^Received \d+ bytes while/) {
1550
1551         # Received 206 bytes while expecting 12103.
1552         $c =~ s@(?<=Received )(\d+)@$h{'Number'}$1$h{'Standard'}@;
1553         $c =~ s@(?<=expecting )(\d+)@$h{'Number'}$1$h{'Standard'}@;
1554
1555     } elsif ($c =~ m/^Connection from/) {
1556
1557         # Connection from 81.163.28.218 dropped due to ACL
1558         $c =~ s@(?<=^Connection from )((?:\d+\.?){4})@$h{'Number'}$1$h{'Standard'}@;
1559
1560     } elsif ($c =~ m/^(?:Reusing|Closing) server socket \d./ or
1561              $c =~ m/^No additional client request/) {
1562
1563         # Reusing server socket 4. Opened for 10.0.0.1.
1564         # Closing server socket 2. Opened for 10.0.0.1.
1565         # No additional client request received in time. \
1566         #  Closing server socket 4, initially opened for 10.0.0.1.
1567         # No additional client request received in time on socket 29.
1568
1569         $c =~ s@(?<= socket )(\d+)@$h{'Number'}$1$h{'Standard'}@;
1570         $c = highlight_matched_host($c, '(?<=for )[^\s]+(?=\.$)');
1571
1572     } elsif ($c =~ m/^Connected to /) {
1573
1574         # Connected to tor-jail[10.0.0.2]:9050.
1575
1576         $c = highlight_matched_host($c, '(?<=\[)[^\]]+');
1577         $c = highlight_matched_host($c, '(?<=Connected to )[^\[\s]+');
1578         $c =~ s@(?<=\]:)(\d+)@$h{'Number'}$1$h{'Standard'}@;
1579
1580     } elsif ($c =~ m/^Could not connect to /) {
1581
1582         # Could not connect to [10.0.0.1]:80.
1583
1584         $c = highlight_matched_host($c, '(?<=\[)[^\]]+');
1585         $c =~ s@(?<=\]:)(\d+)@$h{'Number'}$1$h{'Standard'}@;
1586
1587     } elsif ($c =~ m/^Waiting for the next client request/ or
1588              $c =~ m/^The connection on server socket/ or
1589              $c =~ m/^Client request arrived in time or the client closed the connection/) {
1590
1591         # Waiting for the next client request on socket 3. Keeping the server \
1592         #  socket 12 to a.fsdn.com open.
1593         # The connection on server socket 6 to upload.wikimedia.org isn't reusable. Closing.
1594         # Client request arrived in time or the client closed the connection on socket 12.
1595
1596         $c =~ s@(?<=on socket )(\d+)@$h{'Number'}$1$h{'Standard'}@;
1597         $c =~ s@(?<=server socket )(\d+)@$h{'Number'}$1$h{'Standard'}@;
1598         $c = highlight_matched_host($c, '(?<=to )[^\s]+');
1599
1600     } elsif ($c =~ m/^Marking the server socket/) {
1601
1602         # Marking the server socket 7 tainted.
1603
1604         $c =~ s@(?<=server socket )(\d+)@$h{'Number'}$1$h{'Standard'}@;
1605
1606     } elsif ($c =~ m/^Reduced expected bytes to /) {
1607
1608         # Reduced expected bytes to 0 to account for the 1542 ones we already got.
1609         $c =~ s@(?<=bytes to )(\d+)@$h{'Number'}$1$h{'Standard'}@;
1610         $c =~ s@(?<=for the )(\d+)@$h{'Number'}$1$h{'Standard'}@;
1611
1612     } elsif ($c =~ m/^The client closed socket /) {
1613
1614         # The client closed socket 2 while the server socket 4 is still open.
1615         $c =~ s@(?<=closed socket )(\d+)@$h{'Number'}$1$h{'Standard'}@;
1616         $c =~ s@(?<=server socket )(\d+)@$h{'Number'}$1$h{'Standard'}@;
1617
1618     } elsif ($c =~ m/^Expected client content length set /) {
1619
1620         # Expected client content length set to 667325411 after reading 4999 bytes.
1621         $c =~ s@(?<=set to )(\d+)@$h{'Number'}$1$h{'Standard'}@;
1622         $c =~ s@(?<=reading )(\d+)@$h{'Number'}$1$h{'Standard'}@;
1623
1624     } elsif ($c =~ m/^Waiting for up to /) {
1625
1626         # Waiting for up to 4999 bytes from the client.
1627         $c =~ s@(?<=up to )(\d+)@$h{'Number'}$1$h{'Standard'}@;
1628
1629     } elsif ($c =~ m/^Looks like we / or
1630              $c =~ m/^Unsetting keep-alive flag/ or
1631              $c =~ m/^No connections to wait/ or
1632              $c =~ m/^Complete client request received/ or
1633              $c =~ m/^Possible pipeline attempt detected./ or
1634              $c =~ m/^POST request detected. The connection will not be kept alive./ or
1635              $c =~ m/^The server still wants to talk, but the client hung up on us./ or
1636              $c =~ m/^The server didn't specify how long the connection will stay open/ or
1637              $c =~ m/^There might be a request body. The connection will not be kept alive/ or
1638              $c =~ m/^Stopping to watch the client socket. There's already another request waiting./ or
1639              $c =~ m/^Done reading from the client\.$/) {
1640
1641         # Looks like we reached the end of the last chunk. We better stop reading.
1642         # Looks like we read the end of the last chunk together with the server \
1643         #  headers. We better stop reading.
1644         # Looks like we got the last chunk together with the server headers. \
1645         #  We better stop reading.
1646         # Unsetting keep-alive flag.
1647         # No connections to wait for left.
1648         # Client request arrived in time or the client closed the connection.
1649         # Complete client request received
1650         # Possible pipeline attempt detected. The connection will not be \
1651         #  kept alive and we will only serve the first request.
1652         # POST request detected. The connection will not be kept alive.
1653         # The server still wants to talk, but the client hung up on us.
1654         # The server didn't specify how long the connection will stay open. Assume it's only a second.
1655         # There might be a request body. The connection will not be kept alive.
1656         # Stopping to watch the client socket. There's already another request waiting.
1657         # Done reading from the client\.
1658
1659     } else {
1660
1661         found_unknown_content($c);
1662
1663     }
1664
1665     return $c;
1666 }
1667
1668
1669 sub handle_loglevel_info ($) {
1670
1671     my $c = shift;
1672
1673     if ($c =~ m/^Rewrite detected:/) {
1674
1675         # Rewrite detected: GET http://10.0.0.2:88/blah.txt HTTP/1.1
1676         $c = highlight_matched_request_line($c, '(?<=^Rewrite detected: ).*');
1677
1678     } elsif ($c =~ m/^Decompress(ing deflated|ion didn)/ or
1679              $c =~ m/^Compressed content detected/ or
1680              $c =~ m/^Tagger/
1681             ) {
1682         # Decompressing deflated iob: 117
1683         # Decompression didn't result in any content.
1684         # Compressed content detected, content filtering disabled. Consider recompiling Privoxy\
1685         #  with zlib support or enable the prevent-compression action.
1686         # Tagger 'complete-url' created empty tag. Ignored.
1687
1688         # Ignored for now
1689
1690     } elsif ($c =~ m/^(Re)?loading configuration file /) {
1691
1692         # loading configuration file '/usr/local/etc/privoxy/config':
1693         # Reloading configuration file '/usr/local/etc/privoxy/config'
1694         $c =~ s@(?<=loading configuration file \')([^\']*)@$h{'file'}$1$h{'Standard'}@;
1695
1696     } elsif ($c =~ m/^Loading (actions|filter) file: /) {
1697
1698         # Loading actions file: /usr/local/etc/privoxy/default.action
1699         # Loading filter file: /usr/local/etc/privoxy/default.filter
1700         $c =~ s@(?<= file: )(.*)$@$h{'file'}$1$h{'Standard'}@;
1701
1702     } elsif ($c =~ m/^exiting by signal/) {
1703
1704         # exiting by signal 15 .. bye
1705         $c =~ s@(?<=exiting by signal )(\d+)@$h{'signal'}$1$h{'Standard'}@;
1706
1707     } elsif ($c =~ m/^Privoxy version/) {
1708
1709         # Privoxy version 3.0.7
1710         $c =~ s@(?<=^Privoxy version )(\d+\.\d+\.\d+)$@$h{'version'}$1$h{'Standard'}@;
1711
1712     } elsif ($c =~ m/^Program name: /) {
1713
1714         # Program name: /usr/local/sbin/privoxy
1715         $c =~ s@(?<=Program name: )(.*)@$h{'program-name'}$1$h{'Standard'}@;
1716
1717     } elsif ($c =~ m/^Listening on port /) {
1718
1719         # Listening on port 8118 on IP address 10.0.0.1
1720         $c =~ s@(?<=Listening on port )(\d+)@$h{'port'}$1$h{'Standard'}@;
1721         $c =~ s@(?<=on IP address )(.*)@$h{'ip-address'}$1$h{'Standard'}@;
1722
1723     } elsif ($c =~ m/^\(Re-\)Open(?:ing)? logfile/) {
1724
1725         # (Re-)Open logfile /var/log/privoxy/privoxy.log
1726         $c =~ s@(?<=Open logfile )(.*)@$h{'file'}$1$h{'Standard'}@;
1727
1728     } elsif ($c =~ m/^(Request from|Malformed server response detected)/) {
1729
1730         # Request from 10.0.0.1 denied. limit-connect{,} doesn't allow CONNECT requests to port 443.
1731         # Request from 10.0.0.1 marked for blocking. limit-connect{,} doesn't allow CONNECT requests to port 443.
1732         # Malformed server response detected. Downgrading to HTTP/1.0 impossible.
1733
1734         $c =~ s@(?<=Request from )([^\s]*)@$h{'ip-address'}$1$h{'Standard'}@;
1735         $c =~ s@(denied|blocking)@$h{'warning'}$1$h{'Standard'}@;
1736         $c =~ s@(CONNECT)@$h{'method'}$1$h{'Standard'}@;
1737         $c =~ s@(?<=to port )(\d+)@$h{'port'}$1$h{'Standard'}@;
1738
1739     } elsif ($c =~ m/^Status code/) {
1740
1741         # Status code 304 implies no body.
1742         $c =~ s@(?<=Status code )(\d+)@$h{'status-code'}$1$h{'Standard'}@;
1743
1744     } elsif ($c =~ m/^Method/) {
1745
1746         # Method HEAD implies no body.
1747         $c =~ s@(?<=Method )([^\s]+)@$h{'method'}$1$h{'Standard'}@;
1748
1749     } elsif ($c =~ m/^Buffer limit reached while extending /) {
1750
1751         # Buffer limit reached while extending the buffer (iob). Needed: 4197470. Limit: 4194304
1752         $c =~ s@(?<=Needed: )(\d+)@$h{'Number'}$1$h{'Standard'}@;
1753         $c =~ s@(?<=Limit: )(\d+)@$h{'Number'}$1$h{'Standard'}@;
1754
1755     } elsif ($c =~ m/^No logfile configured/ or
1756              $c =~ m/^Malformerd HTTP headers detected and MS IIS5 hack enabled/ or
1757              $c =~ m/^Invalid \"chunked\" transfer/ or
1758              $c =~ m/^Support for/ or
1759              $c =~ m/^Flushing header and buffers/ or
1760              $c =~ m/^Can not resolve/
1761              ) {
1762
1763         # No logfile configured. Please enable it before reporting any problems.
1764         # Malformerd HTTP headers detected and MS IIS5 hack enabled. Expect an invalid \
1765         #  response or even no response at all.
1766         # No logfile configured. Logging disabled.
1767         # Invalid "chunked" transfer encoding detected and ignored.
1768         # Support for 'Connection: keep-alive' is experimental, incomplete and\
1769         #  known not to work properly in some situations.
1770         # Flushing header and buffers. Stepping back from filtering.
1771         # Can not resolve doesnotexist: hostname nor servname provided, or not known
1772
1773     } else {
1774
1775         found_unknown_content($c);
1776
1777     }
1778
1779     return $c;
1780 }
1781
1782 sub handle_loglevel_cgi ($) {
1783
1784     my $c = shift;
1785
1786     if ($c =~ m/^Granting access to/) {
1787
1788         #Granting access to http://config.privoxy.org/send-stylesheet, referrer http://p.p/ is trustworthy.
1789
1790     } elsif ($c =~ m/^Substituting: s(.)/) {
1791
1792         # Substituting: s/@else-not-FEATURE_ZLIB@.*@endif-FEATURE_ZLIB@//sigTU
1793         # XXX: prone to span several lines
1794
1795         my $delimiter = $1;
1796         #$c =~ s@(?<=failed: )(.*)@$h{'error'}$1$h{'Standard'}@;
1797         $c =~ s@(?!<=\\)($delimiter)@$h{'pcrs-delimiter'}$1$h{'Standard'}@g; # XXX: Too aggressive
1798         #$c =~ s@(?!<=\\)($1)@$h{'pcrs-delimiter'}$1$h{'Standard'}@g;
1799     }
1800
1801     return $c;
1802 }
1803
1804 sub handle_loglevel_force ($) {
1805
1806     my $c = shift;
1807
1808     if ($c =~ m/^Ignored force prefix in request:/) {
1809
1810         # Ignored force prefix in request: "GET http://10.0.0.1/PRIVOXY-FORCE/block HTTP/1.1"
1811         $c =~ s@^(Ignored)@$h{'ignored'}$1$h{'Standard'}@;
1812         $c = highlight_matched_request_line($c, '(?<=request: ")[^"]*');
1813
1814     } elsif ($c =~ m/^Enforcing request:/) {
1815
1816         # Enforcing request: "GET http://10.0.0.1/block HTTP/1.1".
1817         $c = highlight_matched_request_line($c, '(?<=request: ")[^"]*');
1818
1819     } else {
1820
1821         found_unknown_content($c);
1822
1823     }
1824
1825     return $c;
1826 }
1827
1828 sub handle_loglevel_error ($) {
1829
1830     my $c = shift;
1831
1832     if ($c =~ m/^Empty server or forwarder response received on socket \d+./) {
1833
1834         # Empty server or forwarder response received on socket 4.
1835         # Empty server or forwarder response received on socket 3. \
1836         #  Closing client socket 15 without sending data.
1837         $c =~ s@(?<=on socket )(\d+)@$h{'Number'}$1$h{'Standard'}@;
1838         $c =~ s@(?<=client socket )(\d+)@$h{'Number'}$1$h{'Standard'}@;
1839     }
1840     # XXX: There are probably more messages that deserve highlighting.
1841
1842     return $c;
1843 }
1844
1845
1846 sub handle_loglevel_ignore ($) {
1847     return shift;
1848 }
1849
1850 sub gather_loglevel_request_stats ($$) {
1851     my $c = shift;
1852     my $thread = shift;
1853     our %stats;
1854
1855     $stats{requests}++;
1856 }
1857
1858 sub gather_loglevel_crunch_stats ($$) {
1859     my $c = shift;
1860     my $thread = shift;
1861     our %stats;
1862
1863     $stats{requests}++;
1864     $stats{crunches}++;
1865 }
1866
1867
1868 sub gather_loglevel_error_stats ($$) {
1869
1870     my $c = shift;
1871     my $thread = shift;
1872     our %stats;
1873     our %thread_data;
1874
1875     if ($c =~ m/^Empty server or forwarder response received on socket \d+./) {
1876
1877         # Empty server or forwarder response received on socket 4.
1878         $stats{'empty-responses'}++;
1879         if ($thread_data{$thread}{'new_connection'}) {
1880             $stats{'empty-responses-on-new-connections'}++;
1881         } else {
1882             $stats{'empty-responses-on-reused-connections'}++;
1883         }
1884     }
1885 }
1886
1887 sub gather_loglevel_connect_stats ($$) {
1888
1889     my ($c, $thread) = @_;
1890     our %thread_data;
1891     our %stats;
1892
1893     if ($c =~ m/^via ([^\s]+) to: [^\s]+/) {
1894
1895         # Connect: via 10.0.0.1:8123 to: www.example.org.noconnect
1896         $thread_data{$thread}{'forwarder'} = $1; # XXX: is this missue?
1897
1898     } elsif ($c =~ m/^to ([^\s]*)$/) {
1899
1900         # Connect: to lists.sourceforge.net:443
1901
1902         $thread_data{$thread}{'forwarder'} = 'direct connection';
1903
1904     } elsif ($c =~ m/^Created new connection to/) {
1905
1906         # Created new connection to www.privoxy.org:80 on socket 11.
1907
1908         $thread_data{$thread}{'new_connection'} = 1;
1909
1910     } elsif ($c =~ m/^Reusing server socket \d./ or
1911              $c =~ m/^Found reusable socket/) {
1912
1913         # Reusing server socket 4. Opened for 10.0.0.1.
1914         # Found reusable socket 9 for www.privoxy.org:80 in slot 0.
1915
1916         $thread_data{$thread}{'new_connection'} = 0;
1917         $stats{'reused-connections'}++;
1918     }
1919 }
1920
1921 sub gather_loglevel_header_stats ($$) {
1922
1923     my ($c, $thread) = @_;
1924     our %stats;
1925
1926     if ($c =~ m/^A HTTP\/1\.1 response without/ or
1927         $c =~ m/^Keeping the server header 'Connection: keep-alive' around./)
1928     {
1929         # A HTTP/1.1 response without Connection header implies keep-alive.
1930         # Keeping the server header 'Connection: keep-alive' around.
1931         $stats{'server-keep-alive'}++;
1932     }
1933 }
1934
1935 sub init_stats () {
1936     our %stats = (
1937         requests => 0,
1938         crunches => 0,
1939         'server-keep-alive' => 0,
1940         'reused-connections' => 0,
1941         'empty-responses' => 0,
1942         'empty-responses-on-new-connections' => 0,
1943         'empty-responses-on-reused-connections' => 0,
1944         );
1945 }
1946
1947 sub get_percentage ($$) {
1948     my $big = shift;
1949     my $small = shift;
1950     return "NaN" if ($big eq 0);
1951     return sprintf("%.2f%%", $small / $big * 100);
1952 }
1953
1954 sub print_stats () {
1955
1956     our %stats;
1957     my $new_connections = $stats{requests} - $stats{crunches} - $stats{'reused-connections'};
1958     my $outgoing_requests = $stats{requests} - $stats{crunches};
1959
1960     if ($stats{requests} eq 0) {
1961         print "No requests yet.\n";
1962         return;
1963     }
1964
1965     print "Client requests total: " . $stats{requests} . "\n";
1966     print "Crunches: " . $stats{crunches} . " (" .
1967         get_percentage($stats{requests}, $stats{crunches}) . ")\n";
1968     print "Outgoing requests: " . $outgoing_requests . " (" .
1969         get_percentage($stats{requests}, $outgoing_requests) . ")\n";
1970     print "Server keep-alive offers: " . $stats{'server-keep-alive'} . " (" .
1971         get_percentage($stats{requests}, $stats{'server-keep-alive'}) . ")\n";
1972     print "New outgoing connections: " . $new_connections . " (" .
1973         get_percentage($stats{requests}, $new_connections) . ")\n";
1974     print "Reused connections: " . $stats{'reused-connections'} . " (" .
1975         get_percentage($stats{requests}, $stats{'reused-connections'}) . ")\n";
1976     print "Empty responses: " . $stats{'empty-responses'} . " (" .
1977         get_percentage($stats{requests}, $stats{'empty-responses'}) . ")\n";
1978     print "Empty responses on new connections: "
1979          . $stats{'empty-responses-on-new-connections'} . " (" .
1980         get_percentage($stats{requests}, $stats{'empty-responses-on-new-connections'})
1981         . ")\n";
1982     print "Empty responses on reused connections: " .
1983         $stats{'empty-responses-on-reused-connections'} . " (" .
1984         get_percentage($stats{requests}, $stats{'empty-responses-on-reused-connections'}) .
1985         ")\n";
1986 }
1987
1988
1989 ################################################################################
1990 # Functions that actually print stuff
1991 ################################################################################
1992
1993 sub print_clf_message () {
1994
1995     our ($ip, $timestamp, $request_line, $status_code, $size);
1996     my $output = '';
1997
1998     return if DEBUG_SUPPRESS_LOG_MESSAGES;
1999
2000     # Rebuild highlighted
2001     $output .= $h{'Number'} . $ip . $h{'Standard'};
2002     $output .= " - - ";
2003     $output .= "[" . $h{'Timestamp'} . $timestamp . $h{'Standard'} . "]";
2004     $output .= " ";
2005     $output .= "\"" . highlight_request_line("$request_line") . "\"";
2006     $output .= " ";
2007     $output .= $h{'Status'} . $status_code . $h{'Standard'};
2008     $output .= " ";
2009     $output .= $h{'Number'} . $size . $h{'Standard'};
2010     $output .= $line_end;
2011
2012     print $output;
2013 }
2014
2015 sub print_non_clf_message ($) {
2016
2017     my $content = shift;
2018     my $msec_string = "." . $req{$t}{'msecs'} unless $no_msecs_mode;
2019     my $line_start = $html_output_mode ? '' : $h{"Standard"};
2020
2021     return if DEBUG_SUPPRESS_LOG_MESSAGES;
2022
2023     print $line_start
2024         . $time_colours[$time_colour_index % 2]
2025         . $req{$t}{'time-stamp'}
2026         . $msec_string
2027         . $h{Standard} . " "
2028         . $thread_colours{$t}
2029         . $t
2030         . $h{Standard}
2031         . " "
2032         . $h{$req{$t}{'log-level'}}
2033         . $req{$t}{'log-level'}
2034         . $h{Standard}
2035         . ": "
2036         . $content
2037         . $line_end;
2038 }
2039
2040 sub shorten_thread_id ($) {
2041
2042     my $thread_id = shift;
2043
2044     our %short_thread_ids;
2045     our $max_threadid;
2046
2047     unless (defined $short_thread_ids{$thread_id}) {
2048         $short_thread_ids{$thread_id} = sprintf "%.3d", $max_threadid++;
2049     }
2050
2051     return $short_thread_ids{$thread_id}
2052 }
2053
2054 sub parse_loop () {
2055
2056     my ($day, $time_stamp, $thread, $log_level, $content, $c, $msecs);
2057     my $last_msecs  = 0;
2058     my $last_thread = 0;
2059     my $last_timestamp = 0;
2060     my $filters_that_did_nothing;
2061     my $key;
2062     my $time_colour;
2063     $time_colour = paint_it('white');
2064
2065     my %log_level_handlers = (
2066         'Re-Filter'         => \&handle_loglevel_re_filter,
2067         'Header'            => \&handle_loglevel_header,
2068         'Connect'           => \&handle_loglevel_connect,
2069         'Redirect'          => \&handle_loglevel_redirect,
2070         'Request'           => \&handle_loglevel_request,
2071         'Crunch'            => \&handle_loglevel_crunch,
2072         'Gif-Deanimate'     => \&handle_loglevel_gif_deanimate,
2073         'Info'              => \&handle_loglevel_info,
2074         'CGI'               => \&handle_loglevel_cgi,
2075         'Force'             => \&handle_loglevel_force,
2076         'Error'             => \&handle_loglevel_error,
2077         'Fatal error'       => \&handle_loglevel_ignore,
2078         'Writing'           => \&handle_loglevel_ignore,
2079         'Unknown log level' => \&handle_loglevel_ignore,
2080     );
2081
2082     while (<>) {
2083
2084         if (m/^(\w{3} \d{2}) (\d\d:\d\d:\d\d)\.?(\d+)? (?:Privoxy\()?([^\)\s]*)[\)]? ([\w -]*): (.*?)\r?$/) {
2085             $thread = $t = ($shorten_thread_ids) ? shorten_thread_id($4) : $4;
2086             $req{$t}{'day'} = $day = $1;
2087             $req{$t}{'time-stamp'} = $time_stamp = $2;
2088             $req{$t}{'msecs'} = $msecs = $3 ? $3 : 0; # Only the cool kids have micro second resolution;
2089             $req{$t}{'log-level'} = $log_level = $5;
2090             $req{$t}{'content'} = $content = $c = $6;
2091             $req{$t}{'log-message'} = $_;
2092             $no_special_header_highlighting = 0;
2093
2094             if (defined($log_level_handlers{$log_level})) {
2095
2096                 $content = $log_level_handlers{$log_level}($content);
2097
2098             } else {
2099
2100                 die "No handler found for log level \"$log_level\"\n";
2101             }
2102
2103             # Highlight Truncations
2104             if (length($_) > 4000) {
2105                 $content =~ s@(too long, truncated)]$@$h{'Truncation'}$1$h{'Standard'}]@g;
2106             }
2107
2108             next unless $content;
2109
2110             # Register threads to keep the colour constant
2111             if (!defined($thread_colours{$thread})) {
2112                 $thread_colours{$thread} = $all_colours[$thread_colour_index % @all_colours];
2113                 $thread_colour_index++;
2114             }
2115
2116             # Switch timestamp colour if timestamps differ
2117             if (($msecs ne $last_msecs) || ($time_stamp ne $last_timestamp)) {
2118                debug_message("Tick tack!") if DEBUG_TICKS;
2119                $time_colour = $time_colours[$time_colour_index % 2];
2120                $time_colour_index++;
2121                $last_msecs = $msecs;
2122                $last_timestamp = $time_stamp;
2123             }
2124
2125             $last_thread = $thread;
2126
2127             print_non_clf_message($content);
2128
2129         } elsif (m/^((?:\d+\.\d+\.\d+\.\d+|[:\d]+)) - - \[(.*)\] "(.*)" (\d+) (\d+)/) {
2130
2131             # LOG_LEVEL_CLF lines look like this
2132             # 61.152.239.32 - - [04/Mar/2007:18:28:23 +0100] "GET \
2133             #  http://ad.yieldmanager.com/imp?z=1&Z=120x600&s=109339&u=http%3A%2F%2Fwww.365loan.co.uk%2F&r=1\
2134             #  HTTP/1.1" 403 1730
2135             our ($ip, $timestamp, $request_line, $status_code, $size) = ($1, $2, $3, $4, $5);
2136
2137             print_clf_message();
2138
2139         } else {
2140
2141             # Some Privoxy log messages span more than one line,
2142             # usually to dump lots of content that doesn't need any syntax highlighting.
2143             # XXX: add mechanism to forward these lines to the right handler anyway.
2144             chomp();
2145             unless (DEBUG_SUPPRESS_LOG_MESSAGES or (SUPPRESS_EMPTY_LINES and m/^\s+$/)) {
2146                 print and print get_line_end(); # unless (SUPPRESS_EMPTY_LINES and m/^\s+$/);
2147             }
2148         }
2149     }
2150 }
2151
2152 sub stats_loop () {
2153
2154     my ($day, $time_stamp, $msecs, $thread, $log_level, $content);
2155     my %log_level_handlers = (
2156          'Re-Filter'         => \&handle_loglevel_ignore,
2157          'Header'            => \&gather_loglevel_header_stats,
2158          'Connect'           => \&gather_loglevel_connect_stats,
2159          'Redirect'          => \&handle_loglevel_ignore,
2160          'Request'           => \&gather_loglevel_request_stats,
2161          'Crunch'            => \&gather_loglevel_crunch_stats,
2162          'Gif-Deanimate'     => \&handle_loglevel_ignore,
2163          'Info'              => \&handle_loglevel_ignore,
2164          'CGI'               => \&handle_loglevel_ignore,
2165          'Force'             => \&handle_loglevel_ignore,
2166          'Error'             => \&gather_loglevel_error_stats,
2167          'Fatal error'       => \&handle_loglevel_ignore,
2168          'Writing'           => \&handle_loglevel_ignore,
2169          'Unknown log level' => \&handle_loglevel_ignore
2170     );
2171
2172     while (<>) {
2173         if (m/^(\w{3} \d{2}) (\d\d:\d\d:\d\d)\.?(\d+)? (?:Privoxy\()?([^\)\s]*)[\)]? ([\w -]*): (.*?)\r?$/) {
2174             $day = $1;
2175             $time_stamp = $2;
2176             $msecs = $3 ? $3 : 0;
2177             $thread = $4;
2178             $log_level = $5;
2179             $content = $6;
2180
2181             if (defined($log_level_handlers{$log_level})) {
2182
2183                 $content = $log_level_handlers{$log_level}($content, $thread);
2184
2185             } else {
2186
2187                 die "No handler found for log level \"$log_level\"\n";
2188
2189             }
2190         }
2191     }
2192
2193     print_stats();
2194
2195 }
2196
2197 sub VersionMessage {
2198     my $version_message;
2199
2200     $version_message .= 'Privoxy-Log-Parser ' . PRIVOXY_LOG_PARSER_VERSION  . "\n";
2201     $version_message .= 'Copyright (C) 2007-2010 Fabian Keil <fk@fabiankeil.de>' . "\n";
2202     $version_message .= 'http://www.fabiankeil.de/sourcecode/privoxy-log-parser/' . "\n";
2203
2204     print $version_message;
2205 }
2206
2207 sub get_cli_options () {
2208
2209     our %cli_options = (
2210         'html-output'              => CLI_OPTION_DEFAULT_TO_HTML_OUTPUT,
2211         'title'                    => CLI_OPTION_TITLE,
2212         'no-syntax-highlighting'   => CLI_OPTION_NO_SYNTAX_HIGHLIGHTING,
2213         'no-embedded-css'          => CLI_OPTION_NO_EMBEDDED_CSS,
2214         'no-msecs'                 => CLI_OPTION_NO_MSECS,
2215         'shorten-thread-ids'       => CLI_OPTION_SHORTEN_THREAD_IDS,
2216         'show-ineffective-filters' => CLI_OPTION_SHOW_INEFFECTIVE_FILTERS,
2217         'accept-unknown-messages'  => CLI_OPTION_ACCEPT_UNKNOWN_MESSAGES,
2218         'statistics'               => CLI_OPTION_STATISTICS,
2219     );
2220
2221     GetOptions (
2222         'html-output'              => \$cli_options{'html-output'},
2223         'title'                    => \$cli_options{'title'},
2224         'no-syntax-highlighting'   => \$cli_options{'no-syntax-highlighting'},
2225         'no-embedded-css'          => \$cli_options{'no-embedded-css'},
2226         'no-msecs'                 => \$cli_options{'no-msecs'},
2227         'shorten-thread-ids'       => \$cli_options{'shorten-thread-ids'},
2228         'show-ineffective-filters' => \$cli_options{'show-ineffective-filters'},
2229         'accept-unknown-messages'  => \$cli_options{'accept-unknown-messages'},
2230         'statistics'               => \$cli_options{'statistics'},
2231         'version'                  => sub { VersionMessage && exit(0) },
2232         'help'                     => \&help,
2233    ) or exit(1);
2234
2235    $html_output_mode = cli_option_is_set('html-output');
2236    $no_msecs_mode = cli_option_is_set('no-msecs');
2237    $shorten_thread_ids = cli_option_is_set('shorten-thread-ids');
2238    $line_end = get_line_end();
2239 }
2240
2241 sub help () {
2242
2243     our %cli_options;
2244
2245     VersionMessage();
2246
2247     print << "    EOF"
2248
2249 Options and their default values if they have any:
2250     [--accept-unknown-messages]
2251     [--html-output]
2252     [--no-embedded-css]
2253     [--no-msecs]
2254     [--no-syntax-highlighting]
2255     [--shorten-thread-ids]
2256     [--show-ineffective-filters]
2257     [--statistics]
2258     [--title $cli_options{'title'}]
2259     [--version]
2260 see "perldoc $0" for more information
2261     EOF
2262     ;
2263     exit(0);
2264 }
2265
2266 ################################################################################
2267 # main
2268 ################################################################################
2269 sub main () {
2270
2271     get_cli_options();
2272     set_background(DEFAULT_BACKGROUND);
2273     prepare_our_stuff();
2274
2275     print_intro();
2276
2277     if (cli_option_is_set('statistics')) {
2278         stats_loop();
2279     } else {
2280         parse_loop();
2281     }
2282
2283     print_outro();
2284 }
2285
2286 main();
2287
2288 =head1 NAME
2289
2290 B<privoxy-log-parser> - A parser and syntax-highlighter for Privoxy log messages
2291
2292 =head1 SYNOPSIS
2293
2294 B<privoxy-log-parser> [B<--accept-unknown-messages>] [B<--html-output>]
2295 [B<--no-msecs>] [B<--no-syntax-higlighting>] [B<--statistics>]
2296 [B<--shorten-thread-ids>] [B<--show-ineffective-filters>] [B<--version>]
2297
2298 =head1 DESCRIPTION
2299
2300 B<privoxy-log-parser> reads Privoxy log messages and
2301
2302 - syntax-highlights recognized lines,
2303
2304 - reformats some of them for easier comprehension,
2305
2306 - filters out less useful messages, and
2307
2308 - (in some cases) calculates additional information,
2309   like the compression ratio or how a filter affected
2310   the content size.
2311
2312 With B<privoxy-log-parser> you should be able to increase Privoxy's log level
2313 without getting confused by the resulting amount of output. For example for
2314 "debug 64" B<privoxy-log-parser> will (by default) only show messages that
2315 affect the content. If a filter doesn't cause any hits, B<privoxy-log-parser>
2316 will hide the "filter foo caused 0 hits" message.
2317
2318 =head1 OPTIONS
2319
2320 [B<--accept-unknown-messages>] Don't print warnings in case of unknown messages,
2321 just don't highlight them.
2322
2323 [B<--html-output>] Use HTML and CSS for the syntax highlighting. If this option is
2324 omitted, ANSI escape sequences are used unless B<--no-syntax-highlighting> is active.
2325 This option is only intended to make embedding log excerpts in web pages easier.
2326 It does not escape any input!
2327
2328 [B<--no-msecs>] Don't expect milisecond resolution
2329
2330 [B<--no-syntax-highlighting>] Disable syntax-highlighting. Useful when
2331 the filtered output is piped into less in which case the ANSI control
2332 codes don't work, or if the terminal itself doesn't support the control
2333 codes.
2334
2335 [B<--shorten-thread-ids>] Shorten the thread ids to a three-digit decimal number.
2336 Note that the mapping from thread ids to shortended ids is created at
2337 run-time and thus varies with the input.
2338
2339 [B<--show-ineffective-filters>] Don't suppress log lines for filters
2340 that didn't modify the content.
2341
2342 [B<--statistics>] Gather various statistics instead of syntax highlighting
2343 log messages. This is an experimental feature, if the results look wrong
2344 they very well might be. Also note that the results are pretty much guaranteed
2345 to be incorrect if Privoxy and Privoxy-Log-Parser aren't in sync.
2346
2347 [B<--version>] Print version and exit.
2348
2349 =head1 EXAMPLES
2350
2351 To monitor a log file:
2352
2353 tail -F /usr/jails/privoxy-jail/var/log/privoxy/privoxy.log | B<privoxy-log-parser>
2354
2355 Replace '-F' with '-f' if your tail implementation lacks '-F' support
2356 or if the log won't get rotated anyway. The log file location depends
2357 on your system (Doh!).
2358
2359 To monitor Privoxy without having it write to a log file:
2360
2361 privoxy --no-daemon /usr/jails/privoxy-jail/usr/local/etc/privoxy/config 2>&1 | B<privoxy-log-parser>
2362
2363 Again, the config file location depends on your system. Output redirection
2364 depends on your shell, the above works with bourne shells.
2365
2366 To read a processed Privoxy log file from top to bottom, letting the content
2367 scroll by slightly faster than you can read:
2368
2369 B<privoxy-log-parser> < /usr/jails/privoxy-jail/var/log/privoxy/privoxy.log
2370
2371 This is probably only useful to fill screens in the background of haxor movies.
2372
2373 =head1 CAVEATS
2374
2375 Syntax highlighting with ANSI escape sequences will look strange
2376 if your background color isn't black.
2377
2378 Some messages aren't recognized yet and will not be fully highlighted.
2379
2380 B<privoxy-log-parser> is developed with Privoxy 3.0.7 or later in mind,
2381 using earlier Privoxy versions will probably result in an increased amount
2382 of unrecognized log lines.
2383
2384 Privoxy's log files tend to be rather large. If you use HTML
2385 highlighting some browsers can't handle them, get confused and
2386 will eventually crash because of segmentation faults or unexpected
2387 exceptions. This is a problem in the browser and not B<privoxy-log-parser>'s
2388 fault.
2389
2390 =head1 BUGS
2391
2392 Many settings can't be controlled through command line options yet.
2393
2394 =head1 SEE ALSO
2395
2396 privoxy(1)
2397
2398 =head1 AUTHOR
2399
2400 Fabian Keil <fk@fabiankeil.de>
2401
2402 =cut