View Javadoc
1   /**
2    * Logback: the reliable, generic, fast and flexible logging framework.
3    * Copyright (C) 2014, QOS.ch. All rights reserved.
4    *
5    * This program and the accompanying materials are dual-licensed under
6    * either the terms of the Eclipse Public License v1.0 as published by
7    * the Eclipse Foundation
8    *
9    *   or (per the licensee's choosing)
10   *
11   * under the terms of the GNU Lesser General Public License version 2.1
12   * as published by the Free Software Foundation.
13   */
14  package ch.qos.logback.classic.log4j;
15  
16  import java.io.ByteArrayInputStream;
17  import java.io.InputStream;
18  import java.util.Iterator;
19  
20  import javax.xml.XMLConstants;
21  import javax.xml.namespace.NamespaceContext;
22  import javax.xml.parsers.DocumentBuilder;
23  import javax.xml.parsers.DocumentBuilderFactory;
24  import javax.xml.xpath.XPath;
25  import javax.xml.xpath.XPathConstants;
26  import javax.xml.xpath.XPathFactory;
27  
28  import org.apache.log4j.MDC;
29  import org.junit.After;
30  import org.junit.Assert;
31  import org.junit.Before;
32  import org.junit.Test;
33  import org.w3c.dom.Document;
34  import org.w3c.dom.Node;
35  import org.w3c.dom.NodeList;
36  import org.xml.sax.EntityResolver;
37  import org.xml.sax.InputSource;
38  
39  import ch.qos.logback.classic.Level;
40  import ch.qos.logback.classic.Logger;
41  import ch.qos.logback.classic.LoggerContext;
42  import ch.qos.logback.classic.spi.ILoggingEvent;
43  import ch.qos.logback.classic.spi.LoggingEvent;
44  
45  /**
46   * A test for correct (well-formed, valid) log4j XML layout.
47   * 
48   * @author Gabriel Corona
49   */
50  public class XMLLayoutTest {
51  
52      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\">";
53      private static final String NAMESPACE = "http://jakarta.apache.org/log4j/";
54      private static final String DTD_URI = "http://logging.apache.org/log4j/1.2/apidocs/org/apache/log4j/xml/doc-files/log4j.dtd";
55  
56      private static final String MDC_KEY = "key <&>'\"]]>";
57      private static final String MDC_VALUE = "value <&>'\"]]>";
58  
59      private static final String MESSAGE = "test message, <&>'\"";
60  
61      private LoggerContext lc;
62      private Logger root;
63      private XMLLayout layout;
64  
65      @Before
66      public void setUp() throws Exception {
67          lc = new LoggerContext();
68          lc.setName("default");
69  
70          layout = new XMLLayout();
71          layout.setLocationInfo(true);
72          layout.setContext(lc);
73          layout.setProperties(true);
74          layout.setLocationInfo(true);
75          layout.start();
76  
77          root = lc.getLogger(Logger.ROOT_LOGGER_NAME);
78  
79      }
80  
81      @After
82      public void tearDown() throws Exception {
83          lc = null;
84          layout = null;
85          MDC.clear();
86      }
87  
88      @Test
89      public void testDoLayout() throws Exception {
90          ILoggingEvent le = createLoggingEvent();
91  
92          String result = DOCTYPE + "<log4j:eventSet xmlns:log4j='http://jakarta.apache.org/log4j/'>";
93          if (layout.getFileHeader() != null) {
94              result += layout.getFileHeader();
95          }
96          if (layout.getPresentationHeader() != null) {
97              result += layout.getPresentationHeader();
98          }
99          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 }