You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@logging.apache.org by mi...@apache.org on 2017/10/17 20:25:24 UTC
[4/4] logging-log4j2 git commit: LOG4J2-2076 CouchDB and MongoDB
appenders in own modules, remove log4j-nosql module
LOG4J2-2076 CouchDB and MongoDB appenders in own modules, remove log4j-nosql module
Project: http://git-wip-us.apache.org/repos/asf/logging-log4j2/repo
Commit: http://git-wip-us.apache.org/repos/asf/logging-log4j2/commit/ebec215e
Tree: http://git-wip-us.apache.org/repos/asf/logging-log4j2/tree/ebec215e
Diff: http://git-wip-us.apache.org/repos/asf/logging-log4j2/diff/ebec215e
Branch: refs/heads/LOG4J2-2076
Commit: ebec215eb9604963ff9930252f3013121b4be751
Parents: d3f9719
Author: Mikael Ståldal <mi...@staldal.nu>
Authored: Tue Oct 17 22:23:50 2017 +0200
Committer: Mikael Ståldal <mi...@staldal.nu>
Committed: Tue Oct 17 22:23:50 2017 +0200
----------------------------------------------------------------------
log4j-bom/pom.xml | 16 +-
.../appender/db/AbstractDatabaseAppender.java | 2 +-
.../appender/nosql/AbstractNoSqlConnection.java | 48 +++
.../core/appender/nosql/DefaultNoSqlObject.java | 66 +++
.../core/appender/nosql/NoSqlAppender.java | 96 +++++
.../core/appender/nosql/NoSqlConnection.java | 76 ++++
.../appender/nosql/NoSqlDatabaseManager.java | 224 ++++++++++
.../log4j/core/appender/nosql/NoSqlObject.java | 63 +++
.../core/appender/nosql/NoSqlProvider.java | 49 +++
.../log4j/core/appender/nosql/package-info.java | 24 ++
.../core/appender/nosql/NoSqlAppenderTest.java | 62 +++
.../nosql/NoSqlDatabaseManagerTest.java | 427 +++++++++++++++++++
log4j-couchdb/pom.xml | 183 ++++++++
.../appender/couchdb/CouchDbConnection.java | 69 +++
.../nosql/appender/couchdb/CouchDbProvider.java | 161 +++++++
.../nosql/appender/couchdb/package-info.java | 20 +
log4j-couchdb/src/site/markdown/index.md.vm | 48 +++
log4j-couchdb/src/site/site.xml | 52 +++
log4j-distribution/pom.xml | 40 +-
log4j-mongodb/pom.xml | 183 ++++++++
.../appender/mongodb/MongoDbConnection.java | 96 +++++
.../nosql/appender/mongodb/MongoDbObject.java | 66 +++
.../nosql/appender/mongodb/MongoDbProvider.java | 351 +++++++++++++++
.../nosql/appender/mongodb/package-info.java | 20 +
log4j-mongodb/src/site/markdown/index.md.vm | 48 +++
log4j-mongodb/src/site/site.xml | 52 +++
.../nosql/appender/mongodb/MongoDbAuthTest.java | 40 ++
.../appender/mongodb/MongoDbCappedTest.java | 40 ++
.../nosql/appender/mongodb/MongoDbTest.java | 40 ++
.../src/test/resources/log4j2-mongodb-auth.xml | 30 ++
.../test/resources/log4j2-mongodb-capped.xml | 30 ++
.../src/test/resources/log4j2-mongodb.xml | 30 ++
log4j-nosql/pom.xml | 194 ---------
.../nosql/appender/AbstractNoSqlConnection.java | 48 ---
.../nosql/appender/DefaultNoSqlObject.java | 66 ---
.../log4j/nosql/appender/NoSqlAppender.java | 106 -----
.../log4j/nosql/appender/NoSqlConnection.java | 76 ----
.../nosql/appender/NoSqlDatabaseManager.java | 224 ----------
.../log4j/nosql/appender/NoSqlObject.java | 63 ---
.../log4j/nosql/appender/NoSqlProvider.java | 49 ---
.../appender/couchdb/CouchDbConnection.java | 69 ---
.../nosql/appender/couchdb/CouchDbProvider.java | 161 -------
.../nosql/appender/couchdb/package-info.java | 20 -
.../appender/mongodb/MongoDbConnection.java | 96 -----
.../nosql/appender/mongodb/MongoDbObject.java | 66 ---
.../nosql/appender/mongodb/MongoDbProvider.java | 351 ---------------
.../nosql/appender/mongodb/package-info.java | 20 -
.../log4j/nosql/appender/package-info.java | 34 --
log4j-nosql/src/site/markdown/index.md.vm | 77 ----
log4j-nosql/src/site/site.xml | 52 ---
.../log4j/nosql/appender/MongoDbAuthTest.java | 40 --
.../log4j/nosql/appender/MongoDbCappedTest.java | 40 --
.../log4j/nosql/appender/MongoDbTest.java | 40 --
.../log4j/nosql/appender/NoSqlAppenderTest.java | 62 ---
.../appender/NoSqlDatabaseManagerTest.java | 427 -------------------
.../src/test/resources/log4j2-mongodb-auth.xml | 30 --
.../test/resources/log4j2-mongodb-capped.xml | 30 --
.../src/test/resources/log4j2-mongodb.xml | 30 --
pom.xml | 3 +-
src/site/markdown/javadoc.md | 4 +-
src/site/markdown/maven-artifacts.md.vm | 18 +-
src/site/site.xml | 4 +-
src/site/xdoc/runtime-dependencies.xml | 47 +-
63 files changed, 2790 insertions(+), 2509 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/ebec215e/log4j-bom/pom.xml
----------------------------------------------------------------------
diff --git a/log4j-bom/pom.xml b/log4j-bom/pom.xml
index 129aa91..96bf601 100644
--- a/log4j-bom/pom.xml
+++ b/log4j-bom/pom.xml
@@ -84,10 +84,22 @@
<artifactId>log4j-web</artifactId>
<version>${project.version}</version>
</dependency>
- <!-- NoSQL Appender Plugins -->
+ <!-- CouchDB Appender Plugin -->
<dependency>
<groupId>org.apache.logging.log4j</groupId>
- <artifactId>log4j-nosql</artifactId>
+ <artifactId>log4j-couchdb</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <!-- MongoDB Appender Plugin -->
+ <dependency>
+ <groupId>org.apache.logging.log4j</groupId>
+ <artifactId>log4j-mongodb</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <!-- Cassandra Appender Plugin -->
+ <dependency>
+ <groupId>org.apache.logging.log4j</groupId>
+ <artifactId>log4j-cassandra</artifactId>
<version>${project.version}</version>
</dependency>
<!-- Streaming API -->
http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/ebec215e/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/db/AbstractDatabaseAppender.java
----------------------------------------------------------------------
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/db/AbstractDatabaseAppender.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/db/AbstractDatabaseAppender.java
index 538b644..38f412c 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/db/AbstractDatabaseAppender.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/db/AbstractDatabaseAppender.java
@@ -32,7 +32,7 @@ import org.apache.logging.log4j.core.appender.AppenderLoggingException;
* An abstract Appender for writing events to a database of some type, be it relational or NoSQL. All database appenders
* should inherit from this base appender. Three implementations are currently provided:
* {@link org.apache.logging.log4j.core.appender.db.jdbc JDBC}, {@link org.apache.logging.log4j.core.appender.db.jpa
- * JPA}, and <a href="/log4j/2.x/log4j-nosql/apidocs/">NoSQL</a>.
+ * JPA}, and {@link org.apache.logging.log4j.core.appender.nosql NoSQL}.
*
* @param <T> Specifies which type of {@link AbstractDatabaseManager} this Appender requires.
*/
http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/ebec215e/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/nosql/AbstractNoSqlConnection.java
----------------------------------------------------------------------
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/nosql/AbstractNoSqlConnection.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/nosql/AbstractNoSqlConnection.java
new file mode 100644
index 0000000..965dc1e
--- /dev/null
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/nosql/AbstractNoSqlConnection.java
@@ -0,0 +1,48 @@
+/*
+ * 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.nosql;
+
+import java.util.concurrent.atomic.AtomicBoolean;
+
+/**
+ * Facilitates implementations of {@link NoSqlConnection}.
+ *
+ * @param <W>
+ * See {@link NoSqlConnection}.
+ * @param <T>See
+ * {@link NoSqlConnection}.
+ */
+public abstract class AbstractNoSqlConnection<W, T extends NoSqlObject<W>> implements NoSqlConnection<W, T> {
+
+ private final AtomicBoolean closed = new AtomicBoolean(false);
+
+ @Override
+ public void close() {
+ if (this.closed.compareAndSet(false, true)) {
+ closeImpl();
+ }
+ }
+
+ protected abstract void closeImpl();
+
+ @Override
+ public boolean isClosed() {
+ return this.closed.get();
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/ebec215e/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/nosql/DefaultNoSqlObject.java
----------------------------------------------------------------------
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/nosql/DefaultNoSqlObject.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/nosql/DefaultNoSqlObject.java
new file mode 100644
index 0000000..7600e1a
--- /dev/null
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/nosql/DefaultNoSqlObject.java
@@ -0,0 +1,66 @@
+/*
+ * 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.nosql;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Default implementation of {@link NoSqlObject}. Most NoSQL Java APIs tend
+ * to re-use the Map interface for storage and retrieval of the underlying JSON documents. Therefore, this
+ * implementation is provided for convenience.
+ */
+public class DefaultNoSqlObject implements NoSqlObject<Map<String, Object>> {
+
+ private final Map<String, Object> map;
+
+ public DefaultNoSqlObject() {
+ this.map = new HashMap<>();
+ }
+
+ @Override
+ public void set(final String field, final Object value) {
+ this.map.put(field, value);
+ }
+
+ @Override
+ public void set(final String field, final NoSqlObject<Map<String, Object>> value) {
+ this.map.put(field, value.unwrap());
+ }
+
+ @Override
+ public void set(final String field, final Object[] values) {
+ this.map.put(field, Arrays.asList(values));
+ }
+
+ @Override
+ public void set(final String field, final NoSqlObject<Map<String, Object>>[] values) {
+ final List<Map<String, Object>> list = new ArrayList<>(values.length);
+ for (final NoSqlObject<Map<String, Object>> value : values) {
+ list.add(value.unwrap());
+ }
+ this.map.put(field, list);
+ }
+
+ @Override
+ public Map<String, Object> unwrap() {
+ return this.map;
+ }
+}
http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/ebec215e/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/nosql/NoSqlAppender.java
----------------------------------------------------------------------
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/nosql/NoSqlAppender.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/nosql/NoSqlAppender.java
new file mode 100644
index 0000000..3200b3e
--- /dev/null
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/nosql/NoSqlAppender.java
@@ -0,0 +1,96 @@
+/*
+ * 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.nosql;
+
+import org.apache.logging.log4j.core.Appender;
+import org.apache.logging.log4j.core.Filter;
+import org.apache.logging.log4j.core.appender.AbstractAppender;
+import org.apache.logging.log4j.core.appender.db.AbstractDatabaseAppender;
+import org.apache.logging.log4j.core.config.plugins.Plugin;
+import org.apache.logging.log4j.core.config.plugins.PluginAttribute;
+import org.apache.logging.log4j.core.config.plugins.PluginElement;
+import org.apache.logging.log4j.core.config.plugins.PluginFactory;
+import org.apache.logging.log4j.core.util.Booleans;
+
+/**
+ * This Appender writes logging events to a NoSQL database using a configured NoSQL provider. It requires
+ * implementations of {@link NoSqlObject}, {@link NoSqlConnection}, and {@link NoSqlProvider} to "know" how to write
+ * events to the chosen NoSQL database.
+ *
+ * For examples on how to write your own NoSQL provider, see the simple source code for the MongoDB and CouchDB
+ * providers.
+ * </p>
+ *
+ * @see NoSqlObject
+ * @see NoSqlConnection
+ * @see NoSqlProvider
+ */
+@Plugin(name = "NoSql", category = "Core", elementType = Appender.ELEMENT_TYPE, printObject = true)
+public final class NoSqlAppender extends AbstractDatabaseAppender<NoSqlDatabaseManager<?>> {
+ private final String description;
+
+ private NoSqlAppender(final String name, final Filter filter, final boolean ignoreExceptions,
+ final NoSqlDatabaseManager<?> manager) {
+ super(name, filter, ignoreExceptions, manager);
+ this.description = this.getName() + "{ manager=" + this.getManager() + " }";
+ }
+
+ @Override
+ public String toString() {
+ return this.description;
+ }
+
+ /**
+ * Factory method for creating a NoSQL appender within the plugin manager.
+ *
+ * @param name The name of the appender.
+ * @param ignore If {@code "true"} (default) exceptions encountered when appending events are logged; otherwise
+ * they are propagated to the caller.
+ * @param filter The filter, if any, to use.
+ * @param bufferSize If an integer greater than 0, this causes the appender to buffer log events and flush whenever
+ * the buffer reaches this size.
+ * @param provider The NoSQL provider that provides connections to the chosen NoSQL database.
+ * @return a new NoSQL appender.
+ */
+ @PluginFactory
+ public static NoSqlAppender createAppender(
+ @PluginAttribute("name") final String name,
+ @PluginAttribute("ignoreExceptions") final String ignore,
+ @PluginElement("Filter") final Filter filter,
+ @PluginAttribute("bufferSize") final String bufferSize,
+ @PluginElement("NoSqlProvider") final NoSqlProvider<?> provider) {
+ if (provider == null) {
+ LOGGER.error("NoSQL provider not specified for appender [{}].", name);
+ return null;
+ }
+
+ final int bufferSizeInt = AbstractAppender.parseInt(bufferSize, 0);
+ final boolean ignoreExceptions = Booleans.parseBoolean(ignore, true);
+
+ final String managerName = "noSqlManager{ description=" + name + ", bufferSize=" + bufferSizeInt
+ + ", provider=" + provider + " }";
+
+ final NoSqlDatabaseManager<?> manager = NoSqlDatabaseManager.getNoSqlDatabaseManager(
+ managerName, bufferSizeInt, provider
+ );
+ if (manager == null) {
+ return null;
+ }
+
+ return new NoSqlAppender(name, filter, ignoreExceptions, manager);
+ }
+}
http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/ebec215e/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/nosql/NoSqlConnection.java
----------------------------------------------------------------------
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/nosql/NoSqlConnection.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/nosql/NoSqlConnection.java
new file mode 100644
index 0000000..4ca024e
--- /dev/null
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/nosql/NoSqlConnection.java
@@ -0,0 +1,76 @@
+/*
+ * 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.nosql;
+
+import java.io.Closeable;
+
+/**
+ * Represents a connection to the NoSQL database. Serves as a factory for new (empty) objects and an endpoint for
+ * inserted objects.
+ *
+ * @param <T> Specifies which implementation of {@link NoSqlObject} this connection provides.
+ * @param <W> Specifies which type of database object is wrapped by the {@link NoSqlObject} implementation provided.
+ */
+public interface NoSqlConnection<W, T extends NoSqlObject<W>> extends Closeable {
+ /**
+ * Instantiates and returns a {@link NoSqlObject} instance whose properties can be configured before ultimate
+ * insertion via {@link #insertObject(NoSqlObject)}.
+ *
+ * @return a new object.
+ * @see NoSqlObject
+ */
+ T createObject();
+
+ /**
+ * Creates an array of the specified length typed to match the {@link NoSqlObject} implementation appropriate for
+ * this provider.
+ *
+ * @param length the length of the array to create.
+ * @return a new array.
+ * @see NoSqlObject
+ */
+ T[] createList(int length);
+
+ /**
+ * Inserts the given object into the underlying NoSQL database.
+ *
+ * @param object The object to insert.
+ */
+ void insertObject(NoSqlObject<W> object);
+
+ /**
+ * Closes the underlying connection. This method call should be idempotent. Only the first call should have any
+ * effect; all further calls should be ignored. It's possible the underlying connection is stateless (such as an
+ * HTTP web service), in which case this method would be a no-op. This method should also commit any open
+ * transactions, if applicable and if not already committed.
+ * <p>
+ * If this connection is part of a connection pool, executing this method should commit the transaction and return
+ * the connection to the pool, but it should not actually close the underlying connection.
+ * </p>
+ */
+ @Override
+ void close();
+
+ /**
+ * Indicates whether the underlying connection is closed. If the underlying connection is stateless (such as an
+ * HTTP web service), this method would likely always return true. Essentially, this method should only return
+ * {@code true} if a call to {@link #insertObject(NoSqlObject)} <b>will</b> fail due to the state of this object.
+ *
+ * @return {@code true} if this object is considered closed.
+ */
+ boolean isClosed();
+}
http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/ebec215e/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/nosql/NoSqlDatabaseManager.java
----------------------------------------------------------------------
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/nosql/NoSqlDatabaseManager.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/nosql/NoSqlDatabaseManager.java
new file mode 100644
index 0000000..a0a29ce
--- /dev/null
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/nosql/NoSqlDatabaseManager.java
@@ -0,0 +1,224 @@
+/*
+ * 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.nosql;
+
+import org.apache.logging.log4j.Marker;
+import org.apache.logging.log4j.ThreadContext;
+import org.apache.logging.log4j.core.LogEvent;
+import org.apache.logging.log4j.core.appender.AppenderLoggingException;
+import org.apache.logging.log4j.core.appender.ManagerFactory;
+import org.apache.logging.log4j.core.appender.db.AbstractDatabaseManager;
+import org.apache.logging.log4j.core.util.Closer;
+import org.apache.logging.log4j.util.BiConsumer;
+import org.apache.logging.log4j.util.ReadOnlyStringMap;
+
+/**
+ * An {@link AbstractDatabaseManager} implementation for all NoSQL databases.
+ *
+ * @param <W> A type parameter for reassuring the compiler that all operations are using the same {@link NoSqlObject}.
+ */
+public final class NoSqlDatabaseManager<W> extends AbstractDatabaseManager {
+ private static final NoSQLDatabaseManagerFactory FACTORY = new NoSQLDatabaseManagerFactory();
+
+ private final NoSqlProvider<NoSqlConnection<W, ? extends NoSqlObject<W>>> provider;
+
+ private NoSqlConnection<W, ? extends NoSqlObject<W>> connection;
+
+ private NoSqlDatabaseManager(final String name, final int bufferSize,
+ final NoSqlProvider<NoSqlConnection<W, ? extends NoSqlObject<W>>> provider) {
+ super(name, bufferSize);
+ this.provider = provider;
+ }
+
+ @Override
+ protected void startupInternal() {
+ // nothing to see here
+ }
+
+ @Override
+ protected boolean shutdownInternal() {
+ // NoSQL doesn't use transactions, so all we need to do here is simply close the client
+ return Closer.closeSilently(this.connection);
+ }
+
+ @Override
+ protected void connectAndStart() {
+ try {
+ this.connection = this.provider.getConnection();
+ } catch (final Exception e) {
+ throw new AppenderLoggingException("Failed to get connection from NoSQL connection provider.", e);
+ }
+ }
+
+ @Override
+ protected void writeInternal(final LogEvent event) {
+ if (!this.isRunning() || this.connection == null || this.connection.isClosed()) {
+ throw new AppenderLoggingException(
+ "Cannot write logging event; NoSQL manager not connected to the database.");
+ }
+
+ final NoSqlObject<W> entity = this.connection.createObject();
+ entity.set("level", event.getLevel());
+ entity.set("loggerName", event.getLoggerName());
+ entity.set("message", event.getMessage() == null ? null : event.getMessage().getFormattedMessage());
+
+ final StackTraceElement source = event.getSource();
+ if (source == null) {
+ entity.set("source", (Object) null);
+ } else {
+ entity.set("source", this.convertStackTraceElement(source));
+ }
+
+ final Marker marker = event.getMarker();
+ if (marker == null) {
+ entity.set("marker", (Object) null);
+ } else {
+ entity.set("marker", buildMarkerEntity(marker));
+ }
+
+ entity.set("threadId", event.getThreadId());
+ entity.set("threadName", event.getThreadName());
+ entity.set("threadPriority", event.getThreadPriority());
+ entity.set("millis", event.getTimeMillis());
+ entity.set("date", new java.util.Date(event.getTimeMillis()));
+
+ @SuppressWarnings("ThrowableResultOfMethodCallIgnored")
+ Throwable thrown = event.getThrown();
+ if (thrown == null) {
+ entity.set("thrown", (Object) null);
+ } else {
+ final NoSqlObject<W> originalExceptionEntity = this.connection.createObject();
+ NoSqlObject<W> exceptionEntity = originalExceptionEntity;
+ exceptionEntity.set("type", thrown.getClass().getName());
+ exceptionEntity.set("message", thrown.getMessage());
+ exceptionEntity.set("stackTrace", this.convertStackTrace(thrown.getStackTrace()));
+ while (thrown.getCause() != null) {
+ thrown = thrown.getCause();
+ final NoSqlObject<W> causingExceptionEntity = this.connection.createObject();
+ causingExceptionEntity.set("type", thrown.getClass().getName());
+ causingExceptionEntity.set("message", thrown.getMessage());
+ causingExceptionEntity.set("stackTrace", this.convertStackTrace(thrown.getStackTrace()));
+ exceptionEntity.set("cause", causingExceptionEntity);
+ exceptionEntity = causingExceptionEntity;
+ }
+
+ entity.set("thrown", originalExceptionEntity);
+ }
+
+ final ReadOnlyStringMap contextMap = event.getContextData();
+ if (contextMap == null) {
+ entity.set("contextMap", (Object) null);
+ } else {
+ final NoSqlObject<W> contextMapEntity = this.connection.createObject();
+ contextMap.forEach(new BiConsumer<String, String>() {
+ @Override
+ public void accept(final String key, final String val) {
+ contextMapEntity.set(key, val);
+ }
+ });
+ entity.set("contextMap", contextMapEntity);
+ }
+
+ final ThreadContext.ContextStack contextStack = event.getContextStack();
+ if (contextStack == null) {
+ entity.set("contextStack", (Object) null);
+ } else {
+ entity.set("contextStack", contextStack.asList().toArray());
+ }
+
+ this.connection.insertObject(entity);
+ }
+
+ private NoSqlObject<W> buildMarkerEntity(final Marker marker) {
+ final NoSqlObject<W> entity = this.connection.createObject();
+ entity.set("name", marker.getName());
+
+ final Marker[] parents = marker.getParents();
+ if (parents != null) {
+ @SuppressWarnings("unchecked")
+ final NoSqlObject<W>[] parentEntities = new NoSqlObject[parents.length];
+ for (int i = 0; i < parents.length; i++) {
+ parentEntities[i] = buildMarkerEntity(parents[i]);
+ }
+ entity.set("parents", parentEntities);
+ }
+ return entity;
+ }
+
+ @Override
+ protected boolean commitAndClose() {
+ // all NoSQL drivers auto-commit (since NoSQL doesn't generally use the concept of transactions).
+ // also, all our NoSQL drivers use internal connection pooling and provide clients, not connections.
+ // thus, we should not be closing the client until shutdown as NoSQL is very different from SQL.
+ // see LOG4J2-591 and LOG4J2-676
+ return true;
+ }
+
+ private NoSqlObject<W>[] convertStackTrace(final StackTraceElement[] stackTrace) {
+ final NoSqlObject<W>[] stackTraceEntities = this.connection.createList(stackTrace.length);
+ for (int i = 0; i < stackTrace.length; i++) {
+ stackTraceEntities[i] = this.convertStackTraceElement(stackTrace[i]);
+ }
+ return stackTraceEntities;
+ }
+
+ private NoSqlObject<W> convertStackTraceElement(final StackTraceElement element) {
+ final NoSqlObject<W> elementEntity = this.connection.createObject();
+ elementEntity.set("className", element.getClassName());
+ elementEntity.set("methodName", element.getMethodName());
+ elementEntity.set("fileName", element.getFileName());
+ elementEntity.set("lineNumber", element.getLineNumber());
+ return elementEntity;
+ }
+
+ /**
+ * Creates a NoSQL manager for use within the {@link NoSqlAppender}, or returns a suitable one if it already exists.
+ *
+ * @param name The name of the manager, which should include connection details and hashed passwords where possible.
+ * @param bufferSize The size of the log event buffer.
+ * @param provider A provider instance which will be used to obtain connections to the chosen NoSQL database.
+ * @return a new or existing NoSQL manager as applicable.
+ */
+ public static NoSqlDatabaseManager<?> getNoSqlDatabaseManager(final String name, final int bufferSize,
+ final NoSqlProvider<?> provider) {
+ return AbstractDatabaseManager.getManager(name, new FactoryData(bufferSize, provider), FACTORY);
+ }
+
+ /**
+ * Encapsulates data that {@link NoSQLDatabaseManagerFactory} uses to create managers.
+ */
+ private static final class FactoryData extends AbstractDatabaseManager.AbstractFactoryData {
+ private final NoSqlProvider<?> provider;
+
+ protected FactoryData(final int bufferSize, final NoSqlProvider<?> provider) {
+ super(bufferSize);
+ this.provider = provider;
+ }
+ }
+
+ /**
+ * Creates managers.
+ */
+ private static final class NoSQLDatabaseManagerFactory implements
+ ManagerFactory<NoSqlDatabaseManager<?>, FactoryData> {
+ @Override
+ @SuppressWarnings("unchecked")
+ public NoSqlDatabaseManager<?> createManager(final String name, final FactoryData data) {
+ return new NoSqlDatabaseManager(name, data.getBufferSize(), data.provider);
+ }
+ }
+}
http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/ebec215e/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/nosql/NoSqlObject.java
----------------------------------------------------------------------
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/nosql/NoSqlObject.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/nosql/NoSqlObject.java
new file mode 100644
index 0000000..41bc7f9
--- /dev/null
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/nosql/NoSqlObject.java
@@ -0,0 +1,63 @@
+/*
+ * 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.nosql;
+
+/**
+ * Represents a simple POJO object inserted into a NoSQL object.
+ *
+ * @param <W> Specifies what type of underlying object (such as a MongoDB BasicDBObject) this NoSqlObject wraps.
+ */
+public interface NoSqlObject<W> {
+ /**
+ * Sets the value of a property on this object to a String or primitive.
+ *
+ * @param field The name of the property
+ * @param value The value of the property
+ */
+ void set(String field, Object value);
+
+ /**
+ * Sets the value of a property on this object to a nested complex object.
+ *
+ * @param field The name of the property
+ * @param value The value of the property
+ */
+ void set(String field, NoSqlObject<W> value);
+
+ /**
+ * Sets the value of a property on this object to an array of Strings or primitives.
+ *
+ * @param field The name of the property
+ * @param values The values for the property
+ */
+ void set(String field, Object[] values);
+
+ /**
+ * Sets the value of a property on this object to an array of nested complex objects.
+ *
+ * @param field The name of the property
+ * @param values The values for the property
+ */
+ void set(String field, NoSqlObject<W>[] values);
+
+ /**
+ * Obtains the underlying NoSQL library-specific object that this object wraps.
+ *
+ * @return the wrapped object.
+ */
+ W unwrap();
+}
http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/ebec215e/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/nosql/NoSqlProvider.java
----------------------------------------------------------------------
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/nosql/NoSqlProvider.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/nosql/NoSqlProvider.java
new file mode 100644
index 0000000..39c4d60
--- /dev/null
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/nosql/NoSqlProvider.java
@@ -0,0 +1,49 @@
+/*
+ * 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.nosql;
+
+/**
+ * Implementations of this class are plugins for configuring the {@link NoSqlAppender} with the proper provider
+ * (MongoDB, etc.).
+ *
+ * @param <C> Specifies which implementation of {@link NoSqlConnection} this provider provides.
+ */
+public interface NoSqlProvider<C extends NoSqlConnection<?, ? extends NoSqlObject<?>>> {
+
+ /**
+ * Obtains a connection from this provider. The concept of a connection in this case is not strictly an active
+ * duplex UDP or TCP connection to the underlying database. It can be thought of more as a gateway, a path for
+ * inserting objects that may use a persistent connection or may use HTTP web service calls, etc.
+ * <p>
+ * Where applicable, this method should return a connection from the connection pool as opposed to opening a
+ * brand new connection every time.
+ * </p>
+ *
+ * @return a connection that can be used to create and persist objects to this database.
+ * @see NoSqlConnection
+ */
+ C getConnection();
+
+ /**
+ * All implementations must override {@link Object#toString()} to provide information about the provider
+ * configuration (obscuring passwords with one-way hashes).
+ *
+ * @return the string representation of this NoSQL provider.
+ */
+ @Override
+ String toString();
+}
http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/ebec215e/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/nosql/package-info.java
----------------------------------------------------------------------
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/nosql/package-info.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/nosql/package-info.java
new file mode 100644
index 0000000..641762f
--- /dev/null
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/nosql/package-info.java
@@ -0,0 +1,24 @@
+/*
+ * 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.
+ */
+/**
+ * The NoSQL Appender supports writing log events to NoSQL databases. You can also easily
+ * extend this to support various NoSQL databases by implementing just three interfaces:
+ * {@link org.apache.logging.log4j.core.appender.nosql.NoSqlObject NoSqlObject},
+ * {@link org.apache.logging.log4j.core.appender.nosql.NoSqlConnection NoSqlConnection}, and
+ * {@link org.apache.logging.log4j.core.appender.nosql.NoSqlProvider NoSqlProvider}.
+ */
+package org.apache.logging.log4j.core.appender.nosql;
http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/ebec215e/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/nosql/NoSqlAppenderTest.java
----------------------------------------------------------------------
diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/nosql/NoSqlAppenderTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/nosql/NoSqlAppenderTest.java
new file mode 100644
index 0000000..6ab9419
--- /dev/null
+++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/nosql/NoSqlAppenderTest.java
@@ -0,0 +1,62 @@
+/*
+ * 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.nosql;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.runners.MockitoJUnitRunner;
+
+import static org.junit.Assert.*;
+
+@RunWith(MockitoJUnitRunner.class)
+public class NoSqlAppenderTest {
+
+ @Mock
+ private NoSqlProvider<?> provider;
+
+ @Test
+ public void testNoProvider() {
+ final NoSqlAppender appender = NoSqlAppender.createAppender("myName01", null, null, null, null);
+
+ assertNull("The appender should be null.", appender);
+ }
+
+ @Test
+ public void testProvider() {
+ final NoSqlAppender appender = NoSqlAppender.createAppender("myName01", null, null, null, provider);
+
+ assertNotNull("The appender should not be null.", appender);
+ assertEquals("The toString value is not correct.",
+ "myName01{ manager=noSqlManager{ description=myName01, bufferSize=0, provider=" + provider + " } }",
+ appender.toString());
+
+ appender.stop();
+ }
+
+ @Test
+ public void testProviderBuffer() {
+ final NoSqlAppender appender = NoSqlAppender.createAppender("anotherName02", null, null, "25", provider);
+
+ assertNotNull("The appender should not be null.", appender);
+ assertEquals("The toString value is not correct.",
+ "anotherName02{ manager=noSqlManager{ description=anotherName02, bufferSize=25, provider=" + provider
+ + " } }", appender.toString());
+
+ appender.stop();
+ }
+}
http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/ebec215e/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/nosql/NoSqlDatabaseManagerTest.java
----------------------------------------------------------------------
diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/nosql/NoSqlDatabaseManagerTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/nosql/NoSqlDatabaseManagerTest.java
new file mode 100644
index 0000000..c203e90
--- /dev/null
+++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/nosql/NoSqlDatabaseManagerTest.java
@@ -0,0 +1,427 @@
+/*
+ * 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.nosql;
+
+import java.io.IOException;
+import java.sql.SQLException;
+import java.util.Collection;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.logging.log4j.Level;
+import org.apache.logging.log4j.MarkerManager;
+import org.apache.logging.log4j.ThreadContext;
+import org.apache.logging.log4j.core.LogEvent;
+import org.apache.logging.log4j.core.appender.AppenderLoggingException;
+import org.apache.logging.log4j.core.impl.Log4jLogEvent;
+import org.apache.logging.log4j.junit.ThreadContextStackRule;
+import org.apache.logging.log4j.message.Message;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.runners.MockitoJUnitRunner;
+import org.mockito.stubbing.Answer;
+
+import static org.junit.Assert.*;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.BDDMockito.given;
+import static org.mockito.BDDMockito.then;
+import static org.mockito.Mockito.mock;
+
+@RunWith(MockitoJUnitRunner.class)
+public class NoSqlDatabaseManagerTest {
+ @Mock
+ private NoSqlConnection<Map<String, Object>, DefaultNoSqlObject> connection;
+ @Mock
+ private NoSqlProvider<NoSqlConnection<Map<String, Object>, DefaultNoSqlObject>> provider;
+ @Mock
+ private Message message;
+ @Captor
+ private ArgumentCaptor<NoSqlObject<Map<String, Object>>> captor;
+
+ @Rule
+ public final ThreadContextStackRule threadContextRule = new ThreadContextStackRule();
+ @Rule
+ public final ExpectedException expectedException = ExpectedException.none();
+
+ @Before
+ public void setUp() {
+ given(provider.getConnection()).willReturn(connection);
+ given(connection.createObject()).willAnswer(new Answer<DefaultNoSqlObject>() {
+ @Override
+ public DefaultNoSqlObject answer(final InvocationOnMock invocation) throws Throwable {
+ return new DefaultNoSqlObject();
+ }
+ });
+ given(connection.createList(anyInt())).willAnswer(new Answer<DefaultNoSqlObject[]>() {
+ @Override
+ public DefaultNoSqlObject[] answer(final InvocationOnMock invocation) throws Throwable {
+ return new DefaultNoSqlObject[invocation.<Integer>getArgument(0)];
+ }
+ });
+ }
+
+ @Test
+ public void testConnection() {
+ try (final NoSqlDatabaseManager<?> manager = NoSqlDatabaseManager.getNoSqlDatabaseManager("name", 0,
+ provider)) {
+
+ assertNotNull("The manager should not be null.", manager);
+
+ manager.connectAndStart();
+ then(provider).should().getConnection();
+ manager.commitAndClose();
+ }
+ }
+
+ @Test
+ public void testWriteInternalNotConnected01() {
+ try (final NoSqlDatabaseManager<?> manager = NoSqlDatabaseManager.getNoSqlDatabaseManager("name", 0,
+ provider)) {
+ expectedException.expect(AppenderLoggingException.class);
+ manager.writeInternal(mock(LogEvent.class));
+ }
+ }
+
+ @Test
+ public void testWriteInternalNotConnected02() {
+ given(connection.isClosed()).willReturn(true);
+
+ try (final NoSqlDatabaseManager<?> manager = NoSqlDatabaseManager.getNoSqlDatabaseManager("name", 0,
+ provider)) {
+
+ manager.startup();
+ manager.connectAndStart();
+ then(provider).should().getConnection();
+
+ expectedException.expect(AppenderLoggingException.class);
+ manager.writeInternal(mock(LogEvent.class));
+ }
+ }
+
+ @Test
+ public void testWriteInternal01() {
+ given(connection.isClosed()).willReturn(false);
+ given(message.getFormattedMessage()).willReturn("My formatted message 01.");
+
+ try (final NoSqlDatabaseManager<?> manager = NoSqlDatabaseManager.getNoSqlDatabaseManager("name", 0,
+ provider)) {
+
+ manager.startup();
+ manager.connectAndStart();
+ then(provider).should().getConnection();
+
+ final LogEvent event = Log4jLogEvent.newBuilder()
+ .setLevel(Level.WARN)
+ .setLoggerName("com.foo.NoSQLDbTest.testWriteInternal01")
+ .setMessage(message)
+ .setSource(new StackTraceElement("com.foo.Bar", "testMethod01", "Bar.java", 15))
+ .setThreadId(1L)
+ .setThreadName("MyThread-A")
+ .setThreadPriority(1)
+ .setTimeMillis(1234567890123L)
+ .build();
+
+ manager.writeInternal(event);
+ then(connection).should().insertObject(captor.capture());
+
+ final NoSqlObject<Map<String, Object>> inserted = captor.getValue();
+ assertNotNull("The inserted value should not be null.", inserted);
+ final Map<String, Object> object = inserted.unwrap();
+ assertNotNull("The unwrapped object should not be null.", object);
+
+ assertEquals("The level is not correct.", Level.WARN, object.get("level"));
+ assertEquals("The logger is not correct.", "com.foo.NoSQLDbTest.testWriteInternal01",
+ object.get("loggerName"));
+ assertEquals("The message is not correct.", "My formatted message 01.", object.get("message"));
+ assertEquals("The thread is not correct.", "MyThread-A", object.get("threadName"));
+ assertEquals("The millis is not correct.", 1234567890123L, object.get("millis"));
+ assertEquals("The date is not correct.", 1234567890123L, ((Date) object.get("date")).getTime());
+
+ assertTrue("The source should be a map.", object.get("source") instanceof Map);
+ @SuppressWarnings("unchecked")
+ final Map<String, Object> source = (Map<String, Object>) object.get("source");
+ assertEquals("The class is not correct.", "com.foo.Bar", source.get("className"));
+ assertEquals("The method is not correct.", "testMethod01", source.get("methodName"));
+ assertEquals("The file name is not correct.", "Bar.java", source.get("fileName"));
+ assertEquals("The line number is not correct.", 15, source.get("lineNumber"));
+
+ assertNull("The marker should be null.", object.get("marker"));
+
+ assertNull("The thrown should be null.", object.get("thrown"));
+
+ assertTrue("The context map should be empty.", ((Map) object.get("contextMap")).isEmpty());
+
+ assertTrue("The context stack should be null.", ((Collection) object.get("contextStack")).isEmpty());
+
+ }
+ }
+
+ @Test
+ public void testWriteInternal02() {
+ given(connection.isClosed()).willReturn(false);
+ given(message.getFormattedMessage()).willReturn("Another cool message 02.");
+
+ try (final NoSqlDatabaseManager<?> manager = NoSqlDatabaseManager.getNoSqlDatabaseManager("name", 0,
+ provider)) {
+ manager.startup();
+
+ manager.connectAndStart();
+ then(provider).should().getConnection();
+
+ final RuntimeException exception = new RuntimeException("This is something cool!");
+ final Map<String, String> context = new HashMap<>();
+ context.put("hello", "world");
+ context.put("user", "pass");
+
+ ThreadContext.push("message1");
+ ThreadContext.push("stack2");
+ final ThreadContext.ContextStack stack = ThreadContext.getImmutableStack();
+ ThreadContext.clearStack();
+
+ final LogEvent event = Log4jLogEvent.newBuilder()
+ .setLevel(Level.DEBUG)
+ .setLoggerName("com.foo.NoSQLDbTest.testWriteInternal02")
+ .setMessage(message)
+ .setSource(new StackTraceElement("com.bar.Foo", "anotherMethod03", "Foo.java", 9))
+ .setMarker(MarkerManager.getMarker("LoneMarker"))
+ .setThreadId(1L)
+ .setThreadName("AnotherThread-B")
+ .setThreadPriority(1)
+ .setTimeMillis(987654321564L)
+ .setThrown(exception)
+ .setContextMap(context)
+ .setContextStack(stack)
+ .build();
+
+ manager.writeInternal(event);
+ then(connection).should().insertObject(captor.capture());
+
+ final NoSqlObject<Map<String, Object>> inserted = captor.getValue();
+ assertNotNull("The inserted value should not be null.", inserted);
+ final Map<String, Object> object = inserted.unwrap();
+ assertNotNull("The unwrapped object should not be null.", object);
+
+ assertEquals("The level is not correct.", Level.DEBUG, object.get("level"));
+ assertEquals("The logger is not correct.", "com.foo.NoSQLDbTest.testWriteInternal02",
+ object.get("loggerName"));
+ assertEquals("The message is not correct.", "Another cool message 02.", object.get("message"));
+ assertEquals("The thread is not correct.", "AnotherThread-B", object.get("threadName"));
+ assertEquals("The millis is not correct.", 987654321564L, object.get("millis"));
+ assertEquals("The date is not correct.", 987654321564L, ((Date) object.get("date")).getTime());
+
+ assertTrue("The source should be a map.", object.get("source") instanceof Map);
+ @SuppressWarnings("unchecked")
+ final Map<String, Object> source = (Map<String, Object>) object.get("source");
+ assertEquals("The class is not correct.", "com.bar.Foo", source.get("className"));
+ assertEquals("The method is not correct.", "anotherMethod03", source.get("methodName"));
+ assertEquals("The file name is not correct.", "Foo.java", source.get("fileName"));
+ assertEquals("The line number is not correct.", 9, source.get("lineNumber"));
+
+ assertTrue("The marker should be a map.", object.get("marker") instanceof Map);
+ @SuppressWarnings("unchecked")
+ final Map<String, Object> marker = (Map<String, Object>) object.get("marker");
+ assertEquals("The marker name is not correct.", "LoneMarker", marker.get("name"));
+ assertNull("The marker parent should be null.", marker.get("parent"));
+
+ assertTrue("The thrown should be a map.", object.get("thrown") instanceof Map);
+ @SuppressWarnings("unchecked")
+ final Map<String, Object> thrown = (Map<String, Object>) object.get("thrown");
+ assertEquals("The thrown type is not correct.", "java.lang.RuntimeException", thrown.get("type"));
+ assertEquals("The thrown message is not correct.", "This is something cool!", thrown.get("message"));
+ assertTrue("The thrown stack trace should be a list.", thrown.get("stackTrace") instanceof List);
+ @SuppressWarnings("unchecked")
+ final List<Map<String, Object>> stackTrace = (List<Map<String, Object>>) thrown.get("stackTrace");
+ assertEquals("The thrown stack trace length is not correct.", exception.getStackTrace().length,
+ stackTrace.size());
+ for (int i = 0; i < exception.getStackTrace().length; i++) {
+ final StackTraceElement e1 = exception.getStackTrace()[i];
+ final Map<String, Object> e2 = stackTrace.get(i);
+
+ assertEquals("Element class name [" + i + "] is not correct.", e1.getClassName(), e2.get("className"));
+ assertEquals("Element method name [" + i + "] is not correct.", e1.getMethodName(),
+ e2.get("methodName"));
+ assertEquals("Element file name [" + i + "] is not correct.", e1.getFileName(), e2.get("fileName"));
+ assertEquals("Element line number [" + i + "] is not correct.", e1.getLineNumber(),
+ e2.get("lineNumber"));
+ }
+ assertNull("The thrown should have no cause.", thrown.get("cause"));
+
+ assertTrue("The context map should be a map.", object.get("contextMap") instanceof Map);
+ assertEquals("The context map is not correct.", context, object.get("contextMap"));
+
+ assertTrue("The context stack should be list.", object.get("contextStack") instanceof List);
+ assertEquals("The context stack is not correct.", stack.asList(), object.get("contextStack"));
+ }
+ }
+
+ @Test
+ public void testWriteInternal03() {
+ given(connection.isClosed()).willReturn(false);
+ given(message.getFormattedMessage()).willReturn("Another cool message 02.");
+
+ try (final NoSqlDatabaseManager<?> manager = NoSqlDatabaseManager.getNoSqlDatabaseManager("name", 0,
+ provider)) {
+ manager.startup();
+
+ manager.connectAndStart();
+ then(provider).should().getConnection();
+
+ final IOException exception1 = new IOException("This is the cause.");
+ final SQLException exception2 = new SQLException("This is the result.", exception1);
+ final Map<String, String> context = new HashMap<>();
+ context.put("hello", "world");
+ context.put("user", "pass");
+
+ ThreadContext.push("message1");
+ ThreadContext.push("stack2");
+ final ThreadContext.ContextStack stack = ThreadContext.getImmutableStack();
+ ThreadContext.clearStack();
+
+ final LogEvent event = Log4jLogEvent.newBuilder()
+ .setLevel(Level.DEBUG)
+ .setLoggerName("com.foo.NoSQLDbTest.testWriteInternal02")
+ .setMessage(message)
+ .setSource(new StackTraceElement("com.bar.Foo", "anotherMethod03", "Foo.java", 9))
+ .setMarker(MarkerManager.getMarker("AnotherMarker").addParents(
+ MarkerManager.getMarker("Parent1").addParents(MarkerManager.getMarker("GrandParent1")),
+ MarkerManager.getMarker("Parent2")))
+ .setThreadId(1L)
+ .setThreadName("AnotherThread-B")
+ .setThreadPriority(1)
+ .setTimeMillis(987654321564L)
+ .setThrown(exception2)
+ .setContextMap(context)
+ .setContextStack(stack)
+ .build();
+
+ manager.writeInternal(event);
+ then(connection).should().insertObject(captor.capture());
+
+ final NoSqlObject<Map<String, Object>> inserted = captor.getValue();
+ assertNotNull("The inserted value should not be null.", inserted);
+ final Map<String, Object> object = inserted.unwrap();
+ assertNotNull("The unwrapped object should not be null.", object);
+
+ assertEquals("The level is not correct.", Level.DEBUG, object.get("level"));
+ assertEquals("The logger is not correct.", "com.foo.NoSQLDbTest.testWriteInternal02",
+ object.get("loggerName"));
+ assertEquals("The message is not correct.", "Another cool message 02.", object.get("message"));
+ assertEquals("The thread is not correct.", "AnotherThread-B", object.get("threadName"));
+ assertEquals("The millis is not correct.", 987654321564L, object.get("millis"));
+ assertEquals("The date is not correct.", 987654321564L, ((Date) object.get("date")).getTime());
+
+ assertTrue("The source should be a map.", object.get("source") instanceof Map);
+ @SuppressWarnings("unchecked")
+ final Map<String, Object> source = (Map<String, Object>) object.get("source");
+ assertEquals("The class is not correct.", "com.bar.Foo", source.get("className"));
+ assertEquals("The method is not correct.", "anotherMethod03", source.get("methodName"));
+ assertEquals("The file name is not correct.", "Foo.java", source.get("fileName"));
+ assertEquals("The line number is not correct.", 9, source.get("lineNumber"));
+
+ assertTrue("The marker should be a map.", object.get("marker") instanceof Map);
+ @SuppressWarnings("unchecked")
+ final Map<String, Object> marker = (Map<String, Object>) object.get("marker");
+ assertEquals("The marker name is not correct.", "AnotherMarker", marker.get("name"));
+
+ assertTrue("The marker parents should be a list.", marker.get("parents") instanceof List);
+ @SuppressWarnings("unchecked")
+ final List<Object> markerParents = (List<Object>) marker.get("parents");
+ assertEquals("The marker parents should contain two parents", 2, markerParents.size());
+
+ assertTrue("The marker parents[0] should be a map.", markerParents.get(0) instanceof Map);
+ @SuppressWarnings("unchecked")
+ final Map<String, Object> parent1 = (Map<String, Object>) markerParents.get(0);
+ assertEquals("The first marker parent name is not correct.", "Parent1", parent1.get("name"));
+
+ assertTrue("The marker parents[1] should be a map.", markerParents.get(1) instanceof Map);
+ @SuppressWarnings("unchecked")
+ final Map<String, Object> parent2 = (Map<String, Object>) markerParents.get(1);
+ assertEquals("The second marker parent name is not correct.", "Parent2", parent2.get("name"));
+ assertNull("The second marker should have no parent.", parent2.get("parent"));
+
+ assertTrue("The parent1 parents should be a list.", parent1.get("parents") instanceof List);
+ @SuppressWarnings("unchecked")
+ final List<Object> parent1Parents = (List<Object>) parent1.get("parents");
+ assertEquals("The parent1 parents should have only one parent", 1, parent1Parents.size());
+
+ assertTrue("The parent1Parents[0] should be a map.", parent1Parents.get(0) instanceof Map);
+ @SuppressWarnings("unchecked")
+ final Map<String, Object> parent1parent = (Map<String, Object>) parent1Parents.get(0);
+ assertEquals("The first parent1 parent name is not correct.", "GrandParent1", parent1parent.get("name"));
+ assertNull("The parent1parent marker should have no parent.", parent1parent.get("parent"));
+
+ assertTrue("The thrown should be a map.", object.get("thrown") instanceof Map);
+ @SuppressWarnings("unchecked")
+ final Map<String, Object> thrown = (Map<String, Object>) object.get("thrown");
+ assertEquals("The thrown type is not correct.", "java.sql.SQLException", thrown.get("type"));
+ assertEquals("The thrown message is not correct.", "This is the result.", thrown.get("message"));
+ assertTrue("The thrown stack trace should be a list.", thrown.get("stackTrace") instanceof List);
+ @SuppressWarnings("unchecked")
+ final List<Map<String, Object>> stackTrace = (List<Map<String, Object>>) thrown.get("stackTrace");
+ assertEquals("The thrown stack trace length is not correct.", exception2.getStackTrace().length,
+ stackTrace.size());
+ for (int i = 0; i < exception2.getStackTrace().length; i++) {
+ final StackTraceElement e1 = exception2.getStackTrace()[i];
+ final Map<String, Object> e2 = stackTrace.get(i);
+
+ assertEquals("Element class name [" + i + "] is not correct.", e1.getClassName(), e2.get("className"));
+ assertEquals("Element method name [" + i + "] is not correct.", e1.getMethodName(),
+ e2.get("methodName"));
+ assertEquals("Element file name [" + i + "] is not correct.", e1.getFileName(), e2.get("fileName"));
+ assertEquals("Element line number [" + i + "] is not correct.", e1.getLineNumber(),
+ e2.get("lineNumber"));
+ }
+ assertTrue("The thrown cause should be a map.", thrown.get("cause") instanceof Map);
+ @SuppressWarnings("unchecked")
+ final Map<String, Object> cause = (Map<String, Object>) thrown.get("cause");
+ assertEquals("The cause type is not correct.", "java.io.IOException", cause.get("type"));
+ assertEquals("The cause message is not correct.", "This is the cause.", cause.get("message"));
+ assertTrue("The cause stack trace should be a list.", cause.get("stackTrace") instanceof List);
+ @SuppressWarnings("unchecked")
+ final List<Map<String, Object>> causeStackTrace = (List<Map<String, Object>>) cause.get("stackTrace");
+ assertEquals("The cause stack trace length is not correct.", exception1.getStackTrace().length,
+ causeStackTrace.size());
+ for (int i = 0; i < exception1.getStackTrace().length; i++) {
+ final StackTraceElement e1 = exception1.getStackTrace()[i];
+ final Map<String, Object> e2 = causeStackTrace.get(i);
+
+ assertEquals("Element class name [" + i + "] is not correct.", e1.getClassName(), e2.get("className"));
+ assertEquals("Element method name [" + i + "] is not correct.", e1.getMethodName(),
+ e2.get("methodName"));
+ assertEquals("Element file name [" + i + "] is not correct.", e1.getFileName(), e2.get("fileName"));
+ assertEquals("Element line number [" + i + "] is not correct.", e1.getLineNumber(),
+ e2.get("lineNumber"));
+ }
+ assertNull("The cause should have no cause.", cause.get("cause"));
+
+ assertTrue("The context map should be a map.", object.get("contextMap") instanceof Map);
+ assertEquals("The context map is not correct.", context, object.get("contextMap"));
+
+ assertTrue("The context stack should be list.", object.get("contextStack") instanceof List);
+ assertEquals("The context stack is not correct.", stack.asList(), object.get("contextStack"));
+ }
+ }
+}
http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/ebec215e/log4j-couchdb/pom.xml
----------------------------------------------------------------------
diff --git a/log4j-couchdb/pom.xml b/log4j-couchdb/pom.xml
new file mode 100644
index 0000000..2e3e539
--- /dev/null
+++ b/log4j-couchdb/pom.xml
@@ -0,0 +1,183 @@
+<?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.
+ -->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <parent>
+ <groupId>org.apache.logging.log4j</groupId>
+ <artifactId>log4j</artifactId>
+ <version>2.10.0-SNAPSHOT</version>
+ </parent>
+ <modelVersion>4.0.0</modelVersion>
+
+ <artifactId>log4j-couchdb</artifactId>
+ <name>Apache Log4j CouchDB</name>
+ <description>
+ CouchDB appender for Log4j.
+ </description>
+ <properties>
+ <log4jParentDir>${basedir}/..</log4jParentDir>
+ <docLabel>CouchDB Documentation</docLabel>
+ <projectDir>/log4j-couchdb</projectDir>
+ <module.name>org.apache.logging.log4j.couchdb</module.name>
+ </properties>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.apache.logging.log4j</groupId>
+ <artifactId>log4j-core</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.lightcouch</groupId>
+ <artifactId>lightcouch</artifactId>
+ </dependency>
+ <!-- Test Dependencies -->
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.mockito</groupId>
+ <artifactId>mockito-core</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.logging.log4j</groupId>
+ <artifactId>log4j-api</artifactId>
+ <type>test-jar</type>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.logging.log4j</groupId>
+ <artifactId>log4j-core</artifactId>
+ <type>test-jar</type>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.logging.log4j</groupId>
+ <artifactId>log4j-slf4j-impl</artifactId>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>maven-bundle-plugin</artifactId>
+ <configuration>
+ <instructions>
+ <Fragment-Host>org.apache.logging.log4j.core</Fragment-Host>
+ <Export-Package>*</Export-Package>
+ </instructions>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+ <reporting>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-changes-plugin</artifactId>
+ <version>${changes.plugin.version}</version>
+ <reportSets>
+ <reportSet>
+ <reports>
+ <report>changes-report</report>
+ </reports>
+ </reportSet>
+ </reportSets>
+ <configuration>
+ <issueLinkTemplate>%URL%/show_bug.cgi?id=%ISSUE%</issueLinkTemplate>
+ <useJql>true</useJql>
+ </configuration>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-checkstyle-plugin</artifactId>
+ <version>${checkstyle.plugin.version}</version>
+ <configuration>
+ <!--<propertiesLocation>${vfs.parent.dir}/checkstyle.properties</propertiesLocation> -->
+ <configLocation>${log4jParentDir}/checkstyle.xml</configLocation>
+ <suppressionsLocation>${log4jParentDir}/checkstyle-suppressions.xml</suppressionsLocation>
+ <enableRulesSummary>false</enableRulesSummary>
+ <propertyExpansion>basedir=${basedir}</propertyExpansion>
+ <propertyExpansion>licensedir=${log4jParentDir}/checkstyle-header.txt</propertyExpansion>
+ </configuration>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-javadoc-plugin</artifactId>
+ <version>${javadoc.plugin.version}</version>
+ <configuration>
+ <bottom><![CDATA[<p align="center">Copyright © {inceptionYear}-{currentYear} {organizationName}. All Rights Reserved.<br />
+ Apache Logging, Apache Log4j, Log4j, Apache, the Apache feather logo, the Apache Logging project logo,
+ and the Apache Log4j logo are trademarks of The Apache Software Foundation.</p>]]></bottom>
+ <!-- module link generation is completely broken in the javadoc plugin for a multi-module non-aggregating
+ project -->
+ <detectOfflineLinks>false</detectOfflineLinks>
+ <linksource>true</linksource>
+ </configuration>
+ <reportSets>
+ <reportSet>
+ <id>non-aggregate</id>
+ <reports>
+ <report>javadoc</report>
+ </reports>
+ </reportSet>
+ </reportSets>
+ </plugin>
+ <plugin>
+ <groupId>org.codehaus.mojo</groupId>
+ <artifactId>findbugs-maven-plugin</artifactId>
+ <version>${findbugs.plugin.version}</version>
+ <configuration>
+ <fork>true</fork>
+ <jvmArgs>-Duser.language=en</jvmArgs>
+ <threshold>Normal</threshold>
+ <effort>Default</effort>
+ <excludeFilterFile>${log4jParentDir}/findbugs-exclude-filter.xml</excludeFilterFile>
+ </configuration>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-jxr-plugin</artifactId>
+ <version>${jxr.plugin.version}</version>
+ <reportSets>
+ <reportSet>
+ <id>non-aggregate</id>
+ <reports>
+ <report>jxr</report>
+ </reports>
+ </reportSet>
+ <reportSet>
+ <id>aggregate</id>
+ <reports>
+ <report>aggregate</report>
+ </reports>
+ </reportSet>
+ </reportSets>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-pmd-plugin</artifactId>
+ <version>${pmd.plugin.version}</version>
+ <configuration>
+ <targetJdk>${maven.compiler.target}</targetJdk>
+ </configuration>
+ </plugin>
+ </plugins>
+ </reporting>
+</project>
http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/ebec215e/log4j-couchdb/src/main/java/org/apache/logging/log4j/nosql/appender/couchdb/CouchDbConnection.java
----------------------------------------------------------------------
diff --git a/log4j-couchdb/src/main/java/org/apache/logging/log4j/nosql/appender/couchdb/CouchDbConnection.java b/log4j-couchdb/src/main/java/org/apache/logging/log4j/nosql/appender/couchdb/CouchDbConnection.java
new file mode 100644
index 0000000..2c3248d
--- /dev/null
+++ b/log4j-couchdb/src/main/java/org/apache/logging/log4j/nosql/appender/couchdb/CouchDbConnection.java
@@ -0,0 +1,69 @@
+/*
+ * 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.nosql.appender.couchdb;
+
+import java.util.Map;
+
+import org.apache.logging.log4j.core.appender.AppenderLoggingException;
+import org.apache.logging.log4j.core.appender.nosql.AbstractNoSqlConnection;
+import org.apache.logging.log4j.core.appender.nosql.DefaultNoSqlObject;
+import org.apache.logging.log4j.core.appender.nosql.NoSqlConnection;
+import org.apache.logging.log4j.core.appender.nosql.NoSqlObject;
+import org.apache.logging.log4j.util.Strings;
+import org.lightcouch.CouchDbClient;
+import org.lightcouch.Response;
+
+/**
+ * The Apache CouchDB implementation of {@link NoSqlConnection}.
+ */
+public final class CouchDbConnection extends AbstractNoSqlConnection<Map<String, Object>, DefaultNoSqlObject> {
+ private final CouchDbClient client;
+
+ public CouchDbConnection(final CouchDbClient client) {
+ this.client = client;
+ }
+
+ @Override
+ public DefaultNoSqlObject createObject() {
+ return new DefaultNoSqlObject();
+ }
+
+ @Override
+ public DefaultNoSqlObject[] createList(final int length) {
+ return new DefaultNoSqlObject[length];
+ }
+
+ @Override
+ public void insertObject(final NoSqlObject<Map<String, Object>> object) {
+ try {
+ final Response response = this.client.save(object.unwrap());
+ if (Strings.isNotEmpty(response.getError())) {
+ throw new AppenderLoggingException(
+ "Failed to write log event to CouchDB due to error: " + response.getError() + '.');
+ }
+ } catch (final Exception e) {
+ throw new AppenderLoggingException("Failed to write log event to CouchDB due to error: " + e.getMessage(),
+ e);
+ }
+ }
+
+ @Override
+ protected void closeImpl() {
+ this.client.shutdown();
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/ebec215e/log4j-couchdb/src/main/java/org/apache/logging/log4j/nosql/appender/couchdb/CouchDbProvider.java
----------------------------------------------------------------------
diff --git a/log4j-couchdb/src/main/java/org/apache/logging/log4j/nosql/appender/couchdb/CouchDbProvider.java b/log4j-couchdb/src/main/java/org/apache/logging/log4j/nosql/appender/couchdb/CouchDbProvider.java
new file mode 100644
index 0000000..cdae592
--- /dev/null
+++ b/log4j-couchdb/src/main/java/org/apache/logging/log4j/nosql/appender/couchdb/CouchDbProvider.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.nosql.appender.couchdb;
+
+import java.lang.reflect.Method;
+
+import org.apache.logging.log4j.Logger;
+import org.apache.logging.log4j.core.config.plugins.Plugin;
+import org.apache.logging.log4j.core.config.plugins.PluginAttribute;
+import org.apache.logging.log4j.core.config.plugins.PluginFactory;
+import org.apache.logging.log4j.core.config.plugins.convert.TypeConverters;
+import org.apache.logging.log4j.core.config.plugins.validation.constraints.ValidHost;
+import org.apache.logging.log4j.core.config.plugins.validation.constraints.ValidPort;
+import org.apache.logging.log4j.core.util.NameUtil;
+import org.apache.logging.log4j.core.appender.nosql.NoSqlProvider;
+import org.apache.logging.log4j.status.StatusLogger;
+import org.apache.logging.log4j.util.LoaderUtil;
+import org.apache.logging.log4j.util.Strings;
+import org.lightcouch.CouchDbClient;
+import org.lightcouch.CouchDbProperties;
+
+/**
+ * The Apache CouchDB implementation of {@link NoSqlProvider}.
+ */
+@Plugin(name = "CouchDB", category = "Core", printObject = true)
+public final class CouchDbProvider implements NoSqlProvider<CouchDbConnection> {
+ private static final int HTTP = 80;
+ private static final int HTTPS = 443;
+ private static final Logger LOGGER = StatusLogger.getLogger();
+
+ private final CouchDbClient client;
+ private final String description;
+
+ private CouchDbProvider(final CouchDbClient client, final String description) {
+ this.client = client;
+ this.description = "couchDb{ " + description + " }";
+ }
+
+ @Override
+ public CouchDbConnection getConnection() {
+ return new CouchDbConnection(this.client);
+ }
+
+ @Override
+ public String toString() {
+ return this.description;
+ }
+
+ /**
+ * Factory method for creating an Apache CouchDB provider within the plugin manager.
+ *
+ * @param databaseName The name of the database to which log event documents will be written.
+ * @param protocol Either "http" or "https," defaults to "http" and mutually exclusive with
+ * {@code factoryClassName&factoryMethodName!=null}.
+ * @param server The host name of the CouchDB server, defaults to localhost and mutually exclusive with
+ * {@code factoryClassName&factoryMethodName!=null}.
+ * @param port The port that CouchDB is listening on, defaults to 80 if {@code protocol} is "http" and 443 if
+ * {@code protocol} is "https," and mutually exclusive with
+ * {@code factoryClassName&factoryMethodName!=null}.
+ * @param username The username to authenticate against the MongoDB server with, mutually exclusive with
+ * {@code factoryClassName&factoryMethodName!=null}.
+ * @param password The password to authenticate against the MongoDB server with, mutually exclusive with
+ * {@code factoryClassName&factoryMethodName!=null}.
+ * @param factoryClassName A fully qualified class name containing a static factory method capable of returning a
+ * {@link CouchDbClient} or {@link CouchDbProperties}.
+ * @param factoryMethodName The name of the public static factory method belonging to the aforementioned factory
+ * class.
+ * @return a new Apache CouchDB provider.
+ */
+ @PluginFactory
+ public static CouchDbProvider createNoSqlProvider(
+ @PluginAttribute("databaseName") final String databaseName,
+ @PluginAttribute("protocol") String protocol,
+ @PluginAttribute(value = "server", defaultString = "localhost") @ValidHost final String server,
+ @PluginAttribute(value = "port", defaultString = "0") @ValidPort final String port,
+ @PluginAttribute("username") final String username,
+ @PluginAttribute(value = "password", sensitive = true) final String password,
+ @PluginAttribute("factoryClassName") final String factoryClassName,
+ @PluginAttribute("factoryMethodName") final String factoryMethodName) {
+ CouchDbClient client;
+ String description;
+ if (Strings.isNotEmpty(factoryClassName) && Strings.isNotEmpty(factoryMethodName)) {
+ try {
+ final Class<?> factoryClass = LoaderUtil.loadClass(factoryClassName);
+ final Method method = factoryClass.getMethod(factoryMethodName);
+ final Object object = method.invoke(null);
+
+ if (object instanceof CouchDbClient) {
+ client = (CouchDbClient) object;
+ description = "uri=" + client.getDBUri();
+ } else if (object instanceof CouchDbProperties) {
+ final CouchDbProperties properties = (CouchDbProperties) object;
+ client = new CouchDbClient(properties);
+ description = "uri=" + client.getDBUri() + ", username=" + properties.getUsername()
+ + ", passwordHash=" + NameUtil.md5(password + CouchDbProvider.class.getName())
+ + ", maxConnections=" + properties.getMaxConnections() + ", connectionTimeout="
+ + properties.getConnectionTimeout() + ", socketTimeout=" + properties.getSocketTimeout();
+ } else if (object == null) {
+ LOGGER.error("The factory method [{}.{}()] returned null.", factoryClassName, factoryMethodName);
+ return null;
+ } else {
+ LOGGER.error("The factory method [{}.{}()] returned an unsupported type [{}].", factoryClassName,
+ factoryMethodName, object.getClass().getName());
+ return null;
+ }
+ } catch (final ClassNotFoundException e) {
+ LOGGER.error("The factory class [{}] could not be loaded.", factoryClassName, e);
+ return null;
+ } catch (final NoSuchMethodException e) {
+ LOGGER.error("The factory class [{}] does not have a no-arg method named [{}].", factoryClassName,
+ factoryMethodName, e);
+ return null;
+ } catch (final Exception e) {
+ LOGGER.error("The factory method [{}.{}()] could not be invoked.", factoryClassName, factoryMethodName,
+ e);
+ return null;
+ }
+ } else if (Strings.isNotEmpty(databaseName)) {
+ if (protocol != null && protocol.length() > 0) {
+ protocol = protocol.toLowerCase();
+ if (!protocol.equals("http") && !protocol.equals("https")) {
+ LOGGER.error("Only protocols [http] and [https] are supported, [{}] specified.", protocol);
+ return null;
+ }
+ } else {
+ protocol = "http";
+ LOGGER.warn("No protocol specified, using default port [http].");
+ }
+
+ final int portInt = TypeConverters.convert(port, int.class, protocol.equals("https") ? HTTPS : HTTP);
+
+ if (Strings.isEmpty(username) || Strings.isEmpty(password)) {
+ LOGGER.error("You must provide a username and password for the CouchDB provider.");
+ return null;
+ }
+
+ client = new CouchDbClient(databaseName, false, protocol, server, portInt, username, password);
+ description = "uri=" + client.getDBUri() + ", username=" + username + ", passwordHash="
+ + NameUtil.md5(password + CouchDbProvider.class.getName());
+ } else {
+ LOGGER.error("No factory method was provided so the database name is required.");
+ return null;
+ }
+
+ return new CouchDbProvider(client, description);
+ }
+}
http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/ebec215e/log4j-couchdb/src/main/java/org/apache/logging/log4j/nosql/appender/couchdb/package-info.java
----------------------------------------------------------------------
diff --git a/log4j-couchdb/src/main/java/org/apache/logging/log4j/nosql/appender/couchdb/package-info.java b/log4j-couchdb/src/main/java/org/apache/logging/log4j/nosql/appender/couchdb/package-info.java
new file mode 100644
index 0000000..b783f87
--- /dev/null
+++ b/log4j-couchdb/src/main/java/org/apache/logging/log4j/nosql/appender/couchdb/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * 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.
+ */
+/**
+ * The classes in this package contain the Apache CouchDB provider for the NoSQL Appender.
+ */
+package org.apache.logging.log4j.nosql.appender.couchdb;