home *** CD-ROM | disk | FTP | other *** search
/ PC Professionell 2004 December / PCpro_2004_12.ISO / files / webserver / tsw / TSW_3.4.0.exe / Apache2 / perl / PatternLayout.pm < prev    next >
Encoding:
Perl POD Document  |  2003-05-03  |  19.4 KB  |  609 lines

  1. ##################################################
  2. package Log::Log4perl::Layout::PatternLayout;
  3. ##################################################
  4.  
  5. use 5.006;
  6. use strict;
  7. use warnings;
  8. use Carp;
  9. use Log::Log4perl::Level;
  10. use Log::Log4perl::DateFormat;
  11. use Log::Log4perl::NDC;
  12. use Log::Log4perl::MDC;
  13. use File::Spec;
  14.  
  15. our $TIME_HIRES_AVAILABLE;
  16. our $TIME_HIRES_AVAILABLE_WARNED = 0;
  17. our $HOSTNAME;
  18. our $PROGRAM_START_TIME;
  19.  
  20. our %GLOBAL_USER_DEFINED_CSPECS = ();
  21.  
  22. our $CSPECS = 'cCdFHIlLmMnpPrtxX%';
  23.  
  24.  
  25. BEGIN {
  26.     # Check if we've got Time::HiRes. If not, don't make a big fuss,
  27.     # just set a flag so we know later on that we can't have fine-grained
  28.     # time stamps
  29.     $TIME_HIRES_AVAILABLE = 0;
  30.     eval { require Time::HiRes; };
  31.     if($@) {
  32.         $PROGRAM_START_TIME = time();
  33.     } else {
  34.         $TIME_HIRES_AVAILABLE = 1;
  35.         $PROGRAM_START_TIME = [Time::HiRes::gettimeofday()];
  36.     }
  37.  
  38.     # Check if we've got Sys::Hostname. If not, just punt.
  39.     $HOSTNAME = "unknown.host";
  40.     eval { require Sys::Hostname; };
  41.     $HOSTNAME = Sys::Hostname::hostname() unless $@;
  42. }
  43.  
  44. ##################################################
  45. sub current_time {
  46. ##################################################
  47.     # Return secs and optionally msecs if we have Time::HiRes
  48.     if($TIME_HIRES_AVAILABLE) {
  49.         return (Time::HiRes::gettimeofday());
  50.     } else {
  51.         return (time(), 0);
  52.     }
  53. }
  54.  
  55. use base qw(Log::Log4perl::Layout);
  56.  
  57. no strict qw(refs);
  58.  
  59. ##################################################
  60. sub new {
  61. ##################################################
  62.     my $class = shift;
  63.     $class = ref ($class) || $class;
  64.  
  65.     my ($data) = @_;
  66.  
  67.     my ($layout_string);
  68.  
  69.     if (ref $data && !exists $data->{ConversionPattern}{value} or
  70.         !defined $data) {
  71.         #die "No ConversionPattern given for PatternLayout\n";
  72.         $layout_string = '%m%n';  #this is better per http://jakarta.apache.org/log4j/docs/api/org/apache/log4j/PatternLayout.html
  73.     } elsif (ref $data) {
  74.         $layout_string = $data->{ConversionPattern}{value};
  75.     } else {
  76.         $layout_string = $data;
  77.     }
  78.  
  79.     my $self = {
  80.         format      => undef,
  81.         info_needed => {},
  82.         stack       => [],
  83.         CSPECS      => $CSPECS,
  84.         dontCollapseArrayRefs => $data->{dontCollapseArrayRefs}{value},
  85.     };
  86.  
  87.     bless $self, $class;
  88.  
  89.     #add the global user-defined cspecs
  90.     foreach my $f (keys %GLOBAL_USER_DEFINED_CSPECS){
  91.             #add it to the list of letters
  92.         $self->{CSPECS} .= $f;
  93.              #for globals, the coderef is already evaled, 
  94.         $self->{USER_DEFINED_CSPECS}{$f} = $GLOBAL_USER_DEFINED_CSPECS{$f};
  95.     }
  96.  
  97.     #add the user-defined cspecs local to this appender
  98.     foreach my $f (keys %{$data->{cspec}}){
  99.         $self->add_layout_cspec($f, $data->{cspec}{$f}{value});
  100.     }
  101.  
  102.     $self->define($layout_string);
  103.  
  104.     return $self;
  105. }
  106.  
  107. ##################################################
  108. sub define {
  109. ##################################################
  110.     my($self, $format) = @_;
  111.  
  112.         # If the message contains a %m followed by a newline,
  113.         # make a note of that so that we can cut a superfluous 
  114.         # \n off the message later on
  115.     if($format =~ /%m%n/) {
  116.         $self->{message_chompable} = 1;
  117.     } else {
  118.         $self->{message_chompable} = 0;
  119.     }
  120.  
  121.     # Parse the format
  122.     $format =~ s/%(-?\d*(?:\.\d+)?) 
  123.                        ([$self->{CSPECS}])
  124.                        (?:{(.*?)})*/
  125.                        rep($self, $1, $2, $3);
  126.                       /gex;
  127.  
  128.     $self->{printformat} = $format;
  129. }
  130.  
  131. ##################################################
  132. sub rep {
  133. ##################################################
  134.     my($self, $num, $op, $curlies) = @_;
  135.  
  136.     return "%%" if $op eq "%";
  137.  
  138.     # If it's a %d{...} construct, initialize a simple date
  139.     # format formatter, so that we can quickly render later on.
  140.     # If it's just %d, assume %d{yyyy/MM/dd HH:mm:ss}
  141.     my $sdf;
  142.     if($op eq "d") {
  143.         if(defined $curlies) {
  144.             $sdf = Log::Log4perl::DateFormat->new($curlies);
  145.         } else {
  146.             $sdf = Log::Log4perl::DateFormat->new("yyyy/MM/dd HH:mm:ss");
  147.         }
  148.     }
  149.  
  150.     push @{$self->{stack}}, [$op, $sdf || $curlies];
  151.  
  152.     $self->{info_needed}->{$op}++;
  153.  
  154.     return "%${num}s";
  155. }
  156.  
  157. ##################################################
  158. sub render {
  159. ##################################################
  160.     my($self, $message, $category, $priority, $caller_level) = @_;
  161.  
  162.     $caller_level = 0 unless defined  $caller_level;
  163.  
  164.     my %info    = ();
  165.  
  166.     $info{m}    = $message;
  167.         # See 'define'
  168.     chomp $info{m} if $self->{message_chompable};
  169.  
  170.     my @results = ();
  171.  
  172.     if($self->{info_needed}->{L} or
  173.        $self->{info_needed}->{F} or
  174.        $self->{info_needed}->{C} or
  175.        $self->{info_needed}->{l} or
  176.        $self->{info_needed}->{M} or
  177.        0
  178.       ) {
  179.         my ($package, $filename, $line, 
  180.             $subroutine, $hasargs,
  181.             $wantarray, $evaltext, $is_require, 
  182.             $hints, $bitmask) = caller($caller_level);
  183.  
  184.         # If caller() choked because of a whacko caller level,
  185.         # correct undefined values to '[undef]' in order to prevent 
  186.         # warning messages when interpolating later
  187.         unless(defined $bitmask) {
  188.             for($package, 
  189.                 $filename, $line,
  190.                 $subroutine, $hasargs,
  191.                 $wantarray, $evaltext, $is_require,
  192.                 $hints, $bitmask) {
  193.                 $_ = '[undef]' unless defined $_;
  194.             }
  195.         }
  196.  
  197.         $info{L} = $line;
  198.         $info{F} = $filename;
  199.         $info{C} = $package;
  200.  
  201.         if($self->{info_needed}->{M} or
  202.            $self->{info_needed}->{l} or
  203.            0) {
  204.             # For the name of the subroutine the logger was triggered,
  205.             # we need to go one more level up
  206.             $subroutine = (caller($caller_level+1))[3];
  207.             $subroutine = "main::" unless $subroutine;
  208.             $info{M} = $subroutine;
  209.             $info{l} = "$subroutine $filename ($line)";
  210.         }
  211.     }
  212.  
  213.     $info{X} = "[No curlies defined]";
  214.     $info{x} = Log::Log4perl::NDC->get() if $self->{info_needed}->{x};
  215.     $info{c} = $category;
  216.     $info{d} = 1; # Dummy value, corrected later
  217.     $info{n} = "\n";
  218.     $info{p} = $priority;
  219.     $info{P} = $$;
  220.     $info{H} = $HOSTNAME;
  221.  
  222.     if($self->{info_needed}->{r}) {
  223.         if($TIME_HIRES_AVAILABLE) {
  224.             $info{r} = 
  225.                 int((Time::HiRes::tv_interval ( $PROGRAM_START_TIME ))*1000);
  226.         } else {
  227.             if(! $TIME_HIRES_AVAILABLE_WARNED) {
  228.                 $TIME_HIRES_AVAILABLE_WARNED++;
  229.                 # warn "Requested %r pattern without installed Time::HiRes\n";
  230.             }
  231.             $info{r} = time() - $PROGRAM_START_TIME;
  232.         }
  233.     }
  234.  
  235.         # As long as they're not implemented yet ..
  236.     $info{t} = "N/A";
  237.  
  238.     foreach my $cspec (keys %{$self->{USER_DEFINED_CSPECS}}){
  239.         next unless $self->{info_needed}->{$cspec};
  240.         $info{$cspec} = $self->{USER_DEFINED_CSPECS}->{$cspec}->($self, 
  241.                                 $message, $category, $priority, $caller_level);
  242.     }
  243.  
  244.         # Iterate over all info fields on the stack
  245.     for my $e (@{$self->{stack}}) {
  246.         my($op, $curlies) = @$e;
  247.         if(exists $info{$op}) {
  248.             my $result = $info{$op};
  249.             if($curlies) {
  250.                 $result = curly_action($op, $curlies, $info{$op});
  251.             } else {
  252.                 # just for %d
  253.                 if($op eq 'd') {
  254.                     $result = $info{$op}->format(current_time());
  255.                 }
  256.             }
  257.             push @results, $result;
  258.         } else {
  259.             warn "Format %'$op' not implemented (yet)";
  260.             push @results, "FORMAT-ERROR";
  261.         }
  262.     }
  263.  
  264.     #print STDERR "sprintf $self->{printformat}--$results[0]--\n";
  265.  
  266.     return (sprintf $self->{printformat}, @results);
  267. }
  268.  
  269. ##################################################
  270. sub curly_action {
  271. ##################################################
  272.     my($ops, $curlies, $data) = @_;
  273.  
  274.     if($ops eq "c") {
  275.         $data = shrink_category($data, $curlies);
  276.     } elsif($ops eq "C") {
  277.         $data = shrink_category($data, $curlies);
  278.     } elsif($ops eq "X") {
  279.         $data = Log::Log4perl::MDC->get($curlies);
  280.     } elsif($ops eq "d") {
  281.         $data = $curlies->format(current_time());
  282.     } elsif($ops eq "F") {
  283.         my @parts = File::Spec->splitdir($data);
  284.             # Limit it to max curlies entries
  285.         if(@parts > $curlies) {
  286.             splice @parts, 0, @parts - $curlies;
  287.         }
  288.         $data = File::Spec->catfile(@parts);
  289.     }
  290.  
  291.     return $data;
  292. }
  293.  
  294. ##################################################
  295. sub shrink_category {
  296. ##################################################
  297.     my($category, $len) = @_;
  298.  
  299.     my @components = split /\.|::/, $category;
  300.  
  301.     if(@components > $len) {
  302.         splice @components, 0, @components - $len;
  303.         $category = join '.', @components;
  304.     } 
  305.  
  306.     return $category;
  307. }
  308.  
  309. ##################################################
  310. sub add_global_cspec {
  311. ##################################################
  312. # This is a Class method.
  313. # Accepts a coderef or text
  314. ##################################################
  315.  
  316.     unless($Log::Log4perl::ALLOW_CODE_IN_CONFIG_FILE) {
  317.         die "\$Log::Log4perl::ALLOW_CODE_IN_CONFIG_FILE setting " .
  318.             "prohibits user defined cspecs";
  319.     }
  320.  
  321.     my ($letter, $perlcode) = @_;
  322.  
  323.     croak "Illegal value '$letter' in call to add_global_cspec()"
  324.         unless ($letter =~ /^[a-zA-Z]$/);
  325.  
  326.     croak "Missing argument for perlcode for 'cspec.$letter' ".
  327.           "in call to add_global_cspec()"
  328.         unless $perlcode;
  329.  
  330.     croak "Please don't redefine built-in cspecs [$CSPECS]\n".
  331.           "like you do for \"cspec.$letter\"\n "
  332.         if ($CSPECS =~/$letter/);
  333.  
  334.     if (ref $perlcode eq 'CODE') {
  335.         $GLOBAL_USER_DEFINED_CSPECS{$letter} = $perlcode;
  336.  
  337.     }elsif (! ref $perlcode){
  338.         
  339.         $GLOBAL_USER_DEFINED_CSPECS{$letter} = 
  340.             Log::Log4perl::Config::compile_if_perl($perlcode);
  341.  
  342.         if ($@) {
  343.             die qq{Compilation failed for your perl code for }.
  344.                 qq{"log4j.PatternLayout.cspec.$letter":\n}.
  345.                 qq{This is the error message: \t$@\n}.
  346.                 qq{This is the code that failed: \n$perlcode\n};
  347.         }
  348.  
  349.         croak "eval'ing your perlcode for 'log4j.PatternLayout.cspec.$letter' ".
  350.               "doesn't return a coderef \n".
  351.               "Here is the perl code: \n\t$perlcode\n "
  352.             unless (ref $GLOBAL_USER_DEFINED_CSPECS{$letter} eq 'CODE');
  353.  
  354.     }else{
  355.         croak "I don't know how to handle perlcode=$perlcode ".
  356.               "for 'cspec.$letter' in call to add_global_cspec()";
  357.     }
  358. }
  359.  
  360. ##################################################
  361. sub add_layout_cspec {
  362. ##################################################
  363. # object method
  364. # adds a cspec just for this layout
  365. ##################################################
  366.     my ($self, $letter, $perlcode) = @_;
  367.  
  368.     unless($Log::Log4perl::ALLOW_CODE_IN_CONFIG_FILE) {
  369.         die "\$Log::Log4perl::ALLOW_CODE_IN_CONFIG_FILE setting " .
  370.             "prohibits user defined cspecs";
  371.     }
  372.  
  373.     croak "Illegal value '$letter' in call to add_layout_cspec()"
  374.         unless ($letter =~ /^[a-zA-Z]$/);
  375.  
  376.     croak "Missing argument for perlcode for 'cspec.$letter' ".
  377.           "in call to add_layout_cspec()"
  378.         unless $perlcode;
  379.  
  380.     croak "Please don't redefine built-in cspecs [$CSPECS] \n".
  381.           "like you do for 'cspec.$letter'"
  382.         if ($CSPECS =~/$letter/);
  383.  
  384.     if (ref $perlcode eq 'CODE') {
  385.  
  386.         $self->{USER_DEFINED_CSPECS}{$letter} = $perlcode;
  387.  
  388.     }elsif (! ref $perlcode){
  389.         
  390.         $self->{USER_DEFINED_CSPECS}{$letter} =
  391.             Log::Log4perl::Config::compile_if_perl($perlcode);
  392.  
  393.         if ($@) {
  394.             die qq{Compilation failed for your perl code for }.
  395.                 qq{"cspec.$letter":\n}.
  396.                 qq{This is the error message: \t$@\n}.
  397.                 qq{This is the code that failed: \n$perlcode\n};
  398.         }
  399.         croak "eval'ing your perlcode for 'cspec.$letter' ".
  400.               "doesn't return a coderef \n".
  401.               "Here is the perl code: \n\t$perlcode\n "
  402.             unless (ref $self->{USER_DEFINED_CSPECS}{$letter} eq 'CODE');
  403.  
  404.  
  405.     }else{
  406.         croak "I don't know how to handle perlcode=$perlcode ".
  407.               "for 'cspec.$letter' in call to add_layout_cspec()";
  408.     }
  409.  
  410.     $self->{CSPECS} .= $letter;
  411. }
  412.  
  413.  
  414. 1;
  415.  
  416. __END__
  417.  
  418. =head1 NAME
  419.  
  420. Log::Log4perl::Layout::PatternLayout - Pattern Layout
  421.  
  422. =head1 SYNOPSIS
  423.  
  424.   use Log::Log4perl::Layout::PatternLayout;
  425.  
  426.   my $layout = Log::Log4perl::Layout::PatternLayout->new(
  427.                                                    "%d (%F:%L)> %m");
  428.  
  429.  
  430. =head1 DESCRIPTION
  431.  
  432. Creates a pattern layout according to
  433. http://jakarta.apache.org/log4j/docs/api/org/apache/log4j/PatternLayout.html.
  434.  
  435. The C<new()> method creates a new PatternLayout, specifying its log
  436. format. The format
  437. string can contain a number of placeholders which will be
  438. replaced by the logging engine when it's time to log the message:
  439.  
  440.     %c Category of the logging event.
  441.     %C Fully qualified package (or class) name of the caller
  442.     %d Current date in yyyy/MM/dd hh:mm:ss format
  443.     %F File where the logging event occurred
  444.     %H Hostname
  445.     %l Fully qualified name of the calling method followed by the
  446.        callers source the file name and line number between 
  447.        parentheses.
  448.     %L Line number within the file where the log statement was issued
  449.     %m The message to be logged
  450.     %M Method or function where the logging request was issued
  451.     %n Newline (OS-independent)
  452.     %p Priority of the logging event
  453.     %P pid of the current process
  454.     %r Number of milliseconds elapsed from program start to logging 
  455.        event
  456.     %x The topmost NDC (see below)
  457.     %X{key} The entry 'key' of the MDC (see below)
  458.     %% A literal percent (%) sign
  459.  
  460. NDC and MDC are explained in L<Log::Log4perl/"Nested Diagnostic Context (NDC)">
  461. and L<Log::Log4perl/"Mapped Diagnostic Context (MDC)">.
  462.  
  463. =head2 Quantify placeholders
  464.  
  465. All placeholders can be extended with formatting instructions,
  466. just like in I<printf>:
  467.  
  468.     %20c   Reserve 20 chars for the category, left-justify and fill
  469.            with blanks if it is shorter
  470.     %-20c  Same as %20c, but right-justify and fill the left side 
  471.            with blanks
  472.     %09r   Zero-pad the number of milliseconds to 9 digits
  473.     %.8c   Specify the maximum field with and have the formatter
  474.            cut off the rest of the value
  475.  
  476. =head2 Fine-tuning with curlies
  477.  
  478. Some placeholders have special functions defined if you add curlies 
  479. with content after them:
  480.  
  481.     %c{1}  Just show the right-most category compontent, useful in large
  482.            class hierarchies (Foo::Baz::Bar -> Bar)
  483.     %c{2}  Just show the two right most category components
  484.            (Foo::Baz::Bar -> Baz::Bar)
  485.  
  486.     %F     Display source file including full path
  487.     %F{1}  Just display filename
  488.     %F{2}  Display filename and last path component (dir/test.log)
  489.     %F{3}  Display filename and last two path components (d1/d2/test.log)
  490.  
  491. In this way, you're able to shrink the displayed category or
  492. limit file/path components to save space in your logs.
  493.  
  494. =head2 Fine-tune the date
  495.  
  496. If you're not happy with the default %d format for the date which 
  497. looks like
  498.  
  499.     yyyy/MM/DD HH:mm:ss
  500.  
  501. (which is slightly different from Log4j which uses C<yyyy-MM-dd HH:mm:ss,SSS>)
  502. you're free to fine-tune it in order to display only certain characteristics
  503. of a date, according to the SimpleDateFormat in the Java World
  504. (http://java.sun.com/j2se/1.3/docs/api/java/text/SimpleDateFormat.html):
  505.  
  506.     %d{HH:mm}     "23:45" -- Just display hours and minutes
  507.     %d{yy, EEEE}  "02, Monday" -- Just display two-digit year 
  508.                                   and spelled-out weekday
  509. Here's the symbols and their meaning, according to the SimpleDateFormat
  510. specification:
  511.  
  512.     Symbol   Meaning                 Presentation     Example
  513.     ------   -------                 ------------     -------
  514.     G        era designator          (Text)           AD
  515.     y        year                    (Number)         1996 
  516.     M        month in year           (Text & Number)  July & 07
  517.     d        day in month            (Number)         10
  518.     h        hour in am/pm (1-12)    (Number)         12
  519.     H        hour in day (0-23)      (Number)         0
  520.     m        minute in hour          (Number)         30
  521.     s        second in minute        (Number)         55
  522.     E        day in week             (Text)           Tuesday
  523.     D        day in year             (Number)         189
  524.     a        am/pm marker            (Text)           PM
  525.  
  526.     (Text): 4 or more pattern letters--use full form, < 4--use short or 
  527.             abbreviated form if one exists. 
  528.  
  529.     (Number): the minimum number of digits. Shorter numbers are 
  530.               zero-padded to this amount. Year is handled 
  531.               specially; that is, if the count of 'y' is 2, the 
  532.               Year will be truncated to 2 digits. 
  533.  
  534.     (Text & Number): 3 or over, use text, otherwise use number. 
  535.  
  536. There's also a bunch of pre-defined formats:
  537.  
  538.     %d{ABSOLUTE}   "HH:mm:ss,SSS"
  539.     %d{DATE}       "dd MMM yyyy HH:mm:ss,SSS"
  540.     %d{ISO8601}    "yyyy-MM-dd HH:mm:ss,SSS"
  541.  
  542. =head2 Custom cspecs
  543.  
  544. First of all, "cspecs" is short for "conversion specifiers", which is 
  545. the log4j and the printf(3) term for what Mike is calling "placeholders."
  546. I suggested "cspecs" for this part of the api before I saw that Mike was 
  547. using "placeholders" consistently in the log4perl documentation.  Ah, the
  548. joys of collaboration ;=) --kg
  549.  
  550. If the existing corpus of placeholders/cspecs isn't good enough for you,
  551. you can easily roll your own:
  552.  
  553.     #'U' a global user-defined cspec     
  554.     log4j.PatternLayout.cspec.U = sub { return "UID: $< "}
  555.     
  556.     #'K' cspec local to appndr1                 (pid in hex)
  557.     log4j.appender.appndr1.layout.cspec.K = sub { return sprintf "%1x", $$}
  558.     
  559.     #and now you can use them
  560.     log4j.appender.appndr1.layout.ConversionPattern = %K %U %m%n
  561.  
  562. The benefit of this approach is that you can define and use the cspecs 
  563. right next to each other in the config file.
  564.  
  565. If you're an API kind of person, there's also this call:
  566.  
  567.     Log::Log4perl::Layout::PatternLayout::
  568.                     add_global_cspec('Z', sub {'zzzzzzzz'}); #snooze?
  569.  
  570. When the log messages is being put together, your anonymous sub 
  571. will be called with these arguments:
  572.  
  573.     ($layout, $message, $category, $priority, $caller_level);
  574.     
  575.     layout: the PatternLayout object that called it
  576.     message: the logging message (%m)
  577.     category: e.g. groceries.beverages.adult.beer.schlitz
  578.     priority: e.g. DEBUG|WARN|INFO|ERROR|FATAL
  579.     caller_level: how many levels back up the call stack you have 
  580.         to go to find the caller
  581.  
  582. There are currently some issues around providing API access to an 
  583. appender-specific cspec, but let us know if this is something you want.
  584.  
  585. Please note that the subroutines you're defining in this way are going
  586. to be run in the C<main> namespace, so be sure to fully qualify functions
  587. and variables if they're located in different packages.
  588.  
  589. B<SECURITY NOTE>
  590.   
  591. This feature means arbitrary perl code can be embedded in the config file. 
  592. In the rare case where the people who have access to your config file are
  593. different from the people who write your code and shouldn't have execute
  594. rights, you might want to set
  595.  
  596.     $Log::Log4perl::Config->allow_code(0);
  597.  
  598. before you call init().  Alternatively you can supply a restricted set of
  599. Perl opcodes that can be embedded in the config file as described in
  600. L<Log::Log4perl/"Restricting what Opcodes can be in a Perl Hook">.
  601.   
  602. =head1 SEE ALSO
  603.  
  604. =head1 AUTHOR
  605.  
  606. Mike Schilli, E<lt>m@perlmeister.comE<gt>
  607.  
  608. =cut
  609.