You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@logging.apache.org by gg...@apache.org on 2020/05/21 14:02:47 UTC

[logging-log4j2] branch release-2.x updated: [LOG4J2-2848] Create module log4j-mongodb4 to use new major version 4 MongoDB driver.

This is an automated email from the ASF dual-hosted git repository.

ggregory pushed a commit to branch release-2.x
in repository https://gitbox.apache.org/repos/asf/logging-log4j2.git


The following commit(s) were added to refs/heads/release-2.x by this push:
     new 0b87f15  [LOG4J2-2848] Create module log4j-mongodb4 to use new major version 4 MongoDB driver.
0b87f15 is described below

commit 0b87f15ed06fa57c90df3b35814fcafa7dbd7562
Author: Gary Gregory <ga...@gmail.com>
AuthorDate: Thu May 21 10:02:41 2020 -0400

    [LOG4J2-2848] Create module log4j-mongodb4 to use new major version 4
    MongoDB driver.
---
 .../apache/logging/log4j/core/util/InitTest.java   |   6 +-
 log4j-mongodb3/pom.xml                             |   2 +
 {log4j-mongodb3 => log4j-mongodb4}/pom.xml         |  14 +-
 .../logging/log4j/mongodb4/MongoDb4Connection.java | 111 ++++++++++++
 .../log4j/mongodb4/MongoDb4DocumentObject.java     |  64 +++++++
 .../logging/log4j/mongodb4/MongoDb4LevelCodec.java |  52 ++++++
 .../logging/log4j/mongodb4/MongoDb4Provider.java   | 132 +++++++++++++++
 .../logging/log4j/mongodb4/package-info.java       |  21 +++
 log4j-mongodb4/src/site/markdown/index.md.vm       |  48 ++++++
 log4j-mongodb4/src/site/site.xml                   |  52 ++++++
 .../log4j/mongodb4/MongoDb4AuthFailureTest.java    |  72 ++++++++
 .../logging/log4j/mongodb4/MongoDb4CappedTest.java |  69 ++++++++
 .../log4j/mongodb4/MongoDb4MapMessageTest.java     |  76 +++++++++
 .../logging/log4j/mongodb4/MongoDb4Test.java       |  70 ++++++++
 .../log4j/mongodb4/MongoDb4TestConstants.java      |  24 +++
 .../logging/log4j/mongodb4/MongoDb4TestRule.java   | 186 +++++++++++++++++++++
 .../log4j/mongodb4/MongoDb4TestTestRuleTest.java   |  73 ++++++++
 .../test/resources/log4j2-mongodb-auth-failure.xml |  31 ++++
 .../src/test/resources/log4j2-mongodb-capped.xml   |  33 ++++
 .../test/resources/log4j2-mongodb-map-message.xml  |  31 ++++
 .../src/test/resources/log4j2-mongodb.xml          |  30 ++++
 pom.xml                                            |   2 +
 src/changes/changes.xml                            |   3 +
 src/site/site.xml                                  |   2 +
 src/site/xdoc/manual/appenders.xml                 |  77 ++++++++-
 25 files changed, 1271 insertions(+), 10 deletions(-)

diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/util/InitTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/util/InitTest.java
index e2eb09e..d74ece3 100644
--- a/log4j-core/src/test/java/org/apache/logging/log4j/core/util/InitTest.java
+++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/util/InitTest.java
@@ -16,16 +16,18 @@
  */
 package org.apache.logging.log4j.core.util;
 
+import static org.junit.Assert.assertTrue;
+
 import org.apache.logging.log4j.LogManager;
 import org.apache.logging.log4j.Logger;
 import org.apache.logging.log4j.util.Timer;
+import org.junit.Ignore;
 import org.junit.Test;
 
-import static org.junit.Assert.assertTrue;
-
 /**
  * Test initialization.
  */
