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 2022/02/08 01:54:52 UTC
[logging-log4j2] branch release-2.x updated: [LOG4J2-3391] Add optional additional fields to NoSQLAppender.
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 6db1c07 [LOG4J2-3391] Add optional additional fields to NoSQLAppender.
6db1c07 is described below
commit 6db1c07108a91d1ba5851be831b0c75a04111052
Author: Gary Gregory <ga...@gmail.com>
AuthorDate: Mon Feb 7 20:54:45 2022 -0500
[LOG4J2-3391] Add optional additional fields to NoSQLAppender.
---
log4j-core/revapi.json | 1 +
.../log4j/core/appender/AbstractManager.java | 46 ++++++++++
.../core/appender/db/AbstractDatabaseManager.java | 50 ++++++++--
.../log4j/core/appender/nosql/NoSqlAppender.java | 19 ++--
.../core/appender/nosql/NoSqlDatabaseManager.java | 69 ++++++++++----
.../appender/nosql/NoSqlDatabaseManagerTest.java | 12 +--
.../mongodb3/MongoDb3AdditionalFieldsTest.java | 100 ++++++++++++++++++++
.../resources/log4j2-mongodb-additional-fields.xml | 35 +++++++
.../mongodb4/MongoDb4AdditionalFieldsTest.java | 101 +++++++++++++++++++++
.../resources/log4j2-mongodb-additional-fields.xml | 34 +++++++
src/changes/changes.xml | 3 +
11 files changed, 431 insertions(+), 39 deletions(-)
diff --git a/log4j-core/revapi.json b/log4j-core/revapi.json
index 350c4d4..a4ff02a 100644
--- a/log4j-core/revapi.json
+++ b/log4j-core/revapi.json
@@ -6,6 +6,7 @@
"classes": {
"exclude": [
"org\\.apache\\.logging\\.log4j\\.corel\\.impl\\.ContextAnchor",
+ "org\\.apache\\.logging\\.log4j\\.core\\.appender\\.db\\.AbstractDatabaseManager\\.AbstractFactoryData",
"org\\.apache\\.logging\\.log4j\\.core\\.async\\.AsyncLoggerDisruptor",
"org\\.apache\\.logging\\.log4j\\.core\\.async\\.RingBufferLogEvent\\.Factory",
"org\\.apache\\.logging\\.log4j\\.core\\.layout\\.AbstractJacksonLayout",
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/AbstractManager.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/AbstractManager.java
index b253f95..72a8b4a 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/AbstractManager.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/AbstractManager.java
@@ -27,7 +27,9 @@ import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.core.AbstractLifeCycle;
import org.apache.logging.log4j.core.LoggerContext;
+import org.apache.logging.log4j.core.config.Configuration;
import org.apache.logging.log4j.core.config.ConfigurationException;
+import org.apache.logging.log4j.core.lookup.StrSubstitutor;
import org.apache.logging.log4j.message.Message;
import org.apache.logging.log4j.status.StatusLogger;
@@ -43,6 +45,34 @@ import org.apache.logging.log4j.status.StatusLogger;
public abstract class AbstractManager implements AutoCloseable {
/**
+ * Implementations should extend this class for passing data between the getManager method and the manager factory
+ * class.
+ */
+ protected abstract static class AbstractFactoryData {
+
+ private final Configuration configuration;
+
+ /**
+ * Constructs the base factory data.
+ *
+ * @param configuration Configuration creating this instance.
+ */
+ protected AbstractFactoryData(final Configuration configuration) {
+ this.configuration = configuration;
+ }
+
+ /**
+ * Gets my configuration.
+ *
+ * @return my configuration.
+ */
+ public Configuration getConfiguration() {
+ return configuration;
+ }
+
+ }
+
+ /**
* Allow subclasses access to the status logger without creating another instance.
*/
protected static final Logger LOGGER = StatusLogger.getLogger();
@@ -229,6 +259,22 @@ public abstract class AbstractManager implements AutoCloseable {
return new HashMap<>();
}
+ /**
+ * Gets my configuration's StrSubstitutor or null.
+ *
+ * @return my configuration's StrSubstitutor or null.
+ */
+ protected StrSubstitutor getStrSubstitutor() {
+ if (loggerContext == null) {
+ return null;
+ }
+ final Configuration configuration = loggerContext.getConfiguration();
+ if (configuration == null) {
+ return null;
+ }
+ return configuration.getStrSubstitutor();
+ }
+
protected void log(final Level level, final String message, final Throwable throwable) {
final Message m = LOGGER.getMessageFactory().newMessage("{} {} {}: {}",
getClass().getSimpleName(), getName(), message, throwable);
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/db/AbstractDatabaseManager.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/db/AbstractDatabaseManager.java
index f0f38b9..4fa9cbf 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/db/AbstractDatabaseManager.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/db/AbstractDatabaseManager.java
@@ -26,6 +26,7 @@ import org.apache.logging.log4j.core.Layout;
import org.apache.logging.log4j.core.LogEvent;
import org.apache.logging.log4j.core.appender.AbstractManager;
import org.apache.logging.log4j.core.appender.ManagerFactory;
+import org.apache.logging.log4j.core.config.Configuration;
/**
* Manager that allows database appenders to have their configuration reloaded without losing events.
@@ -36,7 +37,7 @@ public abstract class AbstractDatabaseManager extends AbstractManager implements
* Implementations should extend this class for passing data between the getManager method and the manager factory
* class.
*/
- protected abstract static class AbstractFactoryData {
+ protected abstract static class AbstractFactoryData extends AbstractManager.AbstractFactoryData {
private final int bufferSize;
private final Layout<? extends Serializable> layout;
@@ -45,8 +46,20 @@ public abstract class AbstractDatabaseManager extends AbstractManager implements
*
* @param bufferSize The size of the buffer.
* @param layout The appender-level layout
+ * @deprecated Use {@link AbstractFactoryData#AbstractFactoryData(Configuration, int, Layout)}.
*/
protected AbstractFactoryData(final int bufferSize, final Layout<? extends Serializable> layout) {
+ this(null, bufferSize, layout);
+ }
+
+ /**
+ * Constructs the base factory data.
+ * @param configuration Configuration creating this instance.
+ * @param bufferSize The size of the buffer.
+ * @param layout The appender-level layout
+ */
+ protected AbstractFactoryData(final Configuration configuration, final int bufferSize, final Layout<? extends Serializable> layout) {
+ super(configuration);
this.bufferSize = bufferSize;
this.layout = layout;
}
@@ -68,7 +81,9 @@ public abstract class AbstractDatabaseManager extends AbstractManager implements
public Layout<? extends Serializable> getLayout() {
return layout;
}
+
}
+
/**
* Implementations should define their own getManager method and call this method from that to create or get
* existing managers.
@@ -81,11 +96,11 @@ public abstract class AbstractDatabaseManager extends AbstractManager implements
* @param <T> The concrete {@link AbstractFactoryData} type.
* @return a new or existing manager of the specified type and name.
*/
- protected static <M extends AbstractDatabaseManager, T extends AbstractFactoryData> M getManager(
- final String name, final T data, final ManagerFactory<M, T> factory
- ) {
+ protected static <M extends AbstractDatabaseManager, T extends AbstractFactoryData> M getManager(final String name, final T data,
+ final ManagerFactory<M, T> factory) {
return AbstractManager.getManager(name, factory, data);
}
+
private final ArrayList<LogEvent> buffer;
private final int bufferSize;
@@ -94,29 +109,48 @@ public abstract class AbstractDatabaseManager extends AbstractManager implements
private boolean running;
/**
- * Instantiates the base manager.
+ * Constructs the base manager.
*
* @param name The manager name, which should include any configuration details that one might want to be able to
* reconfigure at runtime, such as database name, username, (hashed) password, etc.
* @param bufferSize The size of the log event buffer.
+ * @deprecated Use {@link AbstractDatabaseManager#AbstractDatabaseManager(String, int, Layout, Configuration)}.
*/
+ @Deprecated
protected AbstractDatabaseManager(final String name, final int bufferSize) {
this(name, bufferSize, null);
}
/**
- * Instantiates the base manager.
+ * Constructs the base manager.
*
* @param name The manager name, which should include any configuration details that one might want to be able to
* reconfigure at runtime, such as database name, username, (hashed) password, etc.
* @param layout the Appender-level layout.
* @param bufferSize The size of the log event buffer.
+ * @deprecated Use {@link AbstractDatabaseManager#AbstractDatabaseManager(String, int, Layout, Configuration)}.
*/
+ @Deprecated
protected AbstractDatabaseManager(final String name, final int bufferSize, final Layout<? extends Serializable> layout) {
- super(null, name);
+ this(name, bufferSize, layout, null);
+ }
+
+ /**
+ * Constructs the base manager.
+ *
+ * @param name The manager name, which should include any configuration details that one might want to be able to
+ * reconfigure at runtime, such as database name, username, (hashed) password, etc.
+ * @param layout the Appender-level layout.
+ * @param bufferSize The size of the log event buffer.
+ * @param configuration My configuration.
+ */
+ protected AbstractDatabaseManager(final String name, final int bufferSize, final Layout<? extends Serializable> layout, final Configuration configuration) {
+ // null configuration allowed for backward compatibility.
+ // TODO should super track Configuration instead of LoggerContext?
+ super(configuration != null ? configuration.getLoggerContext() : null, name);
this.bufferSize = bufferSize;
this.buffer = new ArrayList<>(bufferSize + 1);
- this.layout = layout;
+ this.layout = layout; // A null layout is allowed.
}
protected void buffer(final LogEvent event) {
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
index 8ed9efa..1fe625d 100644
--- 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
@@ -29,6 +29,7 @@ import org.apache.logging.log4j.core.config.plugins.PluginBuilderAttribute;
import org.apache.logging.log4j.core.config.plugins.PluginBuilderFactory;
import org.apache.logging.log4j.core.config.plugins.PluginElement;
import org.apache.logging.log4j.core.util.Booleans;
+import org.apache.logging.log4j.core.util.KeyValuePair;
/**
* This Appender writes logging events to a NoSQL database using a configured NoSQL provider. It requires
@@ -61,6 +62,9 @@ public final class NoSqlAppender extends AbstractDatabaseAppender<NoSqlDatabaseM
@PluginElement("NoSqlProvider")
private NoSqlProvider<?> provider;
+ @PluginElement("AdditionalField")
+ private KeyValuePair[] additionalFields;
+
@SuppressWarnings("resource")
@Override
public NoSqlAppender build() {
@@ -72,9 +76,8 @@ public final class NoSqlAppender extends AbstractDatabaseAppender<NoSqlDatabaseM
final String managerName = "noSqlManager{ description=" + name + ", bufferSize=" + bufferSize
+ ", provider=" + provider + " }";
-
- final NoSqlDatabaseManager<?> manager = NoSqlDatabaseManager.getNoSqlDatabaseManager(managerName,
- bufferSize, provider);
+ final NoSqlDatabaseManager<?> manager = NoSqlDatabaseManager.getNoSqlDatabaseManager(managerName, bufferSize, provider, additionalFields,
+ getConfiguration());
if (manager == null) {
return null;
}
@@ -129,13 +132,13 @@ public final class NoSqlAppender extends AbstractDatabaseAppender<NoSqlDatabaseM
@SuppressWarnings("resource")
@Deprecated
public static NoSqlAppender createAppender(
- // @formatter:off
+ // @formatter:off
final String name,
final String ignore,
final Filter filter,
final String bufferSize,
final NoSqlProvider<?> provider) {
- // @formatter:on
+ // @formatter:on
if (provider == null) {
LOGGER.error("NoSQL provider not specified for appender [{}].", name);
return null;
@@ -144,11 +147,9 @@ public final class NoSqlAppender extends AbstractDatabaseAppender<NoSqlDatabaseM
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 String managerName = "noSqlManager{ description=" + name + ", bufferSize=" + bufferSizeInt + ", provider=" + provider + " }";
- final NoSqlDatabaseManager<?> manager = NoSqlDatabaseManager.getNoSqlDatabaseManager(managerName, bufferSizeInt,
- provider);
+ final NoSqlDatabaseManager<?> manager = NoSqlDatabaseManager.getNoSqlDatabaseManager(managerName, bufferSizeInt, provider, null, null);
if (manager == null) {
return null;
}
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
index b270ef1..abd0da2 100644
--- 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
@@ -17,6 +17,8 @@
package org.apache.logging.log4j.core.appender.nosql;
import java.io.Serializable;
+import java.util.Objects;
+import java.util.stream.Stream;
import org.apache.logging.log4j.Marker;
import org.apache.logging.log4j.ThreadContext;
@@ -24,7 +26,9 @@ 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.config.Configuration;
import org.apache.logging.log4j.core.util.Closer;
+import org.apache.logging.log4j.core.util.KeyValuePair;
import org.apache.logging.log4j.message.MapMessage;
import org.apache.logging.log4j.util.ReadOnlyStringMap;
@@ -40,10 +44,13 @@ public final class NoSqlDatabaseManager<W> extends AbstractDatabaseManager {
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);
+ private final KeyValuePair[] additionalFields;
+
+ private NoSqlDatabaseManager(final String name, final int bufferSize, final NoSqlProvider<NoSqlConnection<W, ? extends NoSqlObject<W>>> provider,
+ final KeyValuePair[] additionalFields, final Configuration configuration) {
+ super(name, bufferSize, null, configuration);
this.provider = provider;
+ this.additionalFields = additionalFields;
}
@Override
@@ -69,8 +76,7 @@ public final class NoSqlDatabaseManager<W> extends AbstractDatabaseManager {
@Override
protected void writeInternal(final LogEvent event, final Serializable serializable) {
if (!this.isRunning() || this.connection == null || this.connection.isClosed()) {
- throw new AppenderLoggingException(
- "Cannot write logging event; NoSQL manager not connected to the database.");
+ throw new AppenderLoggingException("Cannot write logging event; NoSQL manager not connected to the database.");
}
final NoSqlObject<W> entity = this.connection.createObject();
@@ -79,10 +85,23 @@ public final class NoSqlDatabaseManager<W> extends AbstractDatabaseManager {
} else {
setFields(event, entity);
}
-
+ setAdditionalFields(entity);
this.connection.insertObject(entity);
}
+ private void setAdditionalFields(final NoSqlObject<W> entity) {
+ if (additionalFields != null) {
+ entity.set("additionalFields", Stream.of(additionalFields).map(this::convertAdditionalField).toArray());
+ }
+ }
+
+ private NoSqlObject<W> convertAdditionalField(KeyValuePair field) {
+ final NoSqlObject<W> object = connection.createObject();
+ object.set("key", field.getKey());
+ object.set("value", getStrSubstitutor().replace(field.getValue()));
+ return object;
+ }
+
private void setFields(final MapMessage<?, ?> mapMessage, final NoSqlObject<W> noSqlObject) {
// Map without calling org.apache.logging.log4j.message.MapMessage#getData() which makes a copy of the map.
mapMessage.forEach((key, value) -> noSqlObject.set(key, value));
@@ -175,7 +194,7 @@ public final class NoSqlDatabaseManager<W> extends AbstractDatabaseManager {
// 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;
+ return true;
}
private NoSqlObject<W>[] convertStackTrace(final StackTraceElement[] stackTrace) {
@@ -202,10 +221,26 @@ public final class NoSqlDatabaseManager<W> extends AbstractDatabaseManager {
* @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.
+ * @deprecated Use {@link #getNoSqlDatabaseManager(String, int, NoSqlProvider, KeyValuePair[], Configuration)}.
+ */
+ @Deprecated
+ public static NoSqlDatabaseManager<?> getNoSqlDatabaseManager(final String name, final int bufferSize, final NoSqlProvider<?> provider) {
+ return AbstractDatabaseManager.getManager(name, new FactoryData(null, bufferSize, provider, null), FACTORY);
+ }
+
+ /**
+ * 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.
+ * @param additionalFields Additional fields.
+ * @param configuration TODO
+ * @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);
+ public static NoSqlDatabaseManager<?> getNoSqlDatabaseManager(final String name, final int bufferSize, final NoSqlProvider<?> provider,
+ final KeyValuePair[] additionalFields, final Configuration configuration) {
+ return AbstractDatabaseManager.getManager(name, new FactoryData(configuration, bufferSize, provider, additionalFields), FACTORY);
}
/**
@@ -213,22 +248,24 @@ public final class NoSqlDatabaseManager<W> extends AbstractDatabaseManager {
*/
private static final class FactoryData extends AbstractDatabaseManager.AbstractFactoryData {
private final NoSqlProvider<?> provider;
+ private final KeyValuePair[] additionalFields;
- protected FactoryData(final int bufferSize, final NoSqlProvider<?> provider) {
- super(bufferSize, null);
- this.provider = provider;
+ protected FactoryData(final Configuration configuration, final int bufferSize, final NoSqlProvider<?> provider, final KeyValuePair[] additionalFields) {
+ super(configuration, bufferSize, null); // no layout
+ this.provider = Objects.requireNonNull(provider, "provider");
+ this.additionalFields = additionalFields; // null OK
}
}
/**
* Creates managers.
*/
- private static final class NoSQLDatabaseManagerFactory implements
- ManagerFactory<NoSqlDatabaseManager<?>, FactoryData> {
+ 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);
+ Objects.requireNonNull(data, "data");
+ return new NoSqlDatabaseManager(name, data.getBufferSize(), data.provider, data.additionalFields, data.getConfiguration());
}
}
}
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
index 2be9a65..369e577 100644
--- 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
@@ -78,7 +78,7 @@ public class NoSqlDatabaseManagerTest {
@Test
public void testConnection() {
try (final NoSqlDatabaseManager<?> manager = NoSqlDatabaseManager.getNoSqlDatabaseManager("name", 0,
- provider)) {
+ provider, null, null)) {
assertNotNull("The manager should not be null.", manager);
@@ -91,7 +91,7 @@ public class NoSqlDatabaseManagerTest {
@Test
public void testWriteInternalNotConnected01() {
try (final NoSqlDatabaseManager<?> manager = NoSqlDatabaseManager.getNoSqlDatabaseManager("name", 0,
- provider)) {
+ provider, null, null)) {
expectedException.expect(AppenderLoggingException.class);
manager.writeInternal(mock(LogEvent.class), null);
}
@@ -102,7 +102,7 @@ public class NoSqlDatabaseManagerTest {
given(connection.isClosed()).willReturn(true);
try (final NoSqlDatabaseManager<?> manager = NoSqlDatabaseManager.getNoSqlDatabaseManager("name", 0,
- provider)) {
+ provider, null, null)) {
manager.startup();
manager.connectAndStart();
@@ -119,7 +119,7 @@ public class NoSqlDatabaseManagerTest {
given(message.getFormattedMessage()).willReturn("My formatted message 01.");
try (final NoSqlDatabaseManager<?> manager = NoSqlDatabaseManager.getNoSqlDatabaseManager("name", 0,
- provider)) {
+ provider, null, null)) {
manager.startup();
manager.connectAndStart();
@@ -177,7 +177,7 @@ public class NoSqlDatabaseManagerTest {
given(message.getFormattedMessage()).willReturn("Another cool message 02.");
try (final NoSqlDatabaseManager<?> manager = NoSqlDatabaseManager.getNoSqlDatabaseManager("name", 0,
- provider)) {
+ provider, null, null)) {
manager.startup();
manager.connectAndStart();
@@ -275,7 +275,7 @@ public class NoSqlDatabaseManagerTest {
given(message.getFormattedMessage()).willReturn("Another cool message 02.");
try (final NoSqlDatabaseManager<?> manager = NoSqlDatabaseManager.getNoSqlDatabaseManager("name", 0,
- provider)) {
+ provider, null, null)) {
manager.startup();
manager.connectAndStart();
diff --git a/log4j-mongodb3/src/test/java/org/apache/logging/log4j/mongodb3/MongoDb3AdditionalFieldsTest.java b/log4j-mongodb3/src/test/java/org/apache/logging/log4j/mongodb3/MongoDb3AdditionalFieldsTest.java
new file mode 100644
index 0000000..9e8225d
--- /dev/null
+++ b/log4j-mongodb3/src/test/java/org/apache/logging/log4j/mongodb3/MongoDb3AdditionalFieldsTest.java
@@ -0,0 +1,100 @@
+/*
+ * 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.mongodb3;
+
+import java.util.List;
+
+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.mongodb3.MongoDb3TestRule.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.MongoClient;
+import com.mongodb.client.FindIterable;
+import com.mongodb.client.MongoCollection;
+import com.mongodb.client.MongoDatabase;
+
+/**
+ *
+ */
+@Category(Appenders.MongoDb.class)
+public class MongoDb3AdditionalFieldsTest {
+
+ private static LoggerContextRule loggerContextTestRule = new LoggerContextRule("log4j2-mongodb-additional-fields.xml");
+
+ private static final AvailablePortSystemPropertyTestRule mongoDbPortTestRule = AvailablePortSystemPropertyTestRule
+ .create(MongoDb3TestConstants.SYS_PROP_NAME_PORT);
+
+ private static final MongoDb3TestRule mongoDbTestRule = new MongoDb3TestRule(mongoDbPortTestRule.getName(), MongoDb3Test.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 1");
+ logger.info("Hello log 2", new RuntimeException("Hello ex 2"));
+ try (final MongoClient mongoClient = mongoDbTestRule.getMongoClient()) {
+ final MongoDatabase database = mongoClient.getDatabase(MongoDb3TestConstants.DATABASE_NAME);
+ Assert.assertNotNull(database);
+ final MongoCollection<Document> collection = database.getCollection(MongoDb3TestConstants.COLLECTION_NAME);
+ Assert.assertNotNull(collection);
+ final FindIterable<Document> found = collection.find();
+ final Document first = found.first();
+ Assert.assertNotNull("first", first);
+ Assert.assertEquals(first.toJson(), "Hello log 1", first.getString("message"));
+ Assert.assertEquals(first.toJson(), "INFO", first.getString("level"));
+ //
+ List<Document> list;
+ final String envPath = System.getenv("PATH");
+ //
+ list = first.getList("additionalFields", Document.class);
+ Assert.assertEquals(first.toJson(), "A", list.get(0).get("key"));
+ Assert.assertEquals(first.toJson(), "1", list.get(0).get("value"));
+ Assert.assertEquals(first.toJson(), "B", list.get(1).get("key"));
+ Assert.assertEquals(first.toJson(), "2", list.get(1).get("value"));
+ Assert.assertEquals(first.toJson(), "env1", list.get(2).get("key"));
+ Assert.assertEquals(first.toJson(), envPath, list.get(2).get("value"));
+ Assert.assertEquals(first.toJson(), "env2", list.get(3).get("key"));
+ Assert.assertEquals(first.toJson(), envPath, list.get(3).get("value"));
+ //
+ found.skip(1);
+ final Document second = found.first();
+ Assert.assertNotNull(second);
+ Assert.assertEquals(second.toJson(), "Hello log 2", second.getString("message"));
+ Assert.assertEquals(second.toJson(), "INFO", second.getString("level"));
+ final Document thrown = second.get("thrown", Document.class);
+ Assert.assertEquals(thrown.toJson(), "Hello ex 2", thrown.getString("message"));
+ //
+ list = second.getList("additionalFields", Document.class);
+ Assert.assertEquals(first.toJson(), "A", list.get(0).get("key"));
+ Assert.assertEquals(first.toJson(), "1", list.get(0).get("value"));
+ Assert.assertEquals(first.toJson(), "B", list.get(1).get("key"));
+ Assert.assertEquals(first.toJson(), "2", list.get(1).get("value"));
+ }
+ }
+}
diff --git a/log4j-mongodb3/src/test/resources/log4j2-mongodb-additional-fields.xml b/log4j-mongodb3/src/test/resources/log4j2-mongodb-additional-fields.xml
new file mode 100644
index 0000000..0c7509a
--- /dev/null
+++ b/log4j-mongodb3/src/test/resources/log4j2-mongodb-additional-fields.xml
@@ -0,0 +1,35 @@
+<?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">
+ <MongoDb3 databaseName="testDb" collectionName="testCollection" server="localhost"
+ port="${sys:MongoDBTestPort:-27017}" />
+ <KeyValuePair key="A" value="1" />
+ <KeyValuePair key="B" value="2" />
+ <KeyValuePair key="env1" value="${env:PATH}" />
+ <KeyValuePair key="env2" value="$${env:PATH}" />
+ </NoSql>
+ </Appenders>
+ <Loggers>
+ <Root level="ALL">
+ <AppenderRef ref="MongoDbAppender" />
+ </Root>
+ </Loggers>
+</Configuration>
diff --git a/log4j-mongodb4/src/test/java/org/apache/logging/log4j/mongodb4/MongoDb4AdditionalFieldsTest.java b/log4j-mongodb4/src/test/java/org/apache/logging/log4j/mongodb4/MongoDb4AdditionalFieldsTest.java
new file mode 100644
index 0000000..715162f
--- /dev/null
+++ b/log4j-mongodb4/src/test/java/org/apache/logging/log4j/mongodb4/MongoDb4AdditionalFieldsTest.java
@@ -0,0 +1,101 @@
+/*
+ * 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.List;
+
+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.FindIterable;
+import com.mongodb.client.MongoClient;
+import com.mongodb.client.MongoCollection;
+import com.mongodb.client.MongoDatabase;
+
+/**
+ *
+ */
+@Category(Appenders.MongoDb.class)
+public class MongoDb4AdditionalFieldsTest {
+
+ private static LoggerContextRule loggerContextTestRule = new LoggerContextRule("log4j2-mongodb-additional-fields.xml");
+
+ private static final AvailablePortSystemPropertyTestRule mongoDbPortTestRule = AvailablePortSystemPropertyTestRule
+ .create(MongoDb4TestConstants.SYS_PROP_NAME_PORT);
+
+ private static final MongoDb4TestRule mongoDbTestRule = new MongoDb4TestRule(mongoDbPortTestRule.getName(), MongoDb4AdditionalFieldsTest.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 1");
+ logger.info("Hello log 2", new RuntimeException("Hello ex 2"));
+ try (final MongoClient mongoClient = mongoDbTestRule.getMongoClient()) {
+ final MongoDatabase database = mongoClient.getDatabase(MongoDb4TestConstants.DATABASE_NAME);
+ Assert.assertNotNull(database);
+ final MongoCollection<Document> collection = database.getCollection(MongoDb4TestConstants.COLLECTION_NAME);
+ Assert.assertNotNull(collection);
+ final FindIterable<Document> found = collection.find();
+ final Document first = found.first();
+ Assert.assertNotNull("first", first);
+ Assert.assertEquals(first.toJson(), "Hello log 1", first.getString("message"));
+ Assert.assertEquals(first.toJson(), "INFO", first.getString("level"));
+ //
+ List<Document> list;
+ final String envPath = System.getenv("PATH");
+ //
+ list = first.getList("additionalFields", Document.class);
+ Assert.assertEquals(first.toJson(), "A", list.get(0).get("key"));
+ Assert.assertEquals(first.toJson(), "1", list.get(0).get("value"));
+ Assert.assertEquals(first.toJson(), "B", list.get(1).get("key"));
+ Assert.assertEquals(first.toJson(), "2", list.get(1).get("value"));
+ Assert.assertEquals(first.toJson(), "env1", list.get(2).get("key"));
+ Assert.assertEquals(first.toJson(), envPath, list.get(2).get("value"));
+ Assert.assertEquals(first.toJson(), "env2", list.get(3).get("key"));
+ Assert.assertEquals(first.toJson(), envPath, list.get(3).get("value"));
+ //
+ found.skip(1);
+ final Document second = found.first();
+ Assert.assertNotNull(second);
+ Assert.assertEquals(second.toJson(), "Hello log 2", second.getString("message"));
+ Assert.assertEquals(second.toJson(), "INFO", second.getString("level"));
+ final Document thrown = second.get("thrown", Document.class);
+ Assert.assertEquals(thrown.toJson(), "Hello ex 2", thrown.getString("message"));
+ //
+ list = second.getList("additionalFields", Document.class);
+ Assert.assertEquals(first.toJson(), "A", list.get(0).get("key"));
+ Assert.assertEquals(first.toJson(), "1", list.get(0).get("value"));
+ Assert.assertEquals(first.toJson(), "B", list.get(1).get("key"));
+ Assert.assertEquals(first.toJson(), "2", list.get(1).get("value"));
+ }
+ }
+}
diff --git a/log4j-mongodb4/src/test/resources/log4j2-mongodb-additional-fields.xml b/log4j-mongodb4/src/test/resources/log4j2-mongodb-additional-fields.xml
new file mode 100644
index 0000000..93f3285
--- /dev/null
+++ b/log4j-mongodb4/src/test/resources/log4j2-mongodb-additional-fields.xml
@@ -0,0 +1,34 @@
+<?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" />
+ <KeyValuePair key="A" value="1" />
+ <KeyValuePair key="B" value="2" />
+ <KeyValuePair key="env1" value="${env:PATH}" />
+ <KeyValuePair key="env2" value="$${env:PATH}" />
+ </NoSql>
+ </Appenders>
+ <Loggers>
+ <Root level="ALL">
+ <AppenderRef ref="MongoDbAppender" />
+ </Root>
+ </Loggers>
+</Configuration>
diff --git a/src/changes/changes.xml b/src/changes/changes.xml
index c59773a..f0b3f3e 100644
--- a/src/changes/changes.xml
+++ b/src/changes/changes.xml
@@ -227,6 +227,9 @@
<action type="add" dev="mattsicker" issue="LOG4J2-3341">
Add shorthand syntax for properties configuration format for specifying a logger level and appender refs and implicit appender names for when no name is specified.
</action>
+ <action issue="LOG4J2-3391" type="add" dev="ggregory" due-to="Gary Gregory">
+ Add optional additional fields to NoSQLAppender.
+ </action>
<!-- UPDATES -->
<action issue="LOG4J2-3368" dev="vy" type="udpate">
Bump Tomcat from 8.5.20 to 10.0.14 in log4j-appserver along with some dependency clean-up.