1package Log::Log4perl::Config::DOMConfigurator;
2use Log::Log4perl::Config::BaseConfigurator;
3
4our @ISA = qw(Log::Log4perl::Config::BaseConfigurator);
5
6#todo
7# DONE(param-text) some params not attrs but values, like <sql>...</sql>
8# DONE see DEBUG!!!  below
9# NO, (really is only used for AsyncAppender) appender-ref in <appender>
10# DONE check multiple appenders in a category
11# DONE in Config.pm re URL loading, steal from XML::DOM
12# DONE, OK see PropConfigurator re importing unlog4j, eval_if_perl
13# NO (is specified in DTD) - need to handle 0/1, true/false?
14# DONEsee Config, need to check version of XML::DOM
15# OK user defined levels? see parse_level
16# OK make sure 2nd test is using log4perl constructs, not log4j
17# OK handle new filter stuff
18# make sure sample code actually works
19# try removing namespace prefixes in the xml
20
21use XML::DOM;
22use Log::Log4perl::Level;
23use strict;
24
25use constant _INTERNAL_DEBUG => 0;
26
27our $VERSION = 0.03;
28
29our $APPENDER_TAG = qr/^((log4j|log4perl):)?appender$/;
30
31our $FILTER_TAG = qr/^(log4perl:)?filter$/;
32our $FILTER_REF_TAG = qr/^(log4perl:)?filter-ref$/;
33
34#can't use ValParser here because we're using namespaces?
35#doesn't seem to work - kg 3/2003
36our $PARSER_CLASS = 'XML::DOM::Parser';
37
38our $LOG4J_PREFIX = 'log4j';
39our $LOG4PERL_PREFIX = 'log4perl';
40
41
42#poor man's export
43*eval_if_perl = \&Log::Log4perl::Config::eval_if_perl;
44*unlog4j      = \&Log::Log4perl::Config::unlog4j;
45
46
47###################################################
48sub parse {
49###################################################
50    my($self, $newtext) = @_;
51
52    $self->text($newtext) if defined $newtext;
53    my $text = $self->{text};
54
55    my $parser = $PARSER_CLASS->new;
56    my $doc = $parser->parse (join('',@$text));
57
58
59    my $l4p_tree = {};
60
61    my $config = $doc->getElementsByTagName("$LOG4J_PREFIX:configuration")->item(0)||
62                 $doc->getElementsByTagName("$LOG4PERL_PREFIX:configuration")->item(0);
63
64    my $threshold = uc(subst($config->getAttribute('threshold')));
65    if ($threshold) {
66        $l4p_tree->{threshold}{value} = $threshold;
67    }
68
69    if (subst($config->getAttribute('oneMessagePerAppender')) eq 'true') {
70        $l4p_tree->{oneMessagePerAppender}{value} = 1;
71    }
72
73    for my $kid ($config->getChildNodes){
74
75        next unless $kid->getNodeType == ELEMENT_NODE;
76
77        my $tag_name = $kid->getTagName;
78
79        if ($tag_name =~ $APPENDER_TAG) {
80            &parse_appender($l4p_tree, $kid);
81
82        }elsif ($tag_name eq 'category' || $tag_name eq 'logger'){
83            &parse_category($l4p_tree, $kid);
84            #Treating them the same is not entirely accurate,
85            #the dtd says 'logger' doesn't accept
86            #a 'class' attribute while 'category' does.
87            #But that's ok, log4perl doesn't do anything with that attribute
88
89        }elsif ($tag_name eq 'root'){
90            &parse_root($l4p_tree, $kid);
91
92        }elsif ($tag_name =~ $FILTER_TAG){
93            #parse log4perl's chainable boolean filters
94            &parse_l4p_filter($l4p_tree, $kid);
95
96        }elsif ($tag_name eq 'renderer'){
97            warn "Log4perl: ignoring renderer tag in config, unimplemented";
98            #"log4j will render the content of the log message according to
99            # user specified criteria. For example, if you frequently need
100            # to log Oranges, an object type used in your current project,
101            # then you can register an OrangeRenderer that will be invoked
102            # whenever an orange needs to be logged. "
103
104        }elsif ($tag_name eq 'PatternLayout'){#log4perl only
105            &parse_patternlayout($l4p_tree, $kid);
106        }
107    }
108    $doc->dispose;
109
110    return $l4p_tree;
111}
112
113#this is just for toplevel log4perl.PatternLayout tags
114#holding the custome cspecs
115sub parse_patternlayout {
116    my ($l4p_tree, $node) = @_;
117
118    my $l4p_branch = {};
119
120    for my $child ($node->getChildNodes) {
121        next unless $child->getNodeType == ELEMENT_NODE;
122
123        my $name = subst($child->getAttribute('name'));
124        my $value;
125
126        foreach my $grandkid ($child->getChildNodes){
127            if ($grandkid->getNodeType == TEXT_NODE) {
128                $value .= $grandkid->getData;
129            }
130        }
131        $value =~ s/^ +//;  #just to make the unit tests pass
132        $value =~ s/ +$//;
133        $l4p_branch->{$name}{value} = subst($value);
134    }
135    $l4p_tree->{PatternLayout}{cspec} = $l4p_branch;
136}
137
138
139#for parsing the root logger, if any
140sub parse_root {
141    my ($l4p_tree, $node) = @_;
142
143    my $l4p_branch = {};
144
145    &parse_children_of_logger_element($l4p_branch, $node);
146
147    $l4p_tree->{category}{value} = $l4p_branch->{value};
148
149}
150
151
152#this parses a custom log4perl-specific filter set up under
153#the root element, as opposed to children of the appenders
154sub parse_l4p_filter {
155    my ($l4p_tree, $node) = @_;
156
157    my $l4p_branch = {};
158
159    my $name = subst($node->getAttribute('name'));
160
161    my $class = subst($node->getAttribute('class'));
162    my $value = subst($node->getAttribute('value'));
163
164    if ($class && $value) {
165        die "Log4perl: only one of class or value allowed, not both, "
166            ."in XMLConfig filter '$name'";
167    }elsif ($class || $value){
168        $l4p_branch->{value} = ($value || $class);
169
170    }
171
172    for my $child ($node->getChildNodes) {
173
174        if ($child->getNodeType == ELEMENT_NODE){
175
176            my $tag_name = $child->getTagName();
177
178            if ($tag_name =~ /^(param|param-nested|param-text)$/) {
179                &parse_any_param($l4p_branch, $child);
180            }
181        }elsif ($child->getNodeType == TEXT_NODE){
182            my $text = $child->getData;
183            next unless $text =~ /\S/;
184            if ($class && $value) {
185                die "Log4perl: only one of class, value or PCDATA allowed, "
186                    ."in XMLConfig filter '$name'";
187            }
188            $l4p_branch->{value} .= subst($text);
189        }
190    }
191
192    $l4p_tree->{filter}{$name} = $l4p_branch;
193}
194
195
196#for parsing a category/logger element
197sub parse_category {
198    my ($l4p_tree, $node) = @_;
199
200    my $name = subst($node->getAttribute('name'));
201
202    $l4p_tree->{category} ||= {};
203
204    my $ptr = $l4p_tree->{category};
205
206    for my $part (split /\.|::/, $name) {
207        $ptr->{$part} = {} unless exists $ptr->{$part};
208        $ptr = $ptr->{$part};
209    }
210
211    my $l4p_branch = $ptr;
212
213    my $class = subst($node->getAttribute('class'));
214    $class                       &&
215       $class ne 'Log::Log4perl' &&
216       $class ne 'org.apache.log4j.Logger' &&
217       warn "setting category $name to class $class ignored, only Log::Log4perl implemented";
218
219    #this is kind of funky, additivity has its own spot in the tree
220    my $additivity = subst(subst($node->getAttribute('additivity')));
221    if (length $additivity > 0) {
222        $l4p_tree->{additivity} ||= {};
223        my $add_ptr = $l4p_tree->{additivity};
224
225        for my $part (split /\.|::/, $name) {
226            $add_ptr->{$part} = {} unless exists $add_ptr->{$part};
227            $add_ptr = $add_ptr->{$part};
228        }
229        $add_ptr->{value} = &parse_boolean($additivity);
230    }
231
232    &parse_children_of_logger_element($l4p_branch, $node);
233}
234
235# parses the children of a category element
236sub parse_children_of_logger_element {
237    my ($l4p_branch, $node) = @_;
238
239    my (@appenders, $priority);
240
241    for my $child ($node->getChildNodes) {
242        next unless $child->getNodeType == ELEMENT_NODE;
243
244        my $tag_name = $child->getTagName();
245
246        if ($tag_name eq 'param') {
247            my $name = subst($child->getAttribute('name'));
248            my $value = subst($child->getAttribute('value'));
249            if ($value =~ /^(all|debug|info|warn|error|fatal|off|null)^/) {
250                $value = uc $value;
251            }
252            $l4p_branch->{$name} = {value => $value};
253
254        }elsif ($tag_name eq 'appender-ref'){
255            push @appenders, subst($child->getAttribute('ref'));
256
257        }elsif ($tag_name eq 'level' || $tag_name eq 'priority'){
258            $priority = &parse_level($child);
259        }
260    }
261    $l4p_branch->{value} = $priority.', '.join(',', @appenders);
262
263    return;
264}
265
266
267sub parse_level {
268    my $node = shift;
269
270    my $level = uc (subst($node->getAttribute('value')));
271
272    die "Log4perl: invalid level in config: $level"
273        unless Log::Log4perl::Level::is_valid($level);
274
275    return $level;
276}
277
278
279
280sub parse_appender {
281    my ($l4p_tree, $node) = @_;
282
283    my $name = subst($node->getAttribute("name"));
284
285    my $l4p_branch = {};
286
287    my $class = subst($node->getAttribute("class"));
288
289    $l4p_branch->{value} = $class;
290
291    print "looking at $name----------------------\n"  if _INTERNAL_DEBUG;
292
293    for my $child ($node->getChildNodes) {
294        next unless $child->getNodeType == ELEMENT_NODE;
295
296        my $tag_name = $child->getTagName();
297
298        my $name = unlog4j(subst($child->getAttribute('name')));
299
300        if ($tag_name =~ /^(param|param-nested|param-text)$/) {
301
302            &parse_any_param($l4p_branch, $child);
303
304            my $value;
305
306        }elsif ($tag_name =~ /($LOG4PERL_PREFIX:)?layout/){
307            $l4p_branch->{layout} = parse_layout($child);
308
309        }elsif ($tag_name =~  $FILTER_TAG){
310            $l4p_branch->{Filter} = parse_filter($child);
311
312        }elsif ($tag_name =~ $FILTER_REF_TAG){
313            $l4p_branch->{Filter} = parse_filter_ref($child);
314
315        }elsif ($tag_name eq 'errorHandler'){
316            die "errorHandlers not supported yet";
317
318        }elsif ($tag_name eq 'appender-ref'){
319            #dtd: Appenders may also reference (or include) other appenders.
320            #This feature in log4j is only for appenders who implement the
321            #AppenderAttachable interface, and the only one that does that
322            #is the AsyncAppender, which writes logs in a separate thread.
323            #I don't see the need to support this on the perl side any
324            #time soon.  --kg 3/2003
325            die "Log4perl: in config file, <appender-ref> tag is unsupported in <appender>";
326        }else{
327            die "Log4perl: in config file, <$tag_name> is unsupported\n";
328        }
329    }
330    $l4p_tree->{appender}{$name} = $l4p_branch;
331}
332
333sub parse_any_param {
334    my ($l4p_branch, $child) = @_;
335
336    my $tag_name = $child->getTagName();
337    my $name = subst($child->getAttribute('name'));
338    my $value;
339
340    print "parse_any_param: <$tag_name name=$name\n" if _INTERNAL_DEBUG;
341
342    #<param-nested>
343    #note we don't set it to { value => $value }
344    #and we don't test for multiple values
345    if ($tag_name eq 'param-nested'){
346
347        if ($l4p_branch->{$name}){
348            die "Log4perl: in config file, multiple param-nested tags for $name not supported";
349        }
350        $l4p_branch->{$name} = &parse_param_nested($child);
351
352        return;
353
354    #<param>
355    }elsif ($tag_name eq 'param') {
356
357         $value = subst($child->getAttribute('value'));
358
359         print "parse_param_nested: got param $name = $value\n"
360             if _INTERNAL_DEBUG;
361
362         if ($value =~ /^(all|debug|info|warn|error|fatal|off|null)$/) {
363             $value = uc $value;
364         }
365
366         if ($name !~ /warp_message|filter/ &&
367            $child->getParentNode->getAttribute('name') ne 'cspec') {
368            $value = eval_if_perl($value);
369         }
370    #<param-text>
371    }elsif ($tag_name eq 'param-text'){
372
373        foreach my $grandkid ($child->getChildNodes){
374            if ($grandkid->getNodeType == TEXT_NODE) {
375                $value .= $grandkid->getData;
376            }
377        }
378        if ($name !~ /warp_message|filter/ &&
379            $child->getParentNode->getAttribute('name') ne 'cspec') {
380            $value = eval_if_perl($value);
381        }
382    }
383
384    $value = subst($value);
385
386     #multiple values for the same param name
387     if (defined $l4p_branch->{$name}{value} ) {
388         if (ref $l4p_branch->{$name}{value} ne 'ARRAY'){
389             my $temp = $l4p_branch->{$name}{value};
390             $l4p_branch->{$name}{value} = [$temp];
391         }
392         push @{$l4p_branch->{$name}{value}}, $value;
393     }else{
394         $l4p_branch->{$name} = {value => $value};
395     }
396}
397
398#handles an appender's <param-nested> elements
399sub parse_param_nested {
400    my ($node) = shift;
401
402    my $l4p_branch = {};
403
404    for my $child ($node->getChildNodes) {
405        next unless $child->getNodeType == ELEMENT_NODE;
406
407        my $tag_name = $child->getTagName();
408
409        if ($tag_name =~ /^param|param-nested|param-text$/) {
410            &parse_any_param($l4p_branch, $child);
411        }
412    }
413
414    return $l4p_branch;
415}
416
417#this handles filters that are children of appenders, as opposed
418#to the custom filters that go under the root element
419sub parse_filter {
420    my $node = shift;
421
422    my $filter_tree = {};
423
424    my $class_name = subst($node->getAttribute('class'));
425
426    $filter_tree->{value} = $class_name;
427
428    print "\tparsing filter on class $class_name\n"  if _INTERNAL_DEBUG;
429
430    for my $child ($node->getChildNodes) {
431        next unless $child->getNodeType == ELEMENT_NODE;
432
433        my $tag_name = $child->getTagName();
434
435        if ($tag_name =~ 'param|param-nested|param-text') {
436            &parse_any_param($filter_tree, $child);
437
438        }else{
439            die "Log4perl: don't know what to do with a ".$child->getTagName()
440                ."inside a filter element";
441        }
442    }
443    return $filter_tree;
444}
445
446sub parse_filter_ref {
447    my $node = shift;
448
449    my $filter_tree = {};
450
451    my $filter_id = subst($node->getAttribute('id'));
452
453    $filter_tree->{value} = $filter_id;
454
455    return $filter_tree;
456}
457
458
459
460sub parse_layout {
461    my $node = shift;
462
463    my $layout_tree = {};
464
465    my $class_name = subst($node->getAttribute('class'));
466
467    $layout_tree->{value} = $class_name;
468    #
469    print "\tparsing layout $class_name\n"  if _INTERNAL_DEBUG;
470    for my $child ($node->getChildNodes) {
471        next unless $child->getNodeType == ELEMENT_NODE;
472        if ($child->getTagName() eq 'param') {
473            my $name = subst($child->getAttribute('name'));
474            my $value = subst($child->getAttribute('value'));
475            if ($value =~ /^(all|debug|info|warn|error|fatal|off|null)$/) {
476                $value = uc $value;
477            }
478            print "\tparse_layout: got param $name = $value\n"
479                if _INTERNAL_DEBUG;
480            $layout_tree->{$name}{value} = $value;
481
482        }elsif ($child->getTagName() eq 'cspec') {
483            my $name = subst($child->getAttribute('name'));
484            my $value;
485            foreach my $grandkid ($child->getChildNodes){
486                if ($grandkid->getNodeType == TEXT_NODE) {
487                    $value .= $grandkid->getData;
488                }
489            }
490            $value =~ s/^ +//;
491            $value =~ s/ +$//;
492            $layout_tree->{cspec}{$name}{value} = subst($value);
493        }
494    }
495    return $layout_tree;
496}
497
498sub parse_boolean {
499    my $a = shift;
500
501    if ($a eq '0' || lc $a eq 'false') {
502        return '0';
503    }elsif ($a eq '1' || lc $a eq 'true'){
504        return '1';
505    }else{
506        return $a; #probably an error, punt
507    }
508}
509
510
511#this handles variable substitution
512sub subst {
513    my $val = shift;
514
515    $val =~ s/\$\{(.*?)}/
516                      Log::Log4perl::Config::var_subst($1, {})/gex;
517    return $val;
518}
519
5201;
521
522__END__
523
524=head1 NAME
525
526Log::Log4perl::Config::DOMConfigurator - reads xml config files
527
528=head1 SYNOPSIS
529
530    --------------------------
531    --using the log4j DTD--
532    --------------------------
533
534    <?xml version="1.0" encoding="UTF-8"?>
535    <!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">
536
537    <log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/">
538
539    <appender name="FileAppndr1" class="org.apache.log4j.FileAppender">
540        <layout class="Log::Log4perl::Layout::PatternLayout">
541                <param name="ConversionPattern"
542                       value="%d %4r [%t] %-5p %c %t - %m%n"/>
543        </layout>
544        <param name="File" value="t/tmp/DOMtest"/>
545        <param name="Append" value="false"/>
546    </appender>
547
548    <category name="a.b.c.d" additivity="false">
549        <level value="warn"/>  <!-- note lowercase! -->
550        <appender-ref ref="FileAppndr1"/>
551    </category>
552
553   <root>
554        <priority value="warn"/>
555        <appender-ref ref="FileAppndr1"/>
556   </root>
557
558   </log4j:configuration>
559
560
561
562   --------------------------
563   --using the log4perl DTD--
564   --------------------------
565
566   <?xml version="1.0" encoding="UTF-8"?>
567    <!DOCTYPE log4perl:configuration SYSTEM "log4perl.dtd">
568
569    <log4perl:configuration xmlns:log4perl="http://log4perl.sourceforge.net/"
570        threshold="debug" oneMessagePerAppender="true">
571
572    <log4perl:appender name="jabbender" class="Log::Dispatch::Jabber">
573
574            <param-nested name="login">
575                   <param name="hostname" value="a.jabber.server"/>
576                   <param name="password" value="12345"/>
577                   <param name="port"     value="5222"/>
578                   <param name="resource" value="logger"/>
579                   <param name="username" value="bobjones"/>
580            </param-nested>
581
582            <param name="to" value="bob@a.jabber.server"/>
583
584            <param-text name="to">
585                  mary@another.jabber.server
586            </param-text>
587
588            <log4perl:layout class="org.apache.log4j.PatternLayout">
589                <param name="ConversionPattern" value = "%K xx %G %U"/>
590                <cspec name="K">
591                    sub { return sprintf "%1x", $$}
592                </cspec>
593                <cspec name="G">
594                    sub {return 'thisistheGcspec'}
595                </cspec>
596            </log4perl:layout>
597    </log4perl:appender>
598
599    <log4perl:appender name="DBAppndr2" class="Log::Log4perl::Appender::DBI">
600              <param name="warp_message" value="0"/>
601              <param name="datasource" value="DBI:CSV:f_dir=t/tmp"/>
602              <param name="bufferSize" value="2"/>
603              <param name="password" value="sub { $ENV{PWD} }"/>
604              <param name="username" value="bobjones"/>
605
606              <param-text name="sql">
607                  INSERT INTO log4perltest
608                            (loglevel, message, shortcaller, thingid,
609                            category, pkg, runtime1, runtime2)
610                  VALUES
611                             (?,?,?,?,?,?,?,?)
612              </param-text>
613
614               <param-nested name="params">
615                    <param name="1" value="%p"/>
616                    <param name="3" value="%5.5l"/>
617                    <param name="5" value="%c"/>
618                    <param name="6" value="%C"/>
619               </param-nested>
620
621               <layout class="Log::Log4perl::Layout::NoopLayout"/>
622    </log4perl:appender>
623
624    <category name="animal.dog">
625               <priority value="info"/>
626               <appender-ref ref="jabbender"/>
627               <appender-ref ref="DBAppndr2"/>
628    </category>
629
630    <category name="plant">
631            <priority value="debug"/>
632            <appender-ref ref="DBAppndr2"/>
633    </category>
634
635    <PatternLayout>
636        <cspec name="U"><![CDATA[
637            sub {
638                return "UID $< GID $(";
639            }
640        ]]></cspec>
641    </PatternLayout>
642
643    </log4perl:configuration>
644
645
646
647
648=head1 DESCRIPTION
649
650This module implements an XML config, complementing the properties-style
651config described elsewhere.
652
653=head1 WHY
654
655"Why would I want my config in XML?" you ask.  Well, there are a couple
656reasons you might want to.  Maybe you have a personal preference
657for XML.  Maybe you manage your config with other tools that have an
658affinity for XML, like XML-aware editors or automated config
659generators.  Or maybe (and this is the big one) you don't like
660having to run your application just to check the syntax of your
661config file.
662
663By using an XML config and referencing a DTD, you can use a namespace-aware
664validating parser to see if your XML config at least follows the rules set
665in the DTD.
666
667=head1 HOW
668
669To reference a DTD, drop this in after the <?xml...> declaration
670in your config file:
671
672    <!DOCTYPE log4perl:configuration SYSTEM "log4perl.dtd">
673
674That tells the parser to validate your config against the DTD in
675"log4perl.dtd", which is available in the xml/ directory of
676the log4perl distribution.  Note that you'll also need to grab
677the log4j-1.2.dtd from there as well, since the it's included
678by log4perl.dtd.
679
680Namespace-aware validating parsers are not the norm in Perl.
681But the Xerces project
682(http://xml.apache.org/xerces-c/index.html --lots of binaries available,
683even rpm's)  does provide just such a parser
684that you can use like this:
685
686    StdInParse -ns -v < my-log4perl-config.xml
687
688This module itself does not use a validating parser, the obvious
689one XML::DOM::ValParser doesn't seem to handle namespaces.
690
691=head1 WHY TWO DTDs
692
693The log4j DTD is from the log4j project, they designed it to
694handle their needs.  log4perl has added some extensions to the
695original log4j functionality which needed some extensions to the
696log4j DTD.  If you aren't using these features then you can validate
697your config against the log4j dtd and know that you're using
698unadulterated log4j config tags.
699
700The features added by the log4perl dtd are:
701
702=over 4
703
704=item 1 oneMessagePerAppender global setting
705
706    log4perl.oneMessagePerAppender=1
707
708=item 2 globally defined user conversion specifiers
709
710    log4perl.PatternLayout.cspec.G=sub { return "UID $< GID $("; }
711
712=item 3 appender-local custom conversion specifiers
713
714     log4j.appender.appndr1.layout.cspec.K = sub {return sprintf "%1x", $$ }
715
716=item 4 nested options
717
718     log4j.appender.jabbender          = Log::Dispatch::Jabber
719     #(note how these are nested under 'login')
720     log4j.appender.jabbender.login.hostname = a.jabber.server
721     log4j.appender.jabbender.login.port     = 5222
722     log4j.appender.jabbender.login.username = bobjones
723
724=item 5 the log4perl-specific filters, see L<Log::Log4perl::Filter>,
725lots of examples in t/044XML-Filter.t, here's a short one:
726
727
728  <?xml version="1.0" encoding="UTF-8"?>
729  <!DOCTYPE log4perl:configuration SYSTEM "log4perl.dtd">
730
731  <log4perl:configuration xmlns:log4perl="http://log4perl.sourceforge.net/">
732
733  <appender name="A1" class="Log::Log4perl::Appender::TestBuffer">
734        <layout class="Log::Log4perl::Layout::SimpleLayout"/>
735        <filter class="Log::Log4perl::Filter::Boolean">
736            <param name="logic" value="!Match3 &amp;&amp; (Match1 || Match2)"/>
737        </filter>
738  </appender>
739
740  <appender name="A2" class="Log::Log4perl::Appender::TestBuffer">
741        <layout class="Log::Log4perl::Layout::SimpleLayout"/>
742        <filter-ref id="Match1"/>
743  </appender>
744
745  <log4perl:filter name="Match1" value="sub { /let this through/ }" />
746
747  <log4perl:filter name="Match2">
748        sub {
749            /and that, too/
750        }
751   </log4perl:filter>
752
753  <log4perl:filter name="Match3" class="Log::Log4perl::Filter::StringMatch">
754    <param name="StringToMatch" value="suppress"/>
755    <param name="AcceptOnMatch" value="true"/>
756  </log4perl:filter>
757
758  <log4perl:filter name="MyBoolean" class="Log::Log4perl::Filter::Boolean">
759    <param name="logic" value="!Match3 &amp;&amp; (Match1 || Match2)"/>
760  </log4perl:filter>
761
762
763   <root>
764           <priority value="info"/>
765           <appender-ref ref="A1"/>
766   </root>
767
768   </log4perl:configuration>
769
770
771=back
772
773
774So we needed to extend the log4j dtd to cover these additions.
775Now I could have just taken a 'steal this code' approach and mixed
776parts of the log4j dtd into a log4perl dtd, but that would be
777cut-n-paste programming.  So I've used namespaces and
778
779=over 4
780
781=item *
782
783replaced three elements:
784
785=over 4
786
787=item <log4perl:configuration>
788
789handles #1) and accepts <PatternLayout>
790
791=item <log4perl:appender>
792
793accepts <param-nested> and <param-text>
794
795=item <log4perl:layout>
796
797accepts custom cspecs for #3)
798
799=back
800
801=item *
802
803added a <param-nested> element (complementing the <param> element)
804    to handle #4)
805
806=item *
807
808added a root <PatternLayout> element to handle #2)
809
810=item *
811
812added <param-text> which lets you put things like perl code
813    into escaped CDATA between the tags, so you don't have to worry
814    about escaping characters and quotes
815
816=item *
817
818added <cspec>
819
820=back
821
822See the examples up in the L<"SYNOPSIS"> for how all that gets used.
823
824=head1 WHY NAMESPACES
825
826I liked the idea of using the log4j DTD I<in situ>, so I used namespaces
827to extend it.  If you really don't like having to type <log4perl:appender>
828instead of just <appender>, you can make your own DTD combining
829the two DTDs and getting rid of the namespace prefixes.  Then you can
830validate against that, and log4perl should accept it just fine.
831
832=head1 VARIABLE SUBSTITUTION
833
834This supports variable substitution like C<${foobar}> in text and in
835attribute values except for appender-ref.  If an environment variable is defined
836for that name, its value is substituted. So you can do stuff like
837
838        <param name="${hostname}" value="${hostnameval}.foo.com"/>
839        <param-text name="to">${currentsysadmin}@foo.com</param-text>
840
841
842=head1 REQUIRES
843
844To use this module you need XML::DOM installed.
845
846To use the log4perl.dtd, you'll have to reference it in your XML config,
847and you'll also need to note that log4perl.dtd references the
848log4j dtd as "log4j-1.2.dtd", so your validator needs to be able
849to find that file as well.  If you don't like having to schlep two
850files around, feel free
851to dump the contents of "log4j-1.2.dtd" into your "log4perl.dtd" file.
852
853=head1 CAVEATS
854
855You can't mix a multiple param-nesteds with the same name, I'm going to
856leave that for now, there's presently no need for a list of structs
857in the config.
858
859=head1 CHANGES
860
8610.03 2/26/2003 Added support for log4perl extensions to the log4j dtd
862
863=head1 SEE ALSO
864
865t/038XML-DOM1.t, t/039XML-DOM2.t for examples
866
867xml/log4perl.dtd, xml/log4j-1.2.dtd
868
869Log::Log4perl::Config
870
871Log::Log4perl::Config::PropertyConfigurator
872
873Log::Log4perl::Config::LDAPConfigurator (coming soon!)
874
875The code is brazenly modeled on log4j's DOMConfigurator class, (by
876Christopher Taylor, Ceki Gülcü, and Anders Kristensen) and any
877perceived similarity is not coincidental.
878
879=head1 LICENSE
880
881Copyright 2002-2012 by Mike Schilli E<lt>m@perlmeister.comE<gt>
882and Kevin Goess E<lt>cpan@goess.orgE<gt>.
883
884This library is free software; you can redistribute it and/or modify
885it under the same terms as Perl itself.
886
887=head1 AUTHOR
888
889Please contribute patches to the project on Github:
890
891    http://github.com/mschilli/log4perl
892
893Send bug reports or requests for enhancements to the authors via our
894
895MAILING LIST (questions, bug reports, suggestions/patches):
896log4perl-devel@lists.sourceforge.net
897
898Authors (please contact them via the list above, not directly):
899Mike Schilli <m@perlmeister.com>,
900Kevin Goess <cpan@goess.org>
901
902Contributors (in alphabetical order):
903Ateeq Altaf, Cory Bennett, Jens Berthold, Jeremy Bopp, Hutton
904Davidson, Chris R. Donnelly, Matisse Enzer, Hugh Esco, Anthony
905Foiani, James FitzGibbon, Carl Franks, Dennis Gregorovic, Andy
906Grundman, Paul Harrington, David Hull, Robert Jacobson, Jason Kohles,
907Jeff Macdonald, Markus Peter, Brett Rann, Peter Rabbitson, Erik
908Selberg, Aaron Straup Cope, Lars Thegler, David Viner, Mac Yang.
909
910