001/** 002 * Logback: the reliable, generic, fast and flexible logging framework. 003 * Copyright (C) 2014, QOS.ch. All rights reserved. 004 * 005 * This program and the accompanying materials are dual-licensed under 006 * either the terms of the Eclipse Public License v1.0 as published by 007 * the Eclipse Foundation 008 * 009 * or (per the licensee's choosing) 010 * 011 * under the terms of the GNU Lesser General Public License version 2.1 012 * as published by the Free Software Foundation. 013 */ 014package ch.qos.logback.classic.log4j; 015 016import java.io.ByteArrayInputStream; 017import java.io.InputStream; 018import java.util.Iterator; 019 020import javax.xml.XMLConstants; 021import javax.xml.namespace.NamespaceContext; 022import javax.xml.parsers.DocumentBuilder; 023import javax.xml.parsers.DocumentBuilderFactory; 024import javax.xml.xpath.XPath; 025import javax.xml.xpath.XPathConstants; 026import javax.xml.xpath.XPathFactory; 027 028import org.apache.log4j.MDC; 029import org.junit.After; 030import org.junit.Assert; 031import org.junit.Before; 032import org.junit.Test; 033import org.w3c.dom.Document; 034import org.w3c.dom.Node; 035import org.w3c.dom.NodeList; 036import org.xml.sax.EntityResolver; 037import org.xml.sax.InputSource; 038 039import ch.qos.logback.classic.Level; 040import ch.qos.logback.classic.Logger; 041import ch.qos.logback.classic.LoggerContext; 042import ch.qos.logback.classic.spi.ILoggingEvent; 043import ch.qos.logback.classic.spi.LoggingEvent; 044 045/** 046 * A test for correct (well-formed, valid) log4j XML layout. 047 * 048 * @author Gabriel Corona 049 */ 050public class XMLLayoutTest { 051 052 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\">"; 053 private static final String NAMESPACE = "http://jakarta.apache.org/log4j/"; 054 private static final String DTD_URI = "http://logging.apache.org/log4j/1.2/apidocs/org/apache/log4j/xml/doc-files/log4j.dtd"; 055 056 private static final String MDC_KEY = "key <&>'\"]]>"; 057 private static final String MDC_VALUE = "value <&>'\"]]>"; 058 059 private static final String MESSAGE = "test message, <&>'\""; 060 061 private LoggerContext lc; 062 private Logger root; 063 private XMLLayout layout; 064 065 @Before 066 public void setUp() throws Exception { 067 lc = new LoggerContext(); 068 lc.setName("default"); 069 070 layout = new XMLLayout(); 071 layout.setLocationInfo(true); 072 layout.setContext(lc); 073 layout.setProperties(true); 074 layout.setLocationInfo(true); 075 layout.start(); 076 077 root = lc.getLogger(Logger.ROOT_LOGGER_NAME); 078 079 } 080 081 @After 082 public void tearDown() throws Exception { 083 lc = null; 084 layout = null; 085 MDC.clear(); 086 } 087 088 @Test 089 public void testDoLayout() throws Exception { 090 ILoggingEvent le = createLoggingEvent(); 091 092 String result = DOCTYPE + "<log4j:eventSet xmlns:log4j='http://jakarta.apache.org/log4j/'>"; 093 if (layout.getFileHeader() != null) { 094 result += layout.getFileHeader(); 095 } 096 if (layout.getPresentationHeader() != null) { 097 result += layout.getPresentationHeader(); 098 } 099 result += layout.doLayout(le); 100 if (layout.getPresentationFooter() != null) { 101 result += layout.getPresentationFooter(); 102 } 103 if (layout.getFileFooter() != null) { 104 result += layout.getFileFooter(); 105 } 106 result += "</log4j:eventSet>"; 107 108 Document document = parse(result); 109 110 XPath xpath = this.newXPath(); 111 112 // Test log4j:event: 113 NodeList eventNodes = (NodeList) xpath.compile("//log4j:event").evaluate(document, XPathConstants.NODESET); 114 Assert.assertEquals(1, eventNodes.getLength()); 115 116 // Test log4g:message: 117 Assert.assertEquals(MESSAGE, xpath.compile("//log4j:message").evaluate(document, XPathConstants.STRING)); 118 119 // Test log4j:data: 120 NodeList dataNodes = (NodeList) xpath.compile("//log4j:data").evaluate(document, XPathConstants.NODESET); 121 boolean foundMdc = false; 122 for (int i = 0; i != dataNodes.getLength(); ++i) { 123 Node dataNode = dataNodes.item(i); 124 if (dataNode.getAttributes().getNamedItem("name").getNodeValue().equals(MDC_KEY)) { 125 foundMdc = true; 126 Assert.assertEquals(MDC_VALUE, dataNode.getAttributes().getNamedItem("value").getNodeValue()); 127 break; 128 } 129 } 130 Assert.assertTrue(foundMdc); 131 } 132 133 /** 134 * Create a XPath instance with xmlns:log4j="http://jakarta.apache.org/log4j/" 135 * 136 * @return XPath instance with log4 namespace 137 */ 138 private XPath newXPath() { 139 XPathFactory xPathfactory = XPathFactory.newInstance(); 140 XPath xpath = xPathfactory.newXPath(); 141 142 xpath.setNamespaceContext(new NamespaceContext() { 143 @SuppressWarnings("rawtypes") 144 public Iterator getPrefixes(String namespaceURI) { 145 throw new UnsupportedOperationException(); 146 } 147 148 public String getPrefix(String namespaceURI) { 149 throw new UnsupportedOperationException(); 150 } 151 152 public String getNamespaceURI(String prefix) { 153 if ("log4j".equals(prefix)) { 154 return NAMESPACE; 155 } else { 156 return XMLConstants.NULL_NS_URI; 157 } 158 } 159 }); 160 161 return xpath; 162 } 163 164 private LoggingEvent createLoggingEvent() { 165 MDC.put(MDC_KEY, MDC_VALUE); 166 LoggingEvent event = new LoggingEvent("com.example.XMLLayoutTest-<&>'\"]]>", root, Level.DEBUG, MESSAGE, new RuntimeException( 167 "Dummy exception: <&>'\"]]>"), null); 168 event.setThreadName("Dummy thread <&>'\""); 169 170 StackTraceElement ste1 = new StackTraceElement("c1", "m1", "f1", 1); 171 StackTraceElement ste2 = new StackTraceElement("c2", "m2", "f2", 2); 172 event.setCallerData(new StackTraceElement[] { ste1, ste2 }); 173 174 return event; 175 } 176 177 /** 178 * Parse and validate Log4j XML 179 * 180 * @param output Log4j XML 181 * @return Document 182 * @throws Exception 183 */ 184 private Document parse(String output) throws Exception { 185 186 // Lookup the DTD in log4j.jar: 187 EntityResolver resolver = new EntityResolver() { 188 public InputSource resolveEntity(String publicId, String systemId) { 189 if (publicId == null && systemId != null && systemId.equals(DTD_URI)) { 190 final String path = "/org/apache/log4j/xml/log4j.dtd"; 191 InputStream in = this.getClass().getResourceAsStream(path); 192 return new InputSource(in); 193 } else { 194 throw new RuntimeException("Not found"); 195 } 196 } 197 }; 198 199 DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); 200 factory.setNamespaceAware(true); 201 factory.setValidating(true); 202 203 DocumentBuilder builder = factory.newDocumentBuilder(); 204 builder.setEntityResolver(resolver); 205 206 return builder.parse(new ByteArrayInputStream(output.getBytes("UTF-8"))); 207 } 208 209}