Repository: logging-log4j2
Updated Branches:
  refs/heads/LOG4J2-1442 [created] 0af515f3b


LOG4J2-1442 Generic HTTP appender


Project: http://git-wip-us.apache.org/repos/asf/logging-log4j2/repo
Commit: http://git-wip-us.apache.org/repos/asf/logging-log4j2/commit/410f9d36
Tree: http://git-wip-us.apache.org/repos/asf/logging-log4j2/tree/410f9d36
Diff: http://git-wip-us.apache.org/repos/asf/logging-log4j2/diff/410f9d36

Branch: refs/heads/LOG4J2-1442
Commit: 410f9d360eabcdc6949c75973b786c9e2cec9c66
Parents: 8852cd1
Author: Mikael StÃ¥ldal <[email protected]>
Authored: Thu May 4 14:21:59 2017 +0200
Committer: Mikael StÃ¥ldal <[email protected]>
Committed: Thu May 4 14:45:04 2017 +0200

----------------------------------------------------------------------
 .../log4j/core/appender/HttpAppender.java       | 161 +++++++++++++++++++
 .../log4j/core/appender/HttpManager.java        |  82 ++++++++++
 .../log4j/core/appender/HttpAppenderTest.java   |  52 ++++++
 .../src/test/resources/HttpAppenderTest.xml     |  43 +++++
 src/changes/changes.xml                         |   3 +
 src/site/site.xml                               |   1 +
 src/site/xdoc/manual/appenders.xml              |  78 +++++++++
 7 files changed, 420 insertions(+)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/410f9d36/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/HttpAppender.java
----------------------------------------------------------------------
diff --git 
a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/HttpAppender.java
 
b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/HttpAppender.java
new file mode 100644
index 0000000..e0f1b27
--- /dev/null
+++ 
b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/HttpAppender.java
@@ -0,0 +1,161 @@
+/*
+ * 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 java.io.IOException;
+import java.io.Serializable;
+import java.util.Objects;
+import java.util.concurrent.TimeUnit;
+
+import org.apache.logging.log4j.core.Appender;
+import org.apache.logging.log4j.core.Filter;
+import org.apache.logging.log4j.core.Layout;
+import org.apache.logging.log4j.core.LogEvent;
+import org.apache.logging.log4j.core.config.Node;
+import org.apache.logging.log4j.core.config.Property;
+import org.apache.logging.log4j.core.config.plugins.Plugin;
+import org.apache.logging.log4j.core.config.plugins.PluginBuilderAttribute;
+import org.apache.logging.log4j.core.config.plugins.PluginBuilderFactory;
+import org.apache.logging.log4j.core.config.plugins.PluginElement;
+import 
org.apache.logging.log4j.core.config.plugins.validation.constraints.Required;
+
+/**
+ * Sends log events over HTTP.
+ */
+@Plugin(name = "Http", category = Node.CATEGORY, elementType = 
Appender.ELEMENT_TYPE, printObject = true)
+public final class HttpAppender extends AbstractAppender {
+
+    /**
+     * Builds HttpAppender instances.
+     * @param <B> The type to build
+     */
+    public static class Builder<B extends Builder<B>> extends 
AbstractAppender.Builder<B>
+            implements 
org.apache.logging.log4j.core.util.Builder<HttpAppender> {
+
+        @PluginBuilderAttribute
+        @Required(message = "No URL provided for HttpAppender")
+        private String url;
+
+        @PluginBuilderAttribute
+        private String method = "POST";
+
+        @PluginBuilderAttribute
+        private int connectTimeoutMillis = 0;
+
+        @PluginBuilderAttribute
+        private int readTimeoutMillis = 0;
+
+        @PluginElement("Headers")
+        private Property[] headers;
+
+        @Override
+        public HttpAppender build() {
+            final HttpManager httpManager = new 
HttpManager(getConfiguration(), getConfiguration().getLoggerContext(),
+                getName(), url, method, connectTimeoutMillis, 
readTimeoutMillis, headers);
+            return new HttpAppender(getName(), getLayout(), getFilter(), 
isIgnoreExceptions(), httpManager);
+        }
+
+        public String getUrl() {
+            return url;
+        }
+
+        public String getMethod() {
+            return method;
+        }
+
+        public int getConnectTimeoutMillis() {
+            return connectTimeoutMillis;
+        }
+
+        public int getReadTimeoutMillis() {
+            return readTimeoutMillis;
+        }
+
+        public Property[] getHeaders() {
+            return headers;
+        }
+
+        public B setUrl(final String url) {
+            this.url = url;
+            return asBuilder();
+        }
+
+        public B setMethod(final String method) {
+            this.method = method;
+            return asBuilder();
+        }
+
+        public B setConnectTimeoutMillis(int connectTimeoutMillis) {
+            this.connectTimeoutMillis = connectTimeoutMillis;
+            return asBuilder();
+        }
+
+        public B setReadTimeoutMillis(int readTimeoutMillis) {
+            this.readTimeoutMillis = readTimeoutMillis;
+            return asBuilder();
+        }
+
+        public B setHeaders(final Property[] headers) {
+            this.headers = headers;
+            return asBuilder();
+        }
+    }
+
+    /**
+     * @return a builder for a HttpAppender.
+     */
+    @PluginBuilderFactory
+    public static <B extends Builder<B>> B newBuilder() {
+        return new Builder<B>().asBuilder();
+    }
+
+    private final HttpManager manager;
+
+    private HttpAppender(final String name, final Layout<? extends 
Serializable> layout, final Filter filter,
+                         final boolean ignoreExceptions, final HttpManager 
manager) {
+        super(name, filter, layout, ignoreExceptions);
+        Objects.requireNonNull(layout, "layout");
+        this.manager = Objects.requireNonNull(manager, "manager");
+    }
+
+    @Override
+    public void append(final LogEvent event) {
+        try {
+            manager.send(getLayout(), event);
+        } catch (final Exception e) {
+            error("Unable to send HTTP in appender [" + getName() + "]", 
event, e);
+        }
+    }
+
+    @Override
+    public boolean stop(final long timeout, final TimeUnit timeUnit) {
+        setStopping();
+        boolean stopped = super.stop(timeout, timeUnit, false);
+        stopped &= manager.stop(timeout, timeUnit);
+        setStopped();
+        return stopped;
+    }
+
+    @Override
+    public String toString() {
+        return "HttpAppender{" +
+            "name=" + getName() +
+            ", state=" + getState() +
+            '}';
+    }
+}

