You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@logging.apache.org by gg...@apache.org on 2013/07/17 18:08:03 UTC

svn commit: r1504184 - in /logging/log4j/log4j2/trunk: core/src/main/java/org/apache/logging/log4j/core/layout/ core/src/test/java/org/apache/logging/log4j/core/appender/ core/src/test/java/org/apache/logging/log4j/core/layout/ core/src/test/resources/...

Author: ggregory
Date: Wed Jul 17 16:08:03 2013
New Revision: 1504184

URL: http://svn.apache.org/r1504184
Log:
[LOG4J2-312] XML layout improvements (compact vs. pretty, namespace, namespace prefix, root element).

Added:
    logging/log4j/log4j2/trunk/core/src/test/java/org/apache/logging/log4j/core/appender/XmlCompactFileAppenderTest.java   (with props)
    logging/log4j/log4j2/trunk/core/src/test/java/org/apache/logging/log4j/core/appender/XmlCompleteFileAppenderTest.java   (with props)
    logging/log4j/log4j2/trunk/core/src/test/resources/XmlCompactFileAppenderTest.xml   (with props)
    logging/log4j/log4j2/trunk/core/src/test/resources/XmlCompleteFileAppenderTest.xml   (with props)
Modified:
    logging/log4j/log4j2/trunk/core/src/main/java/org/apache/logging/log4j/core/layout/XMLLayout.java
    logging/log4j/log4j2/trunk/core/src/test/java/org/apache/logging/log4j/core/appender/FastXmlFileAppenderTest.java
    logging/log4j/log4j2/trunk/core/src/test/java/org/apache/logging/log4j/core/appender/XmlFileAppenderTest.java
    logging/log4j/log4j2/trunk/core/src/test/java/org/apache/logging/log4j/core/layout/XMLLayoutTest.java
    logging/log4j/log4j2/trunk/core/src/test/resources/XmlFileAppenderTest.xml
    logging/log4j/log4j2/trunk/core/src/test/resources/log4j.dtd
    logging/log4j/log4j2/trunk/src/changes/changes.xml
    logging/log4j/log4j2/trunk/src/site/xdoc/manual/layouts.xml.vm

Modified: logging/log4j/log4j2/trunk/core/src/main/java/org/apache/logging/log4j/core/layout/XMLLayout.java
URL: http://svn.apache.org/viewvc/logging/log4j/log4j2/trunk/core/src/main/java/org/apache/logging/log4j/core/layout/XMLLayout.java?rev=1504184&r1=1504183&r2=1504184&view=diff
==============================================================================
--- logging/log4j/log4j2/trunk/core/src/main/java/org/apache/logging/log4j/core/layout/XMLLayout.java (original)
+++ logging/log4j/log4j2/trunk/core/src/main/java/org/apache/logging/log4j/core/layout/XMLLayout.java Wed Jul 17 16:08:03 2013
@@ -33,62 +33,90 @@ import org.apache.logging.log4j.core.con
 import org.apache.logging.log4j.core.config.plugins.PluginAttr;
 import org.apache.logging.log4j.core.config.plugins.PluginFactory;
 import org.apache.logging.log4j.core.helpers.Charsets;