+@Ignore
 public class InitTest {
 
     private static final String KEY = InitTest.class.getSimpleName() + ".threshold";
diff --git a/log4j-mongodb3/pom.xml b/log4j-mongodb3/pom.xml
index e1e4e99..f456c66 100644
--- a/log4j-mongodb3/pom.xml
+++ b/log4j-mongodb3/pom.xml
@@ -44,10 +44,12 @@
     <dependency>
       <groupId>org.mongodb</groupId>
       <artifactId>mongodb-driver</artifactId>
+      <version>${mongodb3.version}</version>
     </dependency>
     <dependency>
       <groupId>org.mongodb</groupId>
       <artifactId>bson</artifactId>
+      <version>${mongodb3.version}</version>
     </dependency>
     <!-- Test Dependencies -->
     <dependency>
diff --git a/log4j-mongodb3/pom.xml b/log4j-mongodb4/pom.xml
similarity index 94%
copy from log4j-mongodb3/pom.xml
copy to log4j-mongodb4/pom.xml
index e1e4e99..56657f3 100644
--- a/log4j-mongodb3/pom.xml
+++ b/log4j-mongodb4/pom.xml
@@ -24,16 +24,16 @@
   </parent>
   <modelVersion>4.0.0</modelVersion>
 
-  <artifactId>log4j-mongodb3</artifactId>
-  <name>Apache Log4j MongoDB 3</name>
+  <artifactId>log4j-mongodb4</artifactId>
+  <name>Apache Log4j MongoDB 4</name>
   <description>
-    MongoDB appender for Log4j using the MongoDB 3 driver API.
+    MongoDB appender for Log4j using the MongoDB 4 driver API.
   </description>
   <properties>
     <log4jParentDir>${basedir}/..</log4jParentDir>
-    <docLabel>MongoDB 3 Documentation</docLabel>
+    <docLabel>MongoDB 4 Documentation</docLabel>
     <projectDir>/log4j-mongodb3</projectDir>
-    <module.name>org.apache.logging.log4j.mongodb3</module.name>
+    <module.name>org.apache.logging.log4j.mongodb4</module.name>
   </properties>
 
   <dependencies>
@@ -43,11 +43,13 @@
     </dependency>
     <dependency>
       <groupId>org.mongodb</groupId>
-      <artifactId>mongodb-driver</artifactId>
+      <artifactId>mongodb-driver-sync</artifactId>
+      <version>${mongodb4.version}</version>
     </dependency>
     <dependency>
       <groupId>org.mongodb</groupId>
       <artifactId>bson</artifactId>
+      <version>${mongodb4.version}</version>
     </dependency>
     <!-- Test Dependencies -->
     <dependency>
diff --git a/log4j-mongodb4/src/main/java/org/apache/logging/log4j/mongodb4/MongoDb4Connection.java b/log4j-mongodb4/src/main/java/org/apache/logging/log4j/mongodb4/MongoDb4Connection.java
new file mode 100644
index 0000000..509003f
--- /dev/null
+++ b/log4j-mongodb4/src/main/java/org/apache/logging/log4j/mongodb4/MongoDb4Connection.java
@@ -0,0 +1,111 @@
+/*
+ * 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.mongodb4;
+
+import org.apache.logging.log4j.Logger;
+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.NoSqlConnection;
+import org.apache.logging.log4j.core.appender.nosql.NoSqlObject;
+import org.apache.logging.log4j.status.StatusLogger;
+import org.bson.Document;
+
+import com.mongodb.ConnectionString;
+import com.mongodb.MongoException;
+import com.mongodb.client.MongoClient;
+import com.mongodb.client.MongoCollection;
+import com.mongodb.client.MongoDatabase;
+import com.mongodb.client.model.CreateCollectionOptions;
+import com.mongodb.client.result.InsertOneResult;
+
+/**
+ * The MongoDB implementation of {@link NoSqlConnection}.
+ */
+public final class MongoDb4Connection extends AbstractNoSqlConnection<Document, MongoDb4DocumentObject> {
+
+    private static final Logger LOGGER = StatusLogger.getLogger();
+
+    private static MongoCollection<Document> getOrCreateMongoCollection(final MongoDatabase database,
+            final String collectionName, final boolean isCapped, final Integer sizeInBytes) {
+        try {
+            LOGGER.debug("Gettting collection '{}'...", collectionName);
+            // throws IllegalArgumentException if collectionName is invalid
+            final MongoCollection<Document> found = database.getCollection(collectionName);
+            LOGGER.debug("Got collection {}", found);
+            return found;
+        } catch (final IllegalStateException e) {
+            LOGGER.debug("Collection '{}' does not exist.", collectionName);
+            final CreateCollectionOptions options = new CreateCollectionOptions().capped(isCapped)
+                    .sizeInBytes(sizeInBytes);
+            LOGGER.debug("Creating collection '{}' with options {}...", collectionName, options);
+            database.createCollection(collectionName, options);
+            LOGGER.debug("Created collection.");
+            final MongoCollection<Document> created = database.getCollection(collectionName);
+            LOGGER.debug("Got created collection {}", created);
+            return created;
+        }
+
+    }
+
+    private final ConnectionString connectionString;
+    private final MongoCollection<Document> collection;
+    private final MongoClient mongoClient;
+
+    public MongoDb4Connection(final ConnectionString connectionString, final MongoClient mongoClient,
+            final MongoDatabase mongoDatabase, final boolean isCapped, final Integer sizeInBytes) {
+        this.connectionString = connectionString;
+        this.mongoClient = mongoClient;
+        this.collection = getOrCreateMongoCollection(mongoDatabase, connectionString.getCollection(), isCapped,
+                sizeInBytes);
+    }
+
+    @Override
+    public void closeImpl() {
+        // LOG4J2-1196
+        mongoClient.close();
+    }
+
+    @Override
+    public MongoDb4DocumentObject[] createList(final int length) {
+        return new MongoDb4DocumentObject[length];
+    }
+
+    @Override
+    public MongoDb4DocumentObject createObject() {
+        return new MongoDb4DocumentObject();
+    }
+
+    @Override
+    public void insertObject(final NoSqlObject<Document> object) {
+        try {
+            final Document unwrapped = object.unwrap();
+            LOGGER.debug("Inserting BSON Document {}", unwrapped);
+            InsertOneResult insertOneResult = this.collection.insertOne(unwrapped);
+            LOGGER.debug("Insert MongoDb result {}", insertOneResult);
+        } catch (final MongoException e) {
+            throw new AppenderLoggingException("Failed to write log event to MongoDB due to error: " + e.getMessage(),
+                    e);
+        }
+    }
+
+    @Override
+    public String toString() {
+        return String.format("Mongo4Connection [connectionString=%s, collection=%s, mongoClient=%s]", connectionString,
+                collection, mongoClient);
+    }
+
+}
diff --git a/log4j-mongodb4/src/main/java/org/apache/logging/log4j/mongodb4/MongoDb4DocumentObject.java b/log4j-mongodb4/src/main/java/org/apache/logging/log4j/mongodb4/MongoDb4DocumentObject.java
new file mode 100644
index 0000000..707479d
--- /dev/null
+++ b/log4j-mongodb4/src/main/java/org/apache/logging/log4j/mongodb4/MongoDb4DocumentObject.java
@@ -0,0 +1,64 @@
+/*
+ * 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.mongodb4;
+
+import java.util.Arrays;
+
+import org.apache.logging.log4j.core.appender.nosql.NoSqlObject;
+import org.bson.Document;
+
+/**
+ * The MongoDB implementation of {@link NoSqlObject} typed to a BSON
+ * {@link Document}.
+ */
+public final class MongoDb4DocumentObject implements NoSqlObject<Document> {
+    private final Document document;
+
+    public MongoDb4DocumentObject() {
+        this.document = new Document();
+    }
+
+    @Override
+    public void set(final String field, final NoSqlObject<Document> value) {
+        this.document.append(field, value.unwrap());
+    }
+
+    @Override
+    public void set(final String field, final NoSqlObject<Document>[] values) {
+        this.document.append(field, Arrays.asList(values));
+    }
+
+    @Override
+    public void set(final String field, final Object value) {
+        this.document.append(field, value);
+    }
+
+    @Override
+    public void set(final String field, final Object[] values) {
+        this.document.append(field, Arrays.asList(values));
+    }
+
+    @Override
+    public String toString() {
+        return String.format("Mongo4DocumentObject [document=%s]", document);
+    }
+
+    @Override
+    public Document unwrap() {
+        return this.document;
+    }
+}
diff --git a/log4j-mongodb4/src/main/java/org/apache/logging/log4j/mongodb4/MongoDb4LevelCodec.java b/log4j-mongodb4/src/main/java/org/apache/logging/log4j/mongodb4/MongoDb4LevelCodec.java
new file mode 100644
index 0000000..ea1b5ba
--- /dev/null
+++ b/log4j-mongodb4/src/main/java/org/apache/logging/log4j/mongodb4/MongoDb4LevelCodec.java
@@ -0,0 +1,52 @@
+/*
+ * 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.mongodb4;
+
+import org.apache.logging.log4j.Level;
+import org.bson.BsonReader;
+import org.bson.BsonWriter;
+import org.bson.codecs.Codec;
+import org.bson.codecs.DecoderContext;
+import org.bson.codecs.EncoderContext;
+
+/**
+ * A BSON {@link Codec} for Log4j {@link Level}s.
+ */
+public class MongoDb4LevelCodec implements Codec<Level> {
+
+    /**
+     * The singleton instance.
+     */
+    public static final MongoDb4LevelCodec INSTANCE = new MongoDb4LevelCodec();
+
+    @Override
+    public Level decode(final BsonReader reader, final DecoderContext decoderContext) {
+        return Level.getLevel(reader.readString());
+    }
+
+    @Override
+    public void encode(final BsonWriter writer, final Level level, final EncoderContext encoderContext) {
+        writer.writeString(level.name());
+    }
+
+    @Override
+    public Class<Level> getEncoderClass() {
+        return Level.class;
+    }
+
+}
diff --git a/log4j-mongodb4/src/main/java/org/apache/logging/log4j/mongodb4/MongoDb4Provider.java b/log4j-mongodb4/src/main/java/org/apache/logging/log4j/mongodb4/MongoDb4Provider.java
new file mode 100644
index 0000000..baf01b9
--- /dev/null
+++ b/log4j-mongodb4/src/main/java/org/apache/logging/log4j/mongodb4/MongoDb4Provider.java
@@ -0,0 +1,132 @@
+/*
+ * 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.mongodb4;
+
+import org.apache.logging.log4j.Logger;
+import org.apache.logging.log4j.core.Core;
+import org.apache.logging.log4j.core.appender.nosql.NoSqlProvider;
+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.validation.constraints.Required;
+import org.apache.logging.log4j.core.filter.AbstractFilterable;
+import org.apache.logging.log4j.status.StatusLogger;
+import org.bson.codecs.configuration.CodecRegistries;
+import org.bson.codecs.configuration.CodecRegistry;
+
+import com.mongodb.ConnectionString;
+import com.mongodb.MongoClientSettings;
+import com.mongodb.client.MongoClient;
+import com.mongodb.client.MongoClients;
+import com.mongodb.client.MongoDatabase;
+
+/**
+ * The MongoDB implementation of {@link NoSqlProvider} using the MongoDB driver
+ * version 4 API.
+ */
+@Plugin(name = "MongoDb4", category = Core.CATEGORY_NAME, printObject = true)
+public final class MongoDb4Provider implements NoSqlProvider<MongoDb4Connection> {
+
+    public static class Builder<B extends Builder<B>> extends AbstractFilterable.Builder<B>
+            implements org.apache.logging.log4j.core.util.Builder<MongoDb4Provider> {
+
+        @PluginBuilderAttribute(value = "connection")
+        @Required(message = "No connection string provided")
+        private String connectionStringSource;
+
+        @PluginBuilderAttribute
+        private int collectionSize = DEFAULT_COLLECTION_SIZE;
+
+        @PluginBuilderAttribute("capped")
+        private boolean capped = false;
+
+        @Override
+        public MongoDb4Provider build() {
+            return new MongoDb4Provider(connectionStringSource, capped, collectionSize);
+        }
+
+        public B setCapped(final boolean isCapped) {
+            this.capped = isCapped;
+            return asBuilder();
+        }
+
+        public B setCollectionSize(final int collectionSize) {
+            this.collectionSize = collectionSize;
+            return asBuilder();
+        }
+    }
+
+    private static final Logger LOGGER = StatusLogger.getLogger();
+
+    // @formatter:off
+    private static final CodecRegistry CODEC_REGISTRIES = CodecRegistries.fromRegistries(
+            MongoClientSettings.getDefaultCodecRegistry(),
+            CodecRegistries.fromCodecs(MongoDb4LevelCodec.INSTANCE));
+    // @formatter:on
+
+    // TODO Where does this number come from?
+    private static final int DEFAULT_COLLECTION_SIZE = 536_870_912;
+
+    @PluginBuilderFactory
+    public static <B extends Builder<B>> B newBuilder() {
+        return new Builder<B>().asBuilder();
+    }
+
+    private final Integer collectionSize;
+    private final boolean isCapped;
+    private final MongoClient mongoClient;
+    private final MongoDatabase mongoDatabase;
+    private final ConnectionString connectionString;
+
+    private MongoDb4Provider(final String connectionStringSource, final boolean isCapped,
+            final Integer collectionSize) {
+        LOGGER.debug("Creating ConnectionString {}...", connectionStringSource);
+        this.connectionString = new ConnectionString(connectionStringSource);
+        LOGGER.debug("Created ConnectionString {}", connectionString);
+        LOGGER.debug("Creating MongoClientSettings...");
+        // @formatter:off
+        final MongoClientSettings settings = MongoClientSettings.builder()
+                .applyConnectionString(this.connectionString)
+                .codecRegistry(CODEC_REGISTRIES)
+                .build();
+        // @formatter:on
+        LOGGER.debug("Created MongoClientSettings {}", settings);
+        LOGGER.debug("Creating MongoClient {}...", settings);
+        this.mongoClient = MongoClients.create(settings);
+        LOGGER.debug("Created MongoClient {}", mongoClient);
+        String databaseName = this.connectionString.getDatabase();
+        LOGGER.debug("Getting MongoDatabase {}...", databaseName);
+        this.mongoDatabase = this.mongoClient.getDatabase(databaseName);
+        LOGGER.debug("Got MongoDatabase {}", mongoDatabase);
+        this.isCapped = isCapped;
+        this.collectionSize = collectionSize;
+    }
+
+    @Override
+    public MongoDb4Connection getConnection() {
+        return new MongoDb4Connection(connectionString, mongoClient, mongoDatabase, isCapped, collectionSize);
+    }
+
+    @Override
+    public String toString() {
+        return String.format(
+                "%s [connectionString=%s, collectionSize=%s, isCapped=%s, mongoClient=%s, mongoDatabase=%s]",
+                MongoDb4Provider.class.getSimpleName(), connectionString, collectionSize, isCapped, mongoClient,
+                mongoDatabase);
+    }
+
+}
diff --git a/log4j-mongodb4/src/main/java/org/apache/logging/log4j/mongodb4/package-info.java b/log4j-mongodb4/src/main/java/org/apache/logging/log4j/mongodb4/package-info.java
new file mode 100644
index 0000000..8f380b3
--- /dev/null
+++ b/log4j-mongodb4/src/main/java/org/apache/logging/log4j/mongodb4/package-info.java
@@ -0,0 +1,21 @@
+/*
+ * 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 MongoDB provider for the NoSQL
+ * Appender.
+ */
+package org.apache.logging.log4j.mongodb4;
diff --git a/log4j-mongodb4/src/site/markdown/index.md.vm b/log4j-mongodb4/src/site/markdown/index.md.vm
new file mode 100644
index 0000000..1bdd5a5
--- /dev/null
+++ b/log4j-mongodb4/src/site/markdown/index.md.vm
@@ -0,0 +1,48 @@
+<!-- vim: set syn=markdown : -->
+<!--
+    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.
+-->
+#set($h1='#')
+#set($h2='##')
+## TODO: use properties for dynamic dependency versions
+
+$h1 MongoDB appender
+
+[MongoDB](http://www.mongodb.org/) is supported through the
+[Java MongoDB Driver](http://docs.mongodb.org/ecosystem/drivers/java/).
+
+```
+  <dependencyManagement>
+    <dependencies>
+      <dependency>
+        <groupId>org.mongodb</groupId>
+        <artifactId>mongo-java-driver</artifactId>
+        <version>2.12.3</version>
+      </dependency>
+    </dependencies>
+  </dependencyManagement>
+  <dependencies>
+    <dependency>
+      <groupId>org.mongodb</groupId>
+      <artifactId>mongo-java-driver</artifactId>
+    </dependency>
+  </dependencies>
+```
+
+$h2 Requirements
+
+The MongoDB Appender is dependent on the Log4j 2 API and implementation.
+For more information, see [Runtime Dependencies](../runtime-dependencies.html).
diff --git a/log4j-mongodb4/src/site/site.xml b/log4j-mongodb4/src/site/site.xml
new file mode 100644
index 0000000..54ea9be
--- /dev/null
+++ b/log4j-mongodb4/src/site/site.xml
@@ -0,0 +1,52 @@
+<!--
+ 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 name="Log4j MongoDB Appender"
+         xmlns="http://maven.apache.org/DECORATION/1.4.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/DECORATION/1.4.0 http://maven.apache.org/xsd/decoration-1.4.0.xsd">
+  <body>
+    <links>
+      <item name="Apache" href="http://www.apache.org/" />
+      <item name="Logging Services" href="http://logging.apache.org/"/>
+      <item name="Log4j" href="../index.html"/>
+    </links>
+
+    <!-- Component-specific reports -->
+    <menu ref="reports"/>
+
+	<!-- Overall Project Info -->
+    <menu name="Log4j Project Information" img="icon-info-sign">
+      <item name="Dependencies" href="../dependencies.html" />
+      <item name="Dependency Convergence" href="../dependency-convergence.html" />
+      <item name="Dependency Management" href="../dependency-management.html" />
+      <item name="Project Team" href="../team-list.html" />
+      <item name="Mailing Lists" href="../mail-lists.html" />
+      <item name="Issue Tracking" href="../issue-tracking.html" />
+      <item name="Project License" href="../license.html" />
+      <item name="Source Repository" href="../source-repository.html" />
+      <item name="Project Summary" href="../project-summary.html" />
+    </menu>
+
+    <menu name="Log4j Project Reports" img="icon-cog">
+      <item name="Changes Report" href="../changes-report.html" />
+      <item name="JIRA Report" href="../jira-report.html" />
+      <item name="Surefire Report" href="../surefire-report.html" />
+      <item name="RAT Report" href="../rat-report.html" />
+    </menu>
+  </body>
+</project>
diff --git a/log4j-mongodb4/src/test/java/org/apache/logging/log4j/mongodb4/MongoDb4AuthFailureTest.java b/log4j-mongodb4/src/test/java/org/apache/logging/log4j/mongodb4/MongoDb4AuthFailureTest.java
new file mode 100644
index 0000000..c84d108
--- /dev/null
+++ b/log4j-mongodb4/src/test/java/org/apache/logging/log4j/mongodb4/MongoDb4AuthFailureTest.java
@@ -0,0 +1,72 @@
+/*
+ * 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.mongodb4;
+
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.apache.logging.log4j.categories.Appenders;
+import org.apache.logging.log4j.junit.LoggerContextRule;
+import org.apache.logging.log4j.mongodb4.MongoDb4TestRule.LoggingTarget;
+import org.apache.logging.log4j.test.AvailablePortSystemPropertyTestRule;
+import org.apache.logging.log4j.test.RuleChainFactory;
+import org.bson.Document;
+import org.junit.Assert;
+import org.junit.ClassRule;
+import org.junit.Ignore;
+import org.junit.Test;
+import org.junit.experimental.categories.Category;
+import org.junit.rules.RuleChain;
+
+import com.mongodb.client.MongoClient;
+import com.mongodb.client.MongoCollection;
+import com.mongodb.client.MongoDatabase;
+
+/**
+ *
+ *
+ * TODO Set up the log4j user in MongoDB.
+ */
+@Ignore("TODO Set up the log4j user in MongoDB")
+@Category(Appenders.MongoDb.class)
+public class MongoDb4AuthFailureTest {
+
+    private static LoggerContextRule loggerContextTestRule = new LoggerContextRule("log4j2-mongodb-auth-failure.xml");
+
+    private static final AvailablePortSystemPropertyTestRule mongoDbPortTestRule = AvailablePortSystemPropertyTestRule
+            .create(MongoDb4TestConstants.SYS_PROP_NAME_PORT);
+
+    private static final MongoDb4TestRule mongoDbTestRule = new MongoDb4TestRule(mongoDbPortTestRule.getName(),
+            MongoDb4AuthFailureTest.class, LoggingTarget.NULL);
+
+    @ClassRule
+    public static RuleChain ruleChain = RuleChainFactory.create(mongoDbPortTestRule, mongoDbTestRule,
+            loggerContextTestRule);
+
+    @Test
+    public void test() {
+        final Logger logger = LogManager.getLogger();
+        logger.info("Hello log");
+        try (final MongoClient mongoClient = mongoDbTestRule.getMongoClient()) {
+            final MongoDatabase database = mongoClient.getDatabase("testDb");
+            Assert.assertNotNull(database);
+            final MongoCollection<Document> collection = database.getCollection("testCollection");
+            Assert.assertNotNull(collection);
+            final Document first = collection.find().first();
+            Assert.assertNull(first);
+        }
+    }
+}
diff --git a/log4j-mongodb4/src/test/java/org/apache/logging/log4j/mongodb4/MongoDb4CappedTest.java b/log4j-mongodb4/src/test/java/org/apache/logging/log4j/mongodb4/MongoDb4CappedTest.java
new file mode 100644
index 0000000..fcd7cd7
--- /dev/null
+++ b/log4j-mongodb4/src/test/java/org/apache/logging/log4j/mongodb4/MongoDb4CappedTest.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.mongodb4;
+
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.apache.logging.log4j.categories.Appenders;
+import org.apache.logging.log4j.junit.LoggerContextRule;
+import org.apache.logging.log4j.mongodb4.MongoDb4TestRule.LoggingTarget;
+import org.apache.logging.log4j.test.AvailablePortSystemPropertyTestRule;
+import org.apache.logging.log4j.test.RuleChainFactory;
+import org.bson.Document;
+import org.junit.Assert;
+import org.junit.ClassRule;
+import org.junit.Test;
+import org.junit.experimental.categories.Category;
+import org.junit.rules.RuleChain;
+
+import com.mongodb.client.MongoClient;
+import com.mongodb.client.MongoCollection;
+import com.mongodb.client.MongoDatabase;
+
+/**
+ *
+ */
+@Category(Appenders.MongoDb.class)
+public class MongoDb4CappedTest {
+
+    private static LoggerContextRule loggerContextTestRule = new LoggerContextRule("log4j2-mongodb-capped.xml");
+
+    private static final AvailablePortSystemPropertyTestRule mongoDbPortTestRule = AvailablePortSystemPropertyTestRule
+            .create(MongoDb4TestConstants.SYS_PROP_NAME_PORT);
+
+    private static final MongoDb4TestRule mongoDbTestRule = new MongoDb4TestRule(mongoDbPortTestRule.getName(),
+            MongoDb4CappedTest.class, LoggingTarget.NULL);
+
+    @ClassRule
+    public static RuleChain ruleChain = RuleChainFactory.create(mongoDbPortTestRule, mongoDbTestRule,
+            loggerContextTestRule);
+
+    @Test
+    public void test() {
+        final Logger logger = LogManager.getLogger();
+        logger.info("Hello log");
+        try (final MongoClient mongoClient = mongoDbTestRule.getMongoClient()) {
+            final MongoDatabase database = mongoClient.getDatabase("testDb");
+            Assert.assertNotNull(database);
+            final MongoCollection<Document> collection = database.getCollection("testCollection");
+            Assert.assertNotNull(collection);
+            final Document first = collection.find().first();
+            Assert.assertNotNull(first);
+            Assert.assertEquals(first.toJson(), "Hello log", first.getString("message"));
+        }
+    }
+}
diff --git a/log4j-mongodb4/src/test/java/org/apache/logging/log4j/mongodb4/MongoDb4MapMessageTest.java b/log4j-mongodb4/src/test/java/org/apache/logging/log4j/mongodb4/MongoDb4MapMessageTest.java
new file mode 100644
index 0000000..70ab9a7
--- /dev/null
+++ b/log4j-mongodb4/src/test/java/org/apache/logging/log4j/mongodb4/MongoDb4MapMessageTest.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.mongodb4;
+
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.apache.logging.log4j.categories.Appenders;
+import org.apache.logging.log4j.junit.LoggerContextRule;
+import org.apache.logging.log4j.message.MapMessage;
+import org.apache.logging.log4j.mongodb4.MongoDb4TestRule.LoggingTarget;
+import org.apache.logging.log4j.test.AvailablePortSystemPropertyTestRule;
+import org.apache.logging.log4j.test.RuleChainFactory;
+import org.bson.Document;
+import org.junit.Assert;
+import org.junit.ClassRule;
+import org.junit.Test;
+import org.junit.experimental.categories.Category;
+import org.junit.rules.RuleChain;
+
+import com.mongodb.client.MongoClient;
+import com.mongodb.client.MongoCollection;
+import com.mongodb.client.MongoDatabase;
+
+/**
+ *
+ */
+@Category(Appenders.MongoDb.class)
+public class MongoDb4MapMessageTest {
+
+    private static LoggerContextRule loggerContextTestRule = new LoggerContextRule("log4j2-mongodb-map-message.xml");
+
+    private static final AvailablePortSystemPropertyTestRule mongoDbPortTestRule = AvailablePortSystemPropertyTestRule
+            .create(MongoDb4TestConstants.SYS_PROP_NAME_PORT);
+
+    private static final MongoDb4TestRule mongoDbTestRule = new MongoDb4TestRule(mongoDbPortTestRule.getName(),
+            MongoDb4MapMessageTest.class, LoggingTarget.NULL);
+
+    @ClassRule
+    public static RuleChain ruleChain = RuleChainFactory.create(mongoDbPortTestRule, mongoDbTestRule,
+            loggerContextTestRule);
+
+    @Test
+    public void test() {
+        final Logger logger = LogManager.getLogger();
+        final MapMessage<?, Object> mapMessage = new MapMessage<>();
+        mapMessage.with("SomeName", "SomeValue");
+        mapMessage.with("SomeInt", 1);
+        logger.info(mapMessage);
+        //
+        try (final MongoClient mongoClient = mongoDbTestRule.getMongoClient()) {
+            final MongoDatabase database = mongoClient.getDatabase("testDb");
+            Assert.assertNotNull(database);
+            final MongoCollection<Document> collection = database.getCollection("testCollection");
+            Assert.assertNotNull(collection);
+            final Document first = collection.find().first();
+            Assert.assertNotNull(first);
+            final String firstJson = first.toJson();
+            Assert.assertEquals(firstJson, "SomeValue", first.getString("SomeName"));
+            Assert.assertEquals(firstJson, Integer.valueOf(1), first.getInteger("SomeInt"));
+        }
+    }
+}
diff --git a/log4j-mongodb4/src/test/java/org/apache/logging/log4j/mongodb4/MongoDb4Test.java b/log4j-mongodb4/src/test/java/org/apache/logging/log4j/mongodb4/MongoDb4Test.java
new file mode 100644
index 0000000..e814f1f
--- /dev/null
+++ b/log4j-mongodb4/src/test/java/org/apache/logging/log4j/mongodb4/MongoDb4Test.java
@@ -0,0 +1,70 @@
+/*
+ * 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.mongodb4;
+
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.apache.logging.log4j.categories.Appenders;
+import org.apache.logging.log4j.junit.LoggerContextRule;
+import org.apache.logging.log4j.mongodb4.MongoDb4TestRule.LoggingTarget;
+import org.apache.logging.log4j.test.AvailablePortSystemPropertyTestRule;
+import org.apache.logging.log4j.test.RuleChainFactory;
+import org.bson.Document;
+import org.junit.Assert;
+import org.junit.ClassRule;
+import org.junit.Test;
+import org.junit.experimental.categories.Category;
+import org.junit.rules.RuleChain;
+
+import com.mongodb.client.MongoClient;
+import com.mongodb.client.MongoCollection;
+import com.mongodb.client.MongoDatabase;
+
+/**
+ *
+ */
+@Category(Appenders.MongoDb.class)
+public class MongoDb4Test {
+
+    private static LoggerContextRule loggerContextTestRule = new LoggerContextRule("log4j2-mongodb.xml");
+
+    private static final AvailablePortSystemPropertyTestRule mongoDbPortTestRule = AvailablePortSystemPropertyTestRule
+            .create(MongoDb4TestConstants.SYS_PROP_NAME_PORT);
+
+    private static final MongoDb4TestRule mongoDbTestRule = new MongoDb4TestRule(mongoDbPortTestRule.getName(),
+            MongoDb4Test.class, LoggingTarget.NULL);
+
+    @ClassRule
+    public static RuleChain ruleChain = RuleChainFactory.create(mongoDbPortTestRule, mongoDbTestRule,
+            loggerContextTestRule);
+
+    @Test
+    public void test() {
+        final Logger logger = LogManager.getLogger();
+        logger.info("Hello log");
+        try (final MongoClient mongoClient = mongoDbTestRule.getMongoClient()) {
+            final MongoDatabase database = mongoClient.getDatabase("testDb");
+            Assert.assertNotNull(database);
+            final MongoCollection<Document> collection = database.getCollection("testCollection");
+            Assert.assertNotNull(collection);
+            final Document first = collection.find().first();
+            Assert.assertNotNull(first);
+            Assert.assertEquals(first.toJson(), "Hello log", first.getString("message"));
+            Assert.assertEquals(first.toJson(), "INFO", first.getString("level"));
+        }
+    }
+}
diff --git a/log4j-mongodb4/src/test/java/org/apache/logging/log4j/mongodb4/MongoDb4TestConstants.java b/log4j-mongodb4/src/test/java/org/apache/logging/log4j/mongodb4/MongoDb4TestConstants.java
new file mode 100644
index 0000000..0d398f0
--- /dev/null
+++ b/log4j-mongodb4/src/test/java/org/apache/logging/log4j/mongodb4/MongoDb4TestConstants.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.
+ */
+
+package org.apache.logging.log4j.mongodb4;
+
+public class MongoDb4TestConstants {
+
+    public static final String SYS_PROP_NAME_PORT = "MongoDBTestPort";
+
+}
diff --git a/log4j-mongodb4/src/test/java/org/apache/logging/log4j/mongodb4/MongoDb4TestRule.java b/log4j-mongodb4/src/test/java/org/apache/logging/log4j/mongodb4/MongoDb4TestRule.java
new file mode 100644
index 0000000..035ef53
--- /dev/null
+++ b/log4j-mongodb4/src/test/java/org/apache/logging/log4j/mongodb4/MongoDb4TestRule.java
@@ -0,0 +1,186 @@
+/*
+ * 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.mongodb4;
+
+import java.util.Objects;
+
+import org.apache.commons.lang3.NotImplementedException;
+import org.junit.rules.TestRule;
+import org.junit.runner.Description;
+import org.junit.runners.model.Statement;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.mongodb.client.MongoClient;
+import com.mongodb.client.MongoClients;
+
+import de.flapdoodle.embed.mongo.Command;
+import de.flapdoodle.embed.mongo.MongodExecutable;
+import de.flapdoodle.embed.mongo.MongodProcess;
+import de.flapdoodle.embed.mongo.MongodStarter;
+import de.flapdoodle.embed.mongo.config.MongodConfigBuilder;
+import de.flapdoodle.embed.mongo.config.Net;
+import de.flapdoodle.embed.mongo.config.RuntimeConfigBuilder;
+import de.flapdoodle.embed.mongo.config.Timeout;
+import de.flapdoodle.embed.mongo.distribution.Version;
+import de.flapdoodle.embed.process.config.IRuntimeConfig;
+import de.flapdoodle.embed.process.config.io.ProcessOutput;
+import de.flapdoodle.embed.process.runtime.Network;
+
+/**
+ * A JUnit test rule to manage a MongoDB embedded instance.
+ *
+ * TODO Move this class to Apache Commons Testing.
+ */
+public class MongoDb4TestRule implements TestRule {
+
+    public enum LoggingTarget {
+        CONSOLE, NULL;
+
+        public static LoggingTarget getLoggingTarget(final String sysPropertyName, final LoggingTarget defaultValue) {
+            return LoggingTarget.valueOf(System.getProperty(sysPropertyName, defaultValue.name()));
+        }
+    }
+
+    private static final int BUILDER_TIMEOUT_MILLIS = 30000;
+
+    public static int getBuilderTimeoutMillis() {
+        return BUILDER_TIMEOUT_MILLIS;
+    }
+
+    private static MongodStarter getMongodStarter(final LoggingTarget loggingTarget) {
+        if (loggingTarget == null) {
+            return MongodStarter.getDefaultInstance();
+        }
+        switch (loggingTarget) {
+        case NULL:
+            final Logger logger = LoggerFactory.getLogger(MongoDb4TestRule.class.getName());
+            final IRuntimeConfig runtimeConfig = new RuntimeConfigBuilder()
+            // @formatter:off
+                    .defaultsWithLogger(Command.MongoD, logger).processOutput(ProcessOutput.getDefaultInstanceSilent())
+                    .build();
+            // @formatter:on
+
+            return MongodStarter.getInstance(runtimeConfig);
+        case CONSOLE:
+            return MongodStarter.getDefaultInstance();
+        default:
+            throw new NotImplementedException(loggingTarget.toString());
+        }
+    }
+
+    protected final LoggingTarget loggingTarget;
+
+    protected MongoClient mongoClient;
+    protected MongodExecutable mongodExecutable;
+    protected MongodProcess mongodProcess;
+    protected final String portSystemPropertyName;
+
+    /**
+     * Store {@link MongodStarter} (or RuntimeConfig) in a static final field if you
+     * want to use artifact store caching (or else disable caching).
+     * <p>
+     * The test framework {@code de.flapdoodle.embed.mongo} requires Java 8.
+     * </p>
+     */
+    protected final MongodStarter starter;
+
+    /**
+     * Constructs a new test rule.
+     *
+     * @param portSystemPropertyName The system property name for the MongoDB port.
+     * @param clazz                  The test case class.
+     * @param defaultLoggingTarget   The logging target.
+     */
+    public MongoDb4TestRule(final String portSystemPropertyName, final Class<?> clazz,
+            final LoggingTarget defaultLoggingTarget) {
+        this.portSystemPropertyName = Objects.requireNonNull(portSystemPropertyName, "portSystemPropertyName");
+        this.loggingTarget = LoggingTarget.getLoggingTarget(clazz.getName() + "." + LoggingTarget.class.getSimpleName(),
+                defaultLoggingTarget);
+        this.starter = getMongodStarter(this.loggingTarget);
+    }
+
+    @Override
+    public Statement apply(final Statement base, final Description description) {
+        return new Statement() {
+
+            @Override
+            public void evaluate() throws Throwable {
+                final String value = Objects.requireNonNull(System.getProperty(portSystemPropertyName),
+                        "System property '" + portSystemPropertyName + "' is null");
+                final int port = Integer.parseInt(value);
+                mongodExecutable = starter.prepare(
+                // @formatter:off
+                        new MongodConfigBuilder().version(Version.Main.PRODUCTION)
+                                .timeout(new Timeout(BUILDER_TIMEOUT_MILLIS))
+                                .net(new Net("localhost", port, Network.localhostIsIPv6())).build());
+                // @formatter:on
+                mongodProcess = mongodExecutable.start();
+                mongoClient = MongoClients.create("mongodb://localhost:" + port);
+                try {
+                    base.evaluate();
+                } finally {
+                    if (mongodProcess != null) {
+                        mongodProcess.stop();
+                        mongodProcess = null;
+                    }
+                    if (mongodExecutable != null) {
+                        mongodExecutable.stop();
+                        mongodExecutable = null;
+                    }
+                }
+            }
+        };
+    }
+
+    public MongoClient getMongoClient() {
+        return mongoClient;
+    }
+
+    public MongodExecutable getMongodExecutable() {
+        return mongodExecutable;
+    }
+
+    public MongodProcess getMongodProcess() {
+        return mongodProcess;
+    }
+
+    public MongodStarter getStarter() {
+        return starter;
+    }
+
+    @Override
+    public String toString() {
+        final StringBuilder builder = new StringBuilder();
+        builder.append("Mongo4TestRule [starter=");
+        builder.append(starter);
+        builder.append(", portSystemPropertyName=");
+        builder.append(portSystemPropertyName);
+        builder.append(", mongoClient=");
+        builder.append(mongoClient);
+        builder.append(", mongodExecutable=");
+        builder.append(mongodExecutable);
+        builder.append(", mongodProcess=");
+        builder.append(mongodProcess);
+        builder.append(", loggingTarget=");
+        builder.append(loggingTarget);
+        builder.append("]");
+        return builder.toString();
+    }
+
+}
\ No newline at end of file
diff --git a/log4j-mongodb4/src/test/java/org/apache/logging/log4j/mongodb4/MongoDb4TestTestRuleTest.java b/log4j-mongodb4/src/test/java/org/apache/logging/log4j/mongodb4/MongoDb4TestTestRuleTest.java
new file mode 100644
index 0000000..086af07
--- /dev/null
+++ b/log4j-mongodb4/src/test/java/org/apache/logging/log4j/mongodb4/MongoDb4TestTestRuleTest.java
@@ -0,0 +1,73 @@
+/*
+ * 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.mongodb4;
+
+import org.apache.commons.lang3.JavaVersion;
+import org.apache.commons.lang3.SystemUtils;
+import org.apache.logging.log4j.mongodb4.MongoDb4TestRule.LoggingTarget;
+import org.apache.logging.log4j.test.AvailablePortSystemPropertyTestRule;
+import org.apache.logging.log4j.test.RuleChainFactory;
+import org.junit.Assert;
+import org.junit.Assume;
+import org.junit.BeforeClass;
+import org.junit.ClassRule;
+import org.junit.Test;
+import org.junit.rules.RuleChain;
+
+import com.mongodb.client.MongoIterable;
+
+/**
+ * Tests MongoDbRule.
+ * <p>
+ * The test framework {@code de.flapdoodle.embed.mongo} requires Java 8.
+ * </p>
+ */
+public class MongoDb4TestTestRuleTest {
+
+    private static final AvailablePortSystemPropertyTestRule mongoDbPortTestRule = AvailablePortSystemPropertyTestRule
+            .create(MongoDb4TestConstants.SYS_PROP_NAME_PORT);
+
+    private static final MongoDb4TestRule mongoDbTestRule = new MongoDb4TestRule(mongoDbPortTestRule.getName(),
+            MongoDb4TestTestRuleTest.class, LoggingTarget.NULL);
+
+    @ClassRule
+    public static RuleChain mongoDbChain = RuleChainFactory.create(mongoDbPortTestRule, mongoDbTestRule);
+
+    @BeforeClass
+    public static void beforeClass() {
+        Assume.assumeTrue(SystemUtils.isJavaVersionAtLeast(JavaVersion.JAVA_1_8));
+    }
+
+    @Test
+    public void testAccess() {
+        @SuppressWarnings("resource")
+        final MongoIterable<String> databaseNames = mongoDbTestRule.getMongoClient().listDatabaseNames();
+        Assert.assertNotNull(databaseNames);
+        Assert.assertNotNull(databaseNames.first());
+    }
+
+    @SuppressWarnings("resource")
+    @Test
+    public void testMongoDbTestRule() {
+        Assert.assertNotNull(mongoDbTestRule);
+        Assert.assertNotNull(mongoDbTestRule.getStarter());
+        Assert.assertNotNull(mongoDbTestRule.getMongoClient());
+        Assert.assertNotNull(mongoDbTestRule.getMongodExecutable());
+        Assert.assertNotNull(mongoDbTestRule.getMongodProcess());
+    }
+}
diff --git a/log4j-mongodb4/src/test/resources/log4j2-mongodb-auth-failure.xml b/log4j-mongodb4/src/test/resources/log4j2-mongodb-auth-failure.xml
new file mode 100644
index 0000000..34be399
--- /dev/null
+++ b/log4j-mongodb4/src/test/resources/log4j2-mongodb-auth-failure.xml
@@ -0,0 +1,31 @@
+<?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 status="WARN">
+  <Appenders>
+    <NoSql name="MongoDbAppender">
+      <MongoDb4 
+        connection="mongodb://log4jUser:12345678@localhost:${sys:MongoDBTestPort:-27017}/testDb.testCollection" />
+    </NoSql>
+  </Appenders>
+  <Loggers>
+    <Root level="ALL">
+      <AppenderRef ref="MongoDbAppender" />
+    </Root>
+  </Loggers>
+</Configuration>
diff --git a/log4j-mongodb4/src/test/resources/log4j2-mongodb-capped.xml b/log4j-mongodb4/src/test/resources/log4j2-mongodb-capped.xml
new file mode 100644
index 0000000..d5f5651
--- /dev/null
+++ b/log4j-mongodb4/src/test/resources/log4j2-mongodb-capped.xml
@@ -0,0 +1,33 @@
+<?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 status="WARN">
+  <Appenders>
+    <NoSql name="MongoDbAppender">
+      <MongoDb4 
+        connection="mongodb://localhost:${sys:MongoDBTestPort:-27017}/testDb.testCollection" 
+        capped="true" 
+        collectionSize="1073741824"/>
+    </NoSql>
+  </Appenders>
+  <Loggers>
+    <Root level="ALL">
+      <AppenderRef ref="MongoDbAppender" />
+    </Root>
+  </Loggers>
+</Configuration>
diff --git a/log4j-mongodb4/src/test/resources/log4j2-mongodb-map-message.xml b/log4j-mongodb4/src/test/resources/log4j2-mongodb-map-message.xml
new file mode 100644
index 0000000..7534477
--- /dev/null
+++ b/log4j-mongodb4/src/test/resources/log4j2-mongodb-map-message.xml
@@ -0,0 +1,31 @@
+<?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 status="WARN">
+  <Appenders>
+    <NoSql name="MongoDbAppender">
+      <MongoDb4 connection="mongodb://localhost:${sys:MongoDBTestPort:-27017}/testDb.testCollection" />
+      <MessageLayout />
+    </NoSql>
+  </Appenders>
+  <Loggers>
+    <Root level="ALL">
+      <AppenderRef ref="MongoDbAppender" />
+    </Root>
+  </Loggers>
+</Configuration>
diff --git a/log4j-mongodb4/src/test/resources/log4j2-mongodb.xml b/log4j-mongodb4/src/test/resources/log4j2-mongodb.xml
new file mode 100644
index 0000000..514bc8c
--- /dev/null
+++ b/log4j-mongodb4/src/test/resources/log4j2-mongodb.xml
@@ -0,0 +1,30 @@
+<?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 status="WARN">
+  <Appenders>
+    <NoSql name="MongoDbAppender">
+      <MongoDb4 connection="mongodb://localhost:${sys:MongoDBTestPort:-27017}/testDb.testCollection" />
+    </NoSql>
+  </Appenders>
+  <Loggers>
+    <Root level="ALL">
+      <AppenderRef ref="MongoDbAppender" />
+    </Root>
+  </Loggers>
+</Configuration>
diff --git a/pom.xml b/pom.xml
index adf329c..bb070e4 100644
--- a/pom.xml
+++ b/pom.xml
@@ -198,6 +198,7 @@
     <conversantDisruptorVersion>1.2.15</conversantDisruptorVersion> <!-- Version 1.2.11 requires Java 8 -->
     <mongodb2.version>2.14.3</mongodb2.version>
     <mongodb3.version>3.12.4</mongodb3.version>
+    <mongodb4.version>4.0.3</mongodb4.version>
     <groovy.version>3.0.3</groovy.version>
     <compiler.plugin.version>3.8.1</compiler.plugin.version>
     <pmd.plugin.version>3.10.0</pmd.plugin.version>
@@ -1399,6 +1400,7 @@
     <module>log4j-couchdb</module>
     <module>log4j-mongodb2</module>
     <module>log4j-mongodb3</module>
+    <module>log4j-mongodb4</module>
     <module>log4j-cassandra</module>
     <module>log4j-web</module>
     <module>log4j-perf</module>
diff --git a/src/changes/changes.xml b/src/changes/changes.xml
index 911c0fc..a87080e 100644
--- a/src/changes/changes.xml
+++ b/src/changes/changes.xml
@@ -33,6 +33,9 @@
       <action issue="LOG4J2-2844" dev="ggregory" type="fix">
         Null pointer exception when no network interfaces are available.
       </action>
+      <action issue="LOG4J2-2848" dev="ggregory" type="add">
+        Create module log4j-mongodb4 to use new major version 4 MongoDB driver.
+      </action>
       <action dev="ggregory" type="update">
         Update MongoDB tests to require Java 8 unconditionally now that Log4j requires Java 8.
       </action>
diff --git a/src/site/site.xml b/src/site/site.xml
index 7934d39..e874168 100644
--- a/src/site/site.xml
+++ b/src/site/site.xml
@@ -179,6 +179,7 @@
         <item name="NoSQL for MongoDB" href="/manual/appenders.html#NoSQLAppenderMongoDB"/>
         <item name="NoSQL for MongoDB 2" href="/manual/appenders.html#NoSQLAppenderMongoDB2"/>
         <item name="NoSQL for MongoDB 3" href="/manual/appenders.html#NoSQLAppenderMongoDB3"/>
+        <item name="NoSQL for MongoDB 4" href="/manual/appenders.html#NoSQLAppenderMongoDB4"/>
         <item name="NoSQL for CouchDB" href="/manual/appenders.html#NoSQLAppenderCouchDB"/>
         <item name="Output Stream" href="/manual/appenders.html#OutputStreamAppender"/>
         <item name="Random Access File" href="/manual/appenders.html#RandomAccessFileAppender"/>
@@ -319,6 +320,7 @@
       <item name="Log4j CouchDB appender" href="log4j-couchdb/index.html"/>
       <item name="Log4j MongoDB2 appender" href="log4j-mongodb2/index.html"/>
       <item name="Log4j MongoDB3 appender" href="log4j-mongodb3/index.html"/>
+      <item name="Log4j MongoDB4 appender" href="log4j-mongodb4/index.html"/>
       <item name="Log4j Cassandra appender" href="log4j-cassandra/index.html"/>
       <item name="Log4j IO Streams" href="log4j-iostreams/index.html"/>
       <item name="Log4j Liquibase Binding" href="log4j-liquibase/index.html"/>
diff --git a/src/site/xdoc/manual/appenders.xml b/src/site/xdoc/manual/appenders.xml
index ab40094..336e5be 100644
--- a/src/site/xdoc/manual/appenders.xml
+++ b/src/site/xdoc/manual/appenders.xml
@@ -2344,8 +2344,9 @@ public class JpaLogEntity extends AbstractLogEventWrapperEntity {
             Starting with Log4 2.11.0, we provide two MongoDB modules:
           </p>
           <ul>
-            <li><code>log4j-mongodb2</code> defines the configuration element <a href="#NoSQLAppenderMongoDB2"><code>MongoDb2</code></a> matching the MongoDB Driver version 2.</li>
-            <li><code>log4j-mongodb3</code> defines the configuration element <a href="#NoSQLAppenderMongoDB3"><code>MongoDb3</code></a> matching the MongoDB Driver version 3.</li>
+            <li>v2.11.0: <code>log4j-mongodb2</code> defines the configuration element <a href="#NoSQLAppenderMongoDB2"><code>MongoDb2</code></a> matching the MongoDB Driver version 2.</li>
+            <li>v2.11.0: <code>log4j-mongodb3</code> defines the configuration element <a href="#NoSQLAppenderMongoDB3"><code>MongoDb3</code></a> matching the MongoDB Driver version 3.</li>
+            <li>v2.14.0: <code>log4j-mongodb4</code> defines the configuration element <a href="#NoSQLAppenderMongoDB4"><code>MongoDb4</code></a> matching the MongoDB Driver version 4.</li>
           </ul>
           <p>
             We no longer provide the module <code>log4j-mongodb</code>.
@@ -2612,6 +2613,78 @@ public class JpaLogEntity extends AbstractLogEventWrapperEntity {
   </Loggers>
 </Configuration>]]></pre>
         </subsection>
+        <a name="NoSQLAppenderMongoDB4"/>
+        <subsection name="NoSQLAppender for MongoDB 4">
+          <p>
+            This section details specializations of the <a href="#NoSQLAppender">NoSQLAppender</a> provider for MongoDB using
+            the MongoDB driver version 4. The NoSQLAppender Appender writes log events to a NoSQL database using an
+            internal lightweight provider interface.
+          </p>
+          <table>
+            <caption align="top">MongoDB4 Provider Parameters</caption>
+            <tr>
+              <th>Parameter Name</th>
+              <th>Type</th>
+              <th>Description</th>
+            </tr>
+            <tr>
+              <td>connection</td>
+              <td>String</td>
+              <td><em>Required.</em> The MongoDB <a href="http://mongodb.github.io/mongo-java-driver/4.0/apidocs/mongodb-driver-core/com/mongodb/ConnectionString.html?is-external=true">
+                connection string</a> in the format <code>mongodb://[username:password@]host1[:port1][,host2[:port2],...[,hostN[:portN]]][/[database.collection][?options]]</code>
+              </td>
+            </tr>
+            <tr>
+              <td>capped</td>
+              <td>boolean</td>
+              <td>Enable support for <a href="https://docs.mongodb.com/manual/core/capped-collections/">capped collections</a></td>
+            </tr>
+            <tr>
+              <td>collectionSize</td>
+              <td>int</td>
+              <td>Specify the size in bytes of the capped collection to use if enabled. The minimum size is 4096 bytes,
+              and larger sizes will be increased to the nearest integer multiple of 256. See the capped collection documentation
+              linked above for more information.</td>
+            </tr>
+          </table>
+          <p>
+            This appender is <a href="messages.html#MapMessage">MapMessage</a>-aware.
+          </p>
+          <p>
+            Here are a few sample configurations for the NoSQLAppender and MongoDB4 provider:
+          </p>
+
+            <pre class="prettyprint linenums lang-xml"><![CDATA[<?xml version="1.0" encoding="UTF-8"?>
+<Configuration status="WARN">
+  <Appenders>
+    <NoSql name="MongoDbAppender">
+      <MongoDb4 connection="mongodb://log4jUser:12345678@localhost:${sys:MongoDBTestPort:-27017}/testDb.testCollection" />
+    </NoSql>
+  </Appenders>
+  <Loggers>
+    <Root level="ALL">
+      <AppenderRef ref="MongoDbAppender" />
+    </Root>
+  </Loggers>
+</Configuration>]]></pre>
+
+            <pre class="prettyprint linenums lang-xml"><![CDATA[<?xml version="1.0" encoding="UTF-8"?>
+<Configuration status="WARN">
+  <Appenders>
+    <NoSql name="MongoDbAppender">
+      <MongoDb4 
+        connection="mongodb://localhost:${sys:MongoDBTestPort:-27017}/testDb.testCollection" 
+        capped="true" 
+        collectionSize="1073741824"/>
+    </NoSql>
+  </Appenders>
+  <Loggers>
+    <Root level="ALL">
+      <AppenderRef ref="MongoDbAppender" />
+    </Root>
+  </Loggers>
+</Configuration>]]></pre>
+        </subsection>
         <a name="NoSQLAppenderApacheCouchDB"/>
         <subsection name="NoSQLAppender for Apache CouchDB">
           <p>