You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@logging.apache.org by Ralph Goers <ra...@dslextreme.com> on 2018/01/14 02:17:43 UTC

Re: logging-log4j2 git commit: [LOG4J2-2181] The JDBC Appender should use keys and values from a Log4j MapMessage. Implementation and documentation.

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>
> 
>