+import org.apache.logging.log4j.core.helpers.Strings;
 import org.apache.logging.log4j.core.helpers.Transform;
 import org.apache.logging.log4j.message.Message;
 import org.apache.logging.log4j.message.MultiformatMessage;
 
 
 /**
- * The output of the XMLLayout consists of a series of log4j:event
- * elements as defined in the <a href="log4j.dtd">log4j.dtd</a>. If configured to do so it will
- * output a complete well-formed XML file. The output is designed to be
- * included as an <em>external entity</em> in a separate file to form
- * a correct XML file.
- * <p/>
- * <p>For example, if <code>abc</code> is the name of the file where
- * the XMLLayout ouput goes, then a well-formed XML file would be:
- * <p/>
+ * Appends a series of {@code event} elements as defined in the <a href="log4j.dtd">log4j.dtd</a>.
+ * 
+ * <h4>Complete well-formed XML vs. fragment XML</h4>
+ * <p>
+ * If you configure {@code complete="true"}, the appender outputs a well-formed XML document where the default namespace
+ * is the log4j namespace {@value #XML_NAMESPACE}. By default, with {@code complete="false"}, you should include the
+ * output as an <em>external entity</em> in a separate file to form a well-formed XML document, in which case the
+ * appender uses {@code namespacePrefix} with a default of {@value #DEFAULT_NS_PREFIX}.
+ * </p>
+ * <p>
+ * A well-formed XML document follows this pattern:
+ * </p>
+ * 
  * <pre>
  * &lt;?xml version="1.0" encoding=&quotUTF-8&quot?&gt;
- *
- * &lt;!DOCTYPE log4j:eventSet SYSTEM "log4j.dtd" [&lt;!ENTITY data SYSTEM "abc"&gt;]&gt;
- *
- * &lt;log4j:eventSet version="1.2" xmlns:log4j="http://logging.apache.org/log4j/"&gt;
- * &nbsp;&nbsp;&data;
- * &lt;/log4j:eventSet&gt;
+ * &lt;events xmlns="http://logging.apache.org/log4j/2.0"&gt;
+ * &nbsp;&nbsp;&lt;event logger="com.foo.Bar" timestamp="1373436580419" level="INFO" thread="main"&gt;
+ * &nbsp;&nbsp;&nbsp;&nbsp;&lt;message>&lt;![CDATA[This is a log message 1]]&gt;&lt;/message&gt;
+ * &nbsp;&nbsp;&lt;/event&gt;
+ * &nbsp;&nbsp;&lt;event logger="com.foo.Baz" timestamp="1373436580420" level="INFO" thread="main"&gt;
+ * &nbsp;&nbsp;&nbsp;&nbsp;&lt;message>&lt;![CDATA[This is a log message 2]]&gt;&lt;/message&gt;
+ * &nbsp;&nbsp;&lt;/event&gt;
+ * &lt;/events&gt;
  * </pre>
- * <p/>
- * <p>This approach enforces the independence of the XMLLayout and the
- * appender where it is embedded.
- * <p/>
- * <p>The <code>version</code> attribute helps components to correctly
- * interpret output generated by XMLLayout. The value of this
- * attribute should be "1.1" for output generated by log4j versions
- * prior to log4j 1.2 (final release), "1.2" for release 1.2, and "2.0" for release 2.0 and
- * later.
- * <p/>
- * Appenders using this layout should have their encoding
- * set to UTF-8 or UTF-16, otherwise events containing
- * non ASCII characters could result in corrupted
- * log files.
+ * <p>
+ * If {@code complete="false"}, the appender does not write the XML processing instruction and the root element.
+ * </p>
+ * <p>
+ * This approach enforces the independence of the XMLLayout and the appender where you embed it.
+ * </p>
+ * <h4>Encoding</h4>
+ * <p>
+ * Appenders using this layout should have their {@code charset} set to {@code UTF-8} or {@code UTF-16}, otherwise
+ * events containing non ASCII characters could result in corrupted log files.
+ * </p>
+ * <h4>Pretty vs. compact XML</h4>
+ * <p>
+ * By default, the XML layout is not compact (a.k.a. not "pretty") with {@code compact="false"}, which means the
+ * appender uses end-of-line characters and indents lines to format the XML. If {@code compact="true"}, then no
+ * end-of-line or indentation is used. Message content may contain, of course, end-of-lines.
+ * </p>
  */
 @Plugin(name = "XMLLayout", category = "Core", elementType = "layout", printObject = true)
 public class XMLLayout extends AbstractStringLayout {
 
+    private static final String XML_NAMESPACE = "http://logging.apache.org/log4j/2.0/";
     private static final int DEFAULT_SIZE = 256;
+    
+    // We yield to \r\n for the default.
+    private static final String DEFAULT_EOL = "\r\n";
+    private static final String COMPACT_EOL = "";
+    private static final String DEFAULT_INDENT = "  ";
+    private static final String COMPACT_INDENT = "";
+    private static final String DEFAULT_NS_PREFIX = "log4j";
 
     private static final String[] FORMATS = new String[] {"xml"};
 
     private final boolean locationInfo;
     private final boolean properties;
     private final boolean complete;
+    private final String namespacePrefix;
+    private final String eol;
+    private final String indent1;
+    private final String indent2;
+    private final String indent3;
 
     protected XMLLayout(final boolean locationInfo, final boolean properties, final boolean complete,
-                        final Charset charset) {
+                        boolean compact, final String nsPrefix, final Charset charset) {
         super(charset);
         this.locationInfo = locationInfo;
         this.properties = properties;
         this.complete = complete;
+        this.eol = compact ? COMPACT_EOL : DEFAULT_EOL;
+        this.indent1 = compact ? COMPACT_INDENT : DEFAULT_INDENT;
+        this.indent2 = this.indent1 + this.indent1;
+        this.indent3 = this.indent2 + this.indent1;
+        this.namespacePrefix = (Strings.isEmpty(nsPrefix) ? DEFAULT_NS_PREFIX : nsPrefix) + ":";
     }
 
     /**
@@ -101,9 +129,12 @@ public class XMLLayout extends AbstractS
     public String toSerializable(final LogEvent event) {
         final StringBuilder buf = new StringBuilder(DEFAULT_SIZE);
 
-        // We yield to the \r\n heresy.
-
-        buf.append("<log4j:event logger=\"");
+        buf.append(this.indent1);
+        buf.append('<');
+        if (!complete) {
+            buf.append(this.namespacePrefix);
+        }
+        buf.append("event logger=\"");
         String name = event.getLoggerName();
         if (name.isEmpty()) {
             name = "root";
@@ -115,7 +146,8 @@ public class XMLLayout extends AbstractS
         buf.append(Transform.escapeTags(String.valueOf(event.getLevel())));
         buf.append("\" thread=\"");
         buf.append(Transform.escapeTags(event.getThreadName()));
-        buf.append("\">\r\n");
+        buf.append("\">");
+        buf.append(this.eol);
 
         final Message msg = event.getMessage();
         if (msg != null) {
@@ -129,39 +161,74 @@ public class XMLLayout extends AbstractS
                     }
                 }
             }
+            buf.append(this.indent2);
+            buf.append('<');
+            if (!complete) {
+                buf.append(this.namespacePrefix);
+            }
+            buf.append("message>");
             if (xmlSupported) {
-                buf.append("<log4j:message>");
                 buf.append(((MultiformatMessage) msg).getFormattedMessage(FORMATS));
-                buf.append("</log4j:message>");
             } else {
-                buf.append("<log4j:message><![CDATA[");
+                buf.append("<![CDATA[");
                 // Append the rendered message. Also make sure to escape any
                 // existing CDATA sections.
                 Transform.appendEscapingCDATA(buf, event.getMessage().getFormattedMessage());
-                buf.append("]]></log4j:message>\r\n");
+                buf.append("]]>");
+            }
+            buf.append("</");
+            if (!complete) {
+                buf.append(this.namespacePrefix);
             }
+            buf.append("message>");
+            buf.append(this.eol);
         }
 
         if (event.getContextStack().getDepth() > 0) {
-            buf.append("<log4j:NDC><![CDATA[");
+            buf.append(this.indent2);
+            buf.append('<');
+            if (!complete) {
+                buf.append(this.namespacePrefix);
+            }
+            buf.append("NDC><![CDATA[");
             Transform.appendEscapingCDATA(buf, event.getContextStack().toString());
-            buf.append("]]></log4j:NDC>\r\n");
+            buf.append("]]></");
+            if (!complete) {
+                buf.append(this.namespacePrefix);
+            }
+            buf.append("NDC>");
+            buf.append(this.eol);
         }
 
         final Throwable throwable = event.getThrown();
         if (throwable != null) {
             final List<String> s = getThrowableString(throwable);
-            buf.append("<log4j:throwable><![CDATA[");
+            buf.append(this.indent2);
+            buf.append('<');
+            if (!complete) {
+                buf.append(this.namespacePrefix);
+            }
+            buf.append("throwable><![CDATA[");
             for (final String str : s) {
                 Transform.appendEscapingCDATA(buf, str);
-                buf.append("\r\n");
+                buf.append(this.eol);
+            }
+            buf.append("]]></");
+            if (!complete) {
+                buf.append(this.namespacePrefix);
             }
-            buf.append("]]></log4j:throwable>\r\n");
+            buf.append("throwable>");
+            buf.append(this.eol);
         }
 
         if (locationInfo) {
             final StackTraceElement element = event.getSource();
-            buf.append("<log4j:locationInfo class=\"");
+            buf.append(this.indent2);
+            buf.append('<');
+            if (!complete) {
+                buf.append(this.namespacePrefix);
+            }
+            buf.append("locationInfo class=\"");
             buf.append(Transform.escapeTags(element.getClassName()));
             buf.append("\" method=\"");
             buf.append(Transform.escapeTags(element.getMethodName()));
@@ -169,28 +236,58 @@ public class XMLLayout extends AbstractS
             buf.append(Transform.escapeTags(element.getFileName()));
             buf.append("\" line=\"");
             buf.append(element.getLineNumber());
-            buf.append("\"/>\r\n");
+            buf.append("\"/>");
+            buf.append(this.eol);
         }
 
         if (properties && event.getContextMap().size() > 0) {
-            buf.append("<log4j:properties>\r\n");
+            buf.append(this.indent2);
+            buf.append('<');
+            if (!complete) {
+                buf.append(this.namespacePrefix);
+            }
+            buf.append("properties>");
+            buf.append(this.eol);
             for (final Map.Entry<String, String> entry : event.getContextMap().entrySet()) {
-                buf.append("<log4j:data name=\"");
+                buf.append(this.indent3);
+                buf.append('<');
+                if (!complete) {
+                    buf.append(this.namespacePrefix);
+                }
+                buf.append("data name=\"");
                 buf.append(Transform.escapeTags(entry.getKey()));
                 buf.append("\" value=\"");
                 buf.append(Transform.escapeTags(String.valueOf(entry.getValue())));
-                buf.append("\"/>\r\n");
+                buf.append("\"/>");
+                buf.append(this.eol);
+            }
+            buf.append(this.indent2);
+            buf.append("</");
+            if (!complete) {
+                buf.append(this.namespacePrefix);
             }
-            buf.append("</log4j:properties>\r\n");
+            buf.append("properties>");
+            buf.append(this.eol);
         }
 
-        buf.append("</log4j:event>\r\n\r\n");
+        buf.append(this.indent1);
+        buf.append("</");
+        if (!complete) {
+            buf.append(this.namespacePrefix);
+        }
+        buf.append("event>");
+        buf.append(this.eol);
 
         return buf.toString();
     }
 
     /**
      * Returns appropriate XML headers.
+     * <ol>
+     * <li>XML processing instruction</li>
+     * <li>XML root element</li>
+     * </ol>
+     * 
      * @return a byte array containing the header.
      */
     @Override
@@ -198,38 +295,42 @@ public class XMLLayout extends AbstractS
         if (!complete) {
             return null;
         }
-        final StringBuilder sbuf = new StringBuilder();
-        sbuf.append("<?xml version=\"1.0\" encoding=\"");
-        sbuf.append(this.getCharset().name());
-        sbuf.append("\"?>\r\n");
-        sbuf.append("<log4j:eventSet xmlns:log4j=\"http://logging.apache.org/log4j/\">\r\n");
-        return sbuf.toString().getBytes(this.getCharset());
+        final StringBuilder buf = new StringBuilder();
+        buf.append("<?xml version=\"1.0\" encoding=\"");
+        buf.append(this.getCharset().name());
+        buf.append("\"?>");
+        buf.append(this.eol);
+        // Make the log4j namespace the default namespace, no need to use more space with a namespace prefix.
+        buf.append("<events xmlns=\"" + XML_NAMESPACE + "\">");
+        buf.append(this.eol);
+        return buf.toString().getBytes(this.getCharset());
     }
 
 
     /**
      * Returns appropriate XML footer.
-     * @return a byte array containing the footer.
+     * 
+     * @return a byte array containing the footer, closing the XML root element.
      */
     @Override
     public byte[] getFooter() {
         if (!complete) {
             return null;
         }
-        return "</log4j:eventSet>\r\n".getBytes(getCharset());
+        return ("</events>" + this.eol).getBytes(getCharset());
     }
 
     /**
      * XMLLayout's content format is specified by:<p/>
      * Key: "dtd" Value: "log4j.dtd"<p/>
-     * Key: "version" Value: "1.2"
+     * Key: "version" Value: "2.0"
      * @return Map of content format keys supporting XMLLayout
      */
     @Override
     public Map<String, String> getContentFormat() {
         final Map<String, String> result = new HashMap<String, String>();
         result.put("dtd", "log4j.dtd");
-        result.put("version", "1.2");
+        result.put("version", "2.0");
         return result;
     }
 