http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/410f9d36/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/HttpManager.java
----------------------------------------------------------------------
diff --git 
a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/HttpManager.java
 
b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/HttpManager.java
new file mode 100644
index 0000000..8f69659
--- /dev/null
+++ 
b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/HttpManager.java
@@ -0,0 +1,82 @@
+/*
+ * 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 java.io.IOException;
+import java.io.OutputStream;
+import java.net.HttpURLConnection;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.Objects;
+
+import org.apache.logging.log4j.core.Layout;
+import org.apache.logging.log4j.core.LogEvent;
+import org.apache.logging.log4j.core.LoggerContext;
+import org.apache.logging.log4j.core.config.Configuration;
+import org.apache.logging.log4j.core.config.ConfigurationException;
+import org.apache.logging.log4j.core.config.Property;
+
+public class HttpManager extends AbstractManager {
+
+    private final Configuration configuration;
+    private final URL url;
+    private final String method;
+    private final int connectTimeoutMillis;
+    private final int readTimeoutMillis;
+    private final Property[] headers;
+
+    public HttpManager(final Configuration configuration, LoggerContext 
loggerContext, final String name,
+                       final String url, final String method, final int 
connectTimeoutMillis, final int readTimeoutMillis,
+                       final Property[] headers) {
+        super(loggerContext, name);
+        this.configuration = Objects.requireNonNull(configuration);
+        try {
+            this.url = new URL(url);
+        } catch (MalformedURLException e) {
+            throw new ConfigurationException(e);
+        }
+        this.method = Objects.requireNonNull(method, "method");
+        this.connectTimeoutMillis = connectTimeoutMillis;
+        this.readTimeoutMillis = readTimeoutMillis;
+        this.headers = headers != null ? headers : new Property[0];
+    }
+
+    public void send(final Layout<?> layout, final LogEvent event) throws 
IOException {
+        HttpURLConnection urlConnection = 
(HttpURLConnection)url.openConnection();
+        urlConnection.setAllowUserInteraction(false);
+        urlConnection.setDoOutput(true);
+        urlConnection.setDoInput(true);
+        urlConnection.setRequestMethod(method);
+        if (connectTimeoutMillis > 0) 
urlConnection.setConnectTimeout(connectTimeoutMillis);
+        if (readTimeoutMillis > 0) 
urlConnection.setReadTimeout(readTimeoutMillis);
+        if (layout.getContentType() != null) 
urlConnection.setRequestProperty("Content-Type", layout.getContentType());
+        for (Property header : headers) {
+            urlConnection.setRequestProperty(
+                header.getName(),
+                header.isValueNeedsLookup() ? 
configuration.getStrSubstitutor().replace(event, header.getValue()) : 
header.getValue());
+        }
+        byte[] msg = layout.toByteArray(event);
+        urlConnection.setFixedLengthStreamingMode(msg.length);
+        urlConnection.connect();
+        try (OutputStream os = urlConnection.getOutputStream()) {
+            os.write(msg);
+        }
+        urlConnection.getInputStream().close();
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/410f9d36/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/HttpAppenderTest.java
----------------------------------------------------------------------
diff --git 
a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/HttpAppenderTest.java
 
b/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/HttpAppenderTest.java
new file mode 100644
index 0000000..98120a1
--- /dev/null
+++ 
b/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/HttpAppenderTest.java
@@ -0,0 +1,52 @@
+package org.apache.logging.log4j.core.appender;
+
+import org.apache.logging.log4j.Level;
+import org.apache.logging.log4j.core.Appender;
+import org.apache.logging.log4j.core.impl.Log4jLogEvent;
+import org.apache.logging.log4j.junit.LoggerContextRule;
+import org.apache.logging.log4j.message.SimpleMessage;
+import org.junit.Rule;
+import org.junit.Test;
+
+// TODO this test requires manual verification
+public class HttpAppenderTest {
+
+    private static final String LOG_MESSAGE = "Hello, world!";
+
+    private static Log4jLogEvent createLogEvent() {
+        return Log4jLogEvent.newBuilder()
+            .setLoggerName(HttpAppenderTest.class.getName())
+            .setLoggerFqcn(HttpAppenderTest.class.getName())
+            .setLevel(Level.INFO)
+            .setMessage(new SimpleMessage(LOG_MESSAGE))
+            .build();
+    }
+
+    @Rule
+    public LoggerContextRule ctx = new 
LoggerContextRule("HttpAppenderTest.xml");
+
+    @Test
+    public void testAppendSuccess() throws Exception {
+        final Appender appender = ctx.getRequiredAppender("HttpSuccess");
+        appender.append(createLogEvent());
+    }
+
+    @Test
+    public void testAppendErrorIgnore() throws Exception {
+        final Appender appender = ctx.getRequiredAppender("HttpErrorIgnore");
+        appender.append(createLogEvent());
+    }
+
+    @Test(expected = AppenderLoggingException.class)
+    public void testAppendError() throws Exception {
+        final Appender appender = ctx.getRequiredAppender("HttpError");
+        appender.append(createLogEvent());
+    }
+
+    @Test
+    public void testAppendSubst() throws Exception {
+        final Appender appender = ctx.getRequiredAppender("HttpSubst");
+        appender.append(createLogEvent());
+    }
+
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/410f9d36/log4j-core/src/test/resources/HttpAppenderTest.xml
----------------------------------------------------------------------
diff --git a/log4j-core/src/test/resources/HttpAppenderTest.xml 
b/log4j-core/src/test/resources/HttpAppenderTest.xml
new file mode 100644
index 0000000..30edaa0
--- /dev/null
+++ b/log4j-core/src/test/resources/HttpAppenderTest.xml
@@ -0,0 +1,43 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ 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.
+  -->
+<Configuration name="HttpAppenderTest" status="WARN">
+  <Appenders>
+    <Http name="HttpSuccess" url="http://localhost:9200/test/log4j/";>
+      <Property name="X-Test" value="header value" />
+      <JsonLayout properties="true"/>
+    </Http>
+    <Http name="HttpErrorIgnore" url="http://localhost:9200/test/log4j/"; 
method="PUT">
+      <JsonLayout properties="true"/>
+    </Http>
+    <Http name="HttpError" url="http://localhost:9200/test/log4j/"; 
method="PUT" ignoreExceptions="false">
+      <JsonLayout properties="true"/>
+    </Http>
+    <Http name="HttpSubst" url="http://localhost:9200/test/log4j/";>
+      <Property name="X-Test" value="$${java:runtime}" />
+      <JsonLayout properties="true"/>
+    </Http>
+  </Appenders>
+  <Loggers>
+    <Root level="info">
+      <AppenderRef ref="HttpSuccess"/>
+      <AppenderRef ref="HttpErrorIgnore"/>
+      <AppenderRef ref="HttpError"/>
+      <AppenderRef ref="HttpSubst"/>
+    </Root>
+  </Loggers>
+</Configuration>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/410f9d36/src/changes/changes.xml
----------------------------------------------------------------------
diff --git a/src/changes/changes.xml b/src/changes/changes.xml
index 379c21e..d0f8133 100644
--- a/src/changes/changes.xml
+++ b/src/changes/changes.xml
@@ -31,6 +31,9 @@
          - "remove" - Removed
     -->
     <release version="2.9.0" date="2017-MM-DD" description="GA Release 2.9.0">
+      <action issue="LOG4J2-1442" dev="mikes" type="add">
+        Generic HTTP appender.
+      </action>
       <action issue="LOG4J2-1854" dev="mikes" type="add" due-to="Xavier 
Jodoin">
         Support null byte delimiter in GelfLayout.
       </action>

http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/410f9d36/src/site/site.xml
----------------------------------------------------------------------
diff --git a/src/site/site.xml b/src/site/site.xml
index e380c82..aa161fe 100644
--- a/src/site/site.xml
+++ b/src/site/site.xml
@@ -134,6 +134,7 @@
         <item name="JDBC" href="/manual/appenders.html#JDBCAppender"/>
         <item name="JMS" href="/manual/appenders.html#JMSAppender"/>
         <item name="JPA" href="/manual/appenders.html#JPAAppender"/>
+        <item name="HTTP" href="/manual/appenders.html#HttpAppender"/>
         <item name="Kafka" href="/manual/appenders.html#KafkaAppender"/>
         <item name="Memory Mapped File" 
href="/manual/appenders.html#MemoryMappedFileAppender"/>
         <item name="NoSQL" href="/manual/appenders.html#NoSQLAppender"/>

http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/410f9d36/src/site/xdoc/manual/appenders.xml
----------------------------------------------------------------------
diff --git a/src/site/xdoc/manual/appenders.xml 
b/src/site/xdoc/manual/appenders.xml
index 28d9aa4..f7721df 100644
--- a/src/site/xdoc/manual/appenders.xml
+++ b/src/site/xdoc/manual/appenders.xml
@@ -1538,6 +1538,84 @@ public class JpaLogEntity extends 
AbstractLogEventWrapperEntity {
     ...
 }]]></pre>
         </subsection>
+        <a name="HttpAppender"/>
+        <subsection name="HttpAppender">
+          <p>
+            The HttpAppender sends log events over HTTP. A Layout must be 
provided to format the LogEvent.
+          </p>
+          <p>
+            Will set the <code>Content-Type</code> header according to the 
layout. Additional headers can be specified
+            with embedded Property elements.
+          </p>
+          <table>
+            <caption align="top">HttpAppender Parameters</caption>
+            <tr>
+              <th>Parameter Name</th>
+              <th>Type</th>
+              <th>Description</th>
+            </tr>
+            <tr>
+              <td>name</td>
+              <td>String</td>
+              <td>The name of the Appender.</td>
+            </tr>
+            <tr>
+              <td>filter</td>
+              <td>Filter</td>
+              <td>A Filter to determine if the event should be handled by this 
Appender. More than one Filter
+                may be used by using a CompositeFilter.</td>
+            </tr>
+            <tr>
+              <td>layout</td>
+              <td>Layout</td>
+              <td>The Layout to use to format the LogEvent.</td>
+            </tr>
+            <tr>
+              <td>url</td>
+              <td>string</td>
+              <td>The URL to use. The URL scheme must be "http" or 
"https".</td>
+            </tr>
+            <tr>
+              <td>method</td>
+              <td>string</td>
+              <td>The HTTP method to use. Optional, default is "POST".</td>
+            </tr>
+            <tr>
+              <td>connectTimeoutMillis</td>
+              <td>integer</td>
+              <td>The connect timeout in milliseconds. Optional, default is 0 
(infinite timeout).</td>
+            </tr>
+            <tr>
+              <td>readTimeoutMillis</td>
+              <td>integer</td>
+              <td>The socket read timeout in milliseconds. Optional, default 
is 0 (infinite timeout).</td>
+            </tr>
+            <tr>
+              <td>headers</td>
+              <td>Property[]</td>
+              <td>Additional HTTP headers to use. The values support <a 
href="lookups.html">lookups</a></td>
+            </tr>
+            <tr>
+              <td>ignoreExceptions</td>
+              <td>boolean</td>
+              <td>The default is <code>true</code>, causing exceptions 
encountered while appending events to be
+                internally logged and then ignored. When set to 
<code>false</code> exceptions will be propagated to the
+                caller, instead. You must set this to <code>false</code> when 
wrapping this Appender in a
+                <a href="#FailoverAppender">FailoverAppender</a>.</td>
+            </tr>
+          </table>
+          <p>
+            Here is a sample HttpAppender configuration snippet:
+          </p>
+          <pre class="prettyprint linenums"><![CDATA[<?xml version="1.0" 
encoding="UTF-8"?>
+  ...
+  <Appenders>
+    <Http name="Http" url="http://localhost:9200/test/log4j/";>
+      <Property name="X-Java-Runtime" value="$${java:runtime}" />
+      <JsonLayout properties="true"/>
+    </Http>
+  </Appenders>]]></pre>
+        </subsection>
         <a name="KafkaAppender"/>
         <subsection name="KafkaAppender">
           <p>

Reply via email to