View Javadoc

    1   /*
    2    * Licensed to the Apache Software Foundation (ASF) under one or more
    3    * contributor license agreements.  See the NOTICE file distributed with
    4    * this work for additional information regarding copyright ownership.
    5    * The ASF licenses this file to You under the Apache License, Version 2.0
    6    * (the "License"); you may not use this file except in compliance with
    7    * the License.  You may obtain a copy of the License at
    8    *
    9    *      http://www.apache.org/licenses/LICENSE-2.0
    10   *
    11   * Unless required by applicable law or agreed to in writing, software
    12   * distributed under the License is distributed on an "AS IS" BASIS,
    13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    14   * See the License for the specific language governing permissions and
    15   * limitations under the License.
    16   */
    17
    18  package org.apache.log4j.xml;
    19
    20  import org.apache.log4j.Layout;
    21  import org.apache.log4j.LayoutTest;
    22  import org.apache.log4j.Level;
    23  import org.apache.log4j.Logger;
    24  import org.apache.log4j.NDC;
    25  import org.apache.log4j.MDC;
    26  import org.apache.log4j.spi.LoggingEvent;
    27
    28  import org.w3c.dom.Document;
    29  import org.w3c.dom.Element;
    30  import org.w3c.dom.Node;
    31  import org.w3c.dom.NodeList;
    32
    33  import org.xml.sax.InputSource;
    34
    35  import java.io.Reader;
    36  import java.io.StringReader;
    37  import java.util.Hashtable;
    38
    39  import javax.xml.parsers.DocumentBuilder;
    40  import javax.xml.parsers.DocumentBuilderFactory;
    41
    42
    43  /***
    44   * Test for XMLLayout.
    45   *
    46   * @author Curt Arnold
    47   */
    48  public class XMLLayoutTest extends LayoutTest {
    49    /***
    50     * Construct new instance of XMLLayoutTest.
    51     *
    52     * @param testName test name.
    53     */
    54    public XMLLayoutTest(final String testName) {
    55      super(testName, "text/plain", false, null, null);
    56    }
    57
    58      /***
    59       * Clear MDC and NDC before test.
    60       */
    61    public void setUp() {
    62        NDC.clear();
    63        if (MDC.getContext() != null) {
    64          MDC.getContext().clear();
    65        }
    66    }
    67
    68      /***
    69       * Clear MDC and NDC after test.
    70       */
    71    public void tearDown() {
    72        setUp();
    73    }
    74
    75    /***
    76     * @{inheritDoc}
    77     */
    78    protected Layout createLayout() {
    79      return new XMLLayout();
    80    }
    81
    82    /***
    83     * Parses the string as the body of an XML document and returns the document element.
    84     * @param source source string.
    85     * @return document element.
    86     * @throws Exception if parser can not be constructed or source is not a valid XML document.
    87     */
    88    private Element parse(final String source) throws Exception {
    89      DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
    90      factory.setNamespaceAware(false);
    91      factory.setCoalescing(true);
    92
    93      DocumentBuilder builder = factory.newDocumentBuilder();
    94      Reader reader = new StringReader(source);
    95      Document doc = builder.parse(new InputSource(reader));
    96
    97      return doc.getDocumentElement();
    98    }
    99
    100   /***
    101    * Checks a log4j:event element against expectations.
    102    * @param element element, may not be null.
    103    * @param event event, may not be null.
    104    */
    105   private void checkEventElement(
    106     final Element element, final LoggingEvent event) {
    107     assertEquals("log4j:event", element.getTagName());
    108     assertEquals(
    109       event.getLoggerName(), element.getAttribute("logger"));
    110     assertEquals(
    111       Long.toString(event.timeStamp), element.getAttribute("timestamp"));
    112     assertEquals(event.getLevel().toString(), element.getAttribute("level"));
    113     assertEquals(event.getThreadName(), element.getAttribute("thread"));
    114   }
    115
    116   /***
    117    * Checks a log4j:message element against expectations.
    118    * @param element element, may not be null.
    119    * @param message expected message.
    120    */
    121   private void checkMessageElement(
    122     final Element element, final String message) {
    123     assertEquals("log4j:message", element.getTagName());
    124
    125     Node messageNode = element.getFirstChild();
    126     assertNotNull(messageNode);
    127     assertEquals(Node.TEXT_NODE, messageNode.getNodeType());
    128     assertEquals(message, messageNode.getNodeValue());
    129     assertNull(messageNode.getNextSibling());
    130   }
    131
    132   /***
    133    * Checks a log4j:message element against expectations.
    134    * @param element element, may not be null.
    135    * @param message expected message.
    136    */
    137   private void checkNDCElement(final Element element, final String message) {
    138     assertEquals("log4j:NDC", element.getTagName());
    139
    140     Node messageNode = element.getFirstChild();
    141     assertNotNull(messageNode);
    142     assertEquals(Node.TEXT_NODE, messageNode.getNodeType());
    143     assertEquals(message, messageNode.getNodeValue());
    144     assertNull(messageNode.getNextSibling());
    145   }
    146
    147   /***
    148    * Checks a log4j:throwable element against expectations.
    149    * @param element element, may not be null.
    150    * @param ex exception, may not be null.
    151    */
    152   private void checkThrowableElement(
    153     final Element element, final Exception ex) {
    154     assertEquals("log4j:throwable", element.getTagName());
    155
    156     Node messageNode = element.getFirstChild();
    157     assertNotNull(messageNode);
    158     assertEquals(Node.TEXT_NODE, messageNode.getNodeType());
    159
    160     String msg = ex.toString();
    161     assertEquals(msg, messageNode.getNodeValue().substring(0, msg.length()));
    162     assertNull(messageNode.getNextSibling());
    163   }
    164
    165     /***
    166      * Checks a log4j:properties element against expectations.
    167      * @param element element, may not be null.
    168      * @param key key.
    169      * @param value value.
    170      */
    171     private void checkPropertiesElement(
    172       final Element element, final String key, final String value) {
    173       assertEquals("log4j:properties", element.getTagName());
    174
    175       int childNodeCount = 0;
    176       for(Node child = element.getFirstChild();
    177                child != null;
    178                child = child.getNextSibling()) {
    179           if (child.getNodeType() == Node.ELEMENT_NODE) {
    180               assertEquals("log4j:data", child.getNodeName());
    181               Element childElement = (Element) child;
    182               assertEquals(key, childElement.getAttribute("name"));
    183               assertEquals(value, childElement.getAttribute("value"));
    184               childNodeCount++;
    185           }
    186       }
    187       assertEquals(1, childNodeCount);
    188     }
    189
    190   /***
    191    * Tests formatted results.
    192    * @throws Exception if parser can not be constructed or source is not a valid XML document.
    193    */
    194   public void testFormat() throws Exception {
    195     Logger logger = Logger.getLogger("org.apache.log4j.xml.XMLLayoutTest");
    196     LoggingEvent event =
    197       new LoggingEvent(
    198         "org.apache.log4j.Logger", logger, Level.INFO, "Hello, World", null);
    199     XMLLayout layout = (XMLLayout) createLayout();
    200     String result = layout.format(event);
    201     Element parsedResult = parse(result);
    202     checkEventElement(parsedResult, event);
    203
    204     int childElementCount = 0;
    205
    206     for (
    207       Node node = parsedResult.getFirstChild(); node != null;
    208         node = node.getNextSibling()) {
    209       switch (node.getNodeType()) {
    210       case Node.ELEMENT_NODE:
    211         childElementCount++;
    212         checkMessageElement((Element) node, "Hello, World");
    213
    214         break;
    215
    216       case Node.COMMENT_NODE:
    217         break;
    218
    219       case Node.TEXT_NODE:
    220
    221         //  should only be whitespace
    222         break;
    223
    224       default:
    225         fail("Unexpected node type");
    226
    227         break;
    228       }
    229     }
    230
    231     assertEquals(1, childElementCount);
    232   }
    233
    234   /***
    235    * Tests formatted results with an exception.
    236    * @throws Exception if parser can not be constructed or source is not a valid XML document.
    237    */
    238   public void testFormatWithException() throws Exception {
    239     Logger logger = Logger.getLogger("org.apache.log4j.xml.XMLLayoutTest");
    240     Exception ex = new IllegalArgumentException("'foo' is not a valid name");
    241     LoggingEvent event =
    242       new LoggingEvent(
    243         "org.apache.log4j.Logger", logger, Level.INFO, "Hello, World", ex);
    244     XMLLayout layout = (XMLLayout) createLayout();
    245     String result = layout.format(event);
    246     Element parsedResult = parse(result);
    247     checkEventElement(parsedResult, event);
    248
    249     int childElementCount = 0;
    250
    251     for (
    252       Node node = parsedResult.getFirstChild(); node != null;
    253         node = node.getNextSibling()) {
    254       switch (node.getNodeType()) {
    255       case Node.ELEMENT_NODE:
    256         childElementCount++;
    257
    258         if (childElementCount == 1) {
    259           checkMessageElement((Element) node, "Hello, World");
    260         } else {
    261           checkThrowableElement((Element) node, ex);
    262         }
    263
    264         break;
    265
    266       case Node.COMMENT_NODE:
    267         break;
    268
    269       case Node.TEXT_NODE:
    270
    271         //  should only be whitespace
    272         break;
    273
    274       default:
    275         fail("Unexpected node type");
    276
    277         break;
    278       }
    279     }
    280
    281     assertEquals(2, childElementCount);
    282   }
    283
    284   /***
    285    * Tests formatted results with an exception.
    286    * @throws Exception if parser can not be constructed or source is not a valid XML document.
    287    */
    288   public void testFormatWithNDC() throws Exception {
    289     Logger logger = Logger.getLogger("org.apache.log4j.xml.XMLLayoutTest");
    290     NDC.push("NDC goes here");
    291
    292     LoggingEvent event =
    293       new LoggingEvent(
    294         "org.apache.log4j.Logger", logger, Level.INFO, "Hello, World", null);
    295     XMLLayout layout = (XMLLayout) createLayout();
    296     String result = layout.format(event);
    297     NDC.pop();
    298
    299     Element parsedResult = parse(result);
    300     checkEventElement(parsedResult, event);
    301
    302     int childElementCount = 0;
    303
    304     for (
    305       Node node = parsedResult.getFirstChild(); node != null;
    306         node = node.getNextSibling()) {
    307       switch (node.getNodeType()) {
    308       case Node.ELEMENT_NODE:
    309         childElementCount++;
    310
    311         if (childElementCount == 1) {
    312           checkMessageElement((Element) node, "Hello, World");
    313         } else {
    314           checkNDCElement((Element) node, "NDC goes here");
    315         }
    316
    317         break;
    318
    319       case Node.COMMENT_NODE:
    320         break;
    321
    322       case Node.TEXT_NODE:
    323
    324         //  should only be whitespace
    325         break;
    326
    327       default:
    328         fail("Unexpected node type");
    329
    330         break;
    331       }
    332     }
    333
    334     assertEquals(2, childElementCount);
    335   }
    336
    337   /***
    338    * Tests getLocationInfo and setLocationInfo.
    339    */
    340   public void testGetSetLocationInfo() {
    341     XMLLayout layout = new XMLLayout();
    342     assertEquals(false, layout.getLocationInfo());
    343     layout.setLocationInfo(true);
    344     assertEquals(true, layout.getLocationInfo());
    345     layout.setLocationInfo(false);
    346     assertEquals(false, layout.getLocationInfo());
    347   }
    348
    349   /***
    350    * Tests activateOptions().
    351    */
    352   public void testActivateOptions() {
    353     XMLLayout layout = new XMLLayout();
    354     layout.activateOptions();
    355   }
    356
    357     /***
    358      * Level with arbitrary toString value.
    359      */
    360     private static final class ProblemLevel extends Level {
    361         /***
    362          * Construct new instance.
    363          * @param levelName level name, may not be null.
    364          */
    365         public ProblemLevel(final String levelName) {
    366             super(6000, levelName, 6);
    367         }
    368     }
    369
    370     /***
    371      * Tests problematic characters in multiple fields.
    372      * @throws Exception if parser can not be constructed or source is not a valid XML document.
    373      */
    374     public void testProblemCharacters() throws Exception {
    375       String problemName = "com.example.bar<>&\"'";
    376       Logger logger = Logger.getLogger(problemName);
    377       Level level = new ProblemLevel(problemName);
    378       Exception ex = new IllegalArgumentException(problemName);
    379       String threadName = Thread.currentThread().getName();
    380       Thread.currentThread().setName(problemName);
    381       NDC.push(problemName);
    382       Hashtable mdcMap = MDC.getContext();
    383       if (mdcMap != null) {
    384           mdcMap.clear();
    385       }
    386       MDC.put(problemName, problemName);
    387       LoggingEvent event =
    388         new LoggingEvent(
    389           problemName, logger, level, problemName, ex);
    390       XMLLayout layout = (XMLLayout) createLayout();
    391       layout.setProperties(true);
    392       String result = layout.format(event);
    393       mdcMap = MDC.getContext();
    394       if (mdcMap != null) {
    395           mdcMap.clear();
    396       }
    397       Thread.currentThread().setName(threadName);
    398
    399       Element parsedResult = parse(result);
    400       checkEventElement(parsedResult, event);
    401
    402       int childElementCount = 0;
    403
    404       for (
    405         Node node = parsedResult.getFirstChild(); node != null;
    406           node = node.getNextSibling()) {
    407         switch (node.getNodeType()) {
    408         case Node.ELEMENT_NODE:
    409           childElementCount++;
    410           switch(childElementCount) {
    411               case 1:
    412               checkMessageElement((Element) node, problemName);
    413               break;
    414
    415               case 2:
    416               checkNDCElement((Element) node, problemName);
    417               break;
    418
    419               case 3:
    420               checkThrowableElement((Element) node, ex);
    421               break;
    422
    423               case 4:
    424               checkPropertiesElement((Element) node, problemName, problemName);
    425               break;
    426
    427               default:
    428               fail("Unexpected element");
    429               break;
    430           }
    431
    432           break;
    433
    434         case Node.COMMENT_NODE:
    435           break;
    436
    437         case Node.TEXT_NODE:
    438
    439           //  should only be whitespace
    440           break;
    441
    442         default:
    443           fail("Unexpected node type");
    444
    445           break;
    446         }
    447       }
    448     }
    449
    450     /***
    451       * Tests CDATA element within NDC content.  See bug 37560.
    452       */
    453     public void testNDCWithCDATA() throws Exception {
    454         Logger logger = Logger.getLogger("com.example.bar");
    455         Level level = Level.INFO;
    456         String ndcMessage ="<envelope><faultstring><![CDATA[The EffectiveDate]]></faultstring><envelope>";
    457         NDC.push(ndcMessage);
    458         LoggingEvent event =
    459           new LoggingEvent(
    460             "com.example.bar", logger, level, "Hello, World", null);
    461         Layout layout = createLayout();
    462         String result = layout.format(event);
    463         NDC.clear();
    464         Element parsedResult = parse(result);
    465         NodeList ndcs = parsedResult.getElementsByTagName("log4j:NDC");
    466         assertEquals(1, ndcs.getLength());
    467         StringBuffer buf = new StringBuffer();
    468         for(Node child = ndcs.item(0).getFirstChild();
    469                 child != null;
    470                 child = child.getNextSibling()) {
    471             buf.append(child.getNodeValue());
    472         }
    473         assertEquals(ndcMessage, buf.toString());
    474    }
    475
    476     /***
    477       * Tests CDATA element within exception.  See bug 37560.
    478       */
    479     public void testExceptionWithCDATA() throws Exception {
    480         Logger logger = Logger.getLogger("com.example.bar");
    481         Level level = Level.INFO;
    482         String exceptionMessage ="<envelope><faultstring><![CDATA[The EffectiveDate]]></faultstring><envelope>";
    483         LoggingEvent event =
    484           new LoggingEvent(
    485             "com.example.bar", logger, level, "Hello, World", new Exception(exceptionMessage));
    486         Layout layout = createLayout();
    487         String result = layout.format(event);
    488         Element parsedResult = parse(result);
    489         NodeList throwables = parsedResult.getElementsByTagName("log4j:throwable");
    490         assertEquals(1, throwables.getLength());
    491         StringBuffer buf = new StringBuffer();
    492         for(Node child = throwables.item(0).getFirstChild();
    493                 child != null;
    494                 child = child.getNextSibling()) {
    495             buf.append(child.getNodeValue());
    496         }
    497         assertTrue(buf.toString().indexOf(exceptionMessage) != -1);
    498    }
    499
    500 }