@@ -270,21 +371,27 @@ public class XMLLayout extends AbstractS
     /**
      * Creates an XML Layout.
      * 
-     * @param locationInfo If "true" include the location information in the generated XML.
-     * @param properties If "true" include the thread context in the generated XML.
-     * @param complete If "true" include the XML header.
-     * @param charsetName The character set to use, if {@code null}, uses UTF-8.
+     * @param locationInfo If "true", includes the location information in the generated XML.
+     * @param properties If "true", includes the thread context in the generated XML.
+     * @param completeStr If "true", includes the XML header and footer, defaults to "false".
+     * @param compactStr If "true", does not use end-of-lines and indentation, defaults to "false".
+     * @param namespacePrefix The namespace prefix, defaults to {@value #DEFAULT_NS_PREFIX}
+     * @param charsetName The character set to use, if {@code null}, uses "UTF-8".
      * @return An XML Layout.
      */
     @PluginFactory
-    public static XMLLayout createLayout(@PluginAttr("locationInfo") final String locationInfo,
+    public static XMLLayout createLayout(
+                                         @PluginAttr("locationInfo") final String locationInfo,
                                          @PluginAttr("properties") final String properties,
-                                         @PluginAttr("complete") final String complete,
+                                         @PluginAttr("complete") final String completeStr,
+                                         @PluginAttr("compact") final String compactStr,
+                                         @PluginAttr("namespacePrefix") final String nanespacePrefix,
                                          @PluginAttr("charset") final String charsetName) {
         final Charset charset = Charsets.getSupportedCharset(charsetName, Charsets.UTF_8);
         final boolean info = Boolean.parseBoolean(locationInfo);
         final boolean props = Boolean.parseBoolean(properties);
-        final boolean comp = Boolean.parseBoolean(complete);
-        return new XMLLayout(info, props, comp, charset);
+        final boolean complete = Boolean.parseBoolean(completeStr);
+        final boolean compact = Boolean.parseBoolean(compactStr);
+        return new XMLLayout(info, props, complete, compact, nanespacePrefix, charset);
     }
 }

