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 && (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 && (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