/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.log4j.xml; import org.apache.log4j.Layout; import org.apache.log4j.LayoutTest; import org.apache.log4j.Level; import org.apache.log4j.Logger; import org.apache.log4j.NDC; import org.apache.log4j.MDC; import org.apache.log4j.spi.LoggingEvent; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.Node; import org.w3c.dom.NodeList; import org.xml.sax.InputSource; import java.io.Reader; import java.io.StringReader; import java.util.Hashtable; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; /** * Test for XMLLayout. * * @author Curt Arnold */ public class XMLLayoutTest extends LayoutTest { /** * Construct new instance of XMLLayoutTest. * * @param testName test name. */ public XMLLayoutTest(final String testName) { super(testName, "text/plain", false, null, null); } /** * Clear MDC and NDC before test. */ public void setUp() { NDC.clear(); if (MDC.getContext() != null) { MDC.getContext().clear(); } } /** * Clear MDC and NDC after test. */ public void tearDown() { setUp(); } /** * @{inheritDoc} */ protected Layout createLayout() { return new XMLLayout(); } /** * Parses the string as the body of an XML document and returns the document element. * @param source source string. * @return document element. * @throws Exception if parser can not be constructed or source is not a valid XML document. */ private Element parse(final String source) throws Exception { DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); factory.setNamespaceAware(false); factory.setCoalescing(true); DocumentBuilder builder = factory.newDocumentBuilder(); Reader reader = new StringReader(source); Document doc = builder.parse(new InputSource(reader)); return doc.getDocumentElement(); } /** * Checks a log4j:event element against expectations. * @param element element, may not be null. * @param event event, may not be null. */ private void checkEventElement( final Element element, final LoggingEvent event) { assertEquals("log4j:event", element.getTagName()); assertEquals( event.getLoggerName(), element.getAttribute("logger")); assertEquals( Long.toString(event.timeStamp), element.getAttribute("timestamp")); assertEquals(event.getLevel().toString(), element.getAttribute("level")); assertEquals(event.getThreadName(), element.getAttribute("thread")); } /** * Checks a log4j:message element against expectations. * @param element element, may not be null. * @param message expected message. */ private void checkMessageElement( final Element element, final String message) { assertEquals("log4j:message", element.getTagName()); Node messageNode = element.getFirstChild(); assertNotNull(messageNode); assertEquals(Node.TEXT_NODE, messageNode.getNodeType()); assertEquals(message, messageNode.getNodeValue()); assertNull(messageNode.getNextSibling()); } /** * Checks a log4j:message element against expectations. * @param element element, may not be null. * @param message expected message. */ private void checkNDCElement(final Element element, final String message) { assertEquals("log4j:NDC", element.getTagName()); Node messageNode = element.getFirstChild(); assertNotNull(messageNode); assertEquals(Node.TEXT_NODE, messageNode.getNodeType()); assertEquals(message, messageNode.getNodeValue()); assertNull(messageNode.getNextSibling()); } /** * Checks a log4j:throwable element against expectations. * @param element element, may not be null. * @param ex exception, may not be null. */ private void checkThrowableElement( final Element element, final Exception ex) { assertEquals("log4j:throwable", element.getTagName()); Node messageNode = element.getFirstChild(); assertNotNull(messageNode); assertEquals(Node.TEXT_NODE, messageNode.getNodeType()); String msg = ex.toString(); assertEquals(msg, messageNode.getNodeValue().substring(0, msg.length())); assertNull(messageNode.getNextSibling()); } /** * Checks a log4j:properties element against expectations. * @param element element, may not be null. * @param key key. * @param value value. */ private void checkPropertiesElement( final Element element, final String key, final String value) { assertEquals("log4j:properties", element.getTagName()); int childNodeCount = 0; for(Node child = element.getFirstChild(); child != null; child = child.getNextSibling()) { if (child.getNodeType() == Node.ELEMENT_NODE) { assertEquals("log4j:data", child.getNodeName()); Element childElement = (Element) child; assertEquals(key, childElement.getAttribute("name")); assertEquals(value, childElement.getAttribute("value")); childNodeCount++; } } assertEquals(1, childNodeCount); } /** * Tests formatted results. * @throws Exception if parser can not be constructed or source is not a valid XML document. */ public void testFormat() throws Exception { Logger logger = Logger.getLogger("org.apache.log4j.xml.XMLLayoutTest"); LoggingEvent event = new LoggingEvent( "org.apache.log4j.Logger", logger, Level.INFO, "Hello, World", null); XMLLayout layout = (XMLLayout) createLayout(); String result = layout.format(event); Element parsedResult = parse(result); checkEventElement(parsedResult, event); int childElementCount = 0; for ( Node node = parsedResult.getFirstChild(); node != null; node = node.getNextSibling()) { switch (node.getNodeType()) { case Node.ELEMENT_NODE: childElementCount++; checkMessageElement((Element) node, "Hello, World"); break; case Node.COMMENT_NODE: break; case Node.TEXT_NODE: // should only be whitespace break; default: fail("Unexpected node type"); break; } } assertEquals(1, childElementCount); } /** * Tests formatted results with an exception. * @throws Exception if parser can not be constructed or source is not a valid XML document. */ public void testFormatWithException() throws Exception { Logger logger = Logger.getLogger("org.apache.log4j.xml.XMLLayoutTest"); Exception ex = new IllegalArgumentException("'foo' is not a valid name"); LoggingEvent event = new LoggingEvent( "org.apache.log4j.Logger", logger, Level.INFO, "Hello, World", ex); XMLLayout layout = (XMLLayout) createLayout(); String result = layout.format(event); Element parsedResult = parse(result); checkEventElement(parsedResult, event); int childElementCount = 0; for ( Node node = parsedResult.getFirstChild(); node != null; node = node.getNextSibling()) { switch (node.getNodeType()) { case Node.ELEMENT_NODE: childElementCount++; if (childElementCount == 1) { checkMessageElement((Element) node, "Hello, World"); } else { checkThrowableElement((Element) node, ex); } break; case Node.COMMENT_NODE: break; case Node.TEXT_NODE: // should only be whitespace break; default: fail("Unexpected node type"); break; } } assertEquals(2, childElementCount); } /** * Tests formatted results with an exception. * @throws Exception if parser can not be constructed or source is not a valid XML document. */ public void testFormatWithNDC() throws Exception { Logger logger = Logger.getLogger("org.apache.log4j.xml.XMLLayoutTest"); NDC.push("NDC goes here"); LoggingEvent event = new LoggingEvent( "org.apache.log4j.Logger", logger, Level.INFO, "Hello, World", null); XMLLayout layout = (XMLLayout) createLayout(); String result = layout.format(event); NDC.pop(); Element parsedResult = parse(result); checkEventElement(parsedResult, event); int childElementCount = 0; for ( Node node = parsedResult.getFirstChild(); node != null; node = node.getNextSibling()) { switch (node.getNodeType()) { case Node.ELEMENT_NODE: childElementCount++; if (childElementCount == 1) { checkMessageElement((Element) node, "Hello, World"); } else { checkNDCElement((Element) node, "NDC goes here"); } break; case Node.COMMENT_NODE: break; case Node.TEXT_NODE: // should only be whitespace break; default: fail("Unexpected node type"); break; } } assertEquals(2, childElementCount); } /** * Tests getLocationInfo and setLocationInfo. */ public void testGetSetLocationInfo() { XMLLayout layout = new XMLLayout(); assertEquals(false, layout.getLocationInfo()); layout.setLocationInfo(true); assertEquals(true, layout.getLocationInfo()); layout.setLocationInfo(false); assertEquals(false, layout.getLocationInfo()); } /** * Tests activateOptions(). */ public void testActivateOptions() { XMLLayout layout = new XMLLayout(); layout.activateOptions(); } /** * Level with arbitrary toString value. */ private static final class ProblemLevel extends Level { private static final long serialVersionUID = 1L; /** * Construct new instance. * @param levelName level name, may not be null. */ public ProblemLevel(final String levelName) { super(6000, levelName, 6); } } /** * Tests problematic characters in multiple fields. * @throws Exception if parser can not be constructed or source is not a valid XML document. */ public void testProblemCharacters() throws Exception { String problemName = "com.example.bar<>&\"'"; Logger logger = Logger.getLogger(problemName); Level level = new ProblemLevel(problemName); Exception ex = new IllegalArgumentException(problemName); String threadName = Thread.currentThread().getName(); Thread.currentThread().setName(problemName); NDC.push(problemName); Hashtable mdcMap = MDC.getContext(); if (mdcMap != null) { mdcMap.clear(); } MDC.put(problemName, problemName); LoggingEvent event = new LoggingEvent( problemName, logger, level, problemName, ex); XMLLayout layout = (XMLLayout) createLayout(); layout.setProperties(true); String result = layout.format(event); mdcMap = MDC.getContext(); if (mdcMap != null) { mdcMap.clear(); } Thread.currentThread().setName(threadName); Element parsedResult = parse(result); checkEventElement(parsedResult, event); int childElementCount = 0; for ( Node node = parsedResult.getFirstChild(); node != null; node = node.getNextSibling()) { switch (node.getNodeType()) { case Node.ELEMENT_NODE: childElementCount++; switch(childElementCount) { case 1: checkMessageElement((Element) node, problemName); break; case 2: checkNDCElement((Element) node, problemName); break; case 3: checkThrowableElement((Element) node, ex); break; case 4: checkPropertiesElement((Element) node, problemName, problemName); break; default: fail("Unexpected element"); break; } break; case Node.COMMENT_NODE: break; case Node.TEXT_NODE: // should only be whitespace break; default: fail("Unexpected node type"); break; } } } /** * Tests CDATA element within NDC content. See bug 37560. */ public void testNDCWithCDATA() throws Exception { Logger logger = Logger.getLogger("com.example.bar"); Level level = Level.INFO; String ndcMessage ="<envelope><faultstring><![CDATA[The EffectiveDate]]></faultstring><envelope>"; NDC.push(ndcMessage); LoggingEvent event = new LoggingEvent( "com.example.bar", logger, level, "Hello, World", null); Layout layout = createLayout(); String result = layout.format(event); NDC.clear(); Element parsedResult = parse(result); NodeList ndcs = parsedResult.getElementsByTagName("log4j:NDC"); assertEquals(1, ndcs.getLength()); StringBuffer buf = new StringBuffer(); for(Node child = ndcs.item(0).getFirstChild(); child != null; child = child.getNextSibling()) { buf.append(child.getNodeValue()); } assertEquals(ndcMessage, buf.toString()); } /** * Tests CDATA element within exception. See bug 37560. */ public void testExceptionWithCDATA() throws Exception { Logger logger = Logger.getLogger("com.example.bar"); Level level = Level.INFO; String exceptionMessage ="<envelope><faultstring><![CDATA[The EffectiveDate]]></faultstring><envelope>"; LoggingEvent event = new LoggingEvent( "com.example.bar", logger, level, "Hello, World", new Exception(exceptionMessage)); Layout layout = createLayout(); String result = layout.format(event); Element parsedResult = parse(result); NodeList throwables = parsedResult.getElementsByTagName("log4j:throwable"); assertEquals(1, throwables.getLength()); StringBuffer buf = new StringBuffer(); for(Node child = throwables.item(0).getFirstChild(); child != null; child = child.getNextSibling()) { buf.append(child.getNodeValue()); } assertTrue(buf.toString().indexOf(exceptionMessage) != -1); } }