Modified: logging/log4j/log4j2/trunk/core/src/test/java/org/apache/logging/log4j/core/appender/FastXmlFileAppenderTest.java
URL: http://svn.apache.org/viewvc/logging/log4j/log4j2/trunk/core/src/test/java/org/apache/logging/log4j/core/appender/FastXmlFileAppenderTest.java?rev=1504184&r1=1504183&r2=1504184&view=diff
==============================================================================
--- logging/log4j/log4j2/trunk/core/src/test/java/org/apache/logging/log4j/core/appender/FastXmlFileAppenderTest.java (original)
+++ logging/log4j/log4j2/trunk/core/src/test/java/org/apache/logging/log4j/core/appender/FastXmlFileAppenderTest.java Wed Jul 17 16:08:03 2013
@@ -71,7 +71,7 @@ public class FastXmlFileAppenderTest {
         assertTrue("line1 incorrect: [" + line1 + "], does not contain: [" + msg1 + "]", line1.equals(msg1));
         
         assertNotNull("line2", line2);
-        final String msg2 = "<log4j:eventSet xmlns:log4j=\"http://logging.apache.org/log4j/\">";
+        final String msg2 = "<log4j:events xmlns:log4j=\"http://logging.apache.org/log4j/\">";
         assertTrue("line2 incorrect: [" + line2 + "], does not contain: [" + msg2 + "]", line2.equals(msg2));
         
         assertNotNull("line3", line3);

Added: logging/log4j/log4j2/trunk/core/src/test/java/org/apache/logging/log4j/core/appender/XmlCompactFileAppenderTest.java
URL: http://svn.apache.org/viewvc/logging/log4j/log4j2/trunk/core/src/test/java/org/apache/logging/log4j/core/appender/XmlCompactFileAppenderTest.java?rev=1504184&view=auto
==============================================================================
--- logging/log4j/log4j2/trunk/core/src/test/java/org/apache/logging/log4j/core/appender/XmlCompactFileAppenderTest.java (added)
+++ logging/log4j/log4j2/trunk/core/src/test/java/org/apache/logging/log4j/core/appender/XmlCompactFileAppenderTest.java Wed Jul 17 16:08:03 2013
@@ -0,0 +1,80 @@
+/*
+ * 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.logging.log4j.core.appender;
+
+import static org.junit.Assert.*;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileReader;
+
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.apache.logging.log4j.core.LifeCycle;
+import org.apache.logging.log4j.core.config.XMLConfigurationFactory;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+/**
+ * Tests a "compact" XML file, no extra spaces or end of lines.
+ */
+public class XmlCompactFileAppenderTest {
+
+    @BeforeClass
+    public static void beforeClass() {
+        System.setProperty(XMLConfigurationFactory.CONFIGURATION_FILE_PROPERTY,
+                "XmlCompactFileAppenderTest.xml");
+    }
+
+    @Test
+    public void testFlushAtEndOfBatch() throws Exception {
+        final File f = new File("target", "XmlCompactFileAppenderTest.log");
+        // System.out.println(f.getAbsolutePath());
+        f.delete();
+        final Logger log = LogManager.getLogger("com.foo.Bar");
+        final String logMsg = "Message flushed with immediate flush=false";
+        log.info(logMsg);
+        ((LifeCycle) LogManager.getContext()).stop(); // stop async thread
+
+        final BufferedReader reader = new BufferedReader(new FileReader(f));
+        String line1;
+        try {
+            line1 = reader.readLine();
+        } finally {
+            reader.close();
+            f.delete();
+        }
+        assertNotNull("line1", line1);
+        final String msg1 = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>";
+        assertTrue("line1 incorrect: [" + line1 + "], does not contain: [" + msg1 + "]", line1.contains(msg1));
+
+        final String msg2 = "<events xmlns=\"http://logging.apache.org/log4j/2.0/\">";
+        assertTrue("line1 incorrect: [" + line1 + "], does not contain: [" + msg2 + "]", line1.contains(msg2));
+
+        final String msg3 = "<event ";
+        assertTrue("line1 incorrect: [" + line1 + "], does not contain: [" + msg3 + "]", line1.contains(msg3));
+
+        final String msg4 = logMsg;
+        assertTrue("line1 incorrect: [" + line1 + "], does not contain: [" + msg4 + "]", line1.contains(msg4));
+
+        final String location = "testFlushAtEndOfBatch";
+        assertTrue("no location", !line1.contains(location));
+
+        assertTrue(line1.indexOf('\r') == -1);
+        assertTrue(line1.indexOf('\n') == -1);
+    }
+}

Propchange: logging/log4j/log4j2/trunk/core/src/test/java/org/apache/logging/log4j/core/appender/XmlCompactFileAppenderTest.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: logging/log4j/log4j2/trunk/core/src/test/java/org/apache/logging/log4j/core/appender/XmlCompactFileAppenderTest.java
------------------------------------------------------------------------------
    svn:keywords = Id

Added: logging/log4j/log4j2/trunk/core/src/test/java/org/apache/logging/log4j/core/appender/XmlCompleteFileAppenderTest.java
URL: http://svn.apache.org/viewvc/logging/log4j/log4j2/trunk/core/src/test/java/org/apache/logging/log4j/core/appender/XmlCompleteFileAppenderTest.java?rev=1504184&view=auto
==============================================================================
--- logging/log4j/log4j2/trunk/core/src/test/java/org/apache/logging/log4j/core/appender/XmlCompleteFileAppenderTest.java (added)
+++ logging/log4j/log4j2/trunk/core/src/test/java/org/apache/logging/log4j/core/appender/XmlCompleteFileAppenderTest.java Wed Jul 17 16:08:03 2013
@@ -0,0 +1,86 @@
+/*
+ * 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.logging.log4j.core.appender;
+
+import static org.junit.Assert.*;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileReader;
+
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.apache.logging.log4j.core.LifeCycle;
+import org.apache.logging.log4j.core.config.XMLConfigurationFactory;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+/**
+ * Tests a "complete" XML file a.k.a. a well-formed XML file.
+ */
+public class XmlCompleteFileAppenderTest {
+
+    @BeforeClass
+    public static void beforeClass() {
+        System.setProperty(XMLConfigurationFactory.CONFIGURATION_FILE_PROPERTY,
+                "XmlCompleteFileAppenderTest.xml");
+    }
+
+    @Test
+    public void testFlushAtEndOfBatch() throws Exception {
+        final File f = new File("target", "XmlCompleteFileAppenderTest.log");
+        // System.out.println(f.getAbsolutePath());
+        f.delete();
+        final Logger log = LogManager.getLogger("com.foo.Bar");
+        final String logMsg = "Message flushed with immediate flush=false";
+        log.info(logMsg);
+        ((LifeCycle) LogManager.getContext()).stop(); // stop async thread
+
+        final BufferedReader reader = new BufferedReader(new FileReader(f));
+        String line1;
+        String line2;
+        String line3;
+        String line4;
+        try {
+            line1 = reader.readLine();
+            line2 = reader.readLine();
+            line3 = reader.readLine();
+            line4 = reader.readLine();
+        } finally {
+            reader.close();
+            f.delete();
+        }
+        assertNotNull("line1", line1);
+        final String msg1 = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>";
+        assertTrue("line1 incorrect: [" + line1 + "], does not contain: [" + msg1 + "]", line1.equals(msg1));
+        
+        assertNotNull("line2", line2);
+        final String msg2 = "<events xmlns=\"http://logging.apache.org/log4j/2.0/\">";
+        assertTrue("line2 incorrect: [" + line2 + "], does not contain: [" + msg2 + "]", line2.equals(msg2));
+        
+        assertNotNull("line3", line3);
+        final String msg3 = "<event ";
+        assertTrue("line3 incorrect: [" + line3 + "], does not contain: [" + msg3 + "]", line3.contains(msg3));
+
+        assertNotNull("line4", line4);
+        final String msg4 = logMsg;
+        assertTrue("line4 incorrect: [" + line4 + "], does not contain: [" + msg4 + "]", line4.contains(msg4));
+
+        final String location = "testFlushAtEndOfBatch";
+        assertTrue("no location", !line1.contains(location));
+    }
+}

Propchange: logging/log4j/log4j2/trunk/core/src/test/java/org/apache/logging/log4j/core/appender/XmlCompleteFileAppenderTest.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: logging/log4j/log4j2/trunk/core/src/test/java/org/apache/logging/log4j/core/appender/XmlCompleteFileAppenderTest.java
------------------------------------------------------------------------------
    svn:keywords = Id

Modified: logging/log4j/log4j2/trunk/core/src/test/java/org/apache/logging/log4j/core/appender/XmlFileAppenderTest.java
URL: http://svn.apache.org/viewvc/logging/log4j/log4j2/trunk/core/src/test/java/org/apache/logging/log4j/core/appender/XmlFileAppenderTest.java?rev=1504184&r1=1504183&r2=1504184&view=diff
==============================================================================
--- logging/log4j/log4j2/trunk/core/src/test/java/org/apache/logging/log4j/core/appender/XmlFileAppenderTest.java (original)
+++ logging/log4j/log4j2/trunk/core/src/test/java/org/apache/logging/log4j/core/appender/XmlFileAppenderTest.java Wed Jul 17 16:08:03 2013
@@ -54,32 +54,28 @@ public class XmlFileAppenderTest {
         String line1;
         String line2;
         String line3;
-        String line4;
         try {
             line1 = reader.readLine();
             line2 = reader.readLine();
             line3 = reader.readLine();
-            line4 = reader.readLine();
         } finally {
             reader.close();
             f.delete();
         }
         assertNotNull("line1", line1);
-        final String msg1 = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>";
-        assertTrue("line1 incorrect: [" + line1 + "], does not contain: [" + msg1 + "]", line1.equals(msg1));
         
+        assertNotNull("line1", line1);
+        final String msg1 = "<log4j:event ";
+        assertTrue("line1 incorrect: [" + line1 + "], does not contain: [" + msg1 + "]", line1.contains(msg1));
+
         assertNotNull("line2", line2);
-        final String msg2 = "<log4j:eventSet xmlns:log4j=\"http://logging.apache.org/log4j/\">";
-        assertTrue("line2 incorrect: [" + line2 + "], does not contain: [" + msg2 + "]", line2.equals(msg2));
-        
+        final String msg2 = logMsg;
+        assertTrue("line2 incorrect: [" + line2 + "], does not contain: [" + msg2 + "]", line2.contains(msg2));
+
         assertNotNull("line3", line3);
-        final String msg3 = "<log4j:event ";
+        final String msg3 = "</log4j:event>";
         assertTrue("line3 incorrect: [" + line3 + "], does not contain: [" + msg3 + "]", line3.contains(msg3));
 
-        assertNotNull("line4", line4);
-        final String msg4 = logMsg;
-        assertTrue("line4 incorrect: [" + line4 + "], does not contain: [" + msg4 + "]", line4.contains(msg4));
-
         final String location = "testFlushAtEndOfBatch";
         assertTrue("no location", !line1.contains(location));
     }

Modified: logging/log4j/log4j2/trunk/core/src/test/java/org/apache/logging/log4j/core/layout/XMLLayoutTest.java
URL: http://svn.apache.org/viewvc/logging/log4j/log4j2/trunk/core/src/test/java/org/apache/logging/log4j/core/layout/XMLLayoutTest.java?rev=1504184&r1=1504183&r2=1504184&view=diff
==============================================================================
--- logging/log4j/log4j2/trunk/core/src/test/java/org/apache/logging/log4j/core/layout/XMLLayoutTest.java (original)
+++ logging/log4j/log4j2/trunk/core/src/test/java/org/apache/logging/log4j/core/layout/XMLLayoutTest.java Wed Jul 17 16:08:03 2013
@@ -39,7 +39,7 @@ import static org.junit.Assert.assertTru
  */
 public class XMLLayoutTest {
     private static final String body =
-        "<log4j:message><![CDATA[empty mdc]]></log4j:message>";
+        "<message><![CDATA[empty mdc]]></message>";
     static ConfigurationFactory cf = new BasicConfigurationFactory();
 
     @AfterClass
@@ -60,13 +60,13 @@ public class XMLLayoutTest {
 
     @Test
     public void testContentType() {
-        final XMLLayout layout = XMLLayout.createLayout(null, null, null, null);
+        final XMLLayout layout = XMLLayout.createLayout(null, null, null, null, null, null);
         assertEquals("text/xml; charset=UTF-8", layout.getContentType());
     }
 
     @Test
     public void testDefaultCharset() {
-        final XMLLayout layout = XMLLayout.createLayout(null, null, null, null);
+        final XMLLayout layout = XMLLayout.createLayout(null, null, null, null, null, null);
         assertEquals(Charsets.UTF_8, layout.getCharset());
     }
 
@@ -77,7 +77,7 @@ public class XMLLayoutTest {
     public void testLayout() throws Exception {
 
         // set up appender
-        final XMLLayout layout = XMLLayout.createLayout("true", "true", "true", null);
+        final XMLLayout layout = XMLLayout.createLayout("true", "true", "true", null, null, null);
         final ListAppender<String> appender = new ListAppender<String>("List", null, layout, true, false);
         appender.start();
 
@@ -107,7 +107,7 @@ public class XMLLayoutTest {
         assertTrue("Incorrect number of lines. Require at least 50 " + list.size(), list.size() > 50);
         final String string = list.get(0);
         assertTrue("Incorrect header: " + string, string.equals("<?xml version=\"1.0\" encoding=\"UTF-8\"?>"));
-        assertTrue("Incorrect footer", list.get(list.size() - 1).equals("</log4j:eventSet>"));
-        assertTrue("Incorrect body. Expected " + body + " Actual: " + list.get(8), list.get(8).equals(body));
+        assertTrue("Incorrect footer", list.get(list.size() - 1).equals("</events>"));
+        assertTrue("Incorrect body. Expected " + body + " Actual: " + list.get(7), list.get(7).trim().equals(body));
     }
 }

Added: logging/log4j/log4j2/trunk/core/src/test/resources/XmlCompactFileAppenderTest.xml
URL: http://svn.apache.org/viewvc/logging/log4j/log4j2/trunk/core/src/test/resources/XmlCompactFileAppenderTest.xml?rev=1504184&view=auto
==============================================================================
--- logging/log4j/log4j2/trunk/core/src/test/resources/XmlCompactFileAppenderTest.xml (added)
+++ logging/log4j/log4j2/trunk/core/src/test/resources/XmlCompactFileAppenderTest.xml Wed Jul 17 16:08:03 2013
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<configuration status="OFF">
+  <appenders>
+    <File name="XmlFile" fileName="target/XmlCompactFileAppenderTest.log" immediateFlush="false" append="false">
+      <XMLLayout complete="true" compact="true"/>
+    </File>
+  </appenders>
+  
+  <loggers>
+    <asyncRoot level="info" includeLocation="false">
+      <appender-ref ref="XmlFile"/>
+    </asyncRoot>
+  </loggers>
+</configuration>

Propchange: logging/log4j/log4j2/trunk/core/src/test/resources/XmlCompactFileAppenderTest.xml
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: logging/log4j/log4j2/trunk/core/src/test/resources/XmlCompactFileAppenderTest.xml
------------------------------------------------------------------------------
    svn:keywords = Id

Added: logging/log4j/log4j2/trunk/core/src/test/resources/XmlCompleteFileAppenderTest.xml
URL: http://svn.apache.org/viewvc/logging/log4j/log4j2/trunk/core/src/test/resources/XmlCompleteFileAppenderTest.xml?rev=1504184&view=auto
==============================================================================
--- logging/log4j/log4j2/trunk/core/src/test/resources/XmlCompleteFileAppenderTest.xml (added)
+++ logging/log4j/log4j2/trunk/core/src/test/resources/XmlCompleteFileAppenderTest.xml Wed Jul 17 16:08:03 2013
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<configuration status="OFF">
+  <appenders>
+    <File name="XmlFile" fileName="target/XmlCompleteFileAppenderTest.log" immediateFlush="false" append="false">
+      <XMLLayout complete="true"/>
+    </File>
+  </appenders>
+  
+  <loggers>
+    <asyncRoot level="info" includeLocation="false">
+      <appender-ref ref="XmlFile"/>
+    </asyncRoot>
+  </loggers>
+</configuration>

Propchange: logging/log4j/log4j2/trunk/core/src/test/resources/XmlCompleteFileAppenderTest.xml
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: logging/log4j/log4j2/trunk/core/src/test/resources/XmlCompleteFileAppenderTest.xml
------------------------------------------------------------------------------
    svn:keywords = Id

Modified: logging/log4j/log4j2/trunk/core/src/test/resources/XmlFileAppenderTest.xml
URL: http://svn.apache.org/viewvc/logging/log4j/log4j2/trunk/core/src/test/resources/XmlFileAppenderTest.xml?rev=1504184&r1=1504183&r2=1504184&view=diff
==============================================================================
--- logging/log4j/log4j2/trunk/core/src/test/resources/XmlFileAppenderTest.xml (original)
+++ logging/log4j/log4j2/trunk/core/src/test/resources/XmlFileAppenderTest.xml Wed Jul 17 16:08:03 2013
@@ -2,7 +2,7 @@
 <configuration status="OFF">
   <appenders>
     <File name="XmlFile" fileName="target/XmlFileAppenderTest.log" immediateFlush="false" append="false">
-      <XMLLayout complete="true"/>
+      <XMLLayout/>
     </File>
   </appenders>
   

Modified: logging/log4j/log4j2/trunk/core/src/test/resources/log4j.dtd
URL: http://svn.apache.org/viewvc/logging/log4j/log4j2/trunk/core/src/test/resources/log4j.dtd?rev=1504184&r1=1504183&r2=1504184&view=diff
==============================================================================
--- logging/log4j/log4j2/trunk/core/src/test/resources/log4j.dtd (original)
+++ logging/log4j/log4j2/trunk/core/src/test/resources/log4j.dtd Wed Jul 17 16:08:03 2013
@@ -136,8 +136,8 @@ element. -->
 <!-- ==================================================================== -->
 <!--                       A logging event                                -->
 <!-- ==================================================================== -->
-<!ELEMENT log4j:eventSet (log4j:event*)>
-<!ATTLIST log4j:eventSet
+<!ELEMENT log4j:events (log4j:event*)>
+<!ATTLIST log4j:events
   xmlns:log4j             CDATA #FIXED "http://jakarta.apache.org/log4j/" 
   version                (1.1|1.2) "1.2" 
   includesLocationInfo   (true|false) "true"

Modified: logging/log4j/log4j2/trunk/src/changes/changes.xml
URL: http://svn.apache.org/viewvc/logging/log4j/log4j2/trunk/src/changes/changes.xml?rev=1504184&r1=1504183&r2=1504184&view=diff
==============================================================================
--- logging/log4j/log4j2/trunk/src/changes/changes.xml (original)
+++ logging/log4j/log4j2/trunk/src/changes/changes.xml Wed Jul 17 16:08:03 2013
@@ -21,6 +21,9 @@
   </properties>
   <body>
     <release version="2.0-beta9" date="soon, very soon" description="Bug fixes and enhancements">
+      <action issue="LOG4J2-312" dev="ggregory" type="update">
+        XML layout improvements (compact vs. pretty, namespace, namespace prefix, root element).
+      </action>
       <action issue="LOG4J2-308" dev="rpopma" type="update">
         Clarified which library versions were used in Async Loggers performance test.
       </action>

Modified: logging/log4j/log4j2/trunk/src/site/xdoc/manual/layouts.xml.vm
URL: http://svn.apache.org/viewvc/logging/log4j/log4j2/trunk/src/site/xdoc/manual/layouts.xml.vm?rev=1504184&r1=1504183&r2=1504184&view=diff
==============================================================================
--- logging/log4j/log4j2/trunk/src/site/xdoc/manual/layouts.xml.vm (original)
+++ logging/log4j/log4j2/trunk/src/site/xdoc/manual/layouts.xml.vm Wed Jul 17 16:08:03 2013
@@ -1148,29 +1148,48 @@ at org.apache.logging.log4j.core.pattern
         </subsection>
         <a name="XMLLayout"/>
         <subsection name="XMLLayout">
-          <p>The output of the XMLLayout consists of a series of log4j:event
-            elements as defined in the <a href="log4j.dtd">log4j.dtd</a>. If configured to do so it will
-            output a complete well-formed XML file. The output is designed to be
-            included as an
-            <em>external entity</em>
-            in a separate file to form
-            a correct XML file.
-          </p>
-          <p>For example, if <code>abc</code> is the name of the file where
-            the XMLLayout output goes, then a well-formed XML file would be:
-          </p>
-          <pre class="prettyprint linenums"><![CDATA[<?xml version="1.0" encoding="UTF-8">
-<!DOCTYPE log4j:eventSet SYSTEM "log4j.dtd" [<!ENTITY data SYSTEM "abc">]>
-<log4j:eventSet version="2.0" xmlns:log4j="http://logging.apache.org/log4j/">
-
-</log4j:eventSet>]]></pre>
-          <p>This approach enforces the independence of the XMLLayout and the appender where it is embedded.
+          <!-- From Javadoc of org.apache.logging.log4j.core.layout.XMLLayout -->
+          <p>
+          Appends a series of <code>event</code> elements as defined in the <a href="log4j.dtd">log4j.dtd</a>.
           </p>
-          <p>The <code>version</code> attribute helps components to correctly intrepret output generated by XMLLayout.
-            The value of this attribute should be "2.0".
+          <h4>Complete well-formed XML vs. fragment XML</h4>
+          <p>
+          If you configure <code>complete="true"</code>, the appender outputs a well-formed XML document where the 
+          default namespace is the log4j namespace <code>"http://logging.apache.org/log4j/2.0/"</code>.  By default, 
+          with <code>complete="false"</code>, you should include the output as an <em>external entity</em> in a 
+          separate file to form a well-formed XML document, in which case the appender uses 
+          <code>namespacePrefix</code> with a default of <code>"log4j"</code>.
           </p>
-          <p>Appenders using this layout should have their encoding set to UTF-8 or UTF-16, otherwise events containing
-            non ASCII characters could result in corrupted log files.
+          <p>
+          A well-formed XML document follows this pattern:
+          </p>
+          <pre class="prettyprint linenums">&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&gt;
+&lt;events xmlns=&quot;http://logging.apache.org/log4j/2.0&quot;&gt;
+  &lt;event logger=&quot;com.foo.Bar&quot; timestamp=&quot;1373436580419&quot; level=&quot;INFO&quot; thread=&quot;main&quot;&gt;
+    &lt;message&gt;&lt;![CDATA[This is a log message 1]]&gt;&lt;/message&gt;
+  &lt;/event&gt;
+  &lt;event logger=&quot;com.foo.Baz&quot; timestamp=&quot;1373436580420&quot; level=&quot;INFO&quot; thread=&quot;main&quot;&gt;
+    &lt;message&gt;&lt;![CDATA[This is a log message 2]]&gt;&lt;/message&gt;
+  &lt;/event&gt;
+&lt;/events&gt;</pre>
+          <p>
+          If <code>complete="false"</code>, the appender does not write the XML processing instruction and the root 
+          element.
+          </p>
+          <p>
+          This approach enforces the independence of the XMLLayout and the appender where you embed it.
+          </p>
+          <h4>Encoding</h4>
+          <p>
+          Appenders using this layout should have their <code>charset</code> set to <code>UTF-8</code> or 
+          <code>UTF-16</code>, otherwise events containing non ASCII characters could result in corrupted log files.
+          </p>
+          <h4>Pretty vs. compact XML</h4>
+          <p>
+          By default, the XML layout is not compact (a.k.a. not "pretty") with <code>compact="false"</code>, which 
+          means the appender uses end-of-line characters and indents lines to format the XML. If 
+          <code>compact="true"</code>,  then no end-of-line or indentation is used. Message content may contain, 
+          of course, end-of-lines.
           </p>
         </subsection>
         <a name="LocationInformation"/>