Allow normal XML syntax with validation in addition to the compact syntax
git-svn-id: https://svn.apache.org/repos/asf/logging/log4j/branches/BRANCH_2_0_EXPERIMENTAL/rgoers@1126341 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/log4j2-core/src/main/java/org/apache/logging/log4j/core/config/XMLConfiguration.java b/log4j2-core/src/main/java/org/apache/logging/log4j/core/config/XMLConfiguration.java
index 3ccf91b..b013186 100644
--- a/log4j2-core/src/main/java/org/apache/logging/log4j/core/config/XMLConfiguration.java
+++ b/log4j2-core/src/main/java/org/apache/logging/log4j/core/config/XMLConfiguration.java
@@ -31,10 +31,19 @@
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
+import javax.xml.XMLConstants;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.transform.Source;
+import javax.xml.transform.stream.StreamSource;
+import javax.xml.validation.Schema;
+import javax.xml.validation.SchemaFactory;
+import javax.xml.validation.Validator;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
import java.io.IOException;
+import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
@@ -48,10 +57,20 @@
private Element rootElement = null;
+ private boolean strict = false;
+
private static final String[] verboseClasses = new String[] { ResolverUtil.class.getName() };
+ private Validator validator;
+
+ private static final String LOG4J_XSD = "Log4J-V2.0.xsd";
+
public XMLConfiguration(InputSource source) {
+ byte[] buffer = null;
+
try {
+ buffer = toByteArray(source.getByteStream());
+ source = new InputSource(new ByteArrayInputStream(buffer));
DocumentBuilder builder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
Document document = builder.parse(source);
rootElement = document.getDocumentElement();
@@ -71,6 +90,8 @@
}
} else if ("name".equalsIgnoreCase(entry.getKey())) {
setName(entry.getValue());
+ } else if ("strict".equalsIgnoreCase(entry.getKey())) {
+ strict = Boolean.parseBoolean(entry.getValue());
}
}
if (status != Level.OFF) {
@@ -88,6 +109,28 @@
} catch (ParserConfigurationException pex) {
logger.error("Error parsing " + source.getSystemId(), pex);
}
+ if (strict && buffer != null) {
+ InputStream is = getClass().getClassLoader().getResourceAsStream(LOG4J_XSD);
+ Source src = new StreamSource(is, LOG4J_XSD);
+ SchemaFactory factory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
+ Schema schema = null;
+ try {
+ schema = factory.newSchema(src);
+ } catch (SAXException ex) {
+ logger.error("Error parsing Log4j schema", ex);
+ }
+ if (schema != null) {
+ validator = schema.newValidator();
+ try {
+ validator.validate(new StreamSource(new ByteArrayInputStream(buffer)));
+ } catch (IOException ioe) {
+ logger.error("Error reading configuration for validation", ioe);
+ } catch (SAXException ex) {
+ logger.error("Error validating configuration", ex);
+ }
+ }
+ }
+
if (getName() == null) {
setName(source.getSystemId());
}
@@ -113,7 +156,7 @@
org.w3c.dom.Node w3cNode = list.item(i);
if (w3cNode instanceof Element) {
Element child = (Element) w3cNode;
- String name = child.getTagName();
+ String name = getType(child);
PluginType type = getPluginManager().getPluginType(name);
Node childNode = new Node(node, name, type);
constructHierarchy(childNode, child);
@@ -139,6 +182,37 @@
}
}
+ private String getType(Element element) {
+ if (strict) {
+ NamedNodeMap attrs = element.getAttributes();
+ for (int i= 0; i < attrs.getLength(); ++i) {
+ org.w3c.dom.Node w3cNode = attrs.item(i);
+ if (w3cNode instanceof Attr) {
+ Attr attr = (Attr) w3cNode;
+ if (attr.getName().equalsIgnoreCase("type")) {
+ String type = attr.getValue();
+ attrs.removeNamedItem(attr.getName());
+ return type;
+ }
+ }
+ }
+ }
+ return element.getTagName();
+ }
+
+ private byte[] toByteArray(InputStream is) throws IOException {
+ ByteArrayOutputStream buffer = new ByteArrayOutputStream();
+
+ int nRead;
+ byte[] data = new byte[16384];
+
+ while ((nRead = is.read(data, 0, data.length)) != -1) {
+ buffer.write(data, 0, nRead);
+ }
+
+ return buffer.toByteArray();
+ }
+
private Map<String, String> processAttributes(Node node, Element element) {
NamedNodeMap attrs = element.getAttributes();
Map<String, String> attributes = node.getAttributes();
@@ -169,6 +243,4 @@
}
}
-
-
}
diff --git a/log4j2-core/src/main/resources/Log4J-V2.0.xsd b/log4j2-core/src/main/resources/Log4J-V2.0.xsd
new file mode 100644
index 0000000..af7cdfc
--- /dev/null
+++ b/log4j2-core/src/main/resources/Log4J-V2.0.xsd
@@ -0,0 +1,100 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<xs:schema attributeFormDefault="unqualified" elementFormDefault="qualified" xmlns:xs="http://www.w3.org/2001/XMLSchema">
+ <xs:element name="configuration" type="configurationType"/>
+ <xs:complexType name="configurationType">
+ <xs:sequence>
+ <xs:element type="propertiesType" name="properties"/>
+ <xs:element type="filtersType" name="filters"/>
+ <xs:element type="appendersType" name="appenders"/>
+ <xs:element type="loggersType" name="loggers"/>
+ </xs:sequence>
+ <xs:attribute type="xs:string" name="packages"/>
+ <xs:attribute type="xs:string" name="status"/>
+ <xs:attribute type="xs:string" name="strict"/>
+ <xs:attribute type="xs:string" name="name"/>
+ </xs:complexType>
+ <xs:complexType name="propertiesType">
+ <xs:sequence>
+ <xs:element type="propertyType" name="property"/>
+ </xs:sequence>
+ </xs:complexType>
+ <xs:complexType name="appenderType">
+ <xs:sequence>
+ <xs:element type="layoutType" name="layout" minOccurs="0"/>
+ <xs:element type="filtersType" name="filters" minOccurs="0"/>
+ </xs:sequence>
+ <xs:attribute type="xs:string" name="type" use="required"/>
+ <xs:attribute type="xs:string" name="name" use="required"/>
+ <xs:attribute type="xs:string" name="fileName" use="optional"/>
+ </xs:complexType>
+ <xs:complexType name="rootType">
+ <xs:sequence>
+ <xs:element type="appender-refType" name="appender-ref" maxOccurs="unbounded" minOccurs="1"/>
+ </xs:sequence>
+ <xs:attribute type="xs:string" name="level"/>
+ </xs:complexType>
+ <xs:complexType name="propertyType">
+ <xs:simpleContent>
+ <xs:extension base="xs:string">
+ <xs:attribute type="xs:string" name="name"/>
+ </xs:extension>
+ </xs:simpleContent>
+ </xs:complexType>
+ <xs:complexType name="KeyValuePairType">
+ <xs:simpleContent>
+ <xs:extension base="xs:string">
+ <xs:attribute type="xs:string" name="key"/>
+ <xs:attribute type="xs:string" name="value"/>
+ </xs:extension>
+ </xs:simpleContent>
+ </xs:complexType>
+ <xs:complexType name="appendersType">
+ <xs:sequence>
+ <xs:element type="appenderType" name="appender" maxOccurs="unbounded" minOccurs="1"/>
+ </xs:sequence>
+ </xs:complexType>
+ <xs:complexType name="appender-refType">
+ <xs:simpleContent>
+ <xs:extension base="xs:string">
+ <xs:attribute type="xs:string" name="ref" use="required"/>
+ </xs:extension>
+ </xs:simpleContent>
+ </xs:complexType>
+ <xs:complexType name="loggerType">
+ <xs:sequence>
+ <xs:element type="filtersType" name="filters" minOccurs="0"/>
+ <xs:element type="appender-refType" name="appender-ref"/>
+ </xs:sequence>
+ <xs:attribute type="xs:string" name="name" use="required"/>
+ <xs:attribute type="xs:string" name="level" use="optional"/>
+ <xs:attribute type="xs:string" name="additivity" use="optional"/>
+ </xs:complexType>
+ <xs:complexType name="filterType" mixed="true">
+ <xs:sequence>
+ <xs:element type="KeyValuePairType" name="KeyValuePair" minOccurs="0"/>
+ </xs:sequence>
+ <xs:attribute type="xs:string" name="type" use="required"/>
+ <xs:attribute type="xs:string" name="level" use="optional"/>
+ <xs:attribute type="xs:string" name="marker" use="optional"/>
+ <xs:attribute type="xs:string" name="onMatch" use="optional"/>
+ <xs:attribute type="xs:string" name="onMismatch" use="optional"/>
+ </xs:complexType>
+ <xs:complexType name="filtersType">
+ <xs:sequence>
+ <xs:element type="filterType" name="filter" maxOccurs="unbounded" minOccurs="0"/>
+ </xs:sequence>
+ </xs:complexType>
+ <xs:complexType name="loggersType" mixed="true">
+ <xs:sequence>
+ <xs:element type="loggerType" name="logger" maxOccurs="unbounded" minOccurs="0"/>
+ <xs:element type="rootType" name="root" maxOccurs="1" minOccurs="1"/>
+ </xs:sequence>
+ </xs:complexType>
+ <xs:complexType name="layoutType" mixed="true">
+ <xs:sequence>
+ <xs:element type="xs:string" name="pattern" minOccurs="0"/>
+ </xs:sequence>
+ <xs:attribute type="xs:string" name="type" use="required"/>
+ <xs:attribute type="xs:string" name="pattern" use="optional"/>
+ </xs:complexType>
+</xs:schema>
\ No newline at end of file
diff --git a/log4j2-core/src/test/java/org/apache/logging/log4j/core/StrictXMLConfigTest.java b/log4j2-core/src/test/java/org/apache/logging/log4j/core/StrictXMLConfigTest.java
new file mode 100644
index 0000000..54b414d
--- /dev/null
+++ b/log4j2-core/src/test/java/org/apache/logging/log4j/core/StrictXMLConfigTest.java
@@ -0,0 +1,120 @@
+/*
+ * 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;
+
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.MarkerManager;
+import org.apache.logging.log4j.ThreadContext;
+import org.apache.logging.log4j.core.config.Configuration;
+import org.apache.logging.log4j.core.config.XMLConfigurationFactory;
+import org.apache.logging.log4j.internal.StatusLogger;
+import org.apache.logging.log4j.message.StructuredDataMessage;
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+import java.util.Date;
+import java.util.Locale;
+
+/**
+ *
+ */
+public class StrictXMLConfigTest {
+
+ private static final String CONFIG = "log4j-strict1.xml";
+
+ @BeforeClass
+ public static void setupClass() {
+ System.setProperty(XMLConfigurationFactory.CONFIGURATION_FILE_PROPERTY, CONFIG);
+ LoggerContext ctx = (LoggerContext) LogManager.getContext();
+ Configuration config = ctx.getConfiguration();
+ }
+
+ @AfterClass
+ public static void cleanupClass() {
+ System.clearProperty(XMLConfigurationFactory.CONFIGURATION_FILE_PROPERTY);
+ LoggerContext ctx = (LoggerContext) LogManager.getContext();
+ ctx.reconfigure();
+ StatusLogger.getLogger().reset();
+ }
+
+ org.apache.logging.log4j.Logger logger = LogManager.getLogger("LoggerTest");
+
+ @Test
+ public void basicFlow() {
+ logger.entry();
+ logger.exit();
+ }
+
+ @Test
+ public void simpleFlow() {
+ logger.entry(CONFIG);
+ logger.exit(0);
+ }
+
+ @Test
+ public void throwing() {
+ logger.throwing(new IllegalArgumentException("Test Exception"));
+ }
+
+ @Test
+ public void catching() {
+ try {
+ throw new NullPointerException();
+ } catch (Exception e) {
+ logger.catching(e);
+ }
+ }
+
+ @Test
+ public void debug() {
+ logger.debug("Debug message");
+ }
+
+ @Test
+ public void debugObject() {
+ logger.debug(new Date());
+ }
+
+ @Test
+ public void debugWithParms() {
+ logger.debug("Hello, {}", "World");
+ }
+
+ @Test
+ public void mdc() {
+
+ ThreadContext.put("TestYear", new Integer(2010));
+ logger.debug("Debug message");
+ ThreadContext.clear();
+ logger.debug("Debug message");
+ }
+
+ @Test
+ public void structuredData() {
+ ThreadContext.put("loginId", "JohnDoe");
+ ThreadContext.put("ipAddress", "192.168.0.120");
+ ThreadContext.put("locale", Locale.US.getDisplayName());
+ StructuredDataMessage msg = new StructuredDataMessage("Audit@18060", "Transfer Complete", "Transfer");
+ msg.put("ToAccount", "123456");
+ msg.put("FromAccount", "123457");
+ msg.put("Amount", "200.00");
+ logger.info(MarkerManager.getMarker("EVENT"), msg);
+ ThreadContext.clear();
+ }
+}
+
diff --git a/log4j2-core/src/test/resources/log4j-strict1.xml b/log4j2-core/src/test/resources/log4j-strict1.xml
new file mode 100644
index 0000000..7ad749b
--- /dev/null
+++ b/log4j2-core/src/test/resources/log4j-strict1.xml
@@ -0,0 +1,57 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<configuration packages="" status="debug" strict="true" name="XMLConfigTest">
+ <properties>
+ <property name="filename">target/test.log</property>
+ </properties>
+ <filters>
+ <filter type="Threshold" level="trace"/>
+ </filters>
+
+ <appenders>
+ <appender type="Console" name="STDOUT">
+ <layout type="PatternLayout" pattern="%m MDC%X%n"/>
+ <filters>
+ <filter type="Marker" marker="FLOW" onMatch="DENY" onMismatch="NEUTRAL"/>
+ <filter type="Marker" marker="EXCEPTION" onMatch="DENY" onMismatch="ACCEPT"/>
+ </filters>
+ </appender>
+ <appender type="Console" name="FLOW">
+ <layout type="PatternLayout" pattern="%C{1}.%M %m %ex%n"/>
+ <filters>
+ <filter type="Marker" marker="FLOW" onMatch="ACCEPT" onMismatch="NEUTRAL"/>
+ <filter type="Marker" marker="EXCEPTION" onMatch="ACCEPT" onMismatch="DENY"/>
+ </filters>
+ </appender>
+ <appender type="File" name="File" fileName="${filename}">
+ <layout type="PatternLayout">
+ <pattern>%d %p %C{1.} [%t] %m%n</pattern>
+ </layout>
+ </appender>
+ <appender type="List" name="List">
+ <filters>
+ <filter type="Threshold" level="error"/>
+ </filters>
+ </appender>
+ </appenders>
+
+ <loggers>
+ <logger name="org.apache.logging.log4j.test1" level="debug" additivity="false">
+ <filters>
+ <filter type="ThreadContextMap">
+ <KeyValuePair key="test" value="123"/>
+ </filter>
+ </filters>
+ <appender-ref ref="STDOUT"/>
+ </logger>>
+
+ <logger name="org.apache.logging.log4j.test2" level="debug" additivity="false">
+ <appender-ref ref="File"/>
+ </logger>>
+
+ <root level="trace">
+ <appender-ref ref="STDOUT"/>
+ <appender-ref ref="FLOW"/>
+ </root>
+ </loggers>
+
+</configuration>
\ No newline at end of file