[LOG4J2-424]: Add non-string data type support to JdbcAppender This utilises the new ColumnMapping plugin created for CassandraAppender. However, this also means that JdbcAppender has two ways to configure columns, and both methods can be used at the same time.
Project: http://git-wip-us.apache.org/repos/asf/logging-log4j2/repo Commit: http://git-wip-us.apache.org/repos/asf/logging-log4j2/commit/6a1339b8 Tree: http://git-wip-us.apache.org/repos/asf/logging-log4j2/tree/6a1339b8 Diff: http://git-wip-us.apache.org/repos/asf/logging-log4j2/diff/6a1339b8 Branch: refs/heads/master Commit: 6a1339b800add2a009a4ba62a1235ad9878abd1c Parents: 230e953 Author: Matt Sicker <[email protected]> Authored: Sat Jan 7 03:39:12 2017 -0600 Committer: Matt Sicker <[email protected]> Committed: Sat Jan 7 03:39:12 2017 -0600 ---------------------------------------------------------------------- .../core/appender/db/jdbc/JdbcAppender.java | 32 ++-- .../appender/db/jdbc/JdbcDatabaseManager.java | 175 ++++++++++++------- .../db/jdbc/AbstractJdbcAppenderTest.java | 6 + .../appender/db/jdbc/JdbcH2AppenderTest.java | 2 +- .../db/jdbc/JdbcHyperSqlAppenderTest.java | 2 +- .../db/jdbc/log4j2-h2-factory-method.xml | 1 + .../db/jdbc/log4j2-hsqldb-factory-method.xml | 1 + src/changes/changes.xml | 5 +- src/site/xdoc/manual/appenders.xml | 31 +++- 9 files changed, 178 insertions(+), 77 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/6a1339b8/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 3aa5644..af0f93f 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 @@ -18,6 +18,7 @@ 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; @@ -27,21 +28,26 @@ import org.apache.logging.log4j.core.Filter; import org.apache.logging.log4j.core.Layout; import org.apache.logging.log4j.core.appender.AbstractAppender; import org.apache.logging.log4j.core.appender.db.AbstractDatabaseAppender; +import org.apache.logging.log4j.core.appender.db.ColumnMapping; import org.apache.logging.log4j.core.config.plugins.Plugin; import org.apache.logging.log4j.core.config.plugins.PluginBuilderAttribute; import org.apache.logging.log4j.core.config.plugins.PluginBuilderFactory; import org.apache.logging.log4j.core.config.plugins.PluginElement; +import org.apache.logging.log4j.core.config.plugins.convert.TypeConverter; import org.apache.logging.log4j.core.config.plugins.validation.constraints.Required; import org.apache.logging.log4j.core.util.Assert; import org.apache.logging.log4j.core.util.Booleans; /** * This Appender writes logging events to a relational database using standard JDBC mechanisms. It takes a list of - * {@link ColumnConfig}s with which it determines how to save the event data into the appropriate columns in the table. + * {@link ColumnConfig}s and/or {@link ColumnMapping}s with which it determines how to save the event data into the + * appropriate columns in the table. ColumnMapping is new as of Log4j 2.8 and supports + * {@linkplain TypeConverter type conversion} and persistence using {@link PreparedStatement#setObject(int, Object)}. * A {@link ConnectionSource} plugin instance instructs the appender (and {@link JdbcDatabaseManager}) how to connect to * the database. This appender can be reconfigured at run time. * * @see ColumnConfig + * @see ColumnMapping * @see ConnectionSource */ @Plugin(name = "JDBC", category = Core.CATEGORY_NAME, elementType = Appender.ELEMENT_TYPE, printObject = true) @@ -113,9 +119,8 @@ public final class JdbcAppender extends AbstractDatabaseAppender<JdbcDatabaseMan @PluginElement("ColumnConfigs") private ColumnConfig[] columnConfigs; - // TODO: LOG4J2-424 -// @PluginElement("ColumnMappings") -// private ColumnMapping[] columnMappings; + @PluginElement("ColumnMappings") + private ColumnMapping[] columnMappings; /** * The connections source from which database connections should be retrieved. @@ -150,17 +155,22 @@ public final class JdbcAppender extends AbstractDatabaseAppender<JdbcDatabaseMan return asBuilder(); } -// public B setColumnMappings(final ColumnMapping... columnMappings) { -// this.columnMappings = columnMappings; -// return asBuilder(); -// } + public B setColumnMappings(final ColumnMapping... columnMappings) { + this.columnMappings = columnMappings; + return asBuilder(); + } @Override public JdbcAppender build() { + if (Assert.isEmpty(columnConfigs) && Assert.isEmpty(columnMappings)) { + LOGGER.error("Cannot create JdbcAppender without any columns configured."); + return null; + } final String managerName = "JdbcManager{name=" + getName() + ", bufferSize=" + bufferSize + ", tableName=" + - tableName + ", columnConfigs=" + Arrays.toString(columnConfigs) + '}'; - final JdbcDatabaseManager manager = JdbcDatabaseManager.getJDBCDatabaseManager(managerName, bufferSize, - connectionSource, tableName, columnConfigs); + tableName + ", columnConfigs=" + Arrays.toString(columnConfigs) + ", columnMappings=" + + Arrays.toString(columnMappings) + '}'; + final JdbcDatabaseManager manager = JdbcDatabaseManager.getManager(managerName, bufferSize, + connectionSource, tableName, columnConfigs, columnMappings); if (manager == null) { return null; } http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/6a1339b8/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 6f9c66a..2d80f70 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 @@ -17,20 +17,30 @@ package org.apache.logging.log4j.core.appender.db.jdbc; import java.io.StringReader; +import java.sql.Clob; import java.sql.Connection; import java.sql.DatabaseMetaData; +import java.sql.NClob; import java.sql.PreparedStatement; import java.sql.SQLException; import java.sql.Timestamp; +import java.sql.Types; import java.util.ArrayList; +import java.util.Date; import java.util.List; import org.apache.logging.log4j.core.LogEvent; import org.apache.logging.log4j.core.appender.AppenderLoggingException; import org.apache.logging.log4j.core.appender.ManagerFactory; import org.apache.logging.log4j.core.appender.db.AbstractDatabaseManager; -import org.apache.logging.log4j.core.layout.PatternLayout; +import org.apache.logging.log4j.core.appender.db.ColumnMapping; +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.spi.ThreadContextMap; +import org.apache.logging.log4j.spi.ThreadContextStack; +import org.apache.logging.log4j.util.ReadOnlyStringMap; +import org.apache.logging.log4j.util.Strings; /** * An {@link AbstractDatabaseManager} implementation for relational databases accessed via JDBC. @@ -39,7 +49,9 @@ public final class JdbcDatabaseManager extends AbstractDatabaseManager { private static final JdbcDatabaseManagerFactory INSTANCE = new JdbcDatabaseManagerFactory(); - private final List<Column> columns; + // NOTE: prepared statements are prepared in this order: column mappings, then column configs + private final List<ColumnMapping> columnMappings; + private final List<ColumnConfig> columnConfigs; private final ConnectionSource connectionSource; private final String sqlStatement; @@ -48,11 +60,13 @@ public final class JdbcDatabaseManager extends AbstractDatabaseManager { private boolean isBatchSupported; private JdbcDatabaseManager(final String name, final int bufferSize, final ConnectionSource connectionSource, - final String sqlStatement, final List<Column> columns) { + final String sqlStatement, final List<ColumnConfig> columnConfigs, + final List<ColumnMapping> columnMappings) { super(name, bufferSize); this.connectionSource = connectionSource; this.sqlStatement = sqlStatement; - this.columns = columns; + this.columnConfigs = columnConfigs; + this.columnMappings = columnMappings; } @Override @@ -95,26 +109,45 @@ public final class JdbcDatabaseManager extends AbstractDatabaseManager { } int i = 1; - for (final Column column : this.columns) { - if (column.isEventTimestamp) { - this.statement.setTimestamp(i++, new Timestamp(event.getTimeMillis())); + for (final ColumnMapping mapping : this.columnMappings) { + if (ThreadContextMap.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))); } else { - if (column.isClob) { - reader = new StringReader(column.layout.toSerializable(event)); - if (column.isUnicode) { - this.statement.setNClob(i++, reader); - } else { - this.statement.setClob(i++, reader); - } + final Object value = TypeConverters.convert(mapping.getLayout().toSerializable(event), + mapping.getType(), null); + if (value == null) { + this.statement.setNull(i++, Types.NULL); } else { - if (column.isUnicode) { - this.statement.setNString(i++, column.layout.toSerializable(event)); - } else { - this.statement.setString(i++, column.layout.toSerializable(event)); - } + this.statement.setObject(i++, value); } } } + for (final ColumnConfig column : this.columnConfigs) { + if (column.isEventTimestamp()) { + this.statement.setTimestamp(i++, new Timestamp(event.getTimeMillis())); + } else if (column.isClob()) { + reader = new StringReader(column.getLayout().toSerializable(event)); + if (column.isUnicode()) { + this.statement.setNClob(i++, reader); + } else { + this.statement.setClob(i++, reader); + } + } else if (column.isUnicode()) { + this.statement.setNString(i++, column.getLayout().toSerializable(event)); + } else { + this.statement.setString(i++, column.getLayout().toSerializable(event)); + } + } if (this.isBatchSupported) { this.statement.addBatch(); @@ -173,15 +206,38 @@ 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 public static JdbcDatabaseManager getJDBCDatabaseManager(final String name, final int bufferSize, final ConnectionSource connectionSource, final String tableName, final ColumnConfig[] columnConfigs) { - return AbstractDatabaseManager.getManager( - name, new FactoryData(bufferSize, connectionSource, tableName, columnConfigs), getFactory() - ); + return getManager(name, + new FactoryData(bufferSize, 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. + */ + 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, connectionSource, tableName, columnConfigs, columnMappings), + getFactory()); } private static JdbcDatabaseManagerFactory getFactory() { @@ -192,16 +248,18 @@ public final class JdbcDatabaseManager extends AbstractDatabaseManager { * Encapsulates data that {@link JdbcDatabaseManagerFactory} uses to create managers. */ private static final class FactoryData extends AbstractDatabaseManager.AbstractFactoryData { - private final ColumnConfig[] columnConfigs; private final ConnectionSource connectionSource; private final String tableName; + private final ColumnConfig[] columnConfigs; + private final ColumnMapping[] columnMappings; protected FactoryData(final int bufferSize, final ConnectionSource connectionSource, final String tableName, - final ColumnConfig[] columnConfigs) { + final ColumnConfig[] columnConfigs, final ColumnMapping[] columnMappings) { super(bufferSize); this.connectionSource = connectionSource; this.tableName = tableName; this.columnConfigs = columnConfigs; + this.columnMappings = columnMappings; } } @@ -211,50 +269,45 @@ public final class JdbcDatabaseManager extends AbstractDatabaseManager { private static final class JdbcDatabaseManagerFactory implements ManagerFactory<JdbcDatabaseManager, FactoryData> { @Override public JdbcDatabaseManager createManager(final String name, final FactoryData data) { - final StringBuilder columnPart = new StringBuilder(); - final StringBuilder valuePart = new StringBuilder(); - final List<Column> columns = new ArrayList<>(); - int i = 0; + final StringBuilder sb = new StringBuilder("INSERT INTO ").append(data.tableName).append(" ("); + // so this gets a little more complicated now that there are two ways to configure column mappings, but + // both mappings follow the same exact pattern for the prepared statement + for (final ColumnMapping mapping : data.columnMappings) { + sb.append(mapping.getName()).append(','); + } for (final ColumnConfig config : data.columnConfigs) { - if (i++ > 0) { - columnPart.append(','); - valuePart.append(','); + sb.append(config.getColumnName()).append(','); + } + // at least one of those arrays is guaranteed to be non-empty + sb.setCharAt(sb.length() - 1, ')'); + sb.append(" VALUES ("); + final List<ColumnMapping> columnMappings = new ArrayList<>(data.columnMappings.length); + for (final ColumnMapping mapping : data.columnMappings) { + if (Strings.isNotEmpty(mapping.getLiteralValue())) { + sb.append(mapping.getLiteralValue()); + } else { + sb.append('?'); + columnMappings.add(mapping); } - - columnPart.append(config.getColumnName()); - - if (config.getLiteralValue() != null) { - valuePart.append(config.getLiteralValue()); + sb.append(','); + } + final List<ColumnConfig> columnConfigs = new ArrayList<>(data.columnConfigs.length); + for (final ColumnConfig config : data.columnConfigs) { + if (Strings.isNotEmpty(config.getLiteralValue())) { + sb.append(config.getLiteralValue()); } else { - columns.add(new Column( - config.getLayout(), config.isEventTimestamp(), config.isUnicode(), config.isClob() - )); - valuePart.append('?'); + sb.append('?'); + columnConfigs.add(config); } + sb.append(','); } + // at least one of those arrays is guaranteed to be non-empty + sb.setCharAt(sb.length() - 1, ')'); + final String sqlStatement = sb.toString(); - final String sqlStatement = "INSERT INTO " + data.tableName + " (" + columnPart + ") VALUES (" + - valuePart + ')'; - - return new JdbcDatabaseManager(name, data.getBufferSize(), data.connectionSource, sqlStatement, columns); + return new JdbcDatabaseManager(name, data.getBufferSize(), data.connectionSource, sqlStatement, + columnConfigs, columnMappings); } } - /** - * Encapsulates information about a database column and how to persist data to it. - */ - private static final class Column { - private final PatternLayout layout; - private final boolean isEventTimestamp; - private final boolean isUnicode; - private final boolean isClob; - - private Column(final PatternLayout layout, final boolean isEventDate, final boolean isUnicode, - final boolean isClob) { - this.layout = layout; - this.isEventTimestamp = isEventDate; - this.isUnicode = isUnicode; - this.isClob = isClob; - } - } } http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/6a1339b8/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/db/jdbc/AbstractJdbcAppenderTest.java ---------------------------------------------------------------------- diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/db/jdbc/AbstractJdbcAppenderTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/db/jdbc/AbstractJdbcAppenderTest.java index 99c229d..e8855dc 100644 --- a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/db/jdbc/AbstractJdbcAppenderTest.java +++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/db/jdbc/AbstractJdbcAppenderTest.java @@ -27,6 +27,7 @@ import javax.sql.DataSource; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.ThreadContext; import org.apache.logging.log4j.core.Appender; import org.apache.logging.log4j.core.LoggerContext; import org.apache.logging.log4j.core.config.ConfigurationFactory; @@ -163,6 +164,7 @@ public abstract class AbstractJdbcAppenderTest { final long millis = System.currentTimeMillis(); + ThreadContext.put("some_int", "42"); final Logger logger = LogManager.getLogger(this.getClass().getName() + ".testFactoryMethodConfig"); logger.debug("Factory logged message 01."); logger.error("Error from factory 02.", exception); @@ -173,6 +175,8 @@ public abstract class AbstractJdbcAppenderTest { assertTrue("There should be at least one row.", resultSet.next()); long date = resultSet.getTimestamp("eventDate").getTime(); + long anotherDate = resultSet.getTimestamp("anotherDate").getTime(); + assertEquals(date, anotherDate); assertTrue("The date should be later than pre-logging (1).", date >= millis); assertTrue("The date should be earlier than now (1).", date <= System.currentTimeMillis()); assertEquals("The literal column is not correct (1).", "Some Other Literal Value", @@ -187,6 +191,8 @@ public abstract class AbstractJdbcAppenderTest { assertTrue("There should be two rows.", resultSet.next()); date = resultSet.getTimestamp("eventDate").getTime(); + anotherDate = resultSet.getTimestamp("anotherDate").getTime(); + assertEquals(date, anotherDate); assertTrue("The date should be later than pre-logging (2).", date >= millis); assertTrue("The date should be earlier than now (2).", date <= System.currentTimeMillis()); assertEquals("The literal column is not correct (2).", "Some Other Literal Value", http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/6a1339b8/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/db/jdbc/JdbcH2AppenderTest.java ---------------------------------------------------------------------- diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/db/jdbc/JdbcH2AppenderTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/db/jdbc/JdbcH2AppenderTest.java index 5f906a0..2d94acd 100644 --- a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/db/jdbc/JdbcH2AppenderTest.java +++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/db/jdbc/JdbcH2AppenderTest.java @@ -49,7 +49,7 @@ public class JdbcH2AppenderTest extends AbstractJdbcAppenderTest { protected String toCreateTableSqlString(final String tableName) { return "CREATE TABLE " + tableName + " ( " + "id INTEGER IDENTITY, eventDate DATETIME, literalColumn VARCHAR(255), level NVARCHAR(10), " + - "logger NVARCHAR(255), message VARCHAR(1024), exception NCLOB" + + "logger NVARCHAR(255), message VARCHAR(1024), exception NCLOB, anotherDate TIMESTAMP" + " )"; } } http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/6a1339b8/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/db/jdbc/JdbcHyperSqlAppenderTest.java ---------------------------------------------------------------------- diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/db/jdbc/JdbcHyperSqlAppenderTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/db/jdbc/JdbcHyperSqlAppenderTest.java index 11ca769..9a066c8 100644 --- a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/db/jdbc/JdbcHyperSqlAppenderTest.java +++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/db/jdbc/JdbcHyperSqlAppenderTest.java @@ -50,7 +50,7 @@ public class JdbcHyperSqlAppenderTest extends AbstractJdbcAppenderTest { protected String toCreateTableSqlString(final String tableName) { return "CREATE TABLE " + tableName + " ( " + "id INTEGER IDENTITY, eventDate DATETIME, literalColumn VARCHAR(255), level VARCHAR(10), " + - "logger VARCHAR(255), message VARCHAR(1024), exception CLOB" + + "logger VARCHAR(255), message VARCHAR(1024), exception CLOB, anotherDate TIMESTAMP" + " )"; } } http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/6a1339b8/log4j-core/src/test/resources/org/apache/logging/log4j/core/appender/db/jdbc/log4j2-h2-factory-method.xml ---------------------------------------------------------------------- diff --git a/log4j-core/src/test/resources/org/apache/logging/log4j/core/appender/db/jdbc/log4j2-h2-factory-method.xml b/log4j-core/src/test/resources/org/apache/logging/log4j/core/appender/db/jdbc/log4j2-h2-factory-method.xml index ac45e75..6b72c83 100644 --- a/log4j-core/src/test/resources/org/apache/logging/log4j/core/appender/db/jdbc/log4j2-h2-factory-method.xml +++ b/log4j-core/src/test/resources/org/apache/logging/log4j/core/appender/db/jdbc/log4j2-h2-factory-method.xml @@ -30,6 +30,7 @@ <Column name="logger" pattern="%logger" /> <Column name="message" pattern="%message" isUnicode="false" /> <Column name="exception" pattern="%ex{full}" isClob="true" /> + <ColumnMapping name="anotherDate" type="java.sql.Timestamp"/> </Jdbc> </Appenders> http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/6a1339b8/log4j-core/src/test/resources/org/apache/logging/log4j/core/appender/db/jdbc/log4j2-hsqldb-factory-method.xml ---------------------------------------------------------------------- diff --git a/log4j-core/src/test/resources/org/apache/logging/log4j/core/appender/db/jdbc/log4j2-hsqldb-factory-method.xml b/log4j-core/src/test/resources/org/apache/logging/log4j/core/appender/db/jdbc/log4j2-hsqldb-factory-method.xml index eff391c..5faced5 100644 --- a/log4j-core/src/test/resources/org/apache/logging/log4j/core/appender/db/jdbc/log4j2-hsqldb-factory-method.xml +++ b/log4j-core/src/test/resources/org/apache/logging/log4j/core/appender/db/jdbc/log4j2-hsqldb-factory-method.xml @@ -30,6 +30,7 @@ <Column name="logger" pattern="%logger" /> <Column name="message" pattern="%message" isUnicode="false" /> <Column name="exception" pattern="%ex{full}" isClob="true" /> + <ColumnMapping name="anotherDate" type="java.sql.Timestamp"/> </Jdbc> </Appenders> http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/6a1339b8/src/changes/changes.xml ---------------------------------------------------------------------- diff --git a/src/changes/changes.xml b/src/changes/changes.xml index bfefc24..f553cc4 100644 --- a/src/changes/changes.xml +++ b/src/changes/changes.xml @@ -228,6 +228,9 @@ <action issue="LOG4J2-1302" dev="rpopma" type="update"> The log4j-slf4j-impl module now declares a runtime dependency on log4j-core. While not technically required, this makes the log4j-slf4j-impl module behave similarly to slf4j-log4j12, and facilitates migration to Log4j 2. </action> + <action issue="LOG4J2-424" dev="mattsicker" type="add"> + Add non-string data type support to JdbcAppender via new ColumnMapping plugin. + </action> <action issue="LOG4J2-1771" dev="mattsicker" type="add"> Add a Builder to ColumnConfig and deprecate ColumnConfig.createColumnConfig(). </action> @@ -238,7 +241,7 @@ Use MethodHandle in ContextDataFactory cached constructor. </action> <action issue="LOG4J2-1730" dev="mattsicker" type="add"> - Add Apache Cassandra appender. + Add Apache Cassandra appender and ColumnMapping plugin. </action> <action issue="LOG4J2-1759" dev="mattsicker" type="add"> Add TypeConverter for java.util.UUID. http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/6a1339b8/src/site/xdoc/manual/appenders.xml ---------------------------------------------------------------------- diff --git a/src/site/xdoc/manual/appenders.xml b/src/site/xdoc/manual/appenders.xml index d1f6223..233bf10 100644 --- a/src/site/xdoc/manual/appenders.xml +++ b/src/site/xdoc/manual/appenders.xml @@ -955,7 +955,12 @@ CREATE TABLE logs ( <p>The JDBCAppender writes log events to a relational database table using standard JDBC. It can be configured to obtain JDBC connections using a JNDI <code>DataSource</code> or a custom factory method. Whichever approach you take, it <strong><em>must</em></strong> be backed by a connection pool. Otherwise, logging - performance will suffer greatly.</p> + performance will suffer greatly. If batch statements are supported by the configured JDBC driver and a + <code>bufferSize</code> is configured to be a positive number, then log events will be batched. Note that as + of Log4j 2.8, there are two ways to configure log event to column mappings: the original <code>ColumnConfig</code> + style that only allows strings and timestamps, and the new <code>ColumnMapping</code> plugin that uses Log4j's + built-in type conversion to allow for more data types (this is the same plugin as in the + <a href="#CassandraAppender">Cassandra Appender</a>).</p> <table> <caption align="top">JDBCAppender Parameters</caption> <tr> @@ -1001,9 +1006,31 @@ CREATE TABLE logs ( <tr> <td>columnConfigs</td> <td>ColumnConfig[]</td> - <td><em>Required.</em> Information about the columns that log event data should be inserted into and how + <td><em>Required (and/or columnMappings).</em> Information about the columns that log event data should be inserted into and how to insert that data. This is represented with multiple <code><Column></code> elements.</td> </tr> + <tr> + <td>columnMappings</td> + <td>ColumnMapping[]</td> + <td><em>Required (and/or columnConfigs).</em> A list of column mapping configurations. Each column must + specify a column name. Each column can have a conversion type specified by its fully qualified class + name. By default, the conversion type is <code>String</code>. If the configured type is + assignment-compatible with + <a class="javadoc" href="../log4j-api/apidocs/org/apache/logging/log4j/util/ReadOnlyStringMap.html">ReadOnlyStringMap</a> + / + <a class="javadoc" href="../log4j-api/apidocs/org/apache/logging/log4j/spi/ThreadContextMap.html">ThreadContextMap</a> + or + <a class="javadoc" href="../log4j-api/apidocs/org/apache/logging/log4j/spi/ThreadContextStack.html">ThreadContextStack</a>, + then that column will be populated with the MDC or NDC respectively (this is database-specific how they + handle inserting a <code>Map</code> or <code>List</code> value). If the configured type is + assignment-compatible with <code>java.util.Date</code>, then the log timestamp will be converted to + that configured date type. If the configured type is assignment-compatible with <code>java.sql.Clob</code> + or <code>java.sql.NClob</code>, then the formatted event will be set as a Clob or NClob respectively + (similar to the traditional ColumnConfig plugin). If a <code>literal</code> attribute is given, then its + value will be used as is in the <code>INSERT</code> query without any escaping. Otherwise, the layout or + pattern specified will be converted into the configured type and stored in that column. + </td> + </tr> </table> <p>When configuring the JDBCAppender, you must specify a <code>ConnectionSource</code> implementation from which the Appender gets JDBC connections. You must use exactly one of the <code><DataSource></code>
