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 2018/01/12 00:52:06 UTC
logging-log4j2 git commit: [LOG4J2-2181] The JDBC Appender should use
keys and values from a Log4j MapMessage. Implementation and documentation.
Repository: logging-log4j2
Updated Branches:
refs/heads/master f530d4c8c -> 187e236ff
[LOG4J2-2181] The JDBC Appender should use keys and values from a Log4j
MapMessage. Implementation and documentation.
Project: http://git-wip-us.apache.org/repos/asf/logging-log4j2/repo
Commit: http://git-wip-us.apache.org/repos/asf/logging-log4j2/commit/187e236f
Tree: http://git-wip-us.apache.org/repos/asf/logging-log4j2/tree/187e236f
Diff: http://git-wip-us.apache.org/repos/asf/logging-log4j2/diff/187e236f
Branch: refs/heads/master
Commit: 187e236ff168238cd2477fad2e78eaeaae5137d5
Parents: f530d4c
Author: Gary Gregory <gg...@apache.org>
Authored: Thu Jan 11 17:52:03 2018 -0700
Committer: Gary Gregory <gg...@apache.org>
Committed: Thu Jan 11 17:52:03 2018 -0700
----------------------------------------------------------------------
.../log4j/cassandra/CassandraManager.java | 2 +-
.../appender/db/AbstractDatabaseManager.java | 31 ++++-
.../log4j/core/appender/db/ColumnMapping.java | 4 +-
.../core/appender/db/jdbc/JdbcAppender.java | 52 +++-----
.../appender/db/jdbc/JdbcDatabaseManager.java | 97 +++++++++++----
.../appender/db/jpa/JpaDatabaseManager.java | 2 +-
.../appender/nosql/NoSqlDatabaseManager.java | 2 +-
.../db/AbstractDatabaseManagerTest.java | 20 ++--
.../JdbcAppenderMapMessageDataSourceTest.java | 118 +++++++++++++++++++
.../db/jdbc/log4j2-data-source-map-message.xml | 43 +++++++
.../mongodb/MongoDbMapMessageTestJava8.java | 8 +-
src/site/xdoc/manual/appenders.xml | 77 ++++++++++++
src/site/xdoc/manual/messages.xml | 4 +
13 files changed, 383 insertions(+), 77 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/187e236f/log4j-cassandra/src/main/java/org/apache/logging/log4j/cassandra/CassandraManager.java
----------------------------------------------------------------------
diff --git a/log4j-cassandra/src/main/java/org/apache/logging/log4j/cassandra/CassandraManager.java b/log4j-cassandra/src/main/java/org/apache/logging/log4j/cassandra/CassandraManager.java
index e9926df..65ee60e 100644
--- a/log4j-cassandra/src/main/java/org/apache/logging/log4j/cassandra/CassandraManager.java
+++ b/log4j-cassandra/src/main/java/org/apache/logging/log4j/cassandra/CassandraManager.java
@@ -197,7 +197,7 @@ public class CassandraManager extends AbstractDatabaseManager {
final String clusterName, final String keyspace, final String table, final String username,
final String password, final boolean useClockForTimestampGenerator, final int bufferSize,
final boolean batched, final BatchStatement.Type batchType) {
- super(bufferSize);
+ super(bufferSize, null);
this.contactPoints = convertAndAddDefaultPorts(contactPoints);
this.columns = columns;
this.useTls = useTls;
http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/187e236f/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/db/AbstractDatabaseManager.java
----------------------------------------------------------------------
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 54c33d1..7624c5e 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
@@ -22,6 +22,7 @@ import java.io.Serializable;
import java.util.ArrayList;
import java.util.concurrent.TimeUnit;
+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;
@@ -32,6 +33,7 @@ import org.apache.logging.log4j.core.appender.ManagerFactory;
public abstract class AbstractDatabaseManager extends AbstractManager implements Flushable {
private final ArrayList<LogEvent> buffer;
private final int bufferSize;
+ private final Layout<? extends Serializable> layout;
private boolean running = false;
@@ -43,9 +45,22 @@ public abstract class AbstractDatabaseManager extends AbstractManager implements
* @param bufferSize The size of the log event buffer.
*/
protected AbstractDatabaseManager(final String name, final int bufferSize) {
+ this(name, bufferSize, null);
+ }
+
+ /**
+ * Instantiates 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.
+ */
+ protected AbstractDatabaseManager(final String name, final int bufferSize, final Layout<? extends Serializable> layout) {
super(null, name);
this.bufferSize = bufferSize;
this.buffer = new ArrayList<>(bufferSize + 1);
+ this.layout = layout;
}
/**
@@ -156,7 +171,7 @@ public abstract class AbstractDatabaseManager extends AbstractManager implements
this.connectAndStart();
try {
for (final LogEvent event : this.buffer) {
- this.writeInternal(event);
+ this.writeInternal(event, layout != null ? layout.toSerializable(event) : null);
}
} finally {
this.commitAndClose();
@@ -233,14 +248,17 @@ public abstract class AbstractDatabaseManager extends AbstractManager implements
*/
protected abstract static class AbstractFactoryData {
private final int bufferSize;
+ private final Layout<? extends Serializable> layout;
/**
* Constructs the base factory data.
*
* @param bufferSize The size of the buffer.
+ * @param bufferSize The appender-level layout
*/
- protected AbstractFactoryData(final int bufferSize) {
+ protected AbstractFactoryData(final int bufferSize, final Layout<? extends Serializable> layout) {
this.bufferSize = bufferSize;
+ this.layout = layout;
}
/**
@@ -251,5 +269,14 @@ public abstract class AbstractDatabaseManager extends AbstractManager implements
public int getBufferSize() {
return bufferSize;
}
+
+ /**
+ * Gets the layout.
+ *
+ * @return the layout.
+ */
+ public Layout<? extends Serializable> getLayout() {
+ return layout;
+ }
}
}
http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/187e236f/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/db/ColumnMapping.java
----------------------------------------------------------------------
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/db/ColumnMapping.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/db/ColumnMapping.java
index 6c106bd..fca50d4 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/db/ColumnMapping.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/db/ColumnMapping.java
@@ -167,8 +167,8 @@ public class ColumnMapping {
.withConfiguration(configuration)
.build();
}
- if (!(layout != null
- || literal != null
+ if (!(layout == null
+ || literal == null
|| Date.class.isAssignableFrom(type)
|| ReadOnlyStringMap.class.isAssignableFrom(type)
|| ThreadContextMap.class.isAssignableFrom(type)
http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/187e236f/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/db/jdbc/JdbcAppender.java
----------------------------------------------------------------------
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/db/jdbc/JdbcAppender.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/db/jdbc/JdbcAppender.java
index af0f93f..974d87e 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/db/jdbc/JdbcAppender.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/db/jdbc/JdbcAppender.java
@@ -17,7 +17,6 @@
package org.apache.logging.log4j.core.appender.db.jdbc;
import java.io.Serializable;
-import java.nio.charset.Charset;
import java.sql.PreparedStatement;
import java.util.Arrays;
import java.util.Objects;
@@ -55,9 +54,9 @@ public final class JdbcAppender extends AbstractDatabaseAppender<JdbcDatabaseMan
private final String description;
- private JdbcAppender(final String name, final Filter filter, final boolean ignoreExceptions,
- final JdbcDatabaseManager manager) {
- super(name, filter, ignoreExceptions, manager);
+ private JdbcAppender(final String name, final Filter filter, final Layout<? extends Serializable> layout,
+ final boolean ignoreExceptions, final JdbcDatabaseManager manager) {
+ super(name, filter, layout, ignoreExceptions, manager);
this.description = this.getName() + "{ manager=" + this.getManager() + " }";
}
@@ -124,6 +123,8 @@ public final class JdbcAppender extends AbstractDatabaseAppender<JdbcDatabaseMan
/**
* The connections source from which database connections should be retrieved.
+ *
+ * @return this
*/
public B setConnectionSource(final ConnectionSource connectionSource) {
this.connectionSource = connectionSource;
@@ -133,6 +134,8 @@ public final class JdbcAppender extends AbstractDatabaseAppender<JdbcDatabaseMan
/**
* If an integer greater than 0, this causes the appender to buffer log events and flush whenever the buffer
* reaches this size.
+ *
+ * @return this
*/
public B setBufferSize(final int bufferSize) {
this.bufferSize = bufferSize;
@@ -141,6 +144,8 @@ public final class JdbcAppender extends AbstractDatabaseAppender<JdbcDatabaseMan
/**
* The name of the database table to insert log events into.
+ *
+ * @return this
*/
public B setTableName(final String tableName) {
this.tableName = tableName;
@@ -149,6 +154,8 @@ public final class JdbcAppender extends AbstractDatabaseAppender<JdbcDatabaseMan
/**
* Information about the columns that log event data should be inserted into and how to insert that data.
+ *
+ * @return this
*/
public B setColumnConfigs(final ColumnConfig... columnConfigs) {
this.columnConfigs = columnConfigs;
@@ -163,42 +170,19 @@ public final class JdbcAppender extends AbstractDatabaseAppender<JdbcDatabaseMan
@Override
public JdbcAppender build() {
if (Assert.isEmpty(columnConfigs) && Assert.isEmpty(columnMappings)) {
- LOGGER.error("Cannot create JdbcAppender without any columns configured.");
+ LOGGER.error("Cannot create JdbcAppender without any columns.");
return null;
}
- final String managerName = "JdbcManager{name=" + getName() + ", bufferSize=" + bufferSize + ", tableName=" +
- tableName + ", columnConfigs=" + Arrays.toString(columnConfigs) + ", columnMappings=" +
- Arrays.toString(columnMappings) + '}';
- final JdbcDatabaseManager manager = JdbcDatabaseManager.getManager(managerName, bufferSize,
- connectionSource, tableName, columnConfigs, columnMappings);
+ final String managerName = "JdbcManager{name=" + getName() + ", bufferSize=" + bufferSize + ", tableName="
+ + tableName + ", columnConfigs=" + Arrays.toString(columnConfigs) + ", columnMappings="
+ + Arrays.toString(columnMappings) + '}';
+ final JdbcDatabaseManager manager = JdbcDatabaseManager.getManager(managerName, bufferSize, getLayout(),
+ connectionSource, tableName, columnConfigs, columnMappings);
if (manager == null) {
return null;
}
- return new JdbcAppender(getName(), getFilter(), isIgnoreExceptions(), manager);
+ return new JdbcAppender(getName(), getFilter(), getLayout(), isIgnoreExceptions(), manager);
}
- @Override
- @Deprecated
- public Layout<? extends Serializable> getLayout() {
- throw new UnsupportedOperationException();
- }
-
- @Override
- @Deprecated
- public B withLayout(final Layout<? extends Serializable> layout) {
- throw new UnsupportedOperationException();
- }
-
- @Override
- @Deprecated
- public Layout<? extends Serializable> getOrCreateLayout() {
- throw new UnsupportedOperationException();
- }
-
- @Override
- @Deprecated
- public Layout<? extends Serializable> getOrCreateLayout(final Charset charset) {
- throw new UnsupportedOperationException();
- }
}
}
http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/187e236f/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/db/jdbc/JdbcDatabaseManager.java
----------------------------------------------------------------------
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/db/jdbc/JdbcDatabaseManager.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/db/jdbc/JdbcDatabaseManager.java
index a652c2c..6c3090a 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/db/jdbc/JdbcDatabaseManager.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/db/jdbc/JdbcDatabaseManager.java
@@ -30,16 +30,23 @@ import java.util.ArrayList;
import java.util.Date;
import java.util.List;
+import org.apache.logging.log4j.core.Layout;
import org.apache.logging.log4j.core.LogEvent;
+import org.apache.logging.log4j.core.StringLayout;
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.appender.db.ColumnMapping;
+import org.apache.logging.log4j.core.appender.nosql.NoSqlObject;
import org.apache.logging.log4j.core.config.plugins.convert.DateTypeConverter;
import org.apache.logging.log4j.core.config.plugins.convert.TypeConverters;
import org.apache.logging.log4j.core.util.Closer;
+import org.apache.logging.log4j.message.MapMessage;
import org.apache.logging.log4j.spi.ThreadContextMap;
import org.apache.logging.log4j.spi.ThreadContextStack;
+import org.apache.logging.log4j.status.StatusLogger;
+import org.apache.logging.log4j.util.BiConsumer;
+import org.apache.logging.log4j.util.IndexedReadOnlyStringMap;
import org.apache.logging.log4j.util.ReadOnlyStringMap;
import org.apache.logging.log4j.util.Strings;
@@ -48,6 +55,10 @@ import org.apache.logging.log4j.util.Strings;
*/
public final class JdbcDatabaseManager extends AbstractDatabaseManager {
+ private static StatusLogger logger() {
+ return StatusLogger.getLogger();
+ }
+
private static final JdbcDatabaseManagerFactory INSTANCE = new JdbcDatabaseManagerFactory();
// NOTE: prepared statements are prepared in this order: column mappings, then column configs
@@ -91,11 +102,11 @@ public final class JdbcDatabaseManager extends AbstractDatabaseManager {
try {
this.connection = this.connectionSource.getConnection();
this.connection.setAutoCommit(false);
+ logger().debug("Preparing SQL: {}", this.sqlStatement);
this.statement = this.connection.prepareStatement(this.sqlStatement);
} catch (final SQLException e) {
throw new AppenderLoggingException(
- "Cannot write logging event or flush buffer; JDBC manager cannot connect to the database.", e
- );
+ "Cannot write logging event or flush buffer; JDBC manager cannot connect to the database.", e);
}
}
@@ -105,6 +116,14 @@ public final class JdbcDatabaseManager extends AbstractDatabaseManager {
writeInternal(event, null);
}
+ private void setFields(final MapMessage<?, ?> mapMessage) throws SQLException {
+ final IndexedReadOnlyStringMap map = mapMessage.getIndexedReadOnlyStringMap();
+ int i = 1; // JDBC indices start at 1
+ for (final ColumnMapping mapping : this.columnMappings) {
+ statement.setObject(i++, map.getValue(mapping.getName()));
+ }
+ }
+
@Override
protected void writeInternal(final LogEvent event, final Serializable serializable) {
StringReader reader = null;
@@ -115,27 +134,35 @@ public final class JdbcDatabaseManager extends AbstractDatabaseManager {
"Cannot write logging event; JDBC manager not connected to the database.");
}
- int i = 1;
+ if (serializable instanceof MapMessage) {
+ setFields((MapMessage<?, ?>) serializable);
+ }
+ int i = 1; // JDBC indices start at 1
for (final ColumnMapping mapping : this.columnMappings) {
if (ThreadContextMap.class.isAssignableFrom(mapping.getType())
- || ReadOnlyStringMap.class.isAssignableFrom(mapping.getType())) {
+ || ReadOnlyStringMap.class.isAssignableFrom(mapping.getType())) {
this.statement.setObject(i++, event.getContextData().toMap());
} else if (ThreadContextStack.class.isAssignableFrom(mapping.getType())) {
this.statement.setObject(i++, event.getContextStack().asList());
} else if (Date.class.isAssignableFrom(mapping.getType())) {
- this.statement.setObject(i++,
- DateTypeConverter.fromMillis(event.getTimeMillis(), mapping.getType().asSubclass(Date.class)));
- } else if (Clob.class.isAssignableFrom(mapping.getType())) {
- this.statement.setClob(i++, new StringReader(mapping.getLayout().toSerializable(event)));
- } else if (NClob.class.isAssignableFrom(mapping.getType())) {
- this.statement.setNClob(i++, new StringReader(mapping.getLayout().toSerializable(event)));
+ this.statement.setObject(i++, DateTypeConverter.fromMillis(event.getTimeMillis(),
+ mapping.getType().asSubclass(Date.class)));
} else {
- final Object value = TypeConverters.convert(mapping.getLayout().toSerializable(event),
- mapping.getType(), null);
- if (value == null) {
- this.statement.setNull(i++, Types.NULL);
- } else {
- this.statement.setObject(i++, value);
+ StringLayout layout = mapping.getLayout();
+ if (layout != null) {
+ if (Clob.class.isAssignableFrom(mapping.getType())) {
+ this.statement.setClob(i++, new StringReader(layout.toSerializable(event)));
+ } else if (NClob.class.isAssignableFrom(mapping.getType())) {
+ this.statement.setNClob(i++, new StringReader(layout.toSerializable(event)));
+ } else {
+ final Object value = TypeConverters.convert(layout.toSerializable(event), mapping.getType(),
+ null);
+ if (value == null) {
+ this.statement.setNull(i++, Types.NULL);
+ } else {
+ this.statement.setObject(i++, value);
+ }
+ }
}
}
}
@@ -213,7 +240,7 @@ public final class JdbcDatabaseManager extends AbstractDatabaseManager {
* @param tableName The name of the database table to insert log events into.
* @param columnConfigs Configuration information about the log table columns.
* @return a new or existing JDBC manager as applicable.
- * @deprecated use {@link #getManager(String, int, ConnectionSource, String, ColumnConfig[], ColumnMapping[])}
+ * @deprecated use {@link #getManager(String, int, Layout, ConnectionSource, String, ColumnConfig[], ColumnMapping[])}
*/
@Deprecated
public static JdbcDatabaseManager getJDBCDatabaseManager(final String name, final int bufferSize,
@@ -222,7 +249,30 @@ public final class JdbcDatabaseManager extends AbstractDatabaseManager {
final ColumnConfig[] columnConfigs) {
return getManager(name,
- new FactoryData(bufferSize, connectionSource, tableName, columnConfigs, new ColumnMapping[0]),
+ new FactoryData(bufferSize, null, connectionSource, tableName, columnConfigs, new ColumnMapping[0]),
+ getFactory());
+ }
+
+ /**
+ * Creates a JDBC manager for use within the {@link JdbcAppender}, 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 connectionSource The source for connections to the database.
+ * @param tableName The name of the database table to insert log events into.
+ * @param columnConfigs Configuration information about the log table columns.
+ * @param columnMappings column mapping configuration (including type conversion).
+ * @return a new or existing JDBC manager as applicable.
+ * @deprecated use {@link #getManager(String, int, Layout, ConnectionSource, String, ColumnConfig[], ColumnMapping[])}
+ */
+ @Deprecated
+ public static JdbcDatabaseManager getManager(final String name,
+ final int bufferSize,
+ final ConnectionSource connectionSource,
+ final String tableName,
+ final ColumnConfig[] columnConfigs,
+ final ColumnMapping[] columnMappings) {
+ return getManager(name, new FactoryData(bufferSize, null, connectionSource, tableName, columnConfigs, columnMappings),
getFactory());
}
@@ -231,6 +281,7 @@ public final class JdbcDatabaseManager extends AbstractDatabaseManager {
*
* @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 layout The Appender-level layout
* @param connectionSource The source for connections to the database.
* @param tableName The name of the database table to insert log events into.
* @param columnConfigs Configuration information about the log table columns.
@@ -239,11 +290,12 @@ public final class JdbcDatabaseManager extends AbstractDatabaseManager {
*/
public static JdbcDatabaseManager getManager(final String name,
final int bufferSize,
+ final Layout<? extends Serializable> layout,
final ConnectionSource connectionSource,
final String tableName,
final ColumnConfig[] columnConfigs,
final ColumnMapping[] columnMappings) {
- return getManager(name, new FactoryData(bufferSize, connectionSource, tableName, columnConfigs, columnMappings),
+ return getManager(name, new FactoryData(bufferSize, layout, connectionSource, tableName, columnConfigs, columnMappings),
getFactory());
}
@@ -260,9 +312,10 @@ public final class JdbcDatabaseManager extends AbstractDatabaseManager {
private final ColumnConfig[] columnConfigs;
private final ColumnMapping[] columnMappings;
- protected FactoryData(final int bufferSize, final ConnectionSource connectionSource, final String tableName,
- final ColumnConfig[] columnConfigs, final ColumnMapping[] columnMappings) {
- super(bufferSize);
+ protected FactoryData(final int bufferSize, final Layout<? extends Serializable> layout,
+ final ConnectionSource connectionSource, final String tableName, final ColumnConfig[] columnConfigs,
+ final ColumnMapping[] columnMappings) {
+ super(bufferSize, layout);
this.connectionSource = connectionSource;
this.tableName = tableName;
this.columnConfigs = columnConfigs;
http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/187e236f/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/db/jpa/JpaDatabaseManager.java
----------------------------------------------------------------------
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/db/jpa/JpaDatabaseManager.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/db/jpa/JpaDatabaseManager.java
index ddfa18e..b2d36a8 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/db/jpa/JpaDatabaseManager.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/db/jpa/JpaDatabaseManager.java
@@ -178,7 +178,7 @@ public final class JpaDatabaseManager extends AbstractDatabaseManager {
protected FactoryData(final int bufferSize, final Class<? extends AbstractLogEventWrapperEntity> entityClass,
final Constructor<? extends AbstractLogEventWrapperEntity> entityConstructor,
final String persistenceUnitName) {
- super(bufferSize);
+ super(bufferSize, null);
this.entityClass = entityClass;
this.entityConstructor = entityConstructor;
http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/187e236f/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/nosql/NoSqlDatabaseManager.java
----------------------------------------------------------------------
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/nosql/NoSqlDatabaseManager.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/nosql/NoSqlDatabaseManager.java
index 361ae2b..80acae7 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
@@ -234,7 +234,7 @@ public final class NoSqlDatabaseManager<W> extends AbstractDatabaseManager {
private final NoSqlProvider<?> provider;
protected FactoryData(final int bufferSize, final NoSqlProvider<?> provider) {
- super(bufferSize);
+ super(bufferSize, null);
this.provider = provider;
}
}
http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/187e236f/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/db/AbstractDatabaseManagerTest.java
----------------------------------------------------------------------
diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/db/AbstractDatabaseManagerTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/db/AbstractDatabaseManagerTest.java
index ae2104b..932724d 100644
--- a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/db/AbstractDatabaseManagerTest.java
+++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/db/AbstractDatabaseManagerTest.java
@@ -144,10 +144,10 @@ public class AbstractDatabaseManagerTest {
manager.write(event4, null);
then(manager).should().connectAndStart();
- then(manager).should().writeInternal(same(event1copy));
- then(manager).should().writeInternal(same(event2copy));
- then(manager).should().writeInternal(same(event3copy));
- then(manager).should().writeInternal(same(event4copy));
+ then(manager).should().writeInternal(same(event1copy), (Serializable) isNull());
+ then(manager).should().writeInternal(same(event2copy), (Serializable) isNull());
+ then(manager).should().writeInternal(same(event3copy), (Serializable) isNull());
+ then(manager).should().writeInternal(same(event4copy), (Serializable) isNull());
then(manager).should().commitAndClose();
then(manager).shouldHaveNoMoreInteractions();
}
@@ -177,9 +177,9 @@ public class AbstractDatabaseManagerTest {
manager.flush();
then(manager).should().connectAndStart();
- then(manager).should().writeInternal(same(event1copy));
- then(manager).should().writeInternal(same(event2copy));
- then(manager).should().writeInternal(same(event3copy));
+ then(manager).should().writeInternal(same(event1copy), (Serializable) isNull());
+ then(manager).should().writeInternal(same(event2copy), (Serializable) isNull());
+ then(manager).should().writeInternal(same(event3copy), (Serializable) isNull());
then(manager).should().commitAndClose();
then(manager).shouldHaveNoMoreInteractions();
}
@@ -209,9 +209,9 @@ public class AbstractDatabaseManagerTest {
manager.shutdown();
then(manager).should().connectAndStart();
- then(manager).should().writeInternal(same(event1copy));
- then(manager).should().writeInternal(same(event2copy));
- then(manager).should().writeInternal(same(event3copy));
+ then(manager).should().writeInternal(same(event1copy), (Serializable) isNull());
+ then(manager).should().writeInternal(same(event2copy), (Serializable) isNull());
+ then(manager).should().writeInternal(same(event3copy), (Serializable) isNull());
then(manager).should().commitAndClose();
then(manager).should().shutdownInternal();
then(manager).shouldHaveNoMoreInteractions();
http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/187e236f/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/db/jdbc/JdbcAppenderMapMessageDataSourceTest.java
----------------------------------------------------------------------
diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/db/jdbc/JdbcAppenderMapMessageDataSourceTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/db/jdbc/JdbcAppenderMapMessageDataSourceTest.java
new file mode 100644
index 0000000..c186537
--- /dev/null
+++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/db/jdbc/JdbcAppenderMapMessageDataSourceTest.java
@@ -0,0 +1,118 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache license, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the license for the specific language governing permissions and
+ * limitations under the license.
+ */
+package org.apache.logging.log4j.core.appender.db.jdbc;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.BDDMockito.given;
+import static org.mockito.Mockito.mock;
+
+import java.io.ByteArrayOutputStream;
+import java.io.PrintWriter;
+import java.sql.Connection;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.Statement;
+
+import javax.sql.DataSource;
+
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.apache.logging.log4j.core.util.Throwables;
+import org.apache.logging.log4j.junit.JdbcRule;
+import org.apache.logging.log4j.junit.JndiRule;
+import org.apache.logging.log4j.junit.LoggerContextRule;
+import org.apache.logging.log4j.message.MapMessage;
+import org.junit.Assert;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.RuleChain;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
+
+/**
+ * Unit tests {@link MapMessage}s for JdbcAppender using a {@link DataSource} configuration.
+ */
+public class JdbcAppenderMapMessageDataSourceTest {
+
+ @Rule
+ public final RuleChain rules;
+ private final JdbcRule jdbcRule;
+
+ public JdbcAppenderMapMessageDataSourceTest() {
+ this(new JdbcRule(JdbcH2TestHelper.TEST_CONFIGURATION_SOURCE,
+ // @formatter:off
+ "CREATE TABLE dsLogEntry (Id INTEGER IDENTITY, ColumnA VARCHAR(255), ColumnB VARCHAR(255))",
+ "DROP TABLE dsLogEntry"));
+ // @formatter:on
+ }
+
+ protected JdbcAppenderMapMessageDataSourceTest(final JdbcRule jdbcRule) {
+ // @formatter:off
+ this.rules = RuleChain.emptyRuleChain()
+ .around(new JndiRule("java:/comp/env/jdbc/TestDataSourceAppender", createMockDataSource()))
+ .around(jdbcRule)
+ .around(new LoggerContextRule("org/apache/logging/log4j/core/appender/db/jdbc/log4j2-data-source-map-message.xml"));
+ // @formatter:on
+ this.jdbcRule = jdbcRule;
+ }
+
+ private DataSource createMockDataSource() {
+ try {
+ final DataSource dataSource = mock(DataSource.class);
+ given(dataSource.getConnection()).willAnswer(new Answer<Connection>() {
+ @Override
+ public Connection answer(final InvocationOnMock invocation) throws Throwable {
+ return jdbcRule.getConnectionSource().getConnection();
+ }
+ });
+ return dataSource;
+ } catch (final SQLException e) {
+ Throwables.rethrow(e);
+ throw new InternalError("unreachable");
+ }
+ }
+
+ @Test
+ public void testDataSourceConfig() throws Exception {
+ try (final Connection connection = jdbcRule.getConnectionSource().getConnection()) {
+ final Error exception = new Error("Final error massage is fatal!");
+ final ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+ final PrintWriter writer = new PrintWriter(outputStream);
+ exception.printStackTrace(writer);
+ writer.close();
+
+ final Logger logger = LogManager.getLogger(this.getClass().getName() + ".testDataSourceConfig");
+ MapMessage mapMessage = new MapMessage();
+ mapMessage.with("Id", 1);
+ mapMessage.with("ColumnA", "ValueA");
+ mapMessage.with("ColumnB", "ValueB");
+ logger.info(mapMessage);
+
+ try (final Statement statement = connection.createStatement();
+ final ResultSet resultSet = statement
+ .executeQuery("SELECT Id, ColumnA, ColumnB FROM dsLogEntry ORDER BY Id")) {
+
+ assertTrue("There should be at least one row.", resultSet.next());
+
+ Assert.assertEquals(1, resultSet.getInt("Id"));
+
+ assertFalse("There should not be two rows.", resultSet.next());
+ }
+ }
+ }
+}
http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/187e236f/log4j-core/src/test/resources/org/apache/logging/log4j/core/appender/db/jdbc/log4j2-data-source-map-message.xml
----------------------------------------------------------------------
diff --git a/log4j-core/src/test/resources/org/apache/logging/log4j/core/appender/db/jdbc/log4j2-data-source-map-message.xml b/log4j-core/src/test/resources/org/apache/logging/log4j/core/appender/db/jdbc/log4j2-data-source-map-message.xml
new file mode 100644
index 0000000..a50bbf6
--- /dev/null
+++ b/log4j-core/src/test/resources/org/apache/logging/log4j/core/appender/db/jdbc/log4j2-data-source-map-message.xml
@@ -0,0 +1,43 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<Configuration status="ERROR">
+
+ <Appenders>
+ <Console name="STDOUT">
+ <PatternLayout pattern="%C{1.} %m %level MDC%X%n"/>
+ </Console>
+ <Jdbc name="databaseAppender" tableName="dsLogEntry" ignoreExceptions="false">
+ <DataSource jndiName="java:/comp/env/jdbc/TestDataSourceAppender" />
+ <ColumnMapping name="Id" />
+ <ColumnMapping name="ColumnA" />
+ <ColumnMapping name="ColumnB" />
+ <MessageLayout />
+ </Jdbc>
+ </Appenders>
+
+ <Loggers>
+ <Logger name="org.apache.logging.log4j.core.appender.db" level="debug" additivity="false">
+ <AppenderRef ref="databaseAppender" />
+ </Logger>
+
+ <Root level="fatal">
+ <AppenderRef ref="STDOUT"/>
+ </Root>
+ </Loggers>
+
+</Configuration>
http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/187e236f/log4j-mongodb/src/test/java/org/apache/logging/log4j/mongodb/MongoDbMapMessageTestJava8.java
----------------------------------------------------------------------
diff --git a/log4j-mongodb/src/test/java/org/apache/logging/log4j/mongodb/MongoDbMapMessageTestJava8.java b/log4j-mongodb/src/test/java/org/apache/logging/log4j/mongodb/MongoDbMapMessageTestJava8.java
index b245ac9..dbcae44 100644
--- a/log4j-mongodb/src/test/java/org/apache/logging/log4j/mongodb/MongoDbMapMessageTestJava8.java
+++ b/log4j-mongodb/src/test/java/org/apache/logging/log4j/mongodb/MongoDbMapMessageTestJava8.java
@@ -54,10 +54,10 @@ public class MongoDbMapMessageTestJava8 {
@Test
public void test() {
final Logger logger = LogManager.getLogger();
- final MapMessage map = new MapMessage();
- map.with("SomeName", "SomeValue");
- map.with("SomeInt", 1);
- logger.info(map);
+ final MapMessage 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("test");
http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/187e236f/src/site/xdoc/manual/appenders.xml
----------------------------------------------------------------------
diff --git a/src/site/xdoc/manual/appenders.xml b/src/site/xdoc/manual/appenders.xml
index 7ecd9d8..d266347 100644
--- a/src/site/xdoc/manual/appenders.xml
+++ b/src/site/xdoc/manual/appenders.xml
@@ -1163,6 +1163,48 @@ CREATE TABLE logs (
</td>
</tr>
</table>
+ <table>
+ <caption align="top">ColumnMapping Parameters</caption>
+ <tr>
+ <th>Parameter Name</th>
+ <th>Type</th>
+ <th>Description</th>
+ </tr>
+ <tr>
+ <td>name</td>
+ <td>String</td>
+ <td><em>Required.</em> The name of the database column.</td>
+ </tr>
+ <tr>
+ <td>pattern</td>
+ <td>String</td>
+ <td>Use this attribute to insert a value or values from the log event in this column using a
+ <code>PatternLayout</code> pattern. Simply specify any legal pattern in this attribute. Either this
+ attribute, <code>literal</code>, or <code>isEventTimestamp="true"</code> must be specified, but not more
+ than one of these.</td>
+ </tr>
+ <tr>
+ <td>literal</td>
+ <td>String</td>
+ <td>Use this attribute to insert a literal value in this column. The value will be included directly in
+ the insert SQL, without any quoting (which means that if you want this to be a string, your value should
+ contain single quotes around it like this: <code>literal="'Literal String'"</code>). This is especially
+ useful for databases that don't support identity columns. For example, if you are using Oracle you could
+ specify <code>literal="NAME_OF_YOUR_SEQUENCE.NEXTVAL"</code> to insert a unique ID in an ID column.
+ Either this attribute, <code>pattern</code>, or <code>isEventTimestamp="true"</code> must be specified,
+ but not more than one of these.</td>
+ </tr>
+ <tr>
+ <td>layout</td>
+ <td>Layout</td>
+ <td>The Layout to format the LogEvent.</td>
+ </tr>
+ <tr>
+ <td>type</td>
+ <td>String</td>
+ <td>Conversion type name, a fully-qualified class name.</td>
+ </tr>
+ </table>
<p>
Here are a couple sample configurations for the JDBCAppender, as well as a sample factory implementation
that uses Commons Pooling and Commons DBCP to pool database connections:
@@ -1247,6 +1289,41 @@ public class ConnectionFactory {
return Singleton.INSTANCE.dataSource.getConnection();
}
}]]></pre>
+ <p>
+ This appender is <a href="messages.html#MapMessage">MapMessage</a>-aware.
+ </p>
+ <p>
+ The following configuration uses a <code>MessageLayout</code> to indicate that the Appender should match
+ the keys of a <code>MapMessage</code> to the names of <code>ColumnMapping</code>s when setting the
+ values of the Appender's SQL INSERT statement. This let you insert rows for custom values in a
+ database table based on a Log4j <code>MapMessage</code> instead of values from <code>LogEvent</code>s.
+ </p>
+ <pre class="prettyprint linenums lang-xml"><![CDATA[<Configuration status="debug">
+
+ <Appenders>
+ <Console name="STDOUT">
+ <PatternLayout pattern="%C{1.} %m %level MDC%X%n"/>
+ </Console>
+ <Jdbc name="databaseAppender" tableName="dsLogEntry" ignoreExceptions="false">
+ <DataSource jndiName="java:/comp/env/jdbc/TestDataSourceAppender" />
+ <ColumnMapping name="Id" />
+ <ColumnMapping name="ColumnA" />
+ <ColumnMapping name="ColumnB" />
+ <MessageLayout />
+ </Jdbc>
+ </Appenders>
+
+ <Loggers>
+ <Logger name="org.apache.logging.log4j.core.appender.db" level="debug" additivity="false">
+ <AppenderRef ref="databaseAppender" />
+ </Logger>
+
+ <Root level="fatal">
+ <AppenderRef ref="STDOUT"/>
+ </Root>
+ </Loggers>
+
+</Configuration>]]></pre>
</subsection>
<a name="JMSAppender"/>
<!-- cool URLs don't change, so here are some old anchors -->
http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/187e236f/src/site/xdoc/manual/messages.xml
----------------------------------------------------------------------
diff --git a/src/site/xdoc/manual/messages.xml b/src/site/xdoc/manual/messages.xml
index c243026..41005c8 100644
--- a/src/site/xdoc/manual/messages.xml
+++ b/src/site/xdoc/manual/messages.xml
@@ -214,6 +214,10 @@ public class MyApp {
<code>MapMessage</code> to a JMS <code>javax.jms.MapMessage</code>.
</li>
<li>
+ When a <a href="appenders.html#JDBCAppender">JDBC Appender</a> is configured with a <code>MessageLayout</code>, it converts a Log4j
+ <code>MapMessage</code> to values in a SQL INSERT statement.
+ </li>
+ <li>
When a <a href="appenders.html#NoSQLAppenderMongoDB">MongoDB Appender</a> is configured with a <code>MessageLayout</code>, it converts a Log4j
<code>MapMessage</code> to fields in a MongoDB object.
</li>
Re: logging-log4j2 git commit: [LOG4J2-2181] The JDBC Appender should
use keys and values from a Log4j MapMessage. Implementation and
documentation.
Posted by Ralph Goers <ra...@dslextreme.com>.
Again 6 commits for the same Jira?
Ralph
> On Jan 11, 2018, at 5:52 PM, ggregory@apache.org wrote:
>
> Repository: logging-log4j2
> Updated Branches:
> refs/heads/master f530d4c8c -> 187e236ff
>
>
> [LOG4J2-2181] The JDBC Appender should use keys and values from a Log4j
> MapMessage. Implementation and documentation.
>
> Project: http://git-wip-us.apache.org/repos/asf/logging-log4j2/repo
> Commit: http://git-wip-us.apache.org/repos/asf/logging-log4j2/commit/187e236f
> Tree: http://git-wip-us.apache.org/repos/asf/logging-log4j2/tree/187e236f
> Diff: http://git-wip-us.apache.org/repos/asf/logging-log4j2/diff/187e236f
>
> Branch: refs/heads/master
> Commit: 187e236ff168238cd2477fad2e78eaeaae5137d5
> Parents: f530d4c
> Author: Gary Gregory <gg...@apache.org>
> Authored: Thu Jan 11 17:52:03 2018 -0700
> Committer: Gary Gregory <gg...@apache.org>
> Committed: Thu Jan 11 17:52:03 2018 -0700
>
> ----------------------------------------------------------------------
> .../log4j/cassandra/CassandraManager.java | 2 +-
> .../appender/db/AbstractDatabaseManager.java | 31 ++++-
> .../log4j/core/appender/db/ColumnMapping.java | 4 +-
> .../core/appender/db/jdbc/JdbcAppender.java | 52 +++-----
> .../appender/db/jdbc/JdbcDatabaseManager.java | 97 +++++++++++----
> .../appender/db/jpa/JpaDatabaseManager.java | 2 +-
> .../appender/nosql/NoSqlDatabaseManager.java | 2 +-
> .../db/AbstractDatabaseManagerTest.java | 20 ++--
> .../JdbcAppenderMapMessageDataSourceTest.java | 118 +++++++++++++++++++
> .../db/jdbc/log4j2-data-source-map-message.xml | 43 +++++++
> .../mongodb/MongoDbMapMessageTestJava8.java | 8 +-
> src/site/xdoc/manual/appenders.xml | 77 ++++++++++++
> src/site/xdoc/manual/messages.xml | 4 +
> 13 files changed, 383 insertions(+), 77 deletions(-)
> ----------------------------------------------------------------------
>
>
> http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/187e236f/log4j-cassandra/src/main/java/org/apache/logging/log4j/cassandra/CassandraManager.java
> ----------------------------------------------------------------------
> diff --git a/log4j-cassandra/src/main/java/org/apache/logging/log4j/cassandra/CassandraManager.java b/log4j-cassandra/src/main/java/org/apache/logging/log4j/cassandra/CassandraManager.java
> index e9926df..65ee60e 100644
> --- a/log4j-cassandra/src/main/java/org/apache/logging/log4j/cassandra/CassandraManager.java
> +++ b/log4j-cassandra/src/main/java/org/apache/logging/log4j/cassandra/CassandraManager.java
> @@ -197,7 +197,7 @@ public class CassandraManager extends AbstractDatabaseManager {
> final String clusterName, final String keyspace, final String table, final String username,
> final String password, final boolean useClockForTimestampGenerator, final int bufferSize,
> final boolean batched, final BatchStatement.Type batchType) {
> - super(bufferSize);
> + super(bufferSize, null);
> this.contactPoints = convertAndAddDefaultPorts(contactPoints);
> this.columns = columns;
> this.useTls = useTls;
>
> http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/187e236f/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/db/AbstractDatabaseManager.java
> ----------------------------------------------------------------------
> 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 54c33d1..7624c5e 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
> @@ -22,6 +22,7 @@ import java.io.Serializable;
> import java.util.ArrayList;
> import java.util.concurrent.TimeUnit;
>
> +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;
> @@ -32,6 +33,7 @@ import org.apache.logging.log4j.core.appender.ManagerFactory;
> public abstract class AbstractDatabaseManager extends AbstractManager implements Flushable {
> private final ArrayList<LogEvent> buffer;
> private final int bufferSize;
> + private final Layout<? extends Serializable> layout;
>
> private boolean running = false;
>
> @@ -43,9 +45,22 @@ public abstract class AbstractDatabaseManager extends AbstractManager implements
> * @param bufferSize The size of the log event buffer.
> */
> protected AbstractDatabaseManager(final String name, final int bufferSize) {
> + this(name, bufferSize, null);
> + }
> +
> + /**
> + * Instantiates 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.
> + */
> + protected AbstractDatabaseManager(final String name, final int bufferSize, final Layout<? extends Serializable> layout) {
> super(null, name);
> this.bufferSize = bufferSize;
> this.buffer = new ArrayList<>(bufferSize + 1);
> + this.layout = layout;
> }
>
> /**
> @@ -156,7 +171,7 @@ public abstract class AbstractDatabaseManager extends AbstractManager implements
> this.connectAndStart();
> try {
> for (final LogEvent event : this.buffer) {
> - this.writeInternal(event);
> + this.writeInternal(event, layout != null ? layout.toSerializable(event) : null);
> }
> } finally {
> this.commitAndClose();
> @@ -233,14 +248,17 @@ public abstract class AbstractDatabaseManager extends AbstractManager implements
> */
> protected abstract static class AbstractFactoryData {
> private final int bufferSize;
> + private final Layout<? extends Serializable> layout;
>
> /**
> * Constructs the base factory data.
> *
> * @param bufferSize The size of the buffer.
> + * @param bufferSize The appender-level layout
> */
> - protected AbstractFactoryData(final int bufferSize) {
> + protected AbstractFactoryData(final int bufferSize, final Layout<? extends Serializable> layout) {
> this.bufferSize = bufferSize;
> + this.layout = layout;
> }
>
> /**
> @@ -251,5 +269,14 @@ public abstract class AbstractDatabaseManager extends AbstractManager implements
> public int getBufferSize() {
> return bufferSize;
> }
> +
> + /**
> + * Gets the layout.
> + *
> + * @return the layout.
> + */
> + public Layout<? extends Serializable> getLayout() {
> + return layout;
> + }
> }
> }
>
> http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/187e236f/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/db/ColumnMapping.java
> ----------------------------------------------------------------------
> diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/db/ColumnMapping.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/db/ColumnMapping.java
> index 6c106bd..fca50d4 100644
> --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/db/ColumnMapping.java
> +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/db/ColumnMapping.java
> @@ -167,8 +167,8 @@ public class ColumnMapping {
> .withConfiguration(configuration)
> .build();
> }
> - if (!(layout != null
> - || literal != null
> + if (!(layout == null
> + || literal == null
> || Date.class.isAssignableFrom(type)
> || ReadOnlyStringMap.class.isAssignableFrom(type)
> || ThreadContextMap.class.isAssignableFrom(type)
>
> http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/187e236f/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/db/jdbc/JdbcAppender.java
> ----------------------------------------------------------------------
> diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/db/jdbc/JdbcAppender.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/db/jdbc/JdbcAppender.java
> index af0f93f..974d87e 100644
> --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/db/jdbc/JdbcAppender.java
> +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/db/jdbc/JdbcAppender.java
> @@ -17,7 +17,6 @@
> package org.apache.logging.log4j.core.appender.db.jdbc;
>
> import java.io.Serializable;
> -import java.nio.charset.Charset;
> import java.sql.PreparedStatement;
> import java.util.Arrays;
> import java.util.Objects;
> @@ -55,9 +54,9 @@ public final class JdbcAppender extends AbstractDatabaseAppender<JdbcDatabaseMan
>
> private final String description;
>
> - private JdbcAppender(final String name, final Filter filter, final boolean ignoreExceptions,
> - final JdbcDatabaseManager manager) {
> - super(name, filter, ignoreExceptions, manager);
> + private JdbcAppender(final String name, final Filter filter, final Layout<? extends Serializable> layout,
> + final boolean ignoreExceptions, final JdbcDatabaseManager manager) {
> + super(name, filter, layout, ignoreExceptions, manager);
> this.description = this.getName() + "{ manager=" + this.getManager() + " }";
> }
>
> @@ -124,6 +123,8 @@ public final class JdbcAppender extends AbstractDatabaseAppender<JdbcDatabaseMan
>
> /**
> * The connections source from which database connections should be retrieved.
> + *
> + * @return this
> */
> public B setConnectionSource(final ConnectionSource connectionSource) {
> this.connectionSource = connectionSource;
> @@ -133,6 +134,8 @@ public final class JdbcAppender extends AbstractDatabaseAppender<JdbcDatabaseMan
> /**
> * If an integer greater than 0, this causes the appender to buffer log events and flush whenever the buffer
> * reaches this size.
> + *
> + * @return this
> */
> public B setBufferSize(final int bufferSize) {
> this.bufferSize = bufferSize;
> @@ -141,6 +144,8 @@ public final class JdbcAppender extends AbstractDatabaseAppender<JdbcDatabaseMan
>
> /**
> * The name of the database table to insert log events into.
> + *
> + * @return this
> */
> public B setTableName(final String tableName) {
> this.tableName = tableName;
> @@ -149,6 +154,8 @@ public final class JdbcAppender extends AbstractDatabaseAppender<JdbcDatabaseMan
>
> /**
> * Information about the columns that log event data should be inserted into and how to insert that data.
> + *
> + * @return this
> */
> public B setColumnConfigs(final ColumnConfig... columnConfigs) {
> this.columnConfigs = columnConfigs;
> @@ -163,42 +170,19 @@ public final class JdbcAppender extends AbstractDatabaseAppender<JdbcDatabaseMan
> @Override
> public JdbcAppender build() {
> if (Assert.isEmpty(columnConfigs) && Assert.isEmpty(columnMappings)) {
> - LOGGER.error("Cannot create JdbcAppender without any columns configured.");
> + LOGGER.error("Cannot create JdbcAppender without any columns.");
> return null;
> }
> - final String managerName = "JdbcManager{name=" + getName() + ", bufferSize=" + bufferSize + ", tableName=" +
> - tableName + ", columnConfigs=" + Arrays.toString(columnConfigs) + ", columnMappings=" +
> - Arrays.toString(columnMappings) + '}';
> - final JdbcDatabaseManager manager = JdbcDatabaseManager.getManager(managerName, bufferSize,
> - connectionSource, tableName, columnConfigs, columnMappings);
> + final String managerName = "JdbcManager{name=" + getName() + ", bufferSize=" + bufferSize + ", tableName="
> + + tableName + ", columnConfigs=" + Arrays.toString(columnConfigs) + ", columnMappings="
> + + Arrays.toString(columnMappings) + '}';
> + final JdbcDatabaseManager manager = JdbcDatabaseManager.getManager(managerName, bufferSize, getLayout(),
> + connectionSource, tableName, columnConfigs, columnMappings);
> if (manager == null) {
> return null;
> }
> - return new JdbcAppender(getName(), getFilter(), isIgnoreExceptions(), manager);
> + return new JdbcAppender(getName(), getFilter(), getLayout(), isIgnoreExceptions(), manager);
> }
>
> - @Override
> - @Deprecated
> - public Layout<? extends Serializable> getLayout() {
> - throw new UnsupportedOperationException();
> - }
> -
> - @Override
> - @Deprecated
> - public B withLayout(final Layout<? extends Serializable> layout) {
> - throw new UnsupportedOperationException();
> - }
> -
> - @Override
> - @Deprecated
> - public Layout<? extends Serializable> getOrCreateLayout() {
> - throw new UnsupportedOperationException();
> - }
> -
> - @Override
> - @Deprecated
> - public Layout<? extends Serializable> getOrCreateLayout(final Charset charset) {
> - throw new UnsupportedOperationException();
> - }
> }
> }
>
> http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/187e236f/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/db/jdbc/JdbcDatabaseManager.java
> ----------------------------------------------------------------------
> diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/db/jdbc/JdbcDatabaseManager.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/db/jdbc/JdbcDatabaseManager.java
> index a652c2c..6c3090a 100644
> --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/db/jdbc/JdbcDatabaseManager.java
> +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/db/jdbc/JdbcDatabaseManager.java
> @@ -30,16 +30,23 @@ import java.util.ArrayList;
> import java.util.Date;
> import java.util.List;
>
> +import org.apache.logging.log4j.core.Layout;
> import org.apache.logging.log4j.core.LogEvent;
> +import org.apache.logging.log4j.core.StringLayout;
> 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.appender.db.ColumnMapping;
> +import org.apache.logging.log4j.core.appender.nosql.NoSqlObject;
> import org.apache.logging.log4j.core.config.plugins.convert.DateTypeConverter;
> import org.apache.logging.log4j.core.config.plugins.convert.TypeConverters;
> import org.apache.logging.log4j.core.util.Closer;
> +import org.apache.logging.log4j.message.MapMessage;
> import org.apache.logging.log4j.spi.ThreadContextMap;
> import org.apache.logging.log4j.spi.ThreadContextStack;
> +import org.apache.logging.log4j.status.StatusLogger;
> +import org.apache.logging.log4j.util.BiConsumer;
> +import org.apache.logging.log4j.util.IndexedReadOnlyStringMap;
> import org.apache.logging.log4j.util.ReadOnlyStringMap;
> import org.apache.logging.log4j.util.Strings;
>
> @@ -48,6 +55,10 @@ import org.apache.logging.log4j.util.Strings;
> */
> public final class JdbcDatabaseManager extends AbstractDatabaseManager {
>
> + private static StatusLogger logger() {
> + return StatusLogger.getLogger();
> + }
> +
> private static final JdbcDatabaseManagerFactory INSTANCE = new JdbcDatabaseManagerFactory();
>
> // NOTE: prepared statements are prepared in this order: column mappings, then column configs
> @@ -91,11 +102,11 @@ public final class JdbcDatabaseManager extends AbstractDatabaseManager {
> try {
> this.connection = this.connectionSource.getConnection();
> this.connection.setAutoCommit(false);
> + logger().debug("Preparing SQL: {}", this.sqlStatement);
> this.statement = this.connection.prepareStatement(this.sqlStatement);
> } catch (final SQLException e) {
> throw new AppenderLoggingException(
> - "Cannot write logging event or flush buffer; JDBC manager cannot connect to the database.", e
> - );
> + "Cannot write logging event or flush buffer; JDBC manager cannot connect to the database.", e);
> }
> }
>
> @@ -105,6 +116,14 @@ public final class JdbcDatabaseManager extends AbstractDatabaseManager {
> writeInternal(event, null);
> }
>
> + private void setFields(final MapMessage<?, ?> mapMessage) throws SQLException {
> + final IndexedReadOnlyStringMap map = mapMessage.getIndexedReadOnlyStringMap();
> + int i = 1; // JDBC indices start at 1
> + for (final ColumnMapping mapping : this.columnMappings) {
> + statement.setObject(i++, map.getValue(mapping.getName()));
> + }
> + }
> +
> @Override
> protected void writeInternal(final LogEvent event, final Serializable serializable) {
> StringReader reader = null;
> @@ -115,27 +134,35 @@ public final class JdbcDatabaseManager extends AbstractDatabaseManager {
> "Cannot write logging event; JDBC manager not connected to the database.");
> }
>
> - int i = 1;
> + if (serializable instanceof MapMessage) {
> + setFields((MapMessage<?, ?>) serializable);
> + }
> + int i = 1; // JDBC indices start at 1
> for (final ColumnMapping mapping : this.columnMappings) {
> if (ThreadContextMap.class.isAssignableFrom(mapping.getType())
> - || ReadOnlyStringMap.class.isAssignableFrom(mapping.getType())) {
> + || ReadOnlyStringMap.class.isAssignableFrom(mapping.getType())) {
> this.statement.setObject(i++, event.getContextData().toMap());
> } else if (ThreadContextStack.class.isAssignableFrom(mapping.getType())) {
> this.statement.setObject(i++, event.getContextStack().asList());
> } else if (Date.class.isAssignableFrom(mapping.getType())) {
> - this.statement.setObject(i++,
> - DateTypeConverter.fromMillis(event.getTimeMillis(), mapping.getType().asSubclass(Date.class)));
> - } else if (Clob.class.isAssignableFrom(mapping.getType())) {
> - this.statement.setClob(i++, new StringReader(mapping.getLayout().toSerializable(event)));
> - } else if (NClob.class.isAssignableFrom(mapping.getType())) {
> - this.statement.setNClob(i++, new StringReader(mapping.getLayout().toSerializable(event)));
> + this.statement.setObject(i++, DateTypeConverter.fromMillis(event.getTimeMillis(),
> + mapping.getType().asSubclass(Date.class)));
> } else {
> - final Object value = TypeConverters.convert(mapping.getLayout().toSerializable(event),
> - mapping.getType(), null);
> - if (value == null) {
> - this.statement.setNull(i++, Types.NULL);
> - } else {
> - this.statement.setObject(i++, value);
> + StringLayout layout = mapping.getLayout();
> + if (layout != null) {
> + if (Clob.class.isAssignableFrom(mapping.getType())) {
> + this.statement.setClob(i++, new StringReader(layout.toSerializable(event)));
> + } else if (NClob.class.isAssignableFrom(mapping.getType())) {
> + this.statement.setNClob(i++, new StringReader(layout.toSerializable(event)));
> + } else {
> + final Object value = TypeConverters.convert(layout.toSerializable(event), mapping.getType(),
> + null);
> + if (value == null) {
> + this.statement.setNull(i++, Types.NULL);
> + } else {
> + this.statement.setObject(i++, value);
> + }
> + }
> }
> }
> }
> @@ -213,7 +240,7 @@ public final class JdbcDatabaseManager extends AbstractDatabaseManager {
> * @param tableName The name of the database table to insert log events into.
> * @param columnConfigs Configuration information about the log table columns.
> * @return a new or existing JDBC manager as applicable.
> - * @deprecated use {@link #getManager(String, int, ConnectionSource, String, ColumnConfig[], ColumnMapping[])}
> + * @deprecated use {@link #getManager(String, int, Layout, ConnectionSource, String, ColumnConfig[], ColumnMapping[])}
> */
> @Deprecated
> public static JdbcDatabaseManager getJDBCDatabaseManager(final String name, final int bufferSize,
> @@ -222,7 +249,30 @@ public final class JdbcDatabaseManager extends AbstractDatabaseManager {
> final ColumnConfig[] columnConfigs) {
>
> return getManager(name,
> - new FactoryData(bufferSize, connectionSource, tableName, columnConfigs, new ColumnMapping[0]),
> + new FactoryData(bufferSize, null, connectionSource, tableName, columnConfigs, new ColumnMapping[0]),
> + getFactory());
> + }
> +
> + /**
> + * Creates a JDBC manager for use within the {@link JdbcAppender}, 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 connectionSource The source for connections to the database.
> + * @param tableName The name of the database table to insert log events into.
> + * @param columnConfigs Configuration information about the log table columns.
> + * @param columnMappings column mapping configuration (including type conversion).
> + * @return a new or existing JDBC manager as applicable.
> + * @deprecated use {@link #getManager(String, int, Layout, ConnectionSource, String, ColumnConfig[], ColumnMapping[])}
> + */
> + @Deprecated
> + public static JdbcDatabaseManager getManager(final String name,
> + final int bufferSize,
> + final ConnectionSource connectionSource,
> + final String tableName,
> + final ColumnConfig[] columnConfigs,
> + final ColumnMapping[] columnMappings) {
> + return getManager(name, new FactoryData(bufferSize, null, connectionSource, tableName, columnConfigs, columnMappings),
> getFactory());
> }
>
> @@ -231,6 +281,7 @@ public final class JdbcDatabaseManager extends AbstractDatabaseManager {
> *
> * @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 layout The Appender-level layout
> * @param connectionSource The source for connections to the database.
> * @param tableName The name of the database table to insert log events into.
> * @param columnConfigs Configuration information about the log table columns.
> @@ -239,11 +290,12 @@ public final class JdbcDatabaseManager extends AbstractDatabaseManager {
> */
> public static JdbcDatabaseManager getManager(final String name,
> final int bufferSize,
> + final Layout<? extends Serializable> layout,
> final ConnectionSource connectionSource,
> final String tableName,
> final ColumnConfig[] columnConfigs,
> final ColumnMapping[] columnMappings) {
> - return getManager(name, new FactoryData(bufferSize, connectionSource, tableName, columnConfigs, columnMappings),
> + return getManager(name, new FactoryData(bufferSize, layout, connectionSource, tableName, columnConfigs, columnMappings),
> getFactory());
> }
>
> @@ -260,9 +312,10 @@ public final class JdbcDatabaseManager extends AbstractDatabaseManager {
> private final ColumnConfig[] columnConfigs;
> private final ColumnMapping[] columnMappings;
>
> - protected FactoryData(final int bufferSize, final ConnectionSource connectionSource, final String tableName,
> - final ColumnConfig[] columnConfigs, final ColumnMapping[] columnMappings) {
> - super(bufferSize);
> + protected FactoryData(final int bufferSize, final Layout<? extends Serializable> layout,
> + final ConnectionSource connectionSource, final String tableName, final ColumnConfig[] columnConfigs,
> + final ColumnMapping[] columnMappings) {
> + super(bufferSize, layout);
> this.connectionSource = connectionSource;
> this.tableName = tableName;
> this.columnConfigs = columnConfigs;
>
> http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/187e236f/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/db/jpa/JpaDatabaseManager.java
> ----------------------------------------------------------------------
> diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/db/jpa/JpaDatabaseManager.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/db/jpa/JpaDatabaseManager.java
> index ddfa18e..b2d36a8 100644
> --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/db/jpa/JpaDatabaseManager.java
> +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/db/jpa/JpaDatabaseManager.java
> @@ -178,7 +178,7 @@ public final class JpaDatabaseManager extends AbstractDatabaseManager {
> protected FactoryData(final int bufferSize, final Class<? extends AbstractLogEventWrapperEntity> entityClass,
> final Constructor<? extends AbstractLogEventWrapperEntity> entityConstructor,
> final String persistenceUnitName) {
> - super(bufferSize);
> + super(bufferSize, null);
>
> this.entityClass = entityClass;
> this.entityConstructor = entityConstructor;
>
> http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/187e236f/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/nosql/NoSqlDatabaseManager.java
> ----------------------------------------------------------------------
> diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/nosql/NoSqlDatabaseManager.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/nosql/NoSqlDatabaseManager.java
> index 361ae2b..80acae7 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
> @@ -234,7 +234,7 @@ public final class NoSqlDatabaseManager<W> extends AbstractDatabaseManager {
> private final NoSqlProvider<?> provider;
>
> protected FactoryData(final int bufferSize, final NoSqlProvider<?> provider) {
> - super(bufferSize);
> + super(bufferSize, null);
> this.provider = provider;
> }
> }
>
> http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/187e236f/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/db/AbstractDatabaseManagerTest.java
> ----------------------------------------------------------------------
> diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/db/AbstractDatabaseManagerTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/db/AbstractDatabaseManagerTest.java
> index ae2104b..932724d 100644
> --- a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/db/AbstractDatabaseManagerTest.java
> +++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/db/AbstractDatabaseManagerTest.java
> @@ -144,10 +144,10 @@ public class AbstractDatabaseManagerTest {
> manager.write(event4, null);
>
> then(manager).should().connectAndStart();
> - then(manager).should().writeInternal(same(event1copy));
> - then(manager).should().writeInternal(same(event2copy));
> - then(manager).should().writeInternal(same(event3copy));
> - then(manager).should().writeInternal(same(event4copy));
> + then(manager).should().writeInternal(same(event1copy), (Serializable) isNull());
> + then(manager).should().writeInternal(same(event2copy), (Serializable) isNull());
> + then(manager).should().writeInternal(same(event3copy), (Serializable) isNull());
> + then(manager).should().writeInternal(same(event4copy), (Serializable) isNull());
> then(manager).should().commitAndClose();
> then(manager).shouldHaveNoMoreInteractions();
> }
> @@ -177,9 +177,9 @@ public class AbstractDatabaseManagerTest {
> manager.flush();
>
> then(manager).should().connectAndStart();
> - then(manager).should().writeInternal(same(event1copy));
> - then(manager).should().writeInternal(same(event2copy));
> - then(manager).should().writeInternal(same(event3copy));
> + then(manager).should().writeInternal(same(event1copy), (Serializable) isNull());
> + then(manager).should().writeInternal(same(event2copy), (Serializable) isNull());
> + then(manager).should().writeInternal(same(event3copy), (Serializable) isNull());
> then(manager).should().commitAndClose();
> then(manager).shouldHaveNoMoreInteractions();
> }
> @@ -209,9 +209,9 @@ public class AbstractDatabaseManagerTest {
> manager.shutdown();
>
> then(manager).should().connectAndStart();
> - then(manager).should().writeInternal(same(event1copy));
> - then(manager).should().writeInternal(same(event2copy));
> - then(manager).should().writeInternal(same(event3copy));
> + then(manager).should().writeInternal(same(event1copy), (Serializable) isNull());
> + then(manager).should().writeInternal(same(event2copy), (Serializable) isNull());
> + then(manager).should().writeInternal(same(event3copy), (Serializable) isNull());
> then(manager).should().commitAndClose();
> then(manager).should().shutdownInternal();
> then(manager).shouldHaveNoMoreInteractions();
>
> http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/187e236f/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/db/jdbc/JdbcAppenderMapMessageDataSourceTest.java
> ----------------------------------------------------------------------
> diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/db/jdbc/JdbcAppenderMapMessageDataSourceTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/db/jdbc/JdbcAppenderMapMessageDataSourceTest.java
> new file mode 100644
> index 0000000..c186537
> --- /dev/null
> +++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/db/jdbc/JdbcAppenderMapMessageDataSourceTest.java
> @@ -0,0 +1,118 @@
> +/*
> + * Licensed to the Apache Software Foundation (ASF) under one or more
> + * contributor license agreements. See the NOTICE file distributed with
> + * this work for additional information regarding copyright ownership.
> + * The ASF licenses this file to You under the Apache license, Version 2.0
> + * (the "License"); you may not use this file except in compliance with
> + * the License. You may obtain a copy of the License at
> + *
> + * http://www.apache.org/licenses/LICENSE-2.0
> + *
> + * Unless required by applicable law or agreed to in writing, software
> + * distributed under the License is distributed on an "AS IS" BASIS,
> + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
> + * See the license for the specific language governing permissions and
> + * limitations under the license.
> + */
> +package org.apache.logging.log4j.core.appender.db.jdbc;
> +
> +import static org.junit.Assert.assertFalse;
> +import static org.junit.Assert.assertTrue;
> +import static org.mockito.BDDMockito.given;
> +import static org.mockito.Mockito.mock;
> +
> +import java.io.ByteArrayOutputStream;
> +import java.io.PrintWriter;
> +import java.sql.Connection;
> +import java.sql.ResultSet;
> +import java.sql.SQLException;
> +import java.sql.Statement;
> +
> +import javax.sql.DataSource;
> +
> +import org.apache.logging.log4j.LogManager;
> +import org.apache.logging.log4j.Logger;
> +import org.apache.logging.log4j.core.util.Throwables;
> +import org.apache.logging.log4j.junit.JdbcRule;
> +import org.apache.logging.log4j.junit.JndiRule;
> +import org.apache.logging.log4j.junit.LoggerContextRule;
> +import org.apache.logging.log4j.message.MapMessage;
> +import org.junit.Assert;
> +import org.junit.Rule;
> +import org.junit.Test;
> +import org.junit.rules.RuleChain;
> +import org.mockito.invocation.InvocationOnMock;
> +import org.mockito.stubbing.Answer;
> +
> +/**
> + * Unit tests {@link MapMessage}s for JdbcAppender using a {@link DataSource} configuration.
> + */
> +public class JdbcAppenderMapMessageDataSourceTest {
> +
> + @Rule
> + public final RuleChain rules;
> + private final JdbcRule jdbcRule;
> +
> + public JdbcAppenderMapMessageDataSourceTest() {
> + this(new JdbcRule(JdbcH2TestHelper.TEST_CONFIGURATION_SOURCE,
> + // @formatter:off
> + "CREATE TABLE dsLogEntry (Id INTEGER IDENTITY, ColumnA VARCHAR(255), ColumnB VARCHAR(255))",
> + "DROP TABLE dsLogEntry"));
> + // @formatter:on
> + }
> +
> + protected JdbcAppenderMapMessageDataSourceTest(final JdbcRule jdbcRule) {
> + // @formatter:off
> + this.rules = RuleChain.emptyRuleChain()
> + .around(new JndiRule("java:/comp/env/jdbc/TestDataSourceAppender", createMockDataSource()))
> + .around(jdbcRule)
> + .around(new LoggerContextRule("org/apache/logging/log4j/core/appender/db/jdbc/log4j2-data-source-map-message.xml"));
> + // @formatter:on
> + this.jdbcRule = jdbcRule;
> + }
> +
> + private DataSource createMockDataSource() {
> + try {
> + final DataSource dataSource = mock(DataSource.class);
> + given(dataSource.getConnection()).willAnswer(new Answer<Connection>() {
> + @Override
> + public Connection answer(final InvocationOnMock invocation) throws Throwable {
> + return jdbcRule.getConnectionSource().getConnection();
> + }
> + });
> + return dataSource;
> + } catch (final SQLException e) {
> + Throwables.rethrow(e);
> + throw new InternalError("unreachable");
> + }
> + }
> +
> + @Test
> + public void testDataSourceConfig() throws Exception {
> + try (final Connection connection = jdbcRule.getConnectionSource().getConnection()) {
> + final Error exception = new Error("Final error massage is fatal!");
> + final ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
> + final PrintWriter writer = new PrintWriter(outputStream);
> + exception.printStackTrace(writer);
> + writer.close();
> +
> + final Logger logger = LogManager.getLogger(this.getClass().getName() + ".testDataSourceConfig");
> + MapMessage mapMessage = new MapMessage();
> + mapMessage.with("Id", 1);
> + mapMessage.with("ColumnA", "ValueA");
> + mapMessage.with("ColumnB", "ValueB");
> + logger.info(mapMessage);
> +
> + try (final Statement statement = connection.createStatement();
> + final ResultSet resultSet = statement
> + .executeQuery("SELECT Id, ColumnA, ColumnB FROM dsLogEntry ORDER BY Id")) {
> +
> + assertTrue("There should be at least one row.", resultSet.next());
> +
> + Assert.assertEquals(1, resultSet.getInt("Id"));
> +
> + assertFalse("There should not be two rows.", resultSet.next());
> + }
> + }
> + }
> +}
>
> http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/187e236f/log4j-core/src/test/resources/org/apache/logging/log4j/core/appender/db/jdbc/log4j2-data-source-map-message.xml
> ----------------------------------------------------------------------
> diff --git a/log4j-core/src/test/resources/org/apache/logging/log4j/core/appender/db/jdbc/log4j2-data-source-map-message.xml b/log4j-core/src/test/resources/org/apache/logging/log4j/core/appender/db/jdbc/log4j2-data-source-map-message.xml
> new file mode 100644
> index 0000000..a50bbf6
> --- /dev/null
> +++ b/log4j-core/src/test/resources/org/apache/logging/log4j/core/appender/db/jdbc/log4j2-data-source-map-message.xml
> @@ -0,0 +1,43 @@
> +<?xml version="1.0" encoding="UTF-8"?>
> +<!--
> + Licensed to the Apache Software Foundation (ASF) under one or more
> + contributor license agreements. See the NOTICE file distributed with
> + this work for additional information regarding copyright ownership.
> + The ASF licenses this file to You under the Apache License, Version 2.0
> + (the "License"); you may not use this file except in compliance with
> + the License. You may obtain a copy of the License at
> +
> + http://www.apache.org/licenses/LICENSE-2.0
> +
> + Unless required by applicable law or agreed to in writing, software
> + distributed under the License is distributed on an "AS IS" BASIS,
> + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
> + See the License for the specific language governing permissions and
> + limitations under the License.
> +-->
> +<Configuration status="ERROR">
> +
> + <Appenders>
> + <Console name="STDOUT">
> + <PatternLayout pattern="%C{1.} %m %level MDC%X%n"/>
> + </Console>
> + <Jdbc name="databaseAppender" tableName="dsLogEntry" ignoreExceptions="false">
> + <DataSource jndiName="java:/comp/env/jdbc/TestDataSourceAppender" />
> + <ColumnMapping name="Id" />
> + <ColumnMapping name="ColumnA" />
> + <ColumnMapping name="ColumnB" />
> + <MessageLayout />
> + </Jdbc>
> + </Appenders>
> +
> + <Loggers>
> + <Logger name="org.apache.logging.log4j.core.appender.db" level="debug" additivity="false">
> + <AppenderRef ref="databaseAppender" />
> + </Logger>
> +
> + <Root level="fatal">
> + <AppenderRef ref="STDOUT"/>
> + </Root>
> + </Loggers>
> +
> +</Configuration>
>
> http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/187e236f/log4j-mongodb/src/test/java/org/apache/logging/log4j/mongodb/MongoDbMapMessageTestJava8.java
> ----------------------------------------------------------------------
> diff --git a/log4j-mongodb/src/test/java/org/apache/logging/log4j/mongodb/MongoDbMapMessageTestJava8.java b/log4j-mongodb/src/test/java/org/apache/logging/log4j/mongodb/MongoDbMapMessageTestJava8.java
> index b245ac9..dbcae44 100644
> --- a/log4j-mongodb/src/test/java/org/apache/logging/log4j/mongodb/MongoDbMapMessageTestJava8.java
> +++ b/log4j-mongodb/src/test/java/org/apache/logging/log4j/mongodb/MongoDbMapMessageTestJava8.java
> @@ -54,10 +54,10 @@ public class MongoDbMapMessageTestJava8 {
> @Test
> public void test() {
> final Logger logger = LogManager.getLogger();
> - final MapMessage map = new MapMessage();
> - map.with("SomeName", "SomeValue");
> - map.with("SomeInt", 1);
> - logger.info(map);
> + final MapMessage 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("test");
>
> http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/187e236f/src/site/xdoc/manual/appenders.xml
> ----------------------------------------------------------------------
> diff --git a/src/site/xdoc/manual/appenders.xml b/src/site/xdoc/manual/appenders.xml
> index 7ecd9d8..d266347 100644
> --- a/src/site/xdoc/manual/appenders.xml
> +++ b/src/site/xdoc/manual/appenders.xml
> @@ -1163,6 +1163,48 @@ CREATE TABLE logs (
> </td>
> </tr>
> </table>
> + <table>
> + <caption align="top">ColumnMapping Parameters</caption>
> + <tr>
> + <th>Parameter Name</th>
> + <th>Type</th>
> + <th>Description</th>
> + </tr>
> + <tr>
> + <td>name</td>
> + <td>String</td>
> + <td><em>Required.</em> The name of the database column.</td>
> + </tr>
> + <tr>
> + <td>pattern</td>
> + <td>String</td>
> + <td>Use this attribute to insert a value or values from the log event in this column using a
> + <code>PatternLayout</code> pattern. Simply specify any legal pattern in this attribute. Either this
> + attribute, <code>literal</code>, or <code>isEventTimestamp="true"</code> must be specified, but not more
> + than one of these.</td>
> + </tr>
> + <tr>
> + <td>literal</td>
> + <td>String</td>
> + <td>Use this attribute to insert a literal value in this column. The value will be included directly in
> + the insert SQL, without any quoting (which means that if you want this to be a string, your value should
> + contain single quotes around it like this: <code>literal="'Literal String'"</code>). This is especially
> + useful for databases that don't support identity columns. For example, if you are using Oracle you could
> + specify <code>literal="NAME_OF_YOUR_SEQUENCE.NEXTVAL"</code> to insert a unique ID in an ID column.
> + Either this attribute, <code>pattern</code>, or <code>isEventTimestamp="true"</code> must be specified,
> + but not more than one of these.</td>
> + </tr>
> + <tr>
> + <td>layout</td>
> + <td>Layout</td>
> + <td>The Layout to format the LogEvent.</td>
> + </tr>
> + <tr>
> + <td>type</td>
> + <td>String</td>
> + <td>Conversion type name, a fully-qualified class name.</td>
> + </tr>
> + </table>
> <p>
> Here are a couple sample configurations for the JDBCAppender, as well as a sample factory implementation
> that uses Commons Pooling and Commons DBCP to pool database connections:
> @@ -1247,6 +1289,41 @@ public class ConnectionFactory {
> return Singleton.INSTANCE.dataSource.getConnection();
> }
> }]]></pre>
> + <p>
> + This appender is <a href="messages.html#MapMessage">MapMessage</a>-aware.
> + </p>
> + <p>
> + The following configuration uses a <code>MessageLayout</code> to indicate that the Appender should match
> + the keys of a <code>MapMessage</code> to the names of <code>ColumnMapping</code>s when setting the
> + values of the Appender's SQL INSERT statement. This let you insert rows for custom values in a
> + database table based on a Log4j <code>MapMessage</code> instead of values from <code>LogEvent</code>s.
> + </p>
> + <pre class="prettyprint linenums lang-xml"><![CDATA[<Configuration status="debug">
> +
> + <Appenders>
> + <Console name="STDOUT">
> + <PatternLayout pattern="%C{1.} %m %level MDC%X%n"/>
> + </Console>
> + <Jdbc name="databaseAppender" tableName="dsLogEntry" ignoreExceptions="false">
> + <DataSource jndiName="java:/comp/env/jdbc/TestDataSourceAppender" />
> + <ColumnMapping name="Id" />
> + <ColumnMapping name="ColumnA" />
> + <ColumnMapping name="ColumnB" />
> + <MessageLayout />
> + </Jdbc>
> + </Appenders>
> +
> + <Loggers>
> + <Logger name="org.apache.logging.log4j.core.appender.db" level="debug" additivity="false">
> + <AppenderRef ref="databaseAppender" />
> + </Logger>
> +
> + <Root level="fatal">
> + <AppenderRef ref="STDOUT"/>
> + </Root>
> + </Loggers>
> +
> +</Configuration>]]></pre>
> </subsection>
> <a name="JMSAppender"/>
> <!-- cool URLs don't change, so here are some old anchors -->
>
> http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/187e236f/src/site/xdoc/manual/messages.xml
> ----------------------------------------------------------------------
> diff --git a/src/site/xdoc/manual/messages.xml b/src/site/xdoc/manual/messages.xml
> index c243026..41005c8 100644
> --- a/src/site/xdoc/manual/messages.xml
> +++ b/src/site/xdoc/manual/messages.xml
> @@ -214,6 +214,10 @@ public class MyApp {
> <code>MapMessage</code> to a JMS <code>javax.jms.MapMessage</code>.
> </li>
> <li>
> + When a <a href="appenders.html#JDBCAppender">JDBC Appender</a> is configured with a <code>MessageLayout</code>, it converts a Log4j
> + <code>MapMessage</code> to values in a SQL INSERT statement.
> + </li>
> + <li>
> When a <a href="appenders.html#NoSQLAppenderMongoDB">MongoDB Appender</a> is configured with a <code>MessageLayout</code>, it converts a Log4j
> <code>MapMessage</code> to fields in a MongoDB object.
> </li>
>
>