/** * Logback: the reliable, generic, fast and flexible logging framework. * Copyright (C) 2014, QOS.ch. All rights reserved. * * This program and the accompanying materials are dual-licensed under * either the terms of the Eclipse Public License v1.0 as published by * the Eclipse Foundation * * or (per the licensee's choosing) * * under the terms of the GNU Lesser General Public License version 2.1 * as published by the Free Software Foundation. */ package ch.qos.logback.classic.log4j; import java.io.ByteArrayInputStream; import java.io.InputStream; import java.util.Iterator; import javax.xml.XMLConstants; import javax.xml.namespace.NamespaceContext; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.xpath.XPath; import javax.xml.xpath.XPathConstants; import javax.xml.xpath.XPathFactory; import org.apache.log4j.MDC; import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.Test; import org.w3c.dom.Document; import org.w3c.dom.Node; import org.w3c.dom.NodeList; import org.xml.sax.EntityResolver; import org.xml.sax.InputSource; import ch.qos.logback.classic.Level; import ch.qos.logback.classic.Logger; import ch.qos.logback.classic.LoggerContext; import ch.qos.logback.classic.spi.ILoggingEvent; import ch.qos.logback.classic.spi.LoggingEvent; /** * A test for correct (well-formed, valid) log4j XML layout. * * @author Gabriel Corona */ public class XMLLayoutTest { private static final String DOCTYPE = "<!DOCTYPE log4j:eventSet SYSTEM \"http://logging.apache.org/log4j/1.2/apidocs/org/apache/log4j/xml/doc-files/log4j.dtd\">"; private static final String NAMESPACE = "http://jakarta.apache.org/log4j/"; private static final String DTD_URI = "http://logging.apache.org/log4j/1.2/apidocs/org/apache/log4j/xml/doc-files/log4j.dtd"; private static final String MDC_KEY = "key <&>'\"]]>"; private static final String MDC_VALUE = "value <&>'\"]]>"; private static final String MESSAGE = "test message, <&>'\""; private LoggerContext lc; private Logger root; private XMLLayout layout; @Before public void setUp() throws Exception { lc = new LoggerContext(); lc.setName("default"); layout = new XMLLayout(); layout.setLocationInfo(true); layout.setContext(lc); layout.setProperties(true); layout.setLocationInfo(true); layout.start(); root = lc.getLogger(Logger.ROOT_LOGGER_NAME); } @After public void tearDown() throws Exception { lc = null; layout = null; MDC.clear(); } @Test public void testDoLayout() throws Exception { ILoggingEvent le = createLoggingEvent(); String result = DOCTYPE + "<log4j:eventSet xmlns:log4j='http://jakarta.apache.org/log4j/'>"; if (layout.getFileHeader() != null) { result += layout.getFileHeader(); } if (layout.getPresentationHeader() != null) { result += layout.getPresentationHeader(); } result += layout.doLayout(le); if (layout.getPresentationFooter() != null) { result += layout.getPresentationFooter(); } if (layout.getFileFooter() != null) { result += layout.getFileFooter(); } result += "</log4j:eventSet>"; Document document = parse(result); XPath xpath = this.newXPath(); // Test log4j:event: NodeList eventNodes = (NodeList) xpath.compile("//log4j:event").evaluate(document, XPathConstants.NODESET); Assert.assertEquals(1, eventNodes.getLength()); // Test log4g:message: Assert.assertEquals(MESSAGE, xpath.compile("//log4j:message").evaluate(document, XPathConstants.STRING)); // Test log4j:data: NodeList dataNodes = (NodeList) xpath.compile("//log4j:data").evaluate(document, XPathConstants.NODESET); boolean foundMdc = false; for (int i = 0; i != dataNodes.getLength(); ++i) { Node dataNode = dataNodes.item(i); if (dataNode.getAttributes().getNamedItem("name").getNodeValue().equals(MDC_KEY)) { foundMdc = true; Assert.assertEquals(MDC_VALUE, dataNode.getAttributes().getNamedItem("value").getNodeValue()); break; } } Assert.assertTrue(foundMdc); } /** * Create a XPath instance with xmlns:log4j="http://jakarta.apache.org/log4j/" * * @return XPath instance with log4 namespace */ private XPath newXPath() { XPathFactory xPathfactory = XPathFactory.newInstance(); XPath xpath = xPathfactory.newXPath(); xpath.setNamespaceContext(new NamespaceContext() { @SuppressWarnings("rawtypes") public Iterator getPrefixes(String namespaceURI) { throw new UnsupportedOperationException(); } public String getPrefix(String namespaceURI) { throw new UnsupportedOperationException(); } public String getNamespaceURI(String prefix) { if ("log4j".equals(prefix)) { return NAMESPACE; } else { return XMLConstants.NULL_NS_URI; } } }); return xpath; } private LoggingEvent createLoggingEvent() { MDC.put(MDC_KEY, MDC_VALUE); LoggingEvent event = new LoggingEvent("com.example.XMLLayoutTest-<&>'\"]]>", root, Level.DEBUG, MESSAGE, new RuntimeException( "Dummy exception: <&>'\"]]>"), null); event.setThreadName("Dummy thread <&>'\""); StackTraceElement ste1 = new StackTraceElement("c1", "m1", "f1", 1); StackTraceElement ste2 = new StackTraceElement("c2", "m2", "f2", 2); event.setCallerData(new StackTraceElement[] { ste1, ste2 }); return event; } /** * Parse and validate Log4j XML * * @param output Log4j XML * @return Document * @throws Exception */ private Document parse(String output) throws Exception { // Lookup the DTD in log4j.jar: EntityResolver resolver = new EntityResolver() { public InputSource resolveEntity(String publicId, String systemId) { if (publicId == null && systemId != null && systemId.equals(DTD_URI)) { final String path = "/org/apache/log4j/xml/log4j.dtd"; InputStream in = this.getClass().getResourceAsStream(path); return new InputSource(in); } else { throw new RuntimeException("Not found"); } } }; DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); factory.setNamespaceAware(true); factory.setValidating(true); DocumentBuilder builder = factory.newDocumentBuilder(); builder.setEntityResolver(resolver); return builder.parse(new ByteArrayInputStream(output.getBytes("UTF-8"))); } }