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 }