Index: checkstyle-import-control.xml =================================================================== --- checkstyle-import-control.xml (revision 0) +++ checkstyle-import-control.xml (working copy) @@ -0,0 +1,72 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Index: checkstyle.xml =================================================================== --- checkstyle.xml (revision 1479389) +++ checkstyle.xml (working copy) @@ -1,4 +1,4 @@ - + @@ -118,6 +118,10 @@ + + + + Index: core/pom.xml =================================================================== --- core/pom.xml (revision 1479389) +++ core/pom.xml (working copy) @@ -23,7 +23,6 @@ 2.0-beta6-SNAPSHOT ../ - org.apache.logging.log4j log4j-core jar Apache Log4J Core @@ -96,6 +95,21 @@ test + org.easymock + easymock + test + + + org.hsqldb + hsqldb + test + + + org.hibernate + hibernate-entitymanager + test + + org.mockejb mockejb test @@ -120,6 +134,24 @@ mail true + + org.hibernate.javax.persistence + hibernate-jpa-2.0-api + compile + true + + + org.mongodb + mongo-java-driver + compile + true + + + org.lightcouch + lightcouch + compile + true + Index: core/src/main/java/org/apache/logging/log4j/core/appender/ConsoleAppender.java =================================================================== --- core/src/main/java/org/apache/logging/log4j/core/appender/ConsoleAppender.java (revision 1479389) +++ core/src/main/java/org/apache/logging/log4j/core/appender/ConsoleAppender.java (working copy) @@ -87,7 +87,7 @@ } if (layout == null) { @SuppressWarnings({"unchecked", "UnnecessaryLocalVariable"}) - Layout l = (Layout)PatternLayout.createLayout(null, null, null, null); + Layout l = (Layout)PatternLayout.createLayout(null, null, null, null, null); layout = l; } final boolean isFollow = follow == null ? false : Boolean.valueOf(follow); Index: core/src/main/java/org/apache/logging/log4j/core/appender/FastFileAppender.java =================================================================== --- core/src/main/java/org/apache/logging/log4j/core/appender/FastFileAppender.java (revision 1479389) +++ core/src/main/java/org/apache/logging/log4j/core/appender/FastFileAppender.java (working copy) @@ -155,7 +155,7 @@ } if (layout == null) { @SuppressWarnings({"unchecked", "UnnecessaryLocalVariable"}) - Layout l = (Layout)PatternLayout.createLayout(null, null, null, null); + Layout l = (Layout)PatternLayout.createLayout(null, null, null, null, null); layout = l; } return new FastFileAppender(name, layout, filter, manager, fileName, Index: core/src/main/java/org/apache/logging/log4j/core/appender/FastRollingFileAppender.java =================================================================== --- core/src/main/java/org/apache/logging/log4j/core/appender/FastRollingFileAppender.java (revision 1479389) +++ core/src/main/java/org/apache/logging/log4j/core/appender/FastRollingFileAppender.java (working copy) @@ -199,7 +199,7 @@ if (layout == null) { @SuppressWarnings({"unchecked", "UnnecessaryLocalVariable"}) - Layout l = (Layout)PatternLayout.createLayout(null, null, null, null); + Layout l = (Layout)PatternLayout.createLayout(null, null, null, null, null); layout = l; } Index: core/src/main/java/org/apache/logging/log4j/core/appender/FileAppender.java =================================================================== --- core/src/main/java/org/apache/logging/log4j/core/appender/FileAppender.java (revision 1479389) +++ core/src/main/java/org/apache/logging/log4j/core/appender/FileAppender.java (working copy) @@ -136,7 +136,7 @@ } if (layout == null) { @SuppressWarnings({"unchecked", "UnnecessaryLocalVariable"}) - Layout l = (Layout)PatternLayout.createLayout(null, null, null, null); + Layout l = (Layout)PatternLayout.createLayout(null, null, null, null, null); layout = l; } Index: core/src/main/java/org/apache/logging/log4j/core/appender/RollingFileAppender.java =================================================================== --- core/src/main/java/org/apache/logging/log4j/core/appender/RollingFileAppender.java (revision 1479389) +++ core/src/main/java/org/apache/logging/log4j/core/appender/RollingFileAppender.java (working copy) @@ -174,7 +174,7 @@ if (layout == null) { @SuppressWarnings({"unchecked", "UnnecessaryLocalVariable"}) - Layout l = (Layout)PatternLayout.createLayout(null, null, null, null); + Layout l = (Layout)PatternLayout.createLayout(null, null, null, null, null); layout = l; } Index: core/src/main/java/org/apache/logging/log4j/core/appender/db/AbstractDatabaseAppender.java =================================================================== --- core/src/main/java/org/apache/logging/log4j/core/appender/db/AbstractDatabaseAppender.java (revision 0) +++ core/src/main/java/org/apache/logging/log4j/core/appender/db/AbstractDatabaseAppender.java (working copy) @@ -0,0 +1,131 @@ +/* + * 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; + +import org.apache.logging.log4j.core.Filter; +import org.apache.logging.log4j.core.Layout; +import org.apache.logging.log4j.core.LogEvent; +import org.apache.logging.log4j.core.appender.AbstractAppender; +import org.apache.logging.log4j.core.appender.AppenderRuntimeException; + +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReadWriteLock; +import java.util.concurrent.locks.ReentrantReadWriteLock; + +/** + * An abstract Appender for writing events to a database of some type, be it relational or NoSQL. All database appenders + * should inherit from this base appender. Three implementations are currently provided: + * {@link org.apache.logging.log4j.core.appender.db.jdbc JDBC}, + * {@link org.apache.logging.log4j.core.appender.db.jpa JPA}, and + * {@link org.apache.logging.log4j.core.appender.db.nosql NoSQL}. + * + * @param Specifies which type of {@link AbstractDatabaseManager} this Appender requires. + */ +public abstract class AbstractDatabaseAppender extends AbstractAppender { + private final ReadWriteLock lock = new ReentrantReadWriteLock(); + private final Lock readLock = lock.readLock(); + private final Lock writeLock = lock.writeLock(); + + private T manager; + + /** + * Instantiates the base appender. + * + * @param name The appender name. + * @param filter The filter, if any, to use. + * @param handleException Whether logging exceptions should be reported to the application. + * @param manager The matching {@link AbstractDatabaseManager} implementation. + */ + protected AbstractDatabaseAppender(String name, Filter filter, boolean handleException, T manager) { + super(name, filter, null, handleException); + this.manager = manager; + } + + /** + * This always returns {@code null}, as database appenders do not use a single layout. The JPA and NoSQL appenders + * do not use a layout at all. The JDBC appender has a layout-per-column pattern. + * + * @return {@code null}. + */ + @Override + public final Layout getLayout() { + return null; + } + + @Override + public final void start() { + if (this.getManager() == null) { + LOGGER.error("No AbstractDatabaseManager set for the appender named [" + this.getName() + "]."); + } + super.start(); + if (this.getManager() != null) { + this.getManager().connect(); + } + } + + @Override + public final void stop() { + super.stop(); + if (this.getManager() != null) { + this.getManager().release(); + } + } + + /** + * Returns the underlying manager in use within this appender. + * + * @return the manager. + */ + public final T getManager() { + return this.manager; + } + + /** + * Replaces the underlying manager in use within this appender. This can be useful for manually changing the + * way log events are written to the database without losing buffered or in-progress events. The existing manager + * is released only after the new manager has been installed. This method is thread-safe. + * + * @param manager The new manager to install. + */ + protected final void replaceManager(final T manager) { + this.writeLock.lock(); + try { + final T old = this.getManager(); + if (!manager.isConnected()) { + manager.connect(); + } + this.manager = manager; + old.release(); + } finally { + this.writeLock.unlock(); + } + } + + @Override + public final void append(LogEvent event) { + this.readLock.lock(); + try { + this.getManager().write(event); + } catch (final AppenderRuntimeException e) { + this.error("Unable to write to database [" + this.getManager().getName() + "] for appender [" + + this.getName() + "].", e); + throw e; + } finally { + this.readLock.unlock(); + } + } +} Index: core/src/main/java/org/apache/logging/log4j/core/appender/db/AbstractDatabaseManager.java =================================================================== --- core/src/main/java/org/apache/logging/log4j/core/appender/db/AbstractDatabaseManager.java (revision 0) +++ core/src/main/java/org/apache/logging/log4j/core/appender/db/AbstractDatabaseManager.java (working copy) @@ -0,0 +1,184 @@ +/* + * 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; + +import org.apache.logging.log4j.core.LogEvent; +import org.apache.logging.log4j.core.appender.AbstractManager; +import org.apache.logging.log4j.core.appender.ManagerFactory; + +import java.util.ArrayList; + +/** + * Manager that allows database appenders to have their configuration reloaded without losing events. + */ +public abstract class AbstractDatabaseManager extends AbstractManager { + private final int bufferSize; + private final ArrayList buffer; + private boolean connected = false; + + /** + * 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 bufferSize The size of the log event buffer. + */ + protected AbstractDatabaseManager(String name, int bufferSize) { + super(name); + this.bufferSize = bufferSize; + this.buffer = new ArrayList(bufferSize + 1); + } + + /** + * Implementations should implement this method to perform any proprietary connection operations. This method will + * never be called twice on the same instance. It is safe to throw any exceptions from this method. + */ + protected abstract void connectInternal(); + + /** + * This method is called within the appender when the appender is started. If it has not already been called, it + * calls {@link #connectInternal()} and catches any exceptions it might throw. + */ + public final synchronized void connect() { + if (!this.isConnected()) { + try { + this.connectInternal(); + this.connected = true; + } catch (Exception e) { + LOGGER.error("Could not connect database logging manager.", e); + } + } + } + + /** + * Implementations should implement this method to perform any proprietary disconnection / shutdown operations. This + * method will never be called twice on the same instance, and it will only be called after + * {@link #connectInternal()}. It is safe to throw any exceptions from this method. + */ + protected abstract void disconnectInternal(); + + /** + * This method is called from the {@link #release()} method when the appender is stopped or the appender's manager + * is replaced. If it has not already been called, it calls {@link #disconnectInternal()} and catches any exceptions + * it might throw. + */ + public final synchronized void disconnect() { + this.flush(); + if (this.isConnected()) { + try { + this.disconnectInternal(); + } catch (Exception e) { + LOGGER.warn("Error while disconnecting database logging manager.", e); + } finally { + this.connected = false; + } + } + } + + /** + * Indicates whether the manager is currently connected {@link #connect()} has been called and {@link #disconnect()} + * has not been called). + * + * @return {@code true} if the manager is connected. + */ + public final boolean isConnected() { + return this.connected; + } + + /** + * Performs the actual writing of the event in an implementation-specific way. This method is called immediately + * from {@link #write(LogEvent)} if buffering is off, or from {@link #flush()} if the buffer has reached its limit. + * + * @param event The event to write to the database. + */ + protected abstract void writeInternal(LogEvent event); + + /** + * This method is called automatically when the buffer size reaches its maximum or at the beginning of a call to + * {@link #disconnect()}. It can also be called manually to flush events to the database. + */ + public final synchronized void flush() { + if (this.isConnected() && this.buffer.size() > 0) { + for (LogEvent event : this.buffer) { + this.writeInternal(event); + } + this.buffer.clear(); + } + } + + /** + * This method manages buffering and writing of events. + * + * @param event The event to write to the database. + */ + public final synchronized void write(LogEvent event) { + if (this.bufferSize > 0) { + this.buffer.add(event); + if (this.buffer.size() >= this.bufferSize || event.isEndOfBatch()) { + this.flush(); + } + } else { + this.writeInternal(event); + } + } + + @Override + public final void releaseSub() { + this.disconnect(); + } + + @Override + public final String toString() { + return this.getName(); + } + + /** + * Implementations should define their own getManager method and call this method from that to create or get + * existing managers. + * + * @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 data The concrete instance of {@link AbstractFactoryData} appropriate for the given manager. + * @param factory A factory instance for creating the appropriate manager. + * @param The concrete manager type. + * @param The concrete {@link AbstractFactoryData} type. + * @return a new or existing manager of the specified type and name. + */ + protected static M getManager( + final String name, final T data, + final ManagerFactory factory + ) { + return AbstractManager.getManager(name, factory, data); + } + + /** + * Implementations should extend this class for passing data between the getManager method and the manager factory + * class. + */ + protected abstract static class AbstractFactoryData { + public final int bufferSize; + + /** + * Constructs the base factory data. + * + * @param bufferSize The size of the buffer. + */ + protected AbstractFactoryData(int bufferSize) { + this.bufferSize = bufferSize; + } + } +} Index: core/src/main/java/org/apache/logging/log4j/core/appender/db/jdbc/ColumnConfig.java =================================================================== --- core/src/main/java/org/apache/logging/log4j/core/appender/db/jdbc/ColumnConfig.java (revision 0) +++ core/src/main/java/org/apache/logging/log4j/core/appender/db/jdbc/ColumnConfig.java (working copy) @@ -0,0 +1,116 @@ +/* + * 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 org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.core.config.Configuration; +import org.apache.logging.log4j.core.config.plugins.Plugin; +import org.apache.logging.log4j.core.config.plugins.PluginAttr; +import org.apache.logging.log4j.core.config.plugins.PluginConfiguration; +import org.apache.logging.log4j.core.config.plugins.PluginFactory; +import org.apache.logging.log4j.core.layout.PatternLayout; +import org.apache.logging.log4j.status.StatusLogger; + +/** + * A configuration element used to configure which event properties are logged to which columns in the database table. + */ +@Plugin(name = "Column", category = "Core", printObject = true) +public final class ColumnConfig { + private static final Logger LOGGER = StatusLogger.getLogger(); + + private final String columnName; + private final PatternLayout layout; + private final String literalValue; + private final boolean eventTimestamp; + + private ColumnConfig(String columnName, PatternLayout layout, String literalValue, boolean eventDate) { + this.columnName = columnName; + this.layout = layout; + this.literalValue = literalValue; + this.eventTimestamp = eventDate; + } + + public String getColumnName() { + return this.columnName; + } + + public PatternLayout getLayout() { + return this.layout; + } + + public String getLiteralValue() { + return this.literalValue; + } + + public boolean isEventTimestamp() { + return this.eventTimestamp; + } + + @Override + public String toString() { + return "{ name=" + this.columnName + ", layout=" + this.layout + ", literal=" + this.literalValue + + ", timestamp=" + this.eventTimestamp + " }"; + } + + /** + * Factory method for creating a column config within the plugin manager. + * + * @param config The configuration object + * @param name The name of the database column as it exists within the database table. + * @param pattern The {@link PatternLayout} pattern to insert in this column. Mutually exclusive + * with {@code literalValue!=null} and {@code eventTimestamp=true} + * @param literalValue The literal value to insert into the column as-is without any quoting or escaping. Mutually + * exclusive with {@code pattern!=null} and {@code eventTimestamp=true}. + * @param eventTimestamp If {@code "true"}, indicates that this column is a date-time column in which the event + * timestamp should be inserted. Mutually exclusive with {@code pattern!=null} and + * {@code literalValue!=null}. + * @return the created column config. + */ + @PluginFactory + public static ColumnConfig createColumnConfig(@PluginConfiguration final Configuration config, + @PluginAttr("name") final String name, + @PluginAttr("pattern") final String pattern, + @PluginAttr("literal") final String literalValue, + @PluginAttr("isEventTimestamp") final String eventTimestamp) { + if (name == null || name.length() == 0) { + LOGGER.error("The column config is not valid because it does not contain a column name."); + return null; + } + + boolean isEventTimestamp = eventTimestamp != null && Boolean.parseBoolean(eventTimestamp); + boolean isLiteralValue = literalValue != null && literalValue.length() > 0; + boolean isPattern = pattern != null && pattern.length() > 0; + + if ((isEventTimestamp && isLiteralValue) || (isEventTimestamp && isPattern) || (isLiteralValue && isPattern)) { + LOGGER.error("The pattern, literal, and isEventTimestamp attributes are mutually exclusive."); + return null; + } + + if (isEventTimestamp) { + return new ColumnConfig(name, null, null, true); + } + if (isLiteralValue) { + return new ColumnConfig(name, null, literalValue, false); + } + if (isPattern) { + return new ColumnConfig(name, PatternLayout.createLayout(pattern, config, null, null, "true"), null, false); + } + + LOGGER.error("To configure a column you must specify a pattern or literal or set isEventDate to true."); + return null; + } +} Index: core/src/main/java/org/apache/logging/log4j/core/appender/db/jdbc/ConnectionSource.java =================================================================== --- core/src/main/java/org/apache/logging/log4j/core/appender/db/jdbc/ConnectionSource.java (revision 0) +++ core/src/main/java/org/apache/logging/log4j/core/appender/db/jdbc/ConnectionSource.java (working copy) @@ -0,0 +1,37 @@ +/* + * 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 java.sql.Connection; +import java.sql.SQLException; + +/** + * Configuration element for {@link JDBCAppender}. If you want to use the {@link JDBCAppender} but none of the provided + * connection sources meet your needs, you can simply create your own connection source. +*/ +public interface ConnectionSource { + /** + * This should return a new connection every time it is called. + * + * @return the SQL connection object. + * @throws SQLException if a database error occurs. + */ + Connection getConnection() throws SQLException; + + @Override + String toString(); +} Index: core/src/main/java/org/apache/logging/log4j/core/appender/db/jdbc/DataSourceConnectionSource.java =================================================================== --- core/src/main/java/org/apache/logging/log4j/core/appender/db/jdbc/DataSourceConnectionSource.java (revision 0) +++ core/src/main/java/org/apache/logging/log4j/core/appender/db/jdbc/DataSourceConnectionSource.java (working copy) @@ -0,0 +1,84 @@ +/* + * 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 org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.core.config.plugins.Plugin; +import org.apache.logging.log4j.core.config.plugins.PluginAttr; +import org.apache.logging.log4j.core.config.plugins.PluginFactory; +import org.apache.logging.log4j.status.StatusLogger; + +import javax.naming.InitialContext; +import javax.naming.NamingException; +import javax.sql.DataSource; +import java.sql.Connection; +import java.sql.SQLException; + +/** + * A {@link JDBCAppender} connection source that uses a {@link DataSource} to connect to the database. + */ +@Plugin(name = "DataSource", category = "Core", elementType = "connectionSource", printObject = true) +public final class DataSourceConnectionSource implements ConnectionSource { + private static final Logger LOGGER = StatusLogger.getLogger(); + + private final DataSource dataSource; + private final String description; + + private DataSourceConnectionSource(String dataSourceName, DataSource dataSource) { + this.dataSource = dataSource; + this.description = "dataSource{ name=" + dataSourceName + ", value=" + dataSource + " }"; + } + + @Override + public Connection getConnection() throws SQLException { + return this.dataSource.getConnection(); + } + + @Override + public String toString() { + return this.description; + } + + /** + * Factory method for creating a connection source within the plugin manager. + * + * @param jndiName The full JNDI path where the data source is bound. Should start with java:/comp/env or + * environment-equivalent. + * @return the created connection source. + */ + @PluginFactory + public static DataSourceConnectionSource createConnectionSource(@PluginAttr("jndiName") final String jndiName) { + if (jndiName == null || jndiName.length() == 0) { + LOGGER.error("No JNDI name provided."); + return null; + } + + try { + InitialContext context = new InitialContext(); + DataSource dataSource = (DataSource) context.lookup(jndiName); + if (dataSource == null) { + LOGGER.error("No data source found with JNDI name [" + jndiName + "]."); + return null; + } + + return new DataSourceConnectionSource(jndiName, dataSource); + } catch (NamingException e) { + LOGGER.error(e.getMessage(), e); + return null; + } + } +} Index: core/src/main/java/org/apache/logging/log4j/core/appender/db/jdbc/DriverManagerConnectionSource.java =================================================================== --- core/src/main/java/org/apache/logging/log4j/core/appender/db/jdbc/DriverManagerConnectionSource.java (revision 0) +++ core/src/main/java/org/apache/logging/log4j/core/appender/db/jdbc/DriverManagerConnectionSource.java (working copy) @@ -0,0 +1,103 @@ +/* + * 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 org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.core.config.plugins.Plugin; +import org.apache.logging.log4j.core.config.plugins.PluginAttr; +import org.apache.logging.log4j.core.config.plugins.PluginFactory; +import org.apache.logging.log4j.core.helpers.NameUtil; +import org.apache.logging.log4j.status.StatusLogger; + +import java.sql.Connection; +import java.sql.Driver; +import java.sql.DriverManager; +import java.sql.SQLException; + +/** +* A {@link JDBCAppender} connection source that uses a standard JDBC URL, username, and password to connect to the + * database. +*/ +@Plugin(name = "DriverManager", category = "Core", elementType = "connectionSource", printObject = true) +public final class DriverManagerConnectionSource implements ConnectionSource { + private static final Logger LOGGER = StatusLogger.getLogger(); + + private final String databaseUrl; + private final String databaseUsername; + private final String databasePassword; + private final String description; + + private DriverManagerConnectionSource(String databaseUrl, String databaseUsername, String databasePassword) { + this.databaseUrl = databaseUrl; + this.databaseUsername = databaseUsername; + this.databasePassword = databasePassword; + this.description = "driverManager{ url=" + this.databaseUrl + ", username=" + this.databaseUsername + + ", passwordHash=" + NameUtil.md5(this.databasePassword + this.getClass().getName()) + " }"; + } + + @Override + public Connection getConnection() throws SQLException { + if (this.databaseUsername == null) { + return DriverManager.getConnection(this.databaseUrl); + } + return DriverManager.getConnection(this.databaseUrl, this.databaseUsername, this.databasePassword); + } + + @Override + public String toString() { + return this.description; + } + + /** + * Factory method for creating a connection source within the plugin manager. + * + * @param url The JDBC URL to use to connect to the logging database. A driver that can accept this URL must be on + * the classpath. + * @param username The username with which to log in to the database, if applicable. + * @param password The password with which to log in to the database, if applicable. + * @return the created connection source. + */ + @PluginFactory + public static DriverManagerConnectionSource createConnectionSource(@PluginAttr("jdbcUrl") final String url, + @PluginAttr("username") String username, + @PluginAttr("password") String password) { + if (url == null || url.length() == 0) { + LOGGER.error("No JDBC URL specified for the database.", url); + return null; + } + + Driver driver; + try { + driver = DriverManager.getDriver(url); + } catch (SQLException e) { + LOGGER.error("No matching driver found for database URL [" + url + "].", e); + return null; + } + + if (driver == null) { + LOGGER.error("No matching driver found for database URL [" + url + "]."); + return null; + } + + if (username == null || username.trim().length() == 0) { + username = null; + password = null; + } + + return new DriverManagerConnectionSource(url, username, password); + } +} Index: core/src/main/java/org/apache/logging/log4j/core/appender/db/jdbc/FactoryMethodConnectionSource.java =================================================================== --- core/src/main/java/org/apache/logging/log4j/core/appender/db/jdbc/FactoryMethodConnectionSource.java (revision 0) +++ core/src/main/java/org/apache/logging/log4j/core/appender/db/jdbc/FactoryMethodConnectionSource.java (working copy) @@ -0,0 +1,156 @@ +/* + * 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 org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.core.config.plugins.Plugin; +import org.apache.logging.log4j.core.config.plugins.PluginAttr; +import org.apache.logging.log4j.core.config.plugins.PluginFactory; +import org.apache.logging.log4j.status.StatusLogger; + +import javax.sql.DataSource; +import java.io.PrintWriter; +import java.lang.reflect.Method; +import java.sql.Connection; +import java.sql.SQLException; + +/** + * A {@link JDBCAppender} connection source that uses a public static factory method to obtain a {@link Connection} or + * {@link DataSource}. + */ +@Plugin(name = "ConnectionFactory", category = "Core", elementType = "connectionSource", printObject = true) +public final class FactoryMethodConnectionSource implements ConnectionSource { + private static final Logger LOGGER = StatusLogger.getLogger(); + + private final DataSource dataSource; + private final String description; + + private FactoryMethodConnectionSource(DataSource dataSource, String className, String methodName, + String returnType) { + this.dataSource = dataSource; + this.description = "factory{ public static " + returnType + " " + className + "." + methodName + "() }"; + } + + @Override + public Connection getConnection() throws SQLException { + return this.dataSource.getConnection(); + } + + @Override + public String toString() { + return this.description; + } + + /** + * Factory method for creating a connection source within the plugin manager. + * + * @param className The name of a public class that contains a static method capable of returning either + * a {@link DataSource} or a {@link Connection}. + * @param methodName The name of the public static method on the aforementioned class that returns the data source + * or connection. If this method returns a {@link Connection}, it should return a new connection + * every call. + * @return the created connection source. + */ + @PluginFactory + public static FactoryMethodConnectionSource createConnectionSource(@PluginAttr("class") final String className, + @PluginAttr("method") final String methodName) { + if (className == null || className.length() == 0 || methodName == null || methodName.length() == 0) { + LOGGER.error("No class name or method name specified for the connection factory method."); + return null; + } + + final Method method; + try { + Class factoryClass = Class.forName(className); + method = factoryClass.getMethod(methodName); + } catch (Exception e) { + LOGGER.error(e.toString(), e); + return null; + } + + Class returnType = method.getReturnType(); + String returnTypeString = returnType.getName(); + DataSource dataSource; + if (returnType == DataSource.class) { + try { + dataSource = (DataSource) method.invoke(null); + returnTypeString += "[" + dataSource + "]"; + } catch (Exception e) { + LOGGER.error(e.toString(), e); + return null; + } + } else if (returnType == Connection.class) { + dataSource = new DataSource() { + @Override + public Connection getConnection() throws SQLException { + try { + return (Connection) method.invoke(null); + } catch (Exception e) { + throw new SQLException("Failed to obtain connection from factory method.", e); + } + } + + @Override + public Connection getConnection(String username, String password) throws SQLException { + throw new UnsupportedOperationException(); + } + + @Override + public PrintWriter getLogWriter() throws SQLException { + throw new UnsupportedOperationException(); + } + + @Override + public void setLogWriter(PrintWriter out) throws SQLException { + throw new UnsupportedOperationException(); + } + + // method must be present to compile on Java 7, @Override must be absent to compile on Java 6 + @SuppressWarnings("unused") + public java.util.logging.Logger getParentLogger() { + throw new UnsupportedOperationException(); + } + + @Override + public void setLoginTimeout(int seconds) throws SQLException { + throw new UnsupportedOperationException(); + } + + @Override + public int getLoginTimeout() throws SQLException { + throw new UnsupportedOperationException(); + } + + @Override + public T unwrap(Class iface) throws SQLException { + return null; + } + + @Override + public boolean isWrapperFor(Class iface) throws SQLException { + return false; + } + }; + } else { + LOGGER.error("Method [{}.{}()] returns unsupported type [{}].", className, methodName, + returnType.getName()); + return null; + } + + return new FactoryMethodConnectionSource(dataSource, className, methodName, returnTypeString); + } +} Index: core/src/main/java/org/apache/logging/log4j/core/appender/db/jdbc/JDBCAppender.java =================================================================== --- core/src/main/java/org/apache/logging/log4j/core/appender/db/jdbc/JDBCAppender.java (revision 0) +++ core/src/main/java/org/apache/logging/log4j/core/appender/db/jdbc/JDBCAppender.java (working copy) @@ -0,0 +1,107 @@ +/* + * 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 org.apache.logging.log4j.core.Filter; +import org.apache.logging.log4j.core.appender.db.AbstractDatabaseAppender; +import org.apache.logging.log4j.core.config.plugins.Plugin; +import org.apache.logging.log4j.core.config.plugins.PluginAttr; +import org.apache.logging.log4j.core.config.plugins.PluginElement; +import org.apache.logging.log4j.core.config.plugins.PluginFactory; + +/** + * 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. + * 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 ConnectionSource + */ +@Plugin(name = "Jdbc", category = "Core", elementType = "appender", printObject = true) +public final class JDBCAppender extends AbstractDatabaseAppender { + private final String description; + + private JDBCAppender(String name, Filter filter, boolean handleException, JDBCDatabaseManager manager) { + super(name, filter, handleException, manager); + + this.description = this.getName() + "{ manager=" + this.getManager() + " }"; + } + + @Override + public String toString() { + return this.description; + } + + /** + * Factory method for creating a JDBC appender within the plugin manager. + * + * @param name The name of the appender. + * @param suppressExceptions {@code "true"} (default) if logging exceptions should be hidden from the application, + * false otherwise. + * @param filter The filter, if any, to use. + * @param connectionSource The connections source from which database connections should be retrieved. + * @param bufferSize If an integer greater than 0, this causes the appender to buffer log events and flush whenever + * the buffer reaches this size. + * @param tableName The name of the database table to insert log events into. + * @param columnConfigs Information about the columns that log event data should be inserted into and how to insert + * that data. + * @return a new JDBC appender. + */ + @PluginFactory + public static JDBCAppender createAppender(@PluginAttr("name") final String name, + @PluginAttr("suppressExceptions") final String suppressExceptions, + @PluginElement("filter") final Filter filter, + @PluginElement("connectionSource") + final ConnectionSource connectionSource, + @PluginAttr("bufferSize") final String bufferSize, + @PluginAttr("tableName") final String tableName, + @PluginElement("columnConfigs") final ColumnConfig[] columnConfigs) { + int bufferSizeInt; + try { + bufferSizeInt = bufferSize == null || bufferSize.length() == 0 ? 0 : Integer.parseInt(bufferSize); + } catch (NumberFormatException e) { + LOGGER.warn("Buffer size [" + bufferSize + "] not an integer, using no buffer."); + bufferSizeInt = 0; + } + + boolean handleExceptions = suppressExceptions == null || !Boolean.parseBoolean(suppressExceptions); + + StringBuilder managerName = new StringBuilder("jdbcManager{ description=").append(name).append(", bufferSize=") + .append(bufferSizeInt).append(", connectionSource=").append(connectionSource.toString()) + .append(", tableName=").append(tableName).append(", columns=[ "); + + int i = 0; + for (ColumnConfig column : columnConfigs) { + if (i++ > 0) { + managerName.append(", "); + } + managerName.append(column.toString()); + } + + managerName.append(" ] }"); + + JDBCDatabaseManager manager = JDBCDatabaseManager.getJDBCDatabaseManager( + managerName.toString(), bufferSizeInt, connectionSource, tableName, columnConfigs + ); + if (manager == null) { + return null; + } + + return new JDBCAppender(name, filter, handleExceptions, manager); + } +} Index: core/src/main/java/org/apache/logging/log4j/core/appender/db/jdbc/JDBCDatabaseManager.java =================================================================== --- core/src/main/java/org/apache/logging/log4j/core/appender/db/jdbc/JDBCDatabaseManager.java (revision 0) +++ core/src/main/java/org/apache/logging/log4j/core/appender/db/jdbc/JDBCDatabaseManager.java (working copy) @@ -0,0 +1,179 @@ +/* + * 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 org.apache.logging.log4j.core.LogEvent; +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 java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.SQLException; +import java.sql.Timestamp; +import java.util.ArrayList; +import java.util.List; + +/** + * An {@link AbstractDatabaseManager} implementation for relational databases accessed via JDBC. + */ +public final class JDBCDatabaseManager extends AbstractDatabaseManager { + private static final JDBCDatabaseManagerFactory FACTORY = new JDBCDatabaseManagerFactory(); + + private final ConnectionSource connectionSource; + private final String sqlStatement; + private final List columns; + private Connection connection; + private PreparedStatement statement; + + private JDBCDatabaseManager(String name, int bufferSize, ConnectionSource connectionSource, String sqlStatement, + List columns) { + super(name, bufferSize); + + this.connectionSource = connectionSource; + this.sqlStatement = sqlStatement; + this.columns = columns; + } + + @Override + protected void connectInternal() { + try { + this.connection = this.connectionSource.getConnection(); + this.statement = this.connection.prepareStatement(this.sqlStatement); + } catch (SQLException e) { + LOGGER.error("Failed to connect to relational database using JDBC connection source [{}] in manager [{}].", + this.connectionSource, this.getName(), e); + } + } + + @Override + protected void disconnectInternal() { + try { + if (this.statement != null && !this.statement.isClosed()) { + this.statement.close(); + } + } catch (SQLException e) { + LOGGER.warn("Error while closing prepared statement in database manager [{}].", this.getName(), e); + } + + try { + if (this.connection != null && !this.connection.isClosed()) { + this.connection.close(); + } + } catch (SQLException e) { + LOGGER.warn("Error while disconnecting from relational database in manager [{}].", this.getName(), e); + } + } + + @Override + protected void writeInternal(LogEvent event) { + try { + if (!this.isConnected() || this.connection == null || this.connection.isClosed()) { + LOGGER.error("Cannot write logging event; manager [{}] not connected to the database.", this.getName()); + return; + } + + int i = 1; + for (Column column : this.columns) { + if (column.isEventTimestamp) { + this.statement.setTimestamp(i++, new Timestamp(event.getMillis())); + } else { + this.statement.setString(i++, column.layout.toSerializable(event)); + } + } + + if (this.statement.executeUpdate() == 0) { + LOGGER.warn("No records inserted in database table for log event in manager [{}].", this.getName()); + } + } catch (SQLException e) { + LOGGER.error("Failed to insert record for log event in manager [{}].", this.getName(), e); + } + } + + /** + * 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. + * @return a new or existing JDBC manager as applicable. + */ + public static JDBCDatabaseManager getJDBCDatabaseManager(String name, int bufferSize, + ConnectionSource connectionSource, + String tableName, ColumnConfig[] columnConfigs) { + + return AbstractDatabaseManager.getManager( + name, new FactoryData(bufferSize, connectionSource, tableName, columnConfigs), FACTORY + ); + } + + private static final class FactoryData extends AbstractDatabaseManager.AbstractFactoryData { + private final ConnectionSource connectionSource; + private final String tableName; + private final ColumnConfig[] columnConfigs; + + protected FactoryData(int bufferSize, ConnectionSource connectionSource, String tableName, + ColumnConfig[] columnConfigs) { + super(bufferSize); + + this.connectionSource = connectionSource; + this.tableName = tableName; + this.columnConfigs = columnConfigs; + } + } + + private static final class JDBCDatabaseManagerFactory implements ManagerFactory { + @Override + public JDBCDatabaseManager createManager(String name, FactoryData data) { + StringBuilder columnPart = new StringBuilder(); + StringBuilder valuePart = new StringBuilder(); + List columns = new ArrayList(); + int i = 0; + for (ColumnConfig config : data.columnConfigs) { + if (i++ > 0) { + columnPart.append(','); + valuePart.append(','); + } + + columnPart.append(config.getColumnName()); + + if (config.getLiteralValue() != null) { + valuePart.append(config.getLiteralValue()); + } else { + columns.add(new Column(config.getLayout(), config.isEventTimestamp())); + valuePart.append('?'); + } + } + + String sqlStatement = "INSERT INTO " + data.tableName + " (" + columnPart + ") VALUES (" + valuePart + ")"; + + return new JDBCDatabaseManager(name, data.bufferSize, data.connectionSource, sqlStatement, columns); + } + } + + private static final class Column { + private final PatternLayout layout; + private final boolean isEventTimestamp; + + private Column(PatternLayout layout, boolean isEventDate) { + this.layout = layout; + this.isEventTimestamp = isEventDate; + } + } +} Index: core/src/main/java/org/apache/logging/log4j/core/appender/db/jdbc/package-info.java =================================================================== --- core/src/main/java/org/apache/logging/log4j/core/appender/db/jdbc/package-info.java (revision 0) +++ core/src/main/java/org/apache/logging/log4j/core/appender/db/jdbc/package-info.java (working copy) @@ -0,0 +1,21 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +/** + * The JDBC Appender supports writing log events to a relational database using standard JDBC connections. You will need + * a JDBC driver on your classpath for the database you wish to log to. + */ +package org.apache.logging.log4j.core.appender.db.jdbc; Index: core/src/main/java/org/apache/logging/log4j/core/appender/db/jpa/JPAAppender.java =================================================================== --- core/src/main/java/org/apache/logging/log4j/core/appender/db/jpa/JPAAppender.java (revision 0) +++ core/src/main/java/org/apache/logging/log4j/core/appender/db/jpa/JPAAppender.java (working copy) @@ -0,0 +1,127 @@ +/* + * 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.jpa; + +import org.apache.logging.log4j.core.Filter; +import org.apache.logging.log4j.core.LogEvent; +import org.apache.logging.log4j.core.appender.db.AbstractDatabaseAppender; +import org.apache.logging.log4j.core.config.plugins.Plugin; +import org.apache.logging.log4j.core.config.plugins.PluginAttr; +import org.apache.logging.log4j.core.config.plugins.PluginElement; +import org.apache.logging.log4j.core.config.plugins.PluginFactory; + +import java.lang.reflect.Constructor; + +/** + * This Appender writes logging events to a relational database using the Java Persistence API. It requires a + * pre-configured JPA persistence unit and a concrete implementation of the abstract {@link LogEventWrapperEntity} + * class decorated with JPA annotations. + * + * @see LogEventWrapperEntity + */ +@Plugin(name = "Jpa", category = "Core", elementType = "appender", printObject = true) +public final class JPAAppender extends AbstractDatabaseAppender { + private final String description; + + private JPAAppender(String name, Filter filter, boolean handleException, JPADatabaseManager manager) { + super(name, filter, handleException, manager); + + this.description = this.getName() + "{ manager=" + this.getManager() + " }"; + } + + @Override + public String toString() { + return this.description; + } + + /** + * Factory method for creating a JPA appender within the plugin manager. + * + * @param name The name of the appender. + * @param suppressExceptions {@code "true"} (default) if logging exceptions should be hidden from the application, + * false otherwise. + * @param filter The filter, if any, to use. + * @param bufferSize If an integer greater than 0, this causes the appender to buffer log events and flush whenever + * the buffer reaches this size. + * @param entityClassName The fully qualified name of the concrete {@link LogEventWrapperEntity} implementation + * that has JPA annotations mapping it to a database table. + * @param persistenceUnitName The name of the JPA persistence unit that should be used for persisting log events. + * @return a new JPA appender. + */ + @PluginFactory + public static JPAAppender createAppender(@PluginAttr("name") final String name, + @PluginAttr("suppressExceptions") final String suppressExceptions, + @PluginElement("filter") final Filter filter, + @PluginAttr("bufferSize") final String bufferSize, + @PluginAttr("entityClassName") final String entityClassName, + @PluginAttr("persistenceUnitName") final String persistenceUnitName) { + if (entityClassName == null || entityClassName.length() == 0 || + persistenceUnitName == null || persistenceUnitName.length() == 0) { + LOGGER.error("Attributes entityClassName and persistenceUnitName are required for JPA Appender."); + return null; + } + + int bufferSizeInt; + try { + bufferSizeInt = bufferSize == null || bufferSize.length() == 0 ? 0 : Integer.parseInt(bufferSize); + } catch (NumberFormatException e) { + LOGGER.warn("Buffer size [" + bufferSize + "] not an integer, using no buffer."); + bufferSizeInt = 0; + } + + boolean handleExceptions = suppressExceptions == null || !Boolean.parseBoolean(suppressExceptions); + + try { + @SuppressWarnings("unchecked") + Class entityClass = + (Class) Class.forName(entityClassName); + + if (!LogEventWrapperEntity.class.isAssignableFrom(entityClass)) { + LOGGER.error("Entity class [{}] does not extend LogEventWrapperEntity.", entityClassName); + return null; + } + + try { + entityClass.getConstructor(); + } catch (NoSuchMethodException e) { + LOGGER.error("Entity class [{}] does not have a no-arg constructor. The JPA provider will reject it.", + entityClassName); + return null; + } + + Constructor entityConstructor = entityClass.getConstructor(LogEvent.class); + + String managerName = "jpaManager{ description=" + name + ", bufferSize=" + bufferSizeInt + + ", persistenceUnitName=" + persistenceUnitName + ", entityClass=" + entityClass.getName() + "}"; + + JPADatabaseManager manager = JPADatabaseManager.getJPADatabaseManager(managerName, bufferSizeInt, + entityClass, entityConstructor, persistenceUnitName); + if (manager == null) { + return null; + } + + return new JPAAppender(name, filter, handleExceptions, manager); + } catch (ClassNotFoundException e) { + LOGGER.error("Could not load entity class [{}].", entityClassName, e); + return null; + } catch (NoSuchMethodException e) { + LOGGER.error("Entity class [{}] does not have a constructor with a single argument of type LogEvent.", + entityClassName); + return null; + } + } +} Index: core/src/main/java/org/apache/logging/log4j/core/appender/db/jpa/JPADatabaseManager.java =================================================================== --- core/src/main/java/org/apache/logging/log4j/core/appender/db/jpa/JPADatabaseManager.java (revision 0) +++ core/src/main/java/org/apache/logging/log4j/core/appender/db/jpa/JPADatabaseManager.java (working copy) @@ -0,0 +1,142 @@ +/* + * 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.jpa; + +import org.apache.logging.log4j.core.LogEvent; +import org.apache.logging.log4j.core.appender.ManagerFactory; +import org.apache.logging.log4j.core.appender.db.AbstractDatabaseManager; + +import javax.persistence.EntityManager; +import javax.persistence.EntityManagerFactory; +import javax.persistence.EntityTransaction; +import javax.persistence.Persistence; +import java.lang.reflect.Constructor; + +/** + * An {@link AbstractDatabaseManager} implementation for relational databases accessed via JPA. + */ +public final class JPADatabaseManager extends AbstractDatabaseManager { + private static final JPADatabaseManagerFactory FACTORY = new JPADatabaseManagerFactory(); + + private final String entityClassName; + private final Constructor entityConstructor; + private final String persistenceUnitName; + + private EntityManagerFactory entityManagerFactory; + private EntityManager entityManager; + private EntityTransaction transaction; + + private JPADatabaseManager(String name, int bufferSize, Class entityClass, + Constructor entityConstructor, + String persistenceUnitName) { + super(name, bufferSize); + + this.entityClassName = entityClass.getName(); + this.entityConstructor = entityConstructor; + this.persistenceUnitName = persistenceUnitName; + } + + @Override + protected void connectInternal() { + this.entityManagerFactory = Persistence.createEntityManagerFactory(this.persistenceUnitName); + this.entityManager = this.entityManagerFactory.createEntityManager(); + this.transaction = this.entityManager.getTransaction(); + } + + @Override + protected void disconnectInternal() { + this.transaction = null; + + if (this.entityManager != null && this.entityManager.isOpen()) { + this.entityManager.close(); + } + + if (this.entityManagerFactory != null && this.entityManagerFactory.isOpen()) { + this.entityManagerFactory.close(); + } + } + + @Override + protected void writeInternal(LogEvent event) { + if (!this.isConnected() || this.transaction == null || this.entityManager == null || + this.entityManagerFactory == null) { + LOGGER.error("Cannot write logging event; manager [{}] not connected to the database.", this.getName()); + return; + } + + LogEventWrapperEntity entity; + try { + entity = this.entityConstructor.newInstance(event); + } catch (Exception e) { + LOGGER.error("Failed to instantiate entity class {}.", this.entityClassName, e); + return; + } + + try { + this.transaction.begin(); + this.entityManager.persist(entity); + this.transaction.commit(); + } catch (Exception e) { + LOGGER.error("Failed to persist log event entity.", e); + this.transaction.rollback(); + } + } + + /** + * Creates a JPA manager for use within the {@link JPAAppender}, or returns a suitable one if it already exists. + * + * @param name The name of the manager, which should include connection details, entity class name, etc. + * @param bufferSize The size of the log event buffer. + * @param entityClass The fully-qualified class name of the {@link LogEventWrapperEntity} concrete implementation. + * @param entityConstructor The one-arg {@link LogEvent} constructor for the concrete entity class. + * @param persistenceUnitName The name of the JPA persistence unit that should be used for persisting log events. + * @return a new or existing JPA manager as applicable. + */ + public static JPADatabaseManager getJPADatabaseManager( + String name, int bufferSize, Class entityClass, + Constructor entityConstructor, String persistenceUnitName) { + + return AbstractDatabaseManager.getManager( + name, new FactoryData(bufferSize, entityClass, entityConstructor, persistenceUnitName), FACTORY + ); + } + + private static final class FactoryData extends AbstractDatabaseManager.AbstractFactoryData { + private final Class entityClass; + private final Constructor entityConstructor; + private final String persistenceUnitName; + + protected FactoryData(int bufferSize, Class entityClass, + Constructor entityConstructor, + String persistenceUnitName) { + super(bufferSize); + + this.entityClass = entityClass; + this.entityConstructor = entityConstructor; + this.persistenceUnitName = persistenceUnitName; + } + } + + private static final class JPADatabaseManagerFactory implements ManagerFactory { + @Override + public JPADatabaseManager createManager(String name, FactoryData data) { + return new JPADatabaseManager( + name, data.bufferSize, data.entityClass, data.entityConstructor, data.persistenceUnitName + ); + } + } +} Index: core/src/main/java/org/apache/logging/log4j/core/appender/db/jpa/LogEventWrapperEntity.java =================================================================== --- core/src/main/java/org/apache/logging/log4j/core/appender/db/jpa/LogEventWrapperEntity.java (revision 0) +++ core/src/main/java/org/apache/logging/log4j/core/appender/db/jpa/LogEventWrapperEntity.java (working copy) @@ -0,0 +1,154 @@ +/* + * 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.jpa; + +import org.apache.logging.log4j.Level; +import org.apache.logging.log4j.Marker; +import org.apache.logging.log4j.ThreadContext; +import org.apache.logging.log4j.core.LogEvent; +import org.apache.logging.log4j.message.Message; + +import javax.persistence.MappedSuperclass; +import javax.persistence.Transient; +import java.util.Map; + +/** + * Users of the JPA appender MUST implement this class, using JPA annotations on the concrete class and all of its + * accessor methods (as needed) to map them to the proper table and columns. Accessors you do not want persisted + * should be annotated with {@link Transient @Transient}. All accessors should call {@link #getWrappedEvent()} and + * delegate the call to the underlying event.
+ *
+ * The concrete class must have two constructors: a public no-arg constructor to convince the JPA provider that it's a + * valid entity (this constructor will call {@link #LogEventWrapperEntity(LogEvent) super(null)}), and a public + * constructor that takes a single {@link LogEvent event} and passes it to the parent class with + * {@link #LogEventWrapperEntity(LogEvent) super(event)}. The concrete class must also have a mutable + * {@link javax.persistence.Id @Id} property with fully-functional mutator and accessor methods (usually configured as + * a {@link javax.persistence.GeneratedValue @GeneratedValue} property, and should be annotated with + * {@link javax.persistence.Entity @Entity}
+ *
+ * Many of the return types of {@link LogEvent} methods (e.g., {@link StackTraceElement}, {@link Message}, + * {@link Marker}) will not be recognized by the JPA provider. In these cases, you must either implement custom + * persistence serializers or (probably easier) mark those methods {@link Transient @Transient} and create + * similar methods that return the String form of these properties.
+ *
+ * The mutator methods in this class not specified in {@link LogEvent} are no-op methods, implemented to satisfy the JPA + * requirement that accessor methods have matching mutator methods. If you create additional accessor methods, you must + * likewise create matching no-op mutator methods. + */ +@MappedSuperclass +public abstract class LogEventWrapperEntity implements LogEvent { + private final LogEvent wrappedEvent; + + /** + * Instantiates the base class. All concrete implementations must have two constructors: a no-arg constructor that + * calls this constructor with a null argument, and a constructor matching this constructor's signature. + * + * @param wrappedEvent The underlying event from which information is obtained. + */ + protected LogEventWrapperEntity(LogEvent wrappedEvent) { + this.wrappedEvent = wrappedEvent; + } + + /** + * All eventual accessor methods must call this method and delegate the method call to the underlying wrapped event. + * + * @return The underlying event from which information is obtained. + */ + @Transient + protected final LogEvent getWrappedEvent() { + return this.wrappedEvent; + } + + @SuppressWarnings("unused") + public void setLevel(Level level) { + // this entity is write-only + } + + @SuppressWarnings("unused") + public void setLoggerName(String name) { + // this entity is write-only + } + + @SuppressWarnings("unused") + public void setSource(StackTraceElement element) { + // this entity is write-only + } + + @SuppressWarnings("unused") + public void setMessage(Message message) { + // this entity is write-only + } + + @SuppressWarnings("unused") + public void setMarker(Marker marker) { + // this entity is write-only + } + + @SuppressWarnings("unused") + public void setThreadName(String name) { + // this entity is write-only + } + + @SuppressWarnings("unused") + public void setMillis(long millis) { + // this entity is write-only + } + + @SuppressWarnings("unused") + public void setThrown(Throwable throwable) { + // this entity is write-only + } + + @SuppressWarnings("unused") + public void setContextMap(Map contextMap) { + // this entity is write-only + } + + @SuppressWarnings("unused") + public void setContextStack(ThreadContext.ContextStack contextStack) { + // this entity is write-only + } + + @SuppressWarnings("unused") + public void setFQCN(String fqcn) { + // this entity is write-only + } + + @Override + @Transient + public final boolean isIncludeLocation() { + return this.getWrappedEvent().isIncludeLocation(); + } + + @Override + @Transient + public final void setIncludeLocation(boolean locationRequired) { + this.getWrappedEvent().setIncludeLocation(locationRequired); + } + + @Override + @Transient + public final boolean isEndOfBatch() { + return this.getWrappedEvent().isEndOfBatch(); + } + + @Override + @Transient + public final void setEndOfBatch(boolean endOfBatch) { + this.getWrappedEvent().setEndOfBatch(endOfBatch); + } +} Index: core/src/main/java/org/apache/logging/log4j/core/appender/db/jpa/package-info.java =================================================================== --- core/src/main/java/org/apache/logging/log4j/core/appender/db/jpa/package-info.java (revision 0) +++ core/src/main/java/org/apache/logging/log4j/core/appender/db/jpa/package-info.java (working copy) @@ -0,0 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +/** + * The JPA Appender supports writing log events to a relational database using the Java Persistence API. You will need + * a JDBC driver on your classpath for the database you wish to log to. You will also need the Java Persistence API + * and your JPA provider of choice on the class path; these Maven dependencies are optional and will not automatically + * be added to your classpath. + */ +package org.apache.logging.log4j.core.appender.db.jpa; Index: core/src/main/java/org/apache/logging/log4j/core/appender/db/nosql/NoSQLAppender.java =================================================================== --- core/src/main/java/org/apache/logging/log4j/core/appender/db/nosql/NoSQLAppender.java (revision 0) +++ core/src/main/java/org/apache/logging/log4j/core/appender/db/nosql/NoSQLAppender.java (working copy) @@ -0,0 +1,97 @@ +/* + * 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.nosql; + +import org.apache.logging.log4j.core.Filter; +import org.apache.logging.log4j.core.appender.db.AbstractDatabaseAppender; +import org.apache.logging.log4j.core.config.plugins.Plugin; +import org.apache.logging.log4j.core.config.plugins.PluginAttr; +import org.apache.logging.log4j.core.config.plugins.PluginElement; +import org.apache.logging.log4j.core.config.plugins.PluginFactory; + +/** + * This Appender writes logging events to a NoSQL database using a configured NoSQL provider. It requires + * implementations of {@link NoSQLObject}, {@link NoSQLConnection}, and {@link NoSQLProvider} to "know" how to write + * events to the chosen NoSQL database. Two provider implementations are provided: MongoDB + * (org.mongodb:mongo-java-driver:2.11.1 or newer must be on the classpath) and Apache CouchDB + * (org.lightcouch:lightcouch:0.0.5 or newer must be on the classpath). For examples on how to write your own NoSQL + * provider, see the simple source code for the MongoDB and CouchDB providers. + * + * @see NoSQLObject + * @see NoSQLConnection + * @see NoSQLProvider + */ +@Plugin(name = "NoSql", category = "Core", elementType = "appender", printObject = true) +public final class NoSQLAppender extends AbstractDatabaseAppender> { + private final String description; + + private NoSQLAppender(String name, Filter filter, boolean handleException, NoSQLDatabaseManager manager) { + super(name, filter, handleException, manager); + + this.description = this.getName() + "{ manager=" + this.getManager() + " }"; + } + + @Override + public String toString() { + return this.description; + } + + /** + * Factory method for creating a NoSQL appender within the plugin manager. + * + * @param name The name of the appender. + * @param suppressExceptions {@code "true"} (default) if logging exceptions should be hidden from the application, + * false otherwise. + * @param filter The filter, if any, to use. + * @param bufferSize If an integer greater than 0, this causes the appender to buffer log events and flush whenever + * the buffer reaches this size. + * @param provider The NoSQL provider that provides connections to the chosen NoSQL database. + * @return a new NoSQL appender. + */ + @PluginFactory + public static NoSQLAppender createAppender(@PluginAttr("name") final String name, + @PluginAttr("suppressExceptions") final String suppressExceptions, + @PluginElement("filter") final Filter filter, + @PluginAttr("bufferSize") final String bufferSize, + @PluginElement("noSqlProvider") final NoSQLProvider provider) { + if (provider == null) { + LOGGER.error("NoSQL provider not specified for appender [{}].", name); + return null; + } + + int bufferSizeInt; + try { + bufferSizeInt = bufferSize == null || bufferSize.length() == 0 ? 0 : Integer.parseInt(bufferSize); + } catch (NumberFormatException e) { + LOGGER.warn("Buffer size [" + bufferSize + "] not an integer, using no buffer."); + bufferSizeInt = 0; + } + + boolean handleExceptions = suppressExceptions == null || !Boolean.parseBoolean(suppressExceptions); + + String managerName = "noSqlManager{ description=" + name + ", bufferSize=" + bufferSizeInt + ", provider=" + + provider + " }"; + + NoSQLDatabaseManager manager = + NoSQLDatabaseManager.getNoSQLDatabaseManager(managerName, bufferSizeInt, provider); + if (manager == null) { + return null; + } + + return new NoSQLAppender(name, filter, handleExceptions, manager); + } +} Index: core/src/main/java/org/apache/logging/log4j/core/appender/db/nosql/NoSQLConnection.java =================================================================== --- core/src/main/java/org/apache/logging/log4j/core/appender/db/nosql/NoSQLConnection.java (revision 0) +++ core/src/main/java/org/apache/logging/log4j/core/appender/db/nosql/NoSQLConnection.java (working copy) @@ -0,0 +1,36 @@ +/* + * 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.nosql; + +/** + * Represents a connection to the NoSQL database. Serves as a factory for new (empty) objects and an endpoint for + * inserted objects. + * + * @param Specifies which implementation of {@link NoSQLObject} this connection provides. + * @param Specifies which type of database object is wrapped by the {@link NoSQLObject} implementation provided. + */ +public interface NoSQLConnection> { + T createObject(); + + T[] createList(int length); + + void insertObject(NoSQLObject object); + + void close(); + + boolean isClosed(); +} Index: core/src/main/java/org/apache/logging/log4j/core/appender/db/nosql/NoSQLDatabaseManager.java =================================================================== --- core/src/main/java/org/apache/logging/log4j/core/appender/db/nosql/NoSQLDatabaseManager.java (revision 0) +++ core/src/main/java/org/apache/logging/log4j/core/appender/db/nosql/NoSQLDatabaseManager.java (working copy) @@ -0,0 +1,198 @@ +/* + * 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.nosql; + +import org.apache.logging.log4j.Marker; +import org.apache.logging.log4j.ThreadContext; +import org.apache.logging.log4j.core.LogEvent; +import org.apache.logging.log4j.core.appender.ManagerFactory; +import org.apache.logging.log4j.core.appender.db.AbstractDatabaseManager; + +import java.util.Map; + +/** + * An {@link AbstractDatabaseManager} implementation for all NoSQL databases. + * + * @param A type parameter for reassuring the compiler that all operations are using the same {@link NoSQLObject}. + */ +public final class NoSQLDatabaseManager extends AbstractDatabaseManager { + private static final NoSQLDatabaseManagerFactory FACTORY = new NoSQLDatabaseManagerFactory(); + + private final NoSQLProvider>> provider; + + private NoSQLConnection> connection; + + private NoSQLDatabaseManager(String name, int bufferSize, + NoSQLProvider>> provider) { + super(name, bufferSize); + + this.provider = provider; + } + + @Override + protected void connectInternal() { + try { + this.connection = this.provider.getConnection(); + } catch (Exception e) { + LOGGER.error("Failed to obtain a connection to the NoSQL database in manager [{}].", this.getName(), e); + } + } + + @Override + protected void disconnectInternal() { + try { + if (this.connection != null && !this.connection.isClosed()) { + this.connection.close(); + } + } catch (Exception e) { + LOGGER.warn("Error while closing NoSQL database connection in manager [{}].", this.getName(), e); + } + } + + @Override + protected void writeInternal(LogEvent event) { + if (!this.isConnected() || this.connection == null || this.connection.isClosed()) { + LOGGER.error("Cannot write logging event; manager [{}] not connected to the database.", this.getName()); + return; + } + + NoSQLObject entity = this.connection.createObject(); + entity.set("level", event.getLevel()); + entity.set("loggerName", event.getLoggerName()); + entity.set("message", event.getMessage() == null ? null : event.getMessage().getFormattedMessage()); + + StackTraceElement source = event.getSource(); + if (source == null) { + entity.set("source", (Object) null); + } else { + entity.set("source", this.convertStackTraceElement(source)); + } + + Marker marker = event.getMarker(); + if (marker == null) { + entity.set("marker", (Object) null); + } else { + NoSQLObject originalMarkerEntity = this.connection.createObject(); + NoSQLObject markerEntity = originalMarkerEntity; + markerEntity.set("name", marker.getName()); + while (marker.getParent() != null) { + marker = marker.getParent(); + NoSQLObject parentMarkerEntity = this.connection.createObject(); + parentMarkerEntity.set("name", marker.getName()); + markerEntity.set("parent", parentMarkerEntity); + markerEntity = parentMarkerEntity; + } + entity.set("marker", originalMarkerEntity); + } + + entity.set("threadName", event.getThreadName()); + entity.set("millis", event.getMillis()); + entity.set("date", new java.util.Date(event.getMillis())); + + @SuppressWarnings("ThrowableResultOfMethodCallIgnored") + Throwable thrown = event.getThrown(); + if (thrown == null) { + entity.set("thrown", (Object) null); + } else { + NoSQLObject originalExceptionEntity = this.connection.createObject(); + NoSQLObject exceptionEntity = originalExceptionEntity; + exceptionEntity.set("type", thrown.getClass().getName()); + exceptionEntity.set("message", thrown.getMessage()); + exceptionEntity.set("stackTrace", this.convertStackTrace(thrown.getStackTrace())); + while (thrown.getCause() != null) { + thrown = thrown.getCause(); + NoSQLObject causingExceptionEntity = this.connection.createObject(); + causingExceptionEntity.set("type", thrown.getClass().getName()); + causingExceptionEntity.set("message", thrown.getMessage()); + causingExceptionEntity.set("stackTrace", this.convertStackTrace(thrown.getStackTrace())); + exceptionEntity.set("cause", causingExceptionEntity); + exceptionEntity = causingExceptionEntity; + } + + entity.set("thrown", originalExceptionEntity); + } + + Map contextMap = event.getContextMap(); + if (contextMap == null) { + entity.set("contextMap", (Object) null); + } else { + NoSQLObject contextMapEntity = this.connection.createObject(); + for (Map.Entry entry : contextMap.entrySet()) { + contextMapEntity.set(entry.getKey(), entry.getValue()); + } + entity.set("contextMap", contextMapEntity); + } + + ThreadContext.ContextStack contextStack = event.getContextStack(); + if (contextStack == null) { + entity.set("contextStack", (Object) null); + } else { + entity.set("contextStack", contextStack.asList().toArray()); + } + + this.connection.insertObject(entity); + } + + private NoSQLObject[] convertStackTrace(StackTraceElement[] stackTrace) { + NoSQLObject[] stackTraceEntities = this.connection.createList(stackTrace.length); + for (int i = 0; i < stackTrace.length; i++) { + stackTraceEntities[i] = this.convertStackTraceElement(stackTrace[i]); + } + return stackTraceEntities; + } + + private NoSQLObject convertStackTraceElement(StackTraceElement element) { + NoSQLObject elementEntity = this.connection.createObject(); + elementEntity.set("className", element.getClassName()); + elementEntity.set("methodName", element.getMethodName()); + elementEntity.set("fileName", element.getFileName()); + elementEntity.set("lineNumber", element.getLineNumber()); + return elementEntity; + } + + /** + * Creates a NoSQL manager for use within the {@link NoSQLAppender}, or returns a suitable one if it already exists. + * + * @param name The name of the manager, which should include connection details and hashed passwords where possible. + * @param bufferSize The size of the log event buffer. + * @param provider A provider instance which will be used to obtain connections to the chosen NoSQL database. + * @return a new or existing NoSQL manager as applicable. + */ + public static NoSQLDatabaseManager getNoSQLDatabaseManager(String name, int bufferSize, + NoSQLProvider provider) { + return AbstractDatabaseManager.getManager(name, new FactoryData(bufferSize, provider), FACTORY); + } + + private static final class FactoryData extends AbstractDatabaseManager.AbstractFactoryData { + private final NoSQLProvider provider; + + protected FactoryData(int bufferSize, NoSQLProvider provider) { + super(bufferSize); + + this.provider = provider; + } + } + + private static final class NoSQLDatabaseManagerFactory + implements ManagerFactory, FactoryData> { + @Override + @SuppressWarnings("unchecked") + public NoSQLDatabaseManager createManager(String name, FactoryData data) { + return new NoSQLDatabaseManager(name, data.bufferSize, data.provider); + } + } +} Index: core/src/main/java/org/apache/logging/log4j/core/appender/db/nosql/NoSQLObject.java =================================================================== --- core/src/main/java/org/apache/logging/log4j/core/appender/db/nosql/NoSQLObject.java (revision 0) +++ core/src/main/java/org/apache/logging/log4j/core/appender/db/nosql/NoSQLObject.java (working copy) @@ -0,0 +1,34 @@ +/* + * 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.nosql; + +/** + * Represents a simple POJO object inserted into a NoSQL object. + * + * @param Specifies what type of underlying object (such as a MongoDB BasicDBObject) this NoSQLObject wraps. + */ +public interface NoSQLObject { + void set(String field, Object value); + + void set(String field, NoSQLObject value); + + void set(String field, Object[] values); + + void set(String field, NoSQLObject[] values); + + W unwrap(); +} Index: core/src/main/java/org/apache/logging/log4j/core/appender/db/nosql/NoSQLProvider.java =================================================================== --- core/src/main/java/org/apache/logging/log4j/core/appender/db/nosql/NoSQLProvider.java (revision 0) +++ core/src/main/java/org/apache/logging/log4j/core/appender/db/nosql/NoSQLProvider.java (working copy) @@ -0,0 +1,27 @@ +/* + * 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.nosql; + +/** + * Implementations of this class are plugins for configuring the {@link NoSQLAppender} with the proper provider + * (MongoDB, etc.). + * + * @param Specifies which implementation of {@link NoSQLConnection} this provider provides. + */ +public interface NoSQLProvider>> { + C getConnection(); +} Index: core/src/main/java/org/apache/logging/log4j/core/appender/db/nosql/couch/CouchDBConnection.java =================================================================== --- core/src/main/java/org/apache/logging/log4j/core/appender/db/nosql/couch/CouchDBConnection.java (revision 0) +++ core/src/main/java/org/apache/logging/log4j/core/appender/db/nosql/couch/CouchDBConnection.java (working copy) @@ -0,0 +1,73 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.appender.db.nosql.couch; + +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.core.appender.db.nosql.NoSQLConnection; +import org.apache.logging.log4j.core.appender.db.nosql.NoSQLObject; +import org.apache.logging.log4j.status.StatusLogger; +import org.lightcouch.CouchDbClient; +import org.lightcouch.Response; + +import java.util.Map; + +/** + * The Apache CouchDB implementation of {@link NoSQLConnection}. + */ +public final class CouchDBConnection implements NoSQLConnection, CouchDBObject> { + private static final Logger LOGGER = StatusLogger.getLogger(); + + private final CouchDbClient client; + private boolean closed = false; + + public CouchDBConnection(CouchDbClient client) { + this.client = client; + } + + @Override + public CouchDBObject createObject() { + return new CouchDBObject(); + } + + @Override + public CouchDBObject[] createList(int length) { + return new CouchDBObject[length]; + } + + @Override + public void insertObject(NoSQLObject> object) { + try { + Response response = this.client.save(object.unwrap()); + if (response.getError() != null && response.getError().length() > 0) { + LOGGER.error("Failed to write log event to CouchDB due to error: [{}].", response.getError()); + } + } catch (Exception e) { + LOGGER.error("Failed to write log event to CouchDB due to error.", e); + } + } + + @Override + public synchronized void close() { + this.closed = true; + this.client.shutdown(); + } + + @Override + public synchronized boolean isClosed() { + return this.closed; + } +} Index: core/src/main/java/org/apache/logging/log4j/core/appender/db/nosql/couch/CouchDBObject.java =================================================================== --- core/src/main/java/org/apache/logging/log4j/core/appender/db/nosql/couch/CouchDBObject.java (revision 0) +++ core/src/main/java/org/apache/logging/log4j/core/appender/db/nosql/couch/CouchDBObject.java (working copy) @@ -0,0 +1,64 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.appender.db.nosql.couch; + +import org.apache.logging.log4j.core.appender.db.nosql.NoSQLObject; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; + +/** + * The Apache CouchDB implementation of {@link NoSQLObject}. + */ +public final class CouchDBObject implements NoSQLObject> { + private final Map map; + + public CouchDBObject() { + this.map = new HashMap(); + } + + @Override + public void set(String field, Object value) { + this.map.put(field, value); + } + + @Override + public void set(String field, NoSQLObject> value) { + this.map.put(field, value.unwrap()); + } + + @Override + public void set(String field, Object[] values) { + this.map.put(field, Arrays.asList(values)); + } + + @Override + public void set(String field, NoSQLObject>[] values) { + ArrayList> list = new ArrayList>(); + for (NoSQLObject> value : values) { + list.add(value.unwrap()); + } + this.map.put(field, list); + } + + @Override + public Map unwrap() { + return this.map; + } +} Index: core/src/main/java/org/apache/logging/log4j/core/appender/db/nosql/couch/CouchDBProvider.java =================================================================== --- core/src/main/java/org/apache/logging/log4j/core/appender/db/nosql/couch/CouchDBProvider.java (revision 0) +++ core/src/main/java/org/apache/logging/log4j/core/appender/db/nosql/couch/CouchDBProvider.java (working copy) @@ -0,0 +1,167 @@ +/* + * 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.nosql.couch; + +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.core.appender.db.nosql.NoSQLProvider; +import org.apache.logging.log4j.core.config.plugins.Plugin; +import org.apache.logging.log4j.core.config.plugins.PluginAttr; +import org.apache.logging.log4j.core.config.plugins.PluginFactory; +import org.apache.logging.log4j.core.helpers.NameUtil; +import org.apache.logging.log4j.status.StatusLogger; +import org.lightcouch.CouchDbClient; +import org.lightcouch.CouchDbProperties; + +import java.lang.reflect.Method; + +/** + * The Apache CouchDB implementation of {@link NoSQLProvider}. + */ +@Plugin(name = "CouchDb", category = "Core", printObject = true) +public final class CouchDBProvider implements NoSQLProvider { + private static final Logger LOGGER = StatusLogger.getLogger(); + private static final int HTTP = 80; + private static final int HTTPS = 443; + + private final CouchDbClient client; + private final String description; + + private CouchDBProvider(CouchDbClient client, String description) { + this.client = client; + this.description = "couchDb{ " + description + " }"; + } + + @Override + public CouchDBConnection getConnection() { + return new CouchDBConnection(this.client); + } + + @Override + public String toString() { + return this.description; + } + + /** + * Factory method for creating an Apache CouchDB provider within the plugin manager. + * + * @param databaseName The name of the database to which log event documents will be written. + * @param protocol Either "http" or "https," defaults to "http" and mutually exclusive with + * {@code factoryClassName&factoryMethodName!=null}. + * @param server The host name of the CouchDB server, defaults to localhost and mutually exclusive with + * {@code factoryClassName&factoryMethodName!=null}. + * @param port The port that CouchDB is listening on, defaults to 80 if {@code protocol} is "http" and 443 if + * {@code protocol} is "https," and mutually exclusive with + * {@code factoryClassName&factoryMethodName!=null}. + * @param username The username to authenticate against the MongoDB server with, mutually exclusive with + * {@code factoryClassName&factoryMethodName!=null}. + * @param password The password to authenticate against the MongoDB server with, mutually exclusive with + * {@code factoryClassName&factoryMethodName!=null}. + * @param factoryClassName A fully qualified class name containing a static factory method capable of returning a + * {@link CouchDbClient} or {@link CouchDbProperties}. + * @param factoryMethodName The name of the public static factory method belonging to the aforementioned factory + * class. + * @return a new Apache CouchDB provider. + */ + @PluginFactory + public static CouchDBProvider createNoSQLProvider(@PluginAttr("databaseName") final String databaseName, + @PluginAttr("protocol") String protocol, + @PluginAttr("server") String server, + @PluginAttr("port") final String port, + @PluginAttr("username") final String username, + @PluginAttr("password") final String password, + @PluginAttr("factoryClassName") final String factoryClassName, + @PluginAttr("factoryMethodName") final String factoryMethodName) { + CouchDbClient client; + String description; + if (factoryClassName != null && factoryClassName.length() > 0 && + factoryMethodName != null && factoryMethodName.length() > 0) { + try { + Class factoryClass = Class.forName(factoryClassName); + Method method = factoryClass.getMethod(factoryMethodName); + Object object = method.invoke(null); + + if (object instanceof CouchDbClient) { + client = (CouchDbClient) object; + description = "uri=" + client.getDBUri(); + } else if (object instanceof CouchDbProperties) { + CouchDbProperties properties = (CouchDbProperties) object; + client = new CouchDbClient(properties); + description = "uri=" + client.getDBUri() + ", username=" + properties.getUsername() + + ", passwordHash=" + NameUtil.md5(password + CouchDBProvider.class.getName()) + + ", maxConnections=" + properties.getMaxConnections() + ", connectionTimeout=" + + properties.getConnectionTimeout() + ", socketTimeout=" + properties.getSocketTimeout(); + } else if (object == null) { + LOGGER.error("The factory method [{}.{}()] returned null.", factoryClassName, factoryMethodName); + return null; + } else { + LOGGER.error("The factory method [{}.{}()] returned an unsupported type [{}].", + factoryClassName, factoryMethodName, object.getClass().getName()); + return null; + } + } catch (ClassNotFoundException e) { + LOGGER.error("The factory class [{}] could not be loaded.", factoryClassName, e); + return null; + } catch (NoSuchMethodException e) { + LOGGER.error("The factory class [{}] does not have a no-arg method named [{}].", + factoryClassName, factoryMethodName, e); + return null; + } catch (Exception e) { + LOGGER.error("The factory method [{}.{}()] could not be invoked.", + factoryClassName, factoryMethodName, e); + return null; + } + } else if (databaseName != null && databaseName.length() > 0) { + if (protocol != null && protocol.length() > 0) { + if (!protocol.equals("http") && !protocol.equals("https")) { + LOGGER.error("Only protocols [http] and [https] are supported, [{}] specified.", protocol); + return null; + } + } else { + protocol = "http"; + LOGGER.warn("No protocol specified, using default port [http]."); + } + + int portInt = protocol.equals("https") ? HTTPS : HTTP; + if (port != null && port.length() > 0) { + try { + portInt = Integer.parseInt(port); + } catch (NumberFormatException ignore) { /* */ } + } else { + LOGGER.warn("No port specified, using default port [{}] for protocol [{}].", portInt, protocol); + } + + if (server == null || server.length() == 0) { + server = "localhost"; + LOGGER.warn("No server specified, using default server localhost."); + } + + if (username == null || username.length() == 0 || password == null || password.length() == 0) { + LOGGER.error("You must provide a username and password for the CouchDB provider."); + return null; + } + + client = new CouchDbClient(databaseName, false, protocol, server, portInt, username, password); + description = "uri=" + client.getDBUri() + ", username=" + username + ", passwordHash=" + + NameUtil.md5(password + CouchDBProvider.class.getName()); + } else { + LOGGER.error("No factory method was provided so the database name is required."); + return null; + } + + return new CouchDBProvider(client, description); + } +} Index: core/src/main/java/org/apache/logging/log4j/core/appender/db/nosql/couch/package-info.java =================================================================== --- core/src/main/java/org/apache/logging/log4j/core/appender/db/nosql/couch/package-info.java (revision 0) +++ core/src/main/java/org/apache/logging/log4j/core/appender/db/nosql/couch/package-info.java (working copy) @@ -0,0 +1,20 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +/** + * The classes in this package contain the Apache CouchDB provider for the NoSQL Appender. + */ +package org.apache.logging.log4j.core.appender.db.nosql.couch; Index: core/src/main/java/org/apache/logging/log4j/core/appender/db/nosql/mongo/MongoDBConnection.java =================================================================== --- core/src/main/java/org/apache/logging/log4j/core/appender/db/nosql/mongo/MongoDBConnection.java (revision 0) +++ core/src/main/java/org/apache/logging/log4j/core/appender/db/nosql/mongo/MongoDBConnection.java (working copy) @@ -0,0 +1,78 @@ +/* + * 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.nosql.mongo; + +import com.mongodb.BasicDBObject; +import com.mongodb.DB; +import com.mongodb.DBCollection; +import com.mongodb.Mongo; +import com.mongodb.MongoException; +import com.mongodb.WriteConcern; +import com.mongodb.WriteResult; +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.core.appender.db.nosql.NoSQLConnection; +import org.apache.logging.log4j.core.appender.db.nosql.NoSQLObject; +import org.apache.logging.log4j.status.StatusLogger; + +/** + * The MongoDB implementation of {@link NoSQLConnection}. + */ +public final class MongoDBConnection implements NoSQLConnection { + private static final Logger LOGGER = StatusLogger.getLogger(); + + private final Mongo mongo; + private final DBCollection collection; + private final WriteConcern writeConcern; + + public MongoDBConnection(DB database, WriteConcern writeConcern, String collectionName) { + this.mongo = database.getMongo(); + this.collection = database.getCollection(collectionName); + this.writeConcern = writeConcern; + } + + @Override + public MongoDBObject createObject() { + return new MongoDBObject(); + } + + @Override + public MongoDBObject[] createList(int length) { + return new MongoDBObject[length]; + } + + @Override + public void insertObject(NoSQLObject object) { + try { + WriteResult result = this.collection.insert(object.unwrap(), this.writeConcern); + if (result.getN() < 1) { + LOGGER.error("Failed to write log event to MongoDB due to invalid result [{}].", result.getN()); + } + } catch (MongoException e) { + LOGGER.error("Failed to write log event to MongoDB due to error.", e); + } + } + + @Override + public void close() { + this.mongo.close(); + } + + @Override + public boolean isClosed() { + return !this.mongo.getConnector().isOpen(); + } +} Index: core/src/main/java/org/apache/logging/log4j/core/appender/db/nosql/mongo/MongoDBObject.java =================================================================== --- core/src/main/java/org/apache/logging/log4j/core/appender/db/nosql/mongo/MongoDBObject.java (revision 0) +++ core/src/main/java/org/apache/logging/log4j/core/appender/db/nosql/mongo/MongoDBObject.java (working copy) @@ -0,0 +1,65 @@ +/* + * 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.nosql.mongo; + +import com.mongodb.BasicDBList; +import com.mongodb.BasicDBObject; +import org.apache.logging.log4j.core.appender.db.nosql.NoSQLObject; + +import java.util.Collections; + +/** + * The MongoDB implementation of {@link NoSQLObject}. + */ +public final class MongoDBObject implements NoSQLObject { + private final BasicDBObject mongoObject; + + public MongoDBObject() { + this.mongoObject = new BasicDBObject(); + } + + @Override + public void set(String field, Object value) { + this.mongoObject.append(field, value); + } + + @Override + public void set(String field, NoSQLObject value) { + this.mongoObject.append(field, value.unwrap()); + } + + @Override + public void set(String field, Object[] values) { + BasicDBList list = new BasicDBList(); + Collections.addAll(list, values); + this.mongoObject.append(field, list); + } + + @Override + public void set(String field, NoSQLObject[] values) { + BasicDBList list = new BasicDBList(); + for (NoSQLObject value : values) { + list.add(value.unwrap()); + } + this.mongoObject.append(field, list); + } + + @Override + public BasicDBObject unwrap() { + return this.mongoObject; + } +} Index: core/src/main/java/org/apache/logging/log4j/core/appender/db/nosql/mongo/MongoDBProvider.java =================================================================== --- core/src/main/java/org/apache/logging/log4j/core/appender/db/nosql/mongo/MongoDBProvider.java (revision 0) +++ core/src/main/java/org/apache/logging/log4j/core/appender/db/nosql/mongo/MongoDBProvider.java (working copy) @@ -0,0 +1,217 @@ +/* + * 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.nosql.mongo; + +import com.mongodb.DB; +import com.mongodb.MongoClient; +import com.mongodb.ServerAddress; +import com.mongodb.WriteConcern; +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.core.appender.db.nosql.NoSQLProvider; +import org.apache.logging.log4j.core.config.plugins.Plugin; +import org.apache.logging.log4j.core.config.plugins.PluginAttr; +import org.apache.logging.log4j.core.config.plugins.PluginFactory; +import org.apache.logging.log4j.core.helpers.NameUtil; +import org.apache.logging.log4j.status.StatusLogger; + +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.util.List; + +/** + * The MongoDB implementation of {@link NoSQLProvider}. + */ +@Plugin(name = "MongoDb", category = "Core", printObject = true) +public final class MongoDBProvider implements NoSQLProvider { + private static final Logger LOGGER = StatusLogger.getLogger(); + + private final DB database; + private final WriteConcern writeConcern; + private final String collectionName; + private final String description; + + private MongoDBProvider(DB database, WriteConcern writeConcern, String collectionName, String description) { + this.database = database; + this.writeConcern = writeConcern; + this.collectionName = collectionName; + this.description = "mongoDb{ " + description + " }"; + } + + @Override + public MongoDBConnection getConnection() { + return new MongoDBConnection(this.database, this.writeConcern, this.collectionName); + } + + @Override + public String toString() { + return this.description; + } + + /** + * Factory method for creating a MongoDB provider within the plugin manager. + * + * @param collectionName The name of the MongoDB collection to which log events should be written. + * @param writeConcernConstant The {@link WriteConcern} constant to control writing details, defaults to + * {@link WriteConcern#ACKNOWLEDGED}. + * @param writeConcernConstantClassName The name of a class containing the aforementioned static WriteConcern + * constant. Defaults to {@link WriteConcern}. + * @param databaseName The name of the MongoDB database containing the collection to which log events should be + * written. Mutually exclusive with {@code factoryClassName&factoryMethodName!=null}. + * @param server The host name of the MongoDB server, defaults to localhost and mutually exclusive with + * {@code factoryClassName&factoryMethodName!=null}. + * @param port The port the MongoDB server is listening on, defaults to the default MongoDB port and mutually + * exclusive with {@code factoryClassName&factoryMethodName!=null}. + * @param username The username to authenticate against the MongoDB server with. + * @param password The password to authenticate against the MongoDB server with. + * @param factoryClassName A fully qualified class name containing a static factory method capable of returning a + * {@link DB} or a {@link MongoClient}. + * @param factoryMethodName The name of the public static factory method belonging to the aforementioned factory + * class. + * @return a new MongoDB provider. + */ + @PluginFactory + public static MongoDBProvider createNoSQLProvider(@PluginAttr("collectionName") final String collectionName, + @PluginAttr("writeConcernConstant") + final String writeConcernConstant, + @PluginAttr("writeConcernConstantClass") + final String writeConcernConstantClassName, + @PluginAttr("databaseName") final String databaseName, + @PluginAttr("server") final String server, + @PluginAttr("port") final String port, + @PluginAttr("username") final String username, + @PluginAttr("password") final String password, + @PluginAttr("factoryClassName") final String factoryClassName, + @PluginAttr("factoryMethodName") final String factoryMethodName) { + DB database; + String description; + if (factoryClassName != null && factoryClassName.length() > 0 && + factoryMethodName != null && factoryMethodName.length() > 0) { + try { + Class factoryClass = Class.forName(factoryClassName); + Method method = factoryClass.getMethod(factoryMethodName); + Object object = method.invoke(null); + + if (object instanceof DB) { + database = (DB) object; + } else if (object instanceof MongoClient) { + if (databaseName != null && databaseName.length() > 0) { + database = ((MongoClient) object).getDB(databaseName); + } else { + LOGGER.error("The factory method [{}.{}()] returned a MongoClient so the database name is " + + "required.", factoryClassName, factoryMethodName); + return null; + } + } else if (object == null) { + LOGGER.error("The factory method [{}.{}()] returned null.", factoryClassName, factoryMethodName); + return null; + } else { + LOGGER.error("The factory method [{}.{}()] returned an unsupported type [{}].", + factoryClassName, factoryMethodName, object.getClass().getName()); + return null; + } + + description = "database=" + database.getName(); + List addresses = database.getMongo().getAllAddress(); + if (addresses.size() == 1) { + description += ", server=" + addresses.get(0).getHost() + ", port=" + addresses.get(0).getPort(); + } else { + description += ", servers=["; + for (ServerAddress address : addresses) { + description += " { " + address.getHost() + ", " + address.getPort() + " } "; + } + description += "]"; + } + } catch (ClassNotFoundException e) { + LOGGER.error("The factory class [{}] could not be loaded.", factoryClassName, e); + return null; + } catch (NoSuchMethodException e) { + LOGGER.error("The factory class [{}] does not have a no-arg method named [{}].", + factoryClassName, factoryMethodName, e); + return null; + } catch (Exception e) { + LOGGER.error("The factory method [{}.{}()] could not be invoked.", + factoryClassName, factoryMethodName, e); + return null; + } + } else if (databaseName != null && databaseName.length() > 0) { + description = "database=" + databaseName; + try { + if (server != null && server.length() > 0) { + int portInt = 0; + if (port != null && port.length() > 0) { + try { + portInt = Integer.parseInt(port); + } catch (NumberFormatException ignore) { /* */ } + } + + description += ", server=" + server; + if (portInt > 0) { + description += ", port=" + portInt; + database = new MongoClient(server, portInt).getDB(databaseName); + } else { + database = new MongoClient(server).getDB(databaseName); + } + } else { + database = new MongoClient().getDB(databaseName); + } + } catch (Exception e) { + LOGGER.error("Failed to obtain a database instance from the MongoClient at server [{}] and " + + "port [{}].", server, port); + return null; + } + } else { + LOGGER.error("No factory method was provided so the database name is required."); + return null; + } + + if (!database.isAuthenticated()) { + if (username != null && username.length() > 0 && password != null && password.length() > 0) { + description += ", username=" + username + ", passwordHash=" + + NameUtil.md5(password + MongoDBProvider.class.getName()); + } else { + LOGGER.error("The database is not already authenticated so you must supply a username and password " + + "for the MongoDB provider."); + return null; + } + } + + WriteConcern writeConcern; + if (writeConcernConstant != null && writeConcernConstant.length() > 0) { + if (writeConcernConstantClassName != null && writeConcernConstantClassName.length() > 0) { + try { + Class writeConcernConstantClass = Class.forName(writeConcernConstantClassName); + Field field = writeConcernConstantClass.getField(writeConcernConstant); + writeConcern = (WriteConcern) field.get(null); + } catch (Exception e) { + LOGGER.error("Write concern constant [{}.{}] not found, using default.", + writeConcernConstantClassName, writeConcernConstant); + writeConcern = WriteConcern.ACKNOWLEDGED; + } + } else { + writeConcern = WriteConcern.valueOf(writeConcernConstant); + if (writeConcern == null) { + LOGGER.warn("Write concern constant [{}] not found, using default.", writeConcernConstant); + writeConcern = WriteConcern.ACKNOWLEDGED; + } + } + } else { + writeConcern = WriteConcern.ACKNOWLEDGED; + } + + return new MongoDBProvider(database, writeConcern, collectionName, description); + } +} Index: core/src/main/java/org/apache/logging/log4j/core/appender/db/nosql/mongo/package-info.java =================================================================== --- core/src/main/java/org/apache/logging/log4j/core/appender/db/nosql/mongo/package-info.java (revision 0) +++ core/src/main/java/org/apache/logging/log4j/core/appender/db/nosql/mongo/package-info.java (working copy) @@ -0,0 +1,20 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +/** + * The classes in this package contain the MongoDB provider for the NoSQL Appender. + */ +package org.apache.logging.log4j.core.appender.db.nosql.mongo; Index: core/src/main/java/org/apache/logging/log4j/core/appender/db/nosql/package-info.java =================================================================== --- core/src/main/java/org/apache/logging/log4j/core/appender/db/nosql/package-info.java (revision 0) +++ core/src/main/java/org/apache/logging/log4j/core/appender/db/nosql/package-info.java (working copy) @@ -0,0 +1,31 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +/** + * The NoSQL Appender supports writing log events to NoSQL databases. The following NoSQL databases are currently + * supported. You can also easily extend this to support other NoSQL databases by implementing just three interfaces: + * {@link NoSQLObject}, {@link NoSQLConnection}, and {@link NoSQLProvider}. You will need the client library for your + * NoSQL database of choice on the classpath to use this appender; these Maven dependencies are optional and will not + * automatically be added to your classpath.
+ *
+ *
    + *
  • MongoDB: org.mongodb:mongo-java-driver:2.11.1 or newer + * must be on the classpath.
  • + *
  • Apache CouchDB: org.lightcouch:lightcouch:0.0.5 or + * newer must be on the classpath.
  • + *
+ */ +package org.apache.logging.log4j.core.appender.db.nosql; Index: core/src/main/java/org/apache/logging/log4j/core/appender/db/package-info.java =================================================================== --- core/src/main/java/org/apache/logging/log4j/core/appender/db/package-info.java (revision 0) +++ core/src/main/java/org/apache/logging/log4j/core/appender/db/package-info.java (working copy) @@ -0,0 +1,21 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +/** + * The classes in this package and sub packages provide appenders for various types of databases and methods for + * accessing databases. + */ +package org.apache.logging.log4j.core.appender.db; Index: core/src/main/java/org/apache/logging/log4j/core/config/BaseConfiguration.java =================================================================== --- core/src/main/java/org/apache/logging/log4j/core/config/BaseConfiguration.java (revision 1479389) +++ core/src/main/java/org/apache/logging/log4j/core/config/BaseConfiguration.java (working copy) @@ -231,7 +231,7 @@ setName(DefaultConfiguration.DEFAULT_NAME); final Layout layout = PatternLayout.createLayout("%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n", - null, null, null); + null, null, null, null); final Appender appender = ConsoleAppender.createAppender(layout, null, "SYSTEM_OUT", "Console", "false", "true"); appender.start(); Index: core/src/main/java/org/apache/logging/log4j/core/config/DefaultConfiguration.java =================================================================== --- core/src/main/java/org/apache/logging/log4j/core/config/DefaultConfiguration.java (revision 1479389) +++ core/src/main/java/org/apache/logging/log4j/core/config/DefaultConfiguration.java (working copy) @@ -49,7 +49,7 @@ setName(DEFAULT_NAME); final Layout layout = - PatternLayout.createLayout("%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n", null, null, null); + PatternLayout.createLayout("%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n", null, null, null, null); final Appender appender = ConsoleAppender.createAppender(layout, null, "SYSTEM_OUT", "Console", "false", "true"); appender.start(); Index: core/src/main/java/org/apache/logging/log4j/core/layout/PatternLayout.java =================================================================== --- core/src/main/java/org/apache/logging/log4j/core/layout/PatternLayout.java (revision 1479389) +++ core/src/main/java/org/apache/logging/log4j/core/layout/PatternLayout.java (working copy) @@ -16,8 +16,6 @@ */ package org.apache.logging.log4j.core.layout; -import java.util.HashMap; -import java.util.Map; import org.apache.logging.log4j.core.LogEvent; import org.apache.logging.log4j.core.config.Configuration; import org.apache.logging.log4j.core.config.plugins.Plugin; @@ -33,7 +31,9 @@ import org.apache.logging.log4j.core.pattern.RegexReplacement; import java.nio.charset.Charset; +import java.util.HashMap; import java.util.List; +import java.util.Map; /** *

A flexible layout configurable with pattern string. The goal of this class @@ -92,6 +92,8 @@ private final RegexReplacement replace; + private final boolean handleExceptions; + /** * Constructs a EnhancedPatternLayout using the supplied conversion pattern. * @@ -99,15 +101,18 @@ * @param replace The regular expression to match. * @param pattern conversion pattern. * @param charset The character set. + * @param handleExceptions Whether or not exceptions should always be handled in this pattern (if {@code true}, + * exceptions will be written even if the pattern does not specify so). */ private PatternLayout(final Configuration config, final RegexReplacement replace, final String pattern, - final Charset charset) { + final Charset charset, boolean handleExceptions) { super(charset); this.replace = replace; this.conversionPattern = pattern; this.config = config; + this.handleExceptions = handleExceptions; final PatternParser parser = createPatternParser(config); - formatters = parser.parse(pattern == null ? DEFAULT_CONVERSION_PATTERN : pattern, true); + formatters = parser.parse(pattern == null ? DEFAULT_CONVERSION_PATTERN : pattern, this.handleExceptions); } /** @@ -123,7 +128,7 @@ return; } final PatternParser parser = createPatternParser(this.config); - formatters = parser.parse(pattern); + formatters = parser.parse(pattern, this.handleExceptions); } public String getConversionPattern() { @@ -190,18 +195,24 @@ /** * Create a pattern layout. + * * @param pattern The pattern. If not specified, defaults to DEFAULT_CONVERSION_PATTERN. * @param config The Configuration. Some Converters require access to the Interpolator. * @param replace A Regex replacement String. * @param charsetName The character set. + * @param suppressExceptions Whether or not exceptions should be suppressed in this pattern (defaults to no, which + * means exceptions will be written even if the pattern does not specify so). * @return The PatternLayout. */ @PluginFactory public static PatternLayout createLayout(@PluginAttr("pattern") final String pattern, @PluginConfiguration final Configuration config, @PluginElement("replace") final RegexReplacement replace, - @PluginAttr("charset") final String charsetName) { + @PluginAttr("charset") final String charsetName, + @PluginAttr("suppressExceptions") final String suppressExceptions) { final Charset charset = Charsets.getSupportedCharset(charsetName); - return new PatternLayout(config, replace, pattern == null ? DEFAULT_CONVERSION_PATTERN : pattern, charset); + boolean handleExceptions = suppressExceptions == null || !Boolean.parseBoolean(suppressExceptions); + return new PatternLayout(config, replace, pattern == null ? DEFAULT_CONVERSION_PATTERN : pattern, charset, + handleExceptions); } } Index: core/src/site/xdoc/index.xml =================================================================== --- core/src/site/xdoc/index.xml (revision 1479389) +++ core/src/site/xdoc/index.xml (working copy) @@ -26,29 +26,33 @@

- The Log4Jj 2 implementation provides the functional components - of the logging system. - Users are free to create their own plugins and include them - in the logging configuration. + The Log4Jj 2 implementation provides the functional components of the logging system. + Users are free to create their own plugins and include them in the logging configuration.

-

- Log4j 2 requires Java 6. - Some features may require optional - dependencies. These dependencies are - specified in the documentation for those features. -

-
    -
  • JSON configuration requires the Jackson JSON-processor.
  • -
  • Async Loggers require the LMAX Disruptor.
  • -
  • SMTPAppender requires Javax Mail.
  • -
  • JMSQueueAppender and JMSTopicAppender require a JMS implementation like - Apache ActiveMQ.
  • -
  • Windows color support requires Jansi.
  • -
+

+ Log4j 2 requires Java 6. Some features may require optional + dependencies. These dependencies are specified in the + documentation for those features. +

+
    +
  • JSON configuration requires the Jackson JSON-processor.
  • +
  • Async Loggers require the LMAX Disruptor.
  • +
  • SMTPAppender requires Javax Mail.
  • +
  • JMSQueueAppender and JMSTopicAppender require a JMS implementation like + Apache ActiveMQ.
  • +
  • Windows color support requires Jansi.
  • +
  • The JDBC Appender requires a JDBC driver for the database you choese to write events to.
  • +
  • The JPA Appender requires the Java Persistence API classes, a JPA provider implementation, + and a decorated entity that the user implements. It also requires an appropriate JDBC driver.
  • +
  • The NoSQL Appender with MongoDB provider requires the MongoDB Java Client driver.
  • +
  • The NoSQL Appender with Apache CouchDB provider requires the LightCouch CouchDB client library.
  • +
  • The NoSQL Appender can be customized with a user-supplied provider, which will require the + appropriate client library.
  • +
Index: core/src/test/java/org/apache/logging/log4j/core/appender/ConsoleAppenderTest.java =================================================================== --- core/src/test/java/org/apache/logging/log4j/core/appender/ConsoleAppenderTest.java (revision 1479389) +++ core/src/test/java/org/apache/logging/log4j/core/appender/ConsoleAppenderTest.java (working copy) @@ -51,7 +51,7 @@ @Test public void testFollow() { final PrintStream ps = System.out; - final Layout layout = PatternLayout.createLayout(null, null, null, null); + final Layout layout = PatternLayout.createLayout(null, null, null, null, null); final ConsoleAppender app = ConsoleAppender.createAppender(layout, null, "SYSTEM_OUT", "Console", "true", "false"); app.start(); final LogEvent event = new Log4jLogEvent("TestLogger", null, ConsoleAppenderTest.class.getName(), Level.INFO, Index: core/src/test/java/org/apache/logging/log4j/core/appender/FileAppenderTest.java =================================================================== --- core/src/test/java/org/apache/logging/log4j/core/appender/FileAppenderTest.java (revision 1479389) +++ core/src/test/java/org/apache/logging/log4j/core/appender/FileAppenderTest.java (working copy) @@ -137,7 +137,7 @@ } private static void writer(final boolean lock, final int count, final String name) throws Exception { - final Layout layout = PatternLayout.createLayout(PatternLayout.SIMPLE_CONVERSION_PATTERN, null, null, null); + final Layout layout = PatternLayout.createLayout(PatternLayout.SIMPLE_CONVERSION_PATTERN, null, null, null, null); final FileAppender app = FileAppender.createAppender(FILENAME, "true", Boolean.toString(lock), "test", "false", "false", "false", layout, null, "false", null, null); final Thread t = Thread.currentThread(); Index: core/src/test/java/org/apache/logging/log4j/core/appender/OutputStreamAppenderTest.java =================================================================== --- core/src/test/java/org/apache/logging/log4j/core/appender/OutputStreamAppenderTest.java (revision 1479389) +++ core/src/test/java/org/apache/logging/log4j/core/appender/OutputStreamAppenderTest.java (working copy) @@ -37,7 +37,7 @@ @Test public void testAppender() { - final Layout layout = PatternLayout.createLayout(null, null, null, null); + final Layout layout = PatternLayout.createLayout(null, null, null, null, null); final InMemoryAppender app = new InMemoryAppender("test", layout, null, false); final LogEvent event = new Log4jLogEvent("TestLogger", null, OutputStreamAppenderTest.class.getName(), Level.INFO, new SimpleMessage("Test"), null); Index: core/src/test/java/org/apache/logging/log4j/core/appender/db/AbstractDatabaseAppenderTest.java =================================================================== --- core/src/test/java/org/apache/logging/log4j/core/appender/db/AbstractDatabaseAppenderTest.java (revision 0) +++ core/src/test/java/org/apache/logging/log4j/core/appender/db/AbstractDatabaseAppenderTest.java (working copy) @@ -0,0 +1,159 @@ +/* + * 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; + +import org.apache.logging.log4j.core.Filter; +import org.apache.logging.log4j.core.LogEvent; +import org.junit.After; +import org.junit.Test; + +import static org.easymock.EasyMock.*; +import static org.junit.Assert.*; + +public class AbstractDatabaseAppenderTest { + private LocalAbstractDatabaseManager manager; + private LocalAbstractDatabaseAppender appender; + + public void setUp(String name) { + this.manager = createMockBuilder(LocalAbstractDatabaseManager.class) + .withConstructor(String.class, int.class) + .withArgs(name, 0) + .addMockedMethod("release") + .createStrictMock(); + + this.appender = createMockBuilder(LocalAbstractDatabaseAppender.class) + .withConstructor(String.class, Filter.class, boolean.class, LocalAbstractDatabaseManager.class) + .withArgs(name, null, true, this.manager) + .createStrictMock(); + } + + @After + public void tearDown() { + verify(this.manager, this.appender); + } + + @Test + public void testNameAndGetLayout01() { + this.setUp("testName01"); + + replay(this.manager, this.appender); + + assertEquals("The name is not correct.", "testName01", this.appender.getName()); + assertNull("The layout should always be null.", this.appender.getLayout()); + } + + @Test + public void testNameAndGetLayout02() { + this.setUp("anotherName02"); + + replay(this.manager, this.appender); + + assertEquals("The name is not correct.", "anotherName02", this.appender.getName()); + assertNull("The layout should always be null.", this.appender.getLayout()); + } + + @Test + public void testStartAndStop() { + this.setUp("name"); + + this.manager.connectInternal(); + expectLastCall(); + replay(this.manager, this.appender); + + this.appender.start(); + + verify(this.manager, this.appender); + reset(this.manager, this.appender); + this.manager.release(); + expectLastCall(); + replay(this.manager, this.appender); + + this.appender.stop(); + } + + @Test + public void testReplaceManager() { + this.setUp("name"); + + replay(this.manager, this.appender); + + LocalAbstractDatabaseManager manager = this.appender.getManager(); + + assertSame("The manager should be the same.", this.manager, manager); + + verify(this.manager, this.appender); + reset(this.manager, this.appender); + this.manager.release(); + expectLastCall(); + LocalAbstractDatabaseManager newManager = createMockBuilder(LocalAbstractDatabaseManager.class) + .withConstructor(String.class, int.class) + .withArgs("name", 0) + .addMockedMethod("release") + .createStrictMock(); + newManager.connectInternal(); + expectLastCall(); + replay(this.manager, this.appender, newManager); + + this.appender.replaceManager(newManager); + + verify(this.manager, this.appender, newManager); + reset(this.manager, this.appender, newManager); + newManager.release(); + expectLastCall(); + replay(this.manager, this.appender, newManager); + + this.appender.stop(); + + verify(newManager); + } + + @Test + public void testAppend() { + this.setUp("name"); + + LogEvent event1 = createStrictMock(LogEvent.class); + LogEvent event2 = createStrictMock(LogEvent.class); + + this.manager.writeInternal(same(event1)); + expectLastCall(); + replay(this.manager, this.appender); + + this.appender.append(event1); + + verify(this.manager, this.appender); + reset(this.manager, this.appender); + this.manager.writeInternal(same(event2)); + expectLastCall(); + replay(this.manager, this.appender); + + this.appender.append(event2); + } + + private static abstract class LocalAbstractDatabaseManager extends AbstractDatabaseManager { + public LocalAbstractDatabaseManager(String name, int bufferSize) { + super(name, bufferSize); + } + } + + private static abstract class LocalAbstractDatabaseAppender + extends AbstractDatabaseAppender { + public LocalAbstractDatabaseAppender(String name, Filter filter, boolean handleException, + LocalAbstractDatabaseManager manager) { + super(name, filter, handleException, manager); + } + } +} Index: core/src/test/java/org/apache/logging/log4j/core/appender/db/AbstractDatabaseManagerTest.java =================================================================== --- core/src/test/java/org/apache/logging/log4j/core/appender/db/AbstractDatabaseManagerTest.java (revision 0) +++ core/src/test/java/org/apache/logging/log4j/core/appender/db/AbstractDatabaseManagerTest.java (working copy) @@ -0,0 +1,248 @@ +/* + * 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; + +import org.apache.logging.log4j.core.LogEvent; +import org.junit.After; +import org.junit.Test; + +import static org.easymock.EasyMock.*; +import static org.junit.Assert.*; + +public class AbstractDatabaseManagerTest { + private AbstractDatabaseManager manager; + + public void setUp(String name, int buffer) { + this.manager = createMockBuilder(AbstractDatabaseManager.class) + .withConstructor(String.class, int.class) + .withArgs(name, buffer) + .addMockedMethod("release") + .createStrictMock(); + } + + @After + public void tearDown() { + verify(this.manager); + } + + @Test + public void testConnection01() { + this.setUp("testName01", 0); + + replay(this.manager); + + assertEquals("The name is not correct.", "testName01", this.manager.getName()); + assertFalse("The manager should not be connected.", this.manager.isConnected()); + + verify(this.manager); + reset(this.manager); + this.manager.connectInternal(); + expectLastCall(); + replay(this.manager); + + this.manager.connect(); + assertTrue("The manager should be connected now.", this.manager.isConnected()); + + verify(this.manager); + reset(this.manager); + this.manager.disconnectInternal(); + expectLastCall(); + replay(this.manager); + + this.manager.disconnect(); + assertFalse("The manager should not be connected anymore.", this.manager.isConnected()); + } + + @Test + public void testConnection02() { + this.setUp("anotherName02", 0); + + replay(this.manager); + + assertEquals("The name is not correct.", "anotherName02", this.manager.getName()); + assertFalse("The manager should not be connected.", this.manager.isConnected()); + + verify(this.manager); + reset(this.manager); + this.manager.connectInternal(); + expectLastCall(); + replay(this.manager); + + this.manager.connect(); + assertTrue("The manager should be connected now.", this.manager.isConnected()); + + verify(this.manager); + reset(this.manager); + this.manager.disconnectInternal(); + expectLastCall(); + replay(this.manager); + + this.manager.releaseSub(); + assertFalse("The manager should not be connected anymore.", this.manager.isConnected()); + } + + @Test + public void testToString01() { + this.setUp("someName01", 0); + + replay(this.manager); + + assertEquals("The string is not correct.", "someName01", this.manager.toString()); + } + + @Test + public void testToString02() { + this.setUp("bufferSize=12, anotherKey02=coolValue02", 12); + + replay(this.manager); + + assertEquals("The string is not correct.", "bufferSize=12, anotherKey02=coolValue02", this.manager.toString()); + } + + @Test + public void testBuffering01() { + this.setUp("name", 0); + + LogEvent event1 = createStrictMock(LogEvent.class); + LogEvent event2 = createStrictMock(LogEvent.class); + LogEvent event3 = createStrictMock(LogEvent.class); + + this.manager.connectInternal(); + expectLastCall(); + this.manager.writeInternal(same(event1)); + expectLastCall(); + replay(this.manager); + + this.manager.connect(); + + this.manager.write(event1); + + verify(this.manager); + reset(this.manager); + this.manager.writeInternal(same(event2)); + expectLastCall(); + replay(this.manager); + + this.manager.write(event2); + + verify(this.manager); + reset(this.manager); + this.manager.writeInternal(same(event3)); + expectLastCall(); + replay(this.manager); + + this.manager.write(event3); + } + + @Test + public void testBuffering02() { + this.setUp("name", 4); + + LogEvent event1 = createStrictMock(LogEvent.class); + LogEvent event2 = createStrictMock(LogEvent.class); + LogEvent event3 = createStrictMock(LogEvent.class); + LogEvent event4 = createStrictMock(LogEvent.class); + + this.manager.connectInternal(); + expectLastCall(); + replay(this.manager); + + this.manager.connect(); + + this.manager.write(event1); + this.manager.write(event2); + this.manager.write(event3); + + verify(this.manager); + reset(this.manager); + this.manager.writeInternal(same(event1)); + expectLastCall(); + this.manager.writeInternal(same(event2)); + expectLastCall(); + this.manager.writeInternal(same(event3)); + expectLastCall(); + this.manager.writeInternal(same(event4)); + expectLastCall(); + replay(this.manager); + + this.manager.write(event4); + } + + @Test + public void testBuffering03() { + this.setUp("name", 10); + + LogEvent event1 = createStrictMock(LogEvent.class); + LogEvent event2 = createStrictMock(LogEvent.class); + LogEvent event3 = createStrictMock(LogEvent.class); + + this.manager.connectInternal(); + expectLastCall(); + replay(this.manager); + + this.manager.connect(); + + this.manager.write(event1); + this.manager.write(event2); + this.manager.write(event3); + + verify(this.manager); + reset(this.manager); + this.manager.writeInternal(same(event1)); + expectLastCall(); + this.manager.writeInternal(same(event2)); + expectLastCall(); + this.manager.writeInternal(same(event3)); + expectLastCall(); + replay(this.manager); + + this.manager.flush(); + } + + @Test + public void testBuffering04() { + this.setUp("name", 10); + + LogEvent event1 = createStrictMock(LogEvent.class); + LogEvent event2 = createStrictMock(LogEvent.class); + LogEvent event3 = createStrictMock(LogEvent.class); + + this.manager.connectInternal(); + expectLastCall(); + replay(this.manager); + + this.manager.connect(); + + this.manager.write(event1); + this.manager.write(event2); + this.manager.write(event3); + + verify(this.manager); + reset(this.manager); + this.manager.writeInternal(same(event1)); + expectLastCall(); + this.manager.writeInternal(same(event2)); + expectLastCall(); + this.manager.writeInternal(same(event3)); + expectLastCall(); + this.manager.disconnectInternal(); + expectLastCall(); + replay(this.manager); + + this.manager.disconnect(); + } +} Index: core/src/test/java/org/apache/logging/log4j/core/appender/db/jdbc/ColumnConfigTest.java =================================================================== --- core/src/test/java/org/apache/logging/log4j/core/appender/db/jdbc/ColumnConfigTest.java (revision 0) +++ core/src/test/java/org/apache/logging/log4j/core/appender/db/jdbc/ColumnConfigTest.java (working copy) @@ -0,0 +1,155 @@ +/* + * 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 org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import static org.junit.Assert.*; + +public class ColumnConfigTest { + @Before + public void setUp() { + + } + + @After + public void tearDown() { + + } + + @Test + public void testNullNameNoConfig() { + ColumnConfig config = ColumnConfig.createColumnConfig(null, null, "%l", null, null); + + assertNull("The result should be null.", config); + } + + @Test + public void testPatternAndLiteralNoConfig() { + ColumnConfig config = ColumnConfig.createColumnConfig(null, "columnName01", "%l", "literal", null); + + assertNull("The result should be null.", config); + } + + @Test + public void testPatternAndDateNoConfig() { + ColumnConfig config = ColumnConfig.createColumnConfig(null, "columnName01", "%l", null, "true"); + + assertNull("The result should be null.", config); + } + + @Test + public void testLiteralAndDateNoConfig() { + ColumnConfig config = ColumnConfig.createColumnConfig(null, "columnName01", null, "literal", "true"); + + assertNull("The result should be null.", config); + } + + @Test + public void testNoSettingNoConfig01() { + ColumnConfig config = ColumnConfig.createColumnConfig(null, "columnName01", null, null, null); + + assertNull("The result should be null.", config); + } + + @Test + public void testNoSettingNoConfig02() { + ColumnConfig config = ColumnConfig.createColumnConfig(null, "columnName01", null, null, "false"); + + assertNull("The result should be null.", config); + } + + @Test + public void testNoSettingNoConfig03() { + ColumnConfig config = ColumnConfig.createColumnConfig(null, "columnName01", "", "", ""); + + assertNull("The result should be null.", config); + } + + @Test + public void testDateColumn01() { + ColumnConfig config = ColumnConfig.createColumnConfig(null, "columnName01", null, null, "true"); + + assertNotNull("The result should not be null.", config); + assertEquals("The column name is not correct.", "columnName01", config.getColumnName()); + assertNull("The pattern should be null.", config.getLayout()); + assertNull("The literal value should be null.", config.getLiteralValue()); + assertTrue("The timestamp flag should be true.", config.isEventTimestamp()); + } + + @Test + public void testDateColumn02() { + ColumnConfig config = ColumnConfig.createColumnConfig(null, "anotherName02", null, null, "true"); + + assertNotNull("The result should not be null.", config); + assertEquals("The column name is not correct.", "anotherName02", config.getColumnName()); + assertNull("The pattern should be null.", config.getLayout()); + assertNull("The literal value should be null.", config.getLiteralValue()); + assertTrue("The timestamp flag should be true.", config.isEventTimestamp()); + } + + @Test + public void testPatternColumn01() { + ColumnConfig config = ColumnConfig.createColumnConfig(null, "columnName01", "%l", null, null); + + assertNotNull("The result should not be null.", config); + assertEquals("The column name is not correct.", "columnName01", config.getColumnName()); + assertNotNull("The pattern should not be null.", config.getLayout()); + assertEquals("The pattern is not correct.", "%l", config.getLayout().toString()); + assertNull("The literal value should be null.", config.getLiteralValue()); + assertFalse("The timestamp flag should be false.", config.isEventTimestamp()); + } + + @Test + public void testPatternColumn02() { + ColumnConfig config = ColumnConfig.createColumnConfig(null, "anotherName02", "%X{id} %level", "", "false"); + + assertNotNull("The result should not be null.", config); + assertEquals("The column name is not correct.", "anotherName02", config.getColumnName()); + assertNotNull("The pattern should not be null.", config.getLayout()); + assertEquals("The pattern is not correct.", "%X{id} %level", config.getLayout().toString()); + assertNull("The literal value should be null.", config.getLiteralValue()); + assertFalse("The timestamp flag should be false.", config.isEventTimestamp()); + } + + @Test + public void testLiteralColumn01() { + ColumnConfig config = ColumnConfig.createColumnConfig(null, "columnName01", null, "literalValue01", null); + + assertNotNull("The result should not be null.", config); + assertEquals("The column name is not correct.", "columnName01", config.getColumnName()); + assertNull("The pattern should be null.", config.getLayout()); + assertNotNull("The literal value should be null.", config.getLiteralValue()); + assertEquals("The literal value is not correct.", "literalValue01", config.getLiteralValue()); + assertFalse("The timestamp flag should be false.", config.isEventTimestamp()); + } + + @Test + public void testLiteralColumn02() { + ColumnConfig config = + ColumnConfig.createColumnConfig(null, "anotherName02", null, "USER1.MY_SEQUENCE.NEXT", null); + + assertNotNull("The result should not be null.", config); + assertEquals("The column name is not correct.", "anotherName02", config.getColumnName()); + assertNull("The pattern should be null.", config.getLayout()); + assertNotNull("The literal value should be null.", config.getLiteralValue()); + assertEquals("The literal value is not correct.", "USER1.MY_SEQUENCE.NEXT", config.getLiteralValue()); + assertFalse("The timestamp flag should be false.", config.isEventTimestamp()); + } +} Index: core/src/test/java/org/apache/logging/log4j/core/appender/db/jdbc/DataSourceConnectionSourceTest.java =================================================================== --- core/src/test/java/org/apache/logging/log4j/core/appender/db/jdbc/DataSourceConnectionSourceTest.java (revision 0) +++ core/src/test/java/org/apache/logging/log4j/core/appender/db/jdbc/DataSourceConnectionSourceTest.java (working copy) @@ -0,0 +1,131 @@ +/* + * 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 org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.mockejb.jndi.MockContextFactory; + +import javax.naming.InitialContext; +import javax.naming.NamingException; +import javax.sql.DataSource; +import java.sql.Connection; +import java.sql.SQLException; + +import static org.easymock.EasyMock.*; +import static org.junit.Assert.*; + +public class DataSourceConnectionSourceTest { + private InitialContext context; + + @Before + public void setUp() throws NamingException { + MockContextFactory.setAsInitial(); + + this.context = new InitialContext(); + this.context.createSubcontext("java:"); + this.context.createSubcontext("java:/comp"); + this.context.createSubcontext("java:/comp/env"); + this.context.createSubcontext("java:/comp/env/jdbc"); + } + + @After + public void tearDown() { + MockContextFactory.revertSetAsInitial(); + } + + @Test + public void testNoJndiName01() { + DataSourceConnectionSource source = DataSourceConnectionSource.createConnectionSource(null); + + assertNull("The connection source should be null.", source); + } + + @Test + public void testNoJndiName02() { + DataSourceConnectionSource source = DataSourceConnectionSource.createConnectionSource(""); + + assertNull("The connection source should be null.", source); + } + + @Test + public void testNoDataSource() { + DataSourceConnectionSource source = + DataSourceConnectionSource.createConnectionSource("java:/comp/env/jdbc/Logging01"); + + assertNull("The connection source should be null.", source); + } + + @Test + public void testDataSource01() throws NamingException, SQLException { + DataSource dataSource = createStrictMock(DataSource.class); + Connection connection1 = createStrictMock(Connection.class); + Connection connection2 = createStrictMock(Connection.class); + + expect(dataSource.getConnection()).andReturn(connection1); + expect(dataSource.getConnection()).andReturn(connection2); + replay(dataSource, connection1, connection2); + + this.context.bind("java:/comp/env/jdbc/Logging01", dataSource); + + DataSourceConnectionSource source = + DataSourceConnectionSource.createConnectionSource("java:/comp/env/jdbc/Logging01"); + + assertNotNull("The connection source should not be null.", source); + assertEquals("The toString value is not correct.", + "dataSource{ name=java:/comp/env/jdbc/Logging01, value=" + dataSource + " }", + source.toString()); + assertSame("The connection is not correct (1).", connection1, source.getConnection()); + assertSame("The connection is not correct (2).", connection2, source.getConnection()); + + source = DataSourceConnectionSource.createConnectionSource("java:/comp/env/jdbc/Logging02"); + + assertNull("The connection source should be null now.", source); + + verify(dataSource, connection1, connection2); + } + + @Test + public void testDataSource02() throws NamingException, SQLException { + DataSource dataSource = createStrictMock(DataSource.class); + Connection connection1 = createStrictMock(Connection.class); + Connection connection2 = createStrictMock(Connection.class); + + expect(dataSource.getConnection()).andReturn(connection1); + expect(dataSource.getConnection()).andReturn(connection2); + replay(dataSource, connection1, connection2); + + this.context.bind("java:/comp/env/jdbc/Logging02", dataSource); + + DataSourceConnectionSource source = + DataSourceConnectionSource.createConnectionSource("java:/comp/env/jdbc/Logging02"); + + assertNotNull("The connection source should not be null.", source); + assertEquals("The toString value is not correct.", + "dataSource{ name=java:/comp/env/jdbc/Logging02, value=" + dataSource + " }", + source.toString()); + assertSame("The connection is not correct (1).", connection1, source.getConnection()); + assertSame("The connection is not correct (2).", connection2, source.getConnection()); + + source = DataSourceConnectionSource.createConnectionSource("java:/comp/env/jdbc/Logging01"); + + assertNull("The connection source should be null now.", source); + + verify(dataSource, connection1, connection2); + } +} Index: core/src/test/java/org/apache/logging/log4j/core/appender/db/jdbc/DriverManagerConnectionSourceTest.java =================================================================== --- core/src/test/java/org/apache/logging/log4j/core/appender/db/jdbc/DriverManagerConnectionSourceTest.java (revision 0) +++ core/src/test/java/org/apache/logging/log4j/core/appender/db/jdbc/DriverManagerConnectionSourceTest.java (working copy) @@ -0,0 +1,212 @@ +/* + * 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 org.apache.logging.log4j.core.helpers.NameUtil; +import org.easymock.Capture; +import org.easymock.CaptureType; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import java.sql.Connection; +import java.sql.Driver; +import java.sql.DriverManager; +import java.sql.SQLException; +import java.util.List; +import java.util.Properties; + +import static org.easymock.EasyMock.*; +import static org.junit.Assert.*; + +public class DriverManagerConnectionSourceTest { + private Driver driver; + + @Before + public void setUp() throws SQLException { + this.driver = createStrictMock(Driver.class); + replay(this.driver); + DriverManager.registerDriver(driver); + verify(this.driver); + reset(this.driver); + } + + @After + public void tearDown() throws SQLException { + DriverManager.deregisterDriver(this.driver); + verify(this.driver); + } + + @Test + public void testNoUrl01() { + replay(this.driver); + + DriverManagerConnectionSource source = + DriverManagerConnectionSource.createConnectionSource(null, "username01", "password01"); + + assertNull("The connection source should be null.", source); + } + + @Test + public void testNoUrl02() { + replay(this.driver); + + DriverManagerConnectionSource source = + DriverManagerConnectionSource.createConnectionSource("", "username01", "password01"); + + assertNull("The connection source should be null.", source); + } + + @Test + public void testInvalidUrl() throws SQLException { + expect(this.driver.acceptsURL("log4j2:test:appender:jdbc:url://localhost:2737/database")).andReturn(false); + replay(this.driver); + + DriverManagerConnectionSource source = DriverManagerConnectionSource.createConnectionSource( + "log4j2:test:appender:jdbc:url://localhost:2737/database", "username01", "password01" + ); + + assertNull("The connection source should be null.", source); + } + + @Test + public void testValidUrlUsernamePassword() throws SQLException { + Connection connection1 = createStrictMock(Connection.class); + Connection connection2 = createStrictMock(Connection.class); + + Capture capture = new Capture(CaptureType.ALL); + + expect(this.driver.acceptsURL("log4j2:test:appender:jdbc:url://localhost:2737/database")).andReturn(true); + expect(this.driver.connect(eq("log4j2:test:appender:jdbc:url://localhost:2737/database"), capture(capture))) + .andReturn(connection1); + expect(this.driver.connect(eq("log4j2:test:appender:jdbc:url://localhost:2737/database"), capture(capture))) + .andReturn(connection2); + replay(this.driver, connection1, connection2); + + DriverManagerConnectionSource source = DriverManagerConnectionSource.createConnectionSource( + "log4j2:test:appender:jdbc:url://localhost:2737/database", "username01", "password01" + ); + + assertNotNull("The connection source should not be null.", source); + assertEquals("The toString value is not correct.", + "driverManager{ url=log4j2:test:appender:jdbc:url://localhost:2737/database, username=username01, " + + "passwordHash=" + NameUtil.md5("password01" + DriverManagerConnectionSource.class.getName()) + + " }", + source.toString()); + assertSame("The connection is not correct (1).", connection1, source.getConnection()); + assertSame("The connection is not correct (2).", connection2, source.getConnection()); + + List captured = capture.getValues(); + assertEquals("The number of captured properties is not correct.", 2, captured.size()); + + Properties properties1 = captured.get(0); + assertNotNull("The properties object should not be null (1).", properties1); + assertEquals("The username is not correct (1).", "username01", properties1.getProperty("user")); + assertEquals("The password is not correct (1).", "password01", properties1.getProperty("password")); + + Properties properties2 = captured.get(1); + assertNotNull("The properties object should not be null (2).", properties2); + assertEquals("The username is not correct (2).", "username01", properties2.getProperty("user")); + assertEquals("The password is not correct (2).", "password01", properties2.getProperty("password")); + + verify(connection1, connection2); + } + + @Test + public void testValidUrlNoUsernamePassword01() throws SQLException { + Connection connection1 = createStrictMock(Connection.class); + Connection connection2 = createStrictMock(Connection.class); + + Capture capture = new Capture(CaptureType.ALL); + + expect(this.driver.acceptsURL("log4j2:test:appender:jdbc:url://localhost:2737/anotherDb")).andReturn(true); + expect(this.driver.connect(eq("log4j2:test:appender:jdbc:url://localhost:2737/anotherDb"), capture(capture))) + .andReturn(connection1); + expect(this.driver.connect(eq("log4j2:test:appender:jdbc:url://localhost:2737/anotherDb"), capture(capture))) + .andReturn(connection2); + replay(this.driver, connection1, connection2); + + DriverManagerConnectionSource source = DriverManagerConnectionSource.createConnectionSource( + "log4j2:test:appender:jdbc:url://localhost:2737/anotherDb", null, null + ); + + assertNotNull("The connection source should not be null.", source); + assertEquals("The toString value is not correct.", + "driverManager{ url=log4j2:test:appender:jdbc:url://localhost:2737/anotherDb, username=null, " + + "passwordHash=" + NameUtil.md5(null + DriverManagerConnectionSource.class.getName()) + " }", + source.toString()); + assertSame("The connection is not correct (1).", connection1, source.getConnection()); + assertSame("The connection is not correct (2).", connection2, source.getConnection()); + + List captured = capture.getValues(); + assertEquals("The number of captured properties is not correct.", 2, captured.size()); + + Properties properties1 = captured.get(0); + assertNotNull("The properties object should not be null (1).", properties1); + assertNull("The username should be null (1).", properties1.getProperty("user")); + assertNull("The password should be null (1).", properties1.getProperty("password")); + + Properties properties2 = captured.get(1); + assertNotNull("The properties object should not be null (2).", properties2); + assertNull("The username should be null (2).", properties2.getProperty("user")); + assertNull("The password should be null (2).", properties2.getProperty("password")); + + verify(connection1, connection2); + } + + @Test + public void testValidUrlNoUsernamePassword02() throws SQLException { + Connection connection1 = createStrictMock(Connection.class); + Connection connection2 = createStrictMock(Connection.class); + + Capture capture = new Capture(CaptureType.ALL); + + expect(this.driver.acceptsURL("log4j2:test:appender:jdbc:url://localhost:2737/finalDb")).andReturn(true); + expect(this.driver.connect(eq("log4j2:test:appender:jdbc:url://localhost:2737/finalDb"), capture(capture))) + .andReturn(connection1); + expect(this.driver.connect(eq("log4j2:test:appender:jdbc:url://localhost:2737/finalDb"), capture(capture))) + .andReturn(connection2); + replay(this.driver, connection1, connection2); + + DriverManagerConnectionSource source = DriverManagerConnectionSource.createConnectionSource( + "log4j2:test:appender:jdbc:url://localhost:2737/finalDb", " ", "" + ); + + assertNotNull("The connection source should not be null.", source); + assertEquals("The toString value is not correct.", + "driverManager{ url=log4j2:test:appender:jdbc:url://localhost:2737/finalDb, username=null, " + + "passwordHash=" + NameUtil.md5(null + DriverManagerConnectionSource.class.getName()) + " }", + source.toString()); + assertSame("The connection is not correct (1).", connection1, source.getConnection()); + assertSame("The connection is not correct (2).", connection2, source.getConnection()); + + List captured = capture.getValues(); + assertEquals("The number of captured properties is not correct.", 2, captured.size()); + + Properties properties1 = captured.get(0); + assertNotNull("The properties object should not be null (1).", properties1); + assertNull("The username should be null (1).", properties1.getProperty("user")); + assertNull("The password should be null (1).", properties1.getProperty("password")); + + Properties properties2 = captured.get(1); + assertNotNull("The properties object should not be null (2).", properties2); + assertNull("The username should be null (2).", properties2.getProperty("user")); + assertNull("The password should be null (2).", properties2.getProperty("password")); + + verify(connection1, connection2); + } +} Index: core/src/test/java/org/apache/logging/log4j/core/appender/db/jdbc/FactoryMethodConnectionSourceTest.java =================================================================== --- core/src/test/java/org/apache/logging/log4j/core/appender/db/jdbc/FactoryMethodConnectionSourceTest.java (revision 0) +++ core/src/test/java/org/apache/logging/log4j/core/appender/db/jdbc/FactoryMethodConnectionSourceTest.java (working copy) @@ -0,0 +1,159 @@ +/* + * 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 org.junit.After; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.Test; + +import javax.sql.DataSource; +import java.sql.Connection; +import java.sql.SQLException; + +import static org.easymock.EasyMock.*; +import static org.junit.Assert.*; + +public class FactoryMethodConnectionSourceTest { + private static ThreadLocal holder = new ThreadLocal(); + + @Before + public void setUp() { + + } + + @After + public void tearDown() { + holder.remove(); + } + + @AfterClass + public static void tearDownClass() { + holder.remove(); + holder = null; + } + + @Test + public void testNoClassName() { + FactoryMethodConnectionSource source = FactoryMethodConnectionSource.createConnectionSource(null, "method"); + + assertNull("The connection source should be null.", source); + } + + @Test + public void testNoMethodName() { + FactoryMethodConnectionSource source = FactoryMethodConnectionSource.createConnectionSource("someClass", null); + + assertNull("The connection source should be null.", source); + } + + @Test + public void testBadClassName() { + FactoryMethodConnectionSource source = + FactoryMethodConnectionSource.createConnectionSource("org.apache.BadClass", "factoryMethod"); + + assertNull("The connection source should be null.", source); + } + + @Test + public void testBadMethodName() { + FactoryMethodConnectionSource source = + FactoryMethodConnectionSource.createConnectionSource(this.getClass().getName(), "factoryMethod"); + + assertNull("The connection source should be null.", source); + } + + @Test + public void testBadReturnType() { + FactoryMethodConnectionSource source = FactoryMethodConnectionSource.createConnectionSource( + BadReturnTypeFactory.class.getName(), "factoryMethod01" + ); + + assertNull("The connection source should be null.", source); + } + + @Test + public void testDataSourceReturnType() throws SQLException { + DataSource dataSource = createStrictMock(DataSource.class); + Connection connection1 = createStrictMock(Connection.class); + Connection connection2 = createStrictMock(Connection.class); + + expect(dataSource.getConnection()).andReturn(connection1); + expect(dataSource.getConnection()).andReturn(connection2); + replay(dataSource, connection1, connection2); + + holder.set(dataSource); + + FactoryMethodConnectionSource source = FactoryMethodConnectionSource.createConnectionSource( + DataSourceFactory.class.getName(), "factoryMethod02" + ); + + assertNotNull("The connection source should not be null.", source); + assertEquals("The toString value is not correct.", + "factory{ public static javax.sql.DataSource[" + dataSource + "] " + DataSourceFactory.class.getName() + + ".factoryMethod02() }", + source.toString()); + assertSame("The connection is not correct (1).", connection1, source.getConnection()); + assertSame("The connection is not correct (2).", connection2, source.getConnection()); + + verify(connection1, connection2); + } + + @Test + public void testConnectionReturnType() throws SQLException { + Connection connection = createStrictMock(Connection.class); + + replay(connection); + + holder.set(connection); + + FactoryMethodConnectionSource source = FactoryMethodConnectionSource.createConnectionSource( + ConnectionFactory.class.getName(), "anotherMethod03" + ); + + assertNotNull("The connection source should not be null.", source); + assertEquals("The toString value is not correct.", + "factory{ public static java.sql.Connection " + ConnectionFactory.class.getName() + + ".anotherMethod03() }", + source.toString()); + assertSame("The connection is not correct (1).", connection, source.getConnection()); + assertSame("The connection is not correct (2).", connection, source.getConnection()); + + verify(connection); + } + + @SuppressWarnings("unused") + protected static final class BadReturnTypeFactory { + public static String factoryMethod01() { + return "hello"; + } + } + + @SuppressWarnings("unused") + protected static final class DataSourceFactory { + public static DataSource factoryMethod02() { + return (DataSource)holder.get(); + } + } + + @SuppressWarnings("unused") + protected static final class ConnectionFactory { + public static Connection anotherMethod03() { + return (Connection)holder.get(); + } + } +} Index: core/src/test/java/org/apache/logging/log4j/core/appender/db/jdbc/JDBCAppenderTest.java =================================================================== --- core/src/test/java/org/apache/logging/log4j/core/appender/db/jdbc/JDBCAppenderTest.java (revision 0) +++ core/src/test/java/org/apache/logging/log4j/core/appender/db/jdbc/JDBCAppenderTest.java (working copy) @@ -0,0 +1,195 @@ +/* + * 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 org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.core.Appender; +import org.apache.logging.log4j.core.LoggerContext; +import org.apache.logging.log4j.core.config.DefaultConfiguration; +import org.apache.logging.log4j.core.config.XMLConfigurationFactory; +import org.apache.logging.log4j.status.StatusLogger; +import org.junit.After; +import org.junit.Test; + +import java.io.ByteArrayOutputStream; +import java.io.PrintWriter; +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.Map; + +import static org.junit.Assert.*; + +public class JDBCAppenderTest { + private Connection connection; + + public void setUp(String tableName, String configFileName) throws SQLException { + this.connection = DriverManager.getConnection("jdbc:hsqldb:mem:Log4j", "sa", ""); + + Statement statement = this.connection.createStatement(); + statement.executeUpdate( + "CREATE TABLE " + tableName + " ( " + + "id INTEGER IDENTITY, eventDate DATETIME, literalColumn VARCHAR(255), level VARCHAR(10), " + + "logger VARCHAR(255), message VARCHAR(1024), exception VARCHAR(1048576)" + + " )" + ); + statement.close(); + + System.setProperty(XMLConfigurationFactory.CONFIGURATION_FILE_PROPERTY, + "org/apache/logging/log4j/core/appender/db/jdbc/" + configFileName); + final LoggerContext context = (LoggerContext) LogManager.getContext(false); + if (context.getConfiguration() instanceof DefaultConfiguration) { + context.reconfigure(); + } + StatusLogger.getLogger().reset(); + } + + @After + public void tearDown() throws SQLException { + final LoggerContext context = (LoggerContext) LogManager.getContext(false); + try { + final Map> list = context.getConfiguration().getAppenders(); + final Appender appender = list.get("databaseAppender"); + assertNotNull("The appender should not be null.", appender); + assertTrue("The appender should be a JDBCAppender.", appender instanceof JDBCAppender); + ((JDBCAppender) appender).getManager().release(); + } finally { + System.clearProperty(XMLConfigurationFactory.CONFIGURATION_FILE_PROPERTY); + context.reconfigure(); + StatusLogger.getLogger().reset(); + + Statement statement = null; + try { + statement = this.connection.createStatement(); + statement.execute("SHUTDOWN"); + } finally { + try { + if (statement != null) { + statement.close(); + } + } catch (SQLException ignore) { /* */ } + } + + this.connection.close(); + } + } + + @Test + public void testDriverManagerConfig() throws SQLException { + this.setUp("dmLogEntry", "log4j2-driver-manager.xml"); + + RuntimeException exception = new RuntimeException("Hello, world!"); + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + PrintWriter writer = new PrintWriter(outputStream); + exception.printStackTrace(writer); + writer.close(); + String stackTrace = outputStream.toString(); + + long millis = System.currentTimeMillis(); + + Logger logger = LogManager.getLogger(this.getClass().getName() + ".testDriverManagerConfig"); + logger.info("Test my message 01."); + logger.warn("This is another message 02.", exception); + + Statement statement = this.connection.createStatement(); + ResultSet resultSet = statement.executeQuery("SELECT * FROM dmLogEntry ORDER BY id"); + + assertTrue("There should be at least one row.", resultSet.next()); + + long date = resultSet.getTimestamp("eventDate").getTime(); + 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).", "Literal Value Test String", + resultSet.getString("literalColumn")); + assertEquals("The level column is not correct (1).", "INFO", resultSet.getString("level")); + assertEquals("The logger column is not correct (1).", logger.getName(), resultSet.getString("logger")); + assertEquals("The message column is not correct (1).", "Test my message 01.", resultSet.getString("message")); + assertEquals("The exception column is not correct (1).", "", resultSet.getString("exception")); + + assertTrue("There should be two rows.", resultSet.next()); + + date = resultSet.getTimestamp("eventDate").getTime(); + 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).", "Literal Value Test String", + resultSet.getString("literalColumn")); + assertEquals("The level column is not correct (2).", "WARN", resultSet.getString("level")); + assertEquals("The logger column is not correct (2).", logger.getName(), resultSet.getString("logger")); + assertEquals("The message column is not correct (2).", "This is another message 02.", + resultSet.getString("message")); + assertEquals("The exception column is not correct (2).", stackTrace, resultSet.getString("exception")); + + assertFalse("There should not be three rows.", resultSet.next()); + } + + @Test + public void testFactoryMethodConfig() throws SQLException { + this.setUp("fmLogEntry", "log4j2-factory-method.xml"); + + SQLException exception = new SQLException("Some other error message!"); + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + PrintWriter writer = new PrintWriter(outputStream); + exception.printStackTrace(writer); + writer.close(); + String stackTrace = outputStream.toString(); + + long millis = System.currentTimeMillis(); + + Logger logger = LogManager.getLogger(this.getClass().getName() + ".testFactoryMethodConfig"); + logger.debug("Factory logged message 01."); + logger.error("Error from factory 02.", exception); + + Statement statement = this.connection.createStatement(); + ResultSet resultSet = statement.executeQuery("SELECT * FROM fmLogEntry ORDER BY id"); + + assertTrue("There should be at least one row.", resultSet.next()); + + long date = resultSet.getTimestamp("eventDate").getTime(); + 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", + resultSet.getString("literalColumn")); + assertEquals("The level column is not correct (1).", "DEBUG", resultSet.getString("level")); + assertEquals("The logger column is not correct (1).", logger.getName(), resultSet.getString("logger")); + assertEquals("The message column is not correct (1).", "Factory logged message 01.", + resultSet.getString("message")); + assertEquals("The exception column is not correct (1).", "", resultSet.getString("exception")); + + assertTrue("There should be two rows.", resultSet.next()); + + date = resultSet.getTimestamp("eventDate").getTime(); + 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", + resultSet.getString("literalColumn")); + assertEquals("The level column is not correct (2).", "ERROR", resultSet.getString("level")); + assertEquals("The logger column is not correct (2).", logger.getName(), resultSet.getString("logger")); + assertEquals("The message column is not correct (2).", "Error from factory 02.", + resultSet.getString("message")); + assertEquals("The exception column is not correct (2).", stackTrace, resultSet.getString("exception")); + + assertFalse("There should not be three rows.", resultSet.next()); + } + + @SuppressWarnings("unused") + public static Connection testFactoryMethodConfigMethod() throws SQLException { + return DriverManager.getConnection("jdbc:hsqldb:mem:Log4j;ifexists=true", "sa", ""); + } +} Index: core/src/test/java/org/apache/logging/log4j/core/appender/db/jpa/JPAAppenderTest.java =================================================================== --- core/src/test/java/org/apache/logging/log4j/core/appender/db/jpa/JPAAppenderTest.java (revision 0) +++ core/src/test/java/org/apache/logging/log4j/core/appender/db/jpa/JPAAppenderTest.java (working copy) @@ -0,0 +1,223 @@ +/* + * 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.jpa; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.core.Appender; +import org.apache.logging.log4j.core.LogEvent; +import org.apache.logging.log4j.core.LoggerContext; +import org.apache.logging.log4j.core.config.DefaultConfiguration; +import org.apache.logging.log4j.core.config.XMLConfigurationFactory; +import org.apache.logging.log4j.status.StatusLogger; +import org.junit.Test; + +import java.io.ByteArrayOutputStream; +import java.io.PrintWriter; +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.Map; + +import static org.junit.Assert.*; + +public class JPAAppenderTest { + private Connection connection; + + public void setUp(String configFileName) throws SQLException { + this.connection = DriverManager.getConnection("jdbc:hsqldb:mem:Log4j", "sa", ""); + + Statement statement = this.connection.createStatement(); + statement.executeUpdate( + "CREATE TABLE jpaLogEntry ( " + + "id INTEGER IDENTITY, eventDate DATETIME, level VARCHAR(10), logger VARCHAR(255), " + + "message VARCHAR(1024), exception VARCHAR(1048576)" + + " )" + ); + statement.close(); + + System.setProperty(XMLConfigurationFactory.CONFIGURATION_FILE_PROPERTY, + "org/apache/logging/log4j/core/appender/db/jpa/" + configFileName); + final LoggerContext context = (LoggerContext) LogManager.getContext(false); + if (context.getConfiguration() instanceof DefaultConfiguration) { + context.reconfigure(); + } + StatusLogger.getLogger().reset(); + } + + public void tearDown() throws SQLException { + final LoggerContext context = (LoggerContext) LogManager.getContext(false); + try { + final Map> list = context.getConfiguration().getAppenders(); + final Appender appender = list.get("databaseAppender"); + assertNotNull("The appender should not be null.", appender); + assertTrue("The appender should be a JDBCAppender.", appender instanceof JPAAppender); + ((JPAAppender) appender).getManager().release(); + } finally { + System.clearProperty(XMLConfigurationFactory.CONFIGURATION_FILE_PROPERTY); + context.reconfigure(); + StatusLogger.getLogger().reset(); + + Statement statement = null; + try { + statement = this.connection.createStatement(); + statement.execute("SHUTDOWN"); + } finally { + try { + if (statement != null) { + statement.close(); + } + } catch (SQLException ignore) { /* */ } + } + + this.connection.close(); + } + } + + @Test + public void testNoEntityClassName() { + JPAAppender appender = JPAAppender.createAppender("name", null, null, null, null, "jpaAppenderTestUnit"); + + assertNull("The appender should be null.", appender); + } + + @Test + public void testNoPersistenceUnitName() { + JPAAppender appender = JPAAppender.createAppender("name", null, null, null, TestEntity.class.getName(), null); + + assertNull("The appender should be null.", appender); + } + + @Test + public void testBadEntityClassName() { + JPAAppender appender = + JPAAppender.createAppender("name", null, null, null, "com.foo.Bar", "jpaAppenderTestUnit"); + + assertNull("The appender should be null.", appender); + } + + @Test + public void testNonLogEventEntity() { + JPAAppender appender = + JPAAppender.createAppender("name", null, null, null, Object.class.getName(), "jpaAppenderTestUnit"); + + assertNull("The appender should be null.", appender); + } + + @Test + public void testBadConstructorEntity01() { + JPAAppender appender = JPAAppender.createAppender( + "name", null, null, null, BadConstructorEntity1.class.getName(), "jpaAppenderTestUnit" + ); + + assertNull("The appender should be null.", appender); + } + + @Test + public void testBadConstructorEntity02() { + JPAAppender appender = JPAAppender.createAppender( + "name", null, null, null, BadConstructorEntity2.class.getName(), "jpaAppenderTestUnit" + ); + + assertNull("The appender should be null.", appender); + } + + @Test + public void testConfiguredAppender() throws SQLException { + try { + this.setUp("log4j2-jpa.xml"); + + RuntimeException exception = new RuntimeException("Hello, world!"); + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + PrintWriter writer = new PrintWriter(outputStream); + exception.printStackTrace(writer); + writer.close(); + String stackTrace = outputStream.toString(); + + long millis = System.currentTimeMillis(); + + Logger logger1 = LogManager.getLogger(this.getClass().getName() + ".testConfiguredAppender"); + Logger logger2 = LogManager.getLogger(this.getClass().getName() + ".testConfiguredAppenderAgain"); + logger1.info("Test my message 01."); + logger1.error("This is another message 02.", exception); + logger2.warn("A final warning has been issued."); + + Statement statement = this.connection.createStatement(); + ResultSet resultSet = statement.executeQuery("SELECT * FROM jpaLogEntry ORDER BY id"); + + assertTrue("There should be at least one row.", resultSet.next()); + + long date = resultSet.getTimestamp("eventDate").getTime(); + 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 level column is not correct (1).", "INFO", resultSet.getString("level")); + assertEquals("The logger column is not correct (1).", logger1.getName(), resultSet.getString("logger")); + assertEquals("The message column is not correct (1).", "Test my message 01.", resultSet.getString("message")); + assertNull("The exception column is not correct (1).", resultSet.getString("exception")); + + assertTrue("There should be at least two rows.", resultSet.next()); + + date = resultSet.getTimestamp("eventDate").getTime(); + 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 level column is not correct (2).", "ERROR", resultSet.getString("level")); + assertEquals("The logger column is not correct (2).", logger1.getName(), resultSet.getString("logger")); + assertEquals("The message column is not correct (2).", "This is another message 02.", + resultSet.getString("message")); + assertEquals("The exception column is not correct (2).", stackTrace, resultSet.getString("exception")); + + assertTrue("There should be three rows.", resultSet.next()); + + date = resultSet.getTimestamp("eventDate").getTime(); + assertTrue("The date should be later than pre-logging (3).", date >= millis); + assertTrue("The date should be earlier than now (3).", date <= System.currentTimeMillis()); + assertEquals("The level column is not correct (3).", "WARN", resultSet.getString("level")); + assertEquals("The logger column is not correct (3).", logger2.getName(), resultSet.getString("logger")); + assertEquals("The message column is not correct (3).", "A final warning has been issued.", + resultSet.getString("message")); + assertNull("The exception column is not correct (3).", resultSet.getString("exception")); + + assertFalse("There should not be four rows.", resultSet.next()); + } finally { + this.tearDown(); + } + } + + @SuppressWarnings("unused") + public static class BadConstructorEntity1 extends TestEntity { + private static final long serialVersionUID = 1L; + + public BadConstructorEntity1(LogEvent wrappedEvent) { + super(wrappedEvent); + } + } + + @SuppressWarnings("unused") + public static class BadConstructorEntity2 extends TestEntity { + private static final long serialVersionUID = 1L; + + public BadConstructorEntity2() { + super(null); + } + + public BadConstructorEntity2(LogEvent wrappedEvent, String badParameter) { + super(wrappedEvent); + } + } +} Index: core/src/test/java/org/apache/logging/log4j/core/appender/db/jpa/TestEntity.java =================================================================== --- core/src/test/java/org/apache/logging/log4j/core/appender/db/jpa/TestEntity.java (revision 0) +++ core/src/test/java/org/apache/logging/log4j/core/appender/db/jpa/TestEntity.java (working copy) @@ -0,0 +1,174 @@ +/* + * 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.jpa; + +import org.apache.logging.log4j.Level; +import org.apache.logging.log4j.Marker; +import org.apache.logging.log4j.ThreadContext; +import org.apache.logging.log4j.core.LogEvent; +import org.apache.logging.log4j.message.Message; + +import javax.persistence.Basic; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.EnumType; +import javax.persistence.Enumerated; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.Table; +import javax.persistence.Temporal; +import javax.persistence.TemporalType; +import javax.persistence.Transient; +import java.io.ByteArrayOutputStream; +import java.io.PrintWriter; +import java.util.Date; +import java.util.Map; + +@Entity +@Table(name="jpaLogEntry") +@SuppressWarnings("unused") +public class TestEntity extends LogEventWrapperEntity { + private static final long serialVersionUID = 1L; + + private long id = 0L; + + public TestEntity() { + super(null); + } + + public TestEntity(LogEvent wrappedEvent) { + super(wrappedEvent); + } + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "id") + public long getId() { + return this.id; + } + + public void setId(long id) { + this.id = id; + } + + @Override + @Enumerated(EnumType.STRING) + @Column(name = "level") + public Level getLevel() { + return getWrappedEvent().getLevel(); + } + + @Override + @Basic + @Column(name = "logger") + public String getLoggerName() { + return getWrappedEvent().getLoggerName(); + } + + @Override + @Transient + public StackTraceElement getSource() { + return getWrappedEvent().getSource(); + } + + @Basic + @Column(name = "message") + public String getMessageString() { + return this.getMessage().getFormattedMessage(); + } + + public void setMessageString(String messageString) { + // this entity is write-only + } + + @Override + @Transient + public Message getMessage() { + return getWrappedEvent().getMessage(); + } + + @Override + @Transient + public Marker getMarker() { + return getWrappedEvent().getMarker(); + } + + @Override + @Transient + public String getThreadName() { + return getWrappedEvent().getThreadName(); + } + + @Temporal(TemporalType.TIMESTAMP) + @Column(name = "eventDate") + public Date getEventDate() { + return new Date(this.getMillis()); + } + + public void setEventDate(Date date) { + // this entity is write-only + } + + @Override + @Transient + public long getMillis() { + return getWrappedEvent().getMillis(); + } + + @Basic + @Column(name = "exception") + @SuppressWarnings("ThrowableResultOfMethodCallIgnored") + public String getException() { + if (this.getThrown() != null) { + ByteArrayOutputStream stream = new ByteArrayOutputStream(); + PrintWriter writer = new PrintWriter(stream); + this.getThrown().printStackTrace(writer); + writer.close(); + return stream.toString(); + } + return null; + } + + public void setException(String exception) { + // this entity is write-only + } + + @Override + @Transient + public Throwable getThrown() { + return getWrappedEvent().getThrown(); + } + + @Override + @Transient + public Map getContextMap() { + return getWrappedEvent().getContextMap(); + } + + @Override + @Transient + public ThreadContext.ContextStack getContextStack() { + return getWrappedEvent().getContextStack(); + } + + @Override + @Transient + public String getFQCN() { + return getWrappedEvent().getFQCN(); + } +} Index: core/src/test/java/org/apache/logging/log4j/core/appender/db/nosql/NoSQLAppenderTest.java =================================================================== --- core/src/test/java/org/apache/logging/log4j/core/appender/db/nosql/NoSQLAppenderTest.java (revision 0) +++ core/src/test/java/org/apache/logging/log4j/core/appender/db/nosql/NoSQLAppenderTest.java (working copy) @@ -0,0 +1,82 @@ +/* + * 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.nosql; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import static org.easymock.EasyMock.*; +import static org.junit.Assert.*; + +public class NoSQLAppenderTest { + @Before + public void setUp() { + + } + + @After + public void tearDown() { + + } + + @Test + public void testNoProvider() { + NoSQLAppender appender = NoSQLAppender.createAppender("myName01", null, null, null, null); + + assertNull("The appender should be null.", appender); + } + + @Test + public void testProvider() { + @SuppressWarnings("unchecked") + NoSQLProvider provider = createStrictMock(NoSQLProvider.class); + + replay(provider); + + NoSQLAppender appender = NoSQLAppender.createAppender("myName01", null, null, null, provider); + + assertNotNull("The appender should not be null.", appender); + assertEquals("The toString value is not correct.", + "myName01{ manager=noSqlManager{ description=myName01, bufferSize=0, provider=" + provider + " } }", + appender.toString()); + + appender.stop(); + + verify(provider); + } + + @Test + public void testProviderBuffer() { + @SuppressWarnings("unchecked") + NoSQLProvider provider = createStrictMock(NoSQLProvider.class); + + replay(provider); + + NoSQLAppender appender = NoSQLAppender.createAppender("anotherName02", null, null, "25", provider); + + assertNotNull("The appender should not be null.", appender); + assertEquals("The toString value is not correct.", + "anotherName02{ manager=noSqlManager{ description=anotherName02, bufferSize=25, provider=" + + provider + " } }", + appender.toString()); + + appender.stop(); + + verify(provider); + } +} Index: core/src/test/java/org/apache/logging/log4j/core/appender/db/nosql/NoSQLDatabaseManagerTest.java =================================================================== --- core/src/test/java/org/apache/logging/log4j/core/appender/db/nosql/NoSQLDatabaseManagerTest.java (revision 0) +++ core/src/test/java/org/apache/logging/log4j/core/appender/db/nosql/NoSQLDatabaseManagerTest.java (working copy) @@ -0,0 +1,534 @@ +/* + * 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.nosql; + +import org.apache.logging.log4j.Level; +import org.apache.logging.log4j.MarkerManager; +import org.apache.logging.log4j.ThreadContext; +import org.apache.logging.log4j.core.LogEvent; +import org.apache.logging.log4j.core.appender.db.nosql.couch.CouchDBObject; +import org.apache.logging.log4j.message.Message; +import org.easymock.Capture; +import org.easymock.IAnswer; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import java.io.IOException; +import java.sql.SQLException; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import static org.easymock.EasyMock.*; +import static org.junit.Assert.*; + +public class NoSQLDatabaseManagerTest { + NoSQLProvider, CouchDBObject>> provider; + NoSQLConnection, CouchDBObject> connection; + + @Before + @SuppressWarnings("unchecked") + public void setUp() { + this.provider = createStrictMock(NoSQLProvider.class); + this.connection = createStrictMock(NoSQLConnection.class); + } + + @After + public void tearDown() { + verify(this.provider, this.connection); + } + + @Test + public void testConnection() { + replay(this.provider, this.connection); + + NoSQLDatabaseManager manager = NoSQLDatabaseManager.getNoSQLDatabaseManager("name", 0, this.provider); + + assertNotNull("The manager should not be null.", manager); + + try { + verify(this.provider, this.connection); + reset(this.provider, this.connection); + expect(this.provider.getConnection()).andReturn(this.connection); + replay(this.provider, this.connection); + + manager.connectInternal(); + + verify(this.provider, this.connection); + reset(this.provider, this.connection); + expect(this.connection.isClosed()).andReturn(false); + this.connection.close(); + expectLastCall(); + replay(this.provider, this.connection); + + manager.disconnectInternal(); + } finally { + try { + manager.release(); + } catch (Throwable ignore) { /* */ } + } + } + + @Test + public void testWriteInternalNotConnected01() { + replay(this.provider, this.connection); + + NoSQLDatabaseManager manager = NoSQLDatabaseManager.getNoSQLDatabaseManager("name", 0, this.provider); + + try { + verify(this.provider, this.connection); + reset(this.provider, this.connection); + + LogEvent event = createStrictMock(LogEvent.class); + replay(this.provider, this.connection, event); + + manager.writeInternal(event); + + verify(event); + } finally { + try { + manager.release(); + } catch (Throwable ignore) { /* */ } + } + } + + @Test + public void testWriteInternalNotConnected02() { + expect(this.provider.getConnection()).andReturn(this.connection); + replay(this.provider, this.connection); + + NoSQLDatabaseManager manager = NoSQLDatabaseManager.getNoSQLDatabaseManager("name", 0, this.provider); + + try { + manager.connect(); + + verify(this.provider, this.connection); + reset(this.provider, this.connection); + + LogEvent event = createStrictMock(LogEvent.class); + expect(this.connection.isClosed()).andReturn(true); + replay(this.provider, this.connection, event); + + manager.writeInternal(event); + + verify(this.provider, this.connection, event); + reset(this.provider, this.connection); + expect(this.connection.isClosed()).andReturn(false); + this.connection.close(); + expectLastCall(); + replay(this.provider, this.connection); + } finally { + try { + manager.release(); + } catch (Throwable ignore) { /* */ } + } + } + + @Test + public void testWriteInternal01() { + expect(this.provider.getConnection()).andReturn(this.connection); + replay(this.provider, this.connection); + + NoSQLDatabaseManager manager = NoSQLDatabaseManager.getNoSQLDatabaseManager("name", 0, this.provider); + + try { + manager.connect(); + + verify(this.provider, this.connection); + reset(this.provider, this.connection); + + Capture>> capture = new Capture>>(); + + LogEvent event = createStrictMock(LogEvent.class); + Message message = createStrictMock(Message.class); + + expect(this.connection.isClosed()).andReturn(false); + expect(this.connection.createObject()).andAnswer(new IAnswer() { + @Override + public CouchDBObject answer() throws Throwable { + return new CouchDBObject(); + } + }).atLeastOnce(); + expect(event.getLevel()).andReturn(Level.WARN); + expect(event.getLoggerName()).andReturn("com.foo.NoSQLDbTest.testWriteInternal01"); + expect(event.getMessage()).andReturn(message).times(2); + expect(message.getFormattedMessage()).andReturn("My formatted message 01."); + expect(event.getSource()).andReturn(new StackTraceElement("com.foo.Bar", "testMethod01", "Bar.java", 15)); + expect(event.getMarker()).andReturn(null); + expect(event.getThreadName()).andReturn("MyThread-A"); + expect(event.getMillis()).andReturn(1234567890123L).times(2); + expect(event.getThrown()).andReturn(null); + expect(event.getContextMap()).andReturn(null); + expect(event.getContextStack()).andReturn(null); + this.connection.insertObject(capture(capture)); + expectLastCall(); + replay(this.provider, this.connection, event, message); + + manager.writeInternal(event); + + NoSQLObject> inserted = capture.getValue(); + assertNotNull("The inserted value should not be null.", inserted); + Map object = inserted.unwrap(); + assertNotNull("The unwrapped object should not be null.", object); + + assertEquals("The level is not correct.", Level.WARN, object.get("level")); + assertEquals("The logger is not correct.", "com.foo.NoSQLDbTest.testWriteInternal01", + object.get("loggerName")); + assertEquals("The message is not correct.", "My formatted message 01.", object.get("message")); + assertEquals("The thread is not correct.", "MyThread-A", object.get("threadName")); + assertEquals("The millis is not correct.", 1234567890123L, object.get("millis")); + assertEquals("The date is not correct.", 1234567890123L, ((Date) object.get("date")).getTime()); + + assertTrue("The source should be a map.", object.get("source") instanceof Map); + @SuppressWarnings("unchecked") + Map source = (Map)object.get("source"); + assertEquals("The class is not correct.", "com.foo.Bar", source.get("className")); + assertEquals("The method is not correct.", "testMethod01", source.get("methodName")); + assertEquals("The file name is not correct.", "Bar.java", source.get("fileName")); + assertEquals("The line number is not correct.", 15, source.get("lineNumber")); + + assertNull("The marker should be null.", object.get("marker")); + + assertNull("The thrown should be null.", object.get("thrown")); + + assertNull("The context map should be null.", object.get("contextMap")); + + assertNull("The context stack should be null.", object.get("contextStack")); + + verify(this.provider, this.connection, event, message); + reset(this.provider, this.connection); + expect(this.connection.isClosed()).andReturn(false); + this.connection.close(); + expectLastCall(); + replay(this.provider, this.connection); + } finally { + try { + manager.release(); + } catch (Throwable ignore) { /* */ } + } + } + + @Test + public void testWriteInternal02() { + expect(this.provider.getConnection()).andReturn(this.connection); + replay(this.provider, this.connection); + + NoSQLDatabaseManager manager = NoSQLDatabaseManager.getNoSQLDatabaseManager("name", 0, this.provider); + + try { + manager.connect(); + + verify(this.provider, this.connection); + reset(this.provider, this.connection); + + Capture>> capture = new Capture>>(); + + RuntimeException exception = new RuntimeException("This is something cool!"); + Map context = new HashMap(); + context.put("hello", "world"); + context.put("user", "pass"); + + LogEvent event = createStrictMock(LogEvent.class); + Message message = createStrictMock(Message.class); + ThreadContext.push("message1"); + ThreadContext.push("stack2"); + ThreadContext.ContextStack stack = ThreadContext.getImmutableStack(); + ThreadContext.clearStack(); + + expect(this.connection.isClosed()).andReturn(false); + expect(this.connection.createObject()).andAnswer(new IAnswer() { + @Override + public CouchDBObject answer() throws Throwable { + return new CouchDBObject(); + } + }).atLeastOnce(); + expect(this.connection.createList(anyInt())).andAnswer(new IAnswer() { + @Override + public CouchDBObject[] answer() throws Throwable { + return new CouchDBObject[(Integer)getCurrentArguments()[0]]; + } + }); + expect(this.connection.createObject()).andAnswer(new IAnswer() { + @Override + public CouchDBObject answer() throws Throwable { + return new CouchDBObject(); + } + }).atLeastOnce(); + expect(event.getLevel()).andReturn(Level.DEBUG); + expect(event.getLoggerName()).andReturn("com.foo.NoSQLDbTest.testWriteInternal02"); + expect(event.getMessage()).andReturn(message).times(2); + expect(message.getFormattedMessage()).andReturn("Another cool message 02."); + expect(event.getSource()).andReturn(new StackTraceElement("com.bar.Foo", "anotherMethod03", "Foo.java", 9)); + expect(event.getMarker()).andReturn(MarkerManager.getMarker("LoneMarker")); + expect(event.getThreadName()).andReturn("AnotherThread-B"); + expect(event.getMillis()).andReturn(987654321564L).times(2); + expect(event.getThrown()).andReturn(exception); + expect(event.getContextMap()).andReturn(context); + expect(event.getContextStack()).andReturn(stack); + this.connection.insertObject(capture(capture)); + expectLastCall(); + replay(this.provider, this.connection, event, message); + + manager.writeInternal(event); + + NoSQLObject> inserted = capture.getValue(); + assertNotNull("The inserted value should not be null.", inserted); + Map object = inserted.unwrap(); + assertNotNull("The unwrapped object should not be null.", object); + + assertEquals("The level is not correct.", Level.DEBUG, object.get("level")); + assertEquals("The logger is not correct.", "com.foo.NoSQLDbTest.testWriteInternal02", + object.get("loggerName")); + assertEquals("The message is not correct.", "Another cool message 02.", object.get("message")); + assertEquals("The thread is not correct.", "AnotherThread-B", object.get("threadName")); + assertEquals("The millis is not correct.", 987654321564L, object.get("millis")); + assertEquals("The date is not correct.", 987654321564L, ((Date) object.get("date")).getTime()); + + assertTrue("The source should be a map.", object.get("source") instanceof Map); + @SuppressWarnings("unchecked") + Map source = (Map)object.get("source"); + assertEquals("The class is not correct.", "com.bar.Foo", source.get("className")); + assertEquals("The method is not correct.", "anotherMethod03", source.get("methodName")); + assertEquals("The file name is not correct.", "Foo.java", source.get("fileName")); + assertEquals("The line number is not correct.", 9, source.get("lineNumber")); + + assertTrue("The marker should be a map.", object.get("marker") instanceof Map); + @SuppressWarnings("unchecked") + Map marker = (Map)object.get("marker"); + assertEquals("The marker name is not correct.", "LoneMarker", marker.get("name")); + assertNull("The marker parent should be null.", marker.get("parent")); + + assertTrue("The thrown should be a map.", object.get("thrown") instanceof Map); + @SuppressWarnings("unchecked") + Map thrown = (Map)object.get("thrown"); + assertEquals("The thrown type is not correct.", "java.lang.RuntimeException", thrown.get("type")); + assertEquals("The thrown message is not correct.", "This is something cool!", thrown.get("message")); + assertTrue("The thrown stack trace should be a list.", thrown.get("stackTrace") instanceof List); + @SuppressWarnings("unchecked") + List> stackTrace = (List>)thrown.get("stackTrace"); + assertEquals("The thrown stack trace length is not correct.", exception.getStackTrace().length, stackTrace.size()); + for (int i = 0; i < exception.getStackTrace().length; i++) { + StackTraceElement e1 = exception.getStackTrace()[i]; + Map e2 = stackTrace.get(i); + + assertEquals("Element class name [" + i + "] is not correct.", e1.getClassName(), e2.get("className")); + assertEquals("Element method name [" + i + "] is not correct.", e1.getMethodName(), + e2.get("methodName")); + assertEquals("Element file name [" + i + "] is not correct.", e1.getFileName(), e2.get("fileName")); + assertEquals("Element line number [" + i + "] is not correct.", e1.getLineNumber(), + e2.get("lineNumber")); + } + assertNull("The thrown should have no cause.", thrown.get("cause")); + + assertTrue("The context map should be a map.", object.get("contextMap") instanceof Map); + assertEquals("The context map is not correct.", context, object.get("contextMap")); + + assertTrue("The context stack should be list.", object.get("contextStack") instanceof List); + assertEquals("The context stack is not correct.", stack.asList(), object.get("contextStack")); + + verify(this.provider, this.connection, event, message); + reset(this.provider, this.connection); + expect(this.connection.isClosed()).andReturn(false); + this.connection.close(); + expectLastCall(); + replay(this.provider, this.connection); + } finally { + try { + manager.release(); + } catch (Throwable ignore) { /* */ } + } + } + + @Test + public void testWriteInternal03() { + expect(this.provider.getConnection()).andReturn(this.connection); + replay(this.provider, this.connection); + + NoSQLDatabaseManager manager = NoSQLDatabaseManager.getNoSQLDatabaseManager("name", 0, this.provider); + + try { + manager.connect(); + + verify(this.provider, this.connection); + reset(this.provider, this.connection); + + Capture>> capture = new Capture>>(); + + IOException exception1 = new IOException("This is the cause."); + SQLException exception2 = new SQLException("This is the result.", exception1); + Map context = new HashMap(); + context.put("hello", "world"); + context.put("user", "pass"); + + LogEvent event = createStrictMock(LogEvent.class); + Message message = createStrictMock(Message.class); + ThreadContext.push("message1"); + ThreadContext.push("stack2"); + ThreadContext.ContextStack stack = ThreadContext.getImmutableStack(); + ThreadContext.clearStack(); + + expect(this.connection.isClosed()).andReturn(false); + expect(this.connection.createObject()).andAnswer(new IAnswer() { + @Override + public CouchDBObject answer() throws Throwable { + return new CouchDBObject(); + } + }).atLeastOnce(); + expect(this.connection.createList(anyInt())).andAnswer(new IAnswer() { + @Override + public CouchDBObject[] answer() throws Throwable { + return new CouchDBObject[(Integer)getCurrentArguments()[0]]; + } + }); + expect(this.connection.createObject()).andAnswer(new IAnswer() { + @Override + public CouchDBObject answer() throws Throwable { + return new CouchDBObject(); + } + }).atLeastOnce(); + expect(this.connection.createList(anyInt())).andAnswer(new IAnswer() { + @Override + public CouchDBObject[] answer() throws Throwable { + return new CouchDBObject[(Integer)getCurrentArguments()[0]]; + } + }); + expect(this.connection.createObject()).andAnswer(new IAnswer() { + @Override + public CouchDBObject answer() throws Throwable { + return new CouchDBObject(); + } + }).atLeastOnce(); + expect(event.getLevel()).andReturn(Level.DEBUG); + expect(event.getLoggerName()).andReturn("com.foo.NoSQLDbTest.testWriteInternal02"); + expect(event.getMessage()).andReturn(message).times(2); + expect(message.getFormattedMessage()).andReturn("Another cool message 02."); + expect(event.getSource()).andReturn(new StackTraceElement("com.bar.Foo", "anotherMethod03", "Foo.java", 9)); + expect(event.getMarker()).andReturn( + MarkerManager.getMarker("AnotherMarker", MarkerManager.getMarker("Parent1", + MarkerManager.getMarker("Grandparent2"))) + ); + expect(event.getThreadName()).andReturn("AnotherThread-B"); + expect(event.getMillis()).andReturn(987654321564L).times(2); + expect(event.getThrown()).andReturn(exception2); + expect(event.getContextMap()).andReturn(context); + expect(event.getContextStack()).andReturn(stack); + this.connection.insertObject(capture(capture)); + expectLastCall(); + replay(this.provider, this.connection, event, message); + + manager.writeInternal(event); + + NoSQLObject> inserted = capture.getValue(); + assertNotNull("The inserted value should not be null.", inserted); + Map object = inserted.unwrap(); + assertNotNull("The unwrapped object should not be null.", object); + + assertEquals("The level is not correct.", Level.DEBUG, object.get("level")); + assertEquals("The logger is not correct.", "com.foo.NoSQLDbTest.testWriteInternal02", + object.get("loggerName")); + assertEquals("The message is not correct.", "Another cool message 02.", object.get("message")); + assertEquals("The thread is not correct.", "AnotherThread-B", object.get("threadName")); + assertEquals("The millis is not correct.", 987654321564L, object.get("millis")); + assertEquals("The date is not correct.", 987654321564L, ((Date) object.get("date")).getTime()); + + assertTrue("The source should be a map.", object.get("source") instanceof Map); + @SuppressWarnings("unchecked") + Map source = (Map)object.get("source"); + assertEquals("The class is not correct.", "com.bar.Foo", source.get("className")); + assertEquals("The method is not correct.", "anotherMethod03", source.get("methodName")); + assertEquals("The file name is not correct.", "Foo.java", source.get("fileName")); + assertEquals("The line number is not correct.", 9, source.get("lineNumber")); + + assertTrue("The marker should be a map.", object.get("marker") instanceof Map); + @SuppressWarnings("unchecked") + Map marker = (Map)object.get("marker"); + assertEquals("The marker name is not correct.", "AnotherMarker", marker.get("name")); + assertTrue("The marker parent should be a map.", marker.get("parent") instanceof Map); + @SuppressWarnings("unchecked") + Map parentMarker = (Map)marker.get("parent"); + assertEquals("The marker parent name is not correct.", "Parent1", parentMarker.get("name")); + assertTrue("The marker grandparent should be a map.", parentMarker.get("parent") instanceof Map); + @SuppressWarnings("unchecked") + Map grandparentMarker = (Map)parentMarker.get("parent"); + assertEquals("The marker grandparent name is not correct.", "Grandparent2", grandparentMarker.get("name")); + assertNull("The grandparent marker should have no parent.", grandparentMarker.get("parent")); + + assertTrue("The thrown should be a map.", object.get("thrown") instanceof Map); + @SuppressWarnings("unchecked") + Map thrown = (Map)object.get("thrown"); + assertEquals("The thrown type is not correct.", "java.sql.SQLException", thrown.get("type")); + assertEquals("The thrown message is not correct.", "This is the result.", thrown.get("message")); + assertTrue("The thrown stack trace should be a list.", thrown.get("stackTrace") instanceof List); + @SuppressWarnings("unchecked") + List> stackTrace = (List>)thrown.get("stackTrace"); + assertEquals("The thrown stack trace length is not correct.", + exception2.getStackTrace().length, stackTrace.size()); + for (int i = 0; i < exception2.getStackTrace().length; i++) { + StackTraceElement e1 = exception2.getStackTrace()[i]; + Map e2 = stackTrace.get(i); + + assertEquals("Element class name [" + i + "] is not correct.", e1.getClassName(), e2.get("className")); + assertEquals("Element method name [" + i + "] is not correct.", e1.getMethodName(), + e2.get("methodName")); + assertEquals("Element file name [" + i + "] is not correct.", e1.getFileName(), e2.get("fileName")); + assertEquals("Element line number [" + i + "] is not correct.", e1.getLineNumber(), + e2.get("lineNumber")); + } + assertTrue("The thrown cause should be a map.", thrown.get("cause") instanceof Map); + @SuppressWarnings("unchecked") + Map cause = (Map)thrown.get("cause"); + assertEquals("The cause type is not correct.", "java.io.IOException", cause.get("type")); + assertEquals("The cause message is not correct.", "This is the cause.", cause.get("message")); + assertTrue("The cause stack trace should be a list.", cause.get("stackTrace") instanceof List); + @SuppressWarnings("unchecked") + List> causeStackTrace = (List>)cause.get("stackTrace"); + assertEquals("The cause stack trace length is not correct.", + exception1.getStackTrace().length, causeStackTrace.size()); + for (int i = 0; i < exception1.getStackTrace().length; i++) { + StackTraceElement e1 = exception1.getStackTrace()[i]; + Map e2 = causeStackTrace.get(i); + + assertEquals("Element class name [" + i + "] is not correct.", e1.getClassName(), e2.get("className")); + assertEquals("Element method name [" + i + "] is not correct.", e1.getMethodName(), + e2.get("methodName")); + assertEquals("Element file name [" + i + "] is not correct.", e1.getFileName(), e2.get("fileName")); + assertEquals("Element line number [" + i + "] is not correct.", e1.getLineNumber(), + e2.get("lineNumber")); + } + assertNull("The cause should have no cause.", cause.get("cause")); + + assertTrue("The context map should be a map.", object.get("contextMap") instanceof Map); + assertEquals("The context map is not correct.", context, object.get("contextMap")); + + assertTrue("The context stack should be list.", object.get("contextStack") instanceof List); + assertEquals("The context stack is not correct.", stack.asList(), object.get("contextStack")); + + verify(this.provider, this.connection, event, message); + reset(this.provider, this.connection); + expect(this.connection.isClosed()).andReturn(false); + this.connection.close(); + expectLastCall(); + replay(this.provider, this.connection); + } finally { + try { + manager.release(); + } catch (Throwable ignore) { /* */ } + } + } +} Index: core/src/test/java/org/apache/logging/log4j/core/layout/PatternLayoutTest.java =================================================================== --- core/src/test/java/org/apache/logging/log4j/core/layout/PatternLayoutTest.java (revision 1479389) +++ core/src/test/java/org/apache/logging/log4j/core/layout/PatternLayoutTest.java (working copy) @@ -74,7 +74,7 @@ final String mdcMsgPattern5 = "%m : %X{key1},%X{key2},%X{key3}%n"; // set up appender - final PatternLayout layout = PatternLayout.createLayout(msgPattern, ctx.getConfiguration(), null, null); + final PatternLayout layout = PatternLayout.createLayout(msgPattern, ctx.getConfiguration(), null, null, null); //FileOutputStream fos = new FileOutputStream(OUTPUT_FILE + "_mdc"); final FileAppender appender = FileAppender.createAppender(OUTPUT_FILE + "_mdc", "false", "false", "File", "false", "true", "false", layout, null, "false", null, null); @@ -137,7 +137,7 @@ public void testRegex() throws Exception { final LoggerContext ctx = (LoggerContext) LogManager.getContext(); final PatternLayout layout = PatternLayout.createLayout(regexPattern, ctx.getConfiguration(), - null, null); + null, null, null); final LogEvent event = new Log4jLogEvent(this.getClass().getName(), null, "org.apache.logging.log4j.core.Logger", Level.INFO, new SimpleMessage("Hello, world!"), null); final byte[] result = layout.toByteArray(event); Index: core/src/test/java/org/apache/logging/log4j/core/net/JMSQueueTest.java =================================================================== --- core/src/test/java/org/apache/logging/log4j/core/net/JMSQueueTest.java (revision 1479389) +++ core/src/test/java/org/apache/logging/log4j/core/net/JMSQueueTest.java (working copy) @@ -102,7 +102,7 @@ final CompositeFilter serverFilters = CompositeFilter.createFilters(new Filter[]{serverFilter}); final ListAppender listApp = new ListAppender("Events", serverFilters, null, false, false); listApp.start(); - final PatternLayout layout = PatternLayout.createLayout("%m %ex%n", null, null, null); + final PatternLayout layout = PatternLayout.createLayout("%m %ex%n", null, null, null, null); final ConsoleAppender console = ConsoleAppender.createAppender(layout, null, "SYSTEM_OUT", "Console", "false", "true"); console.start(); final Logger serverLogger = ctx.getLogger(JMSTopicReceiver.class.getName()); Index: core/src/test/java/org/apache/logging/log4j/core/net/JMSTopicTest.java =================================================================== --- core/src/test/java/org/apache/logging/log4j/core/net/JMSTopicTest.java (revision 1479389) +++ core/src/test/java/org/apache/logging/log4j/core/net/JMSTopicTest.java (working copy) @@ -103,7 +103,7 @@ final CompositeFilter serverFilters = CompositeFilter.createFilters(new Filter[]{serverFilter}); final ListAppender listApp = new ListAppender("Events", serverFilters, null, false, false); listApp.start(); - final PatternLayout layout = PatternLayout.createLayout("%m %ex%n", null, null, null); + final PatternLayout layout = PatternLayout.createLayout("%m %ex%n", null, null, null, null); final ConsoleAppender console = ConsoleAppender.createAppender(layout, null, "SYSTEM_OUT", "Console", "false", "true"); console.start(); Index: core/src/test/java/org/apache/logging/log4j/core/net/SocketServerTest.java =================================================================== --- core/src/test/java/org/apache/logging/log4j/core/net/SocketServerTest.java (revision 1479389) +++ core/src/test/java/org/apache/logging/log4j/core/net/SocketServerTest.java (working copy) @@ -91,7 +91,7 @@ appender.start(); final ListAppender listApp = new ListAppender("Events", serverFilter, null, false, false); listApp.start(); - final PatternLayout layout = PatternLayout.createLayout("%m %ex%n", null, null, null); + final PatternLayout layout = PatternLayout.createLayout("%m %ex%n", null, null, null, null); final ConsoleAppender console = ConsoleAppender.createAppender(layout, null, "SYSTEM_OUT", "Console", "false", "true"); final Logger serverLogger = ctx.getLogger(SocketServer.class.getName()); serverLogger.addAppender(console); Index: core/src/test/resources/META-INF/persistence.xml =================================================================== --- core/src/test/resources/META-INF/persistence.xml (revision 0) +++ core/src/test/resources/META-INF/persistence.xml (working copy) @@ -0,0 +1,40 @@ + + + + + + org.hibernate.ejb.HibernatePersistence + org.apache.logging.log4j.core.appender.db.jpa.TestEntity + false + + + + + + + + + + + + Index: core/src/test/resources/org/apache/logging/log4j/core/appender/db/jdbc/log4j2-driver-manager.xml =================================================================== --- core/src/test/resources/org/apache/logging/log4j/core/appender/db/jdbc/log4j2-driver-manager.xml (revision 0) +++ core/src/test/resources/org/apache/logging/log4j/core/appender/db/jdbc/log4j2-driver-manager.xml (working copy) @@ -0,0 +1,46 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Index: core/src/test/resources/org/apache/logging/log4j/core/appender/db/jdbc/log4j2-factory-method.xml =================================================================== --- core/src/test/resources/org/apache/logging/log4j/core/appender/db/jdbc/log4j2-factory-method.xml (revision 0) +++ core/src/test/resources/org/apache/logging/log4j/core/appender/db/jdbc/log4j2-factory-method.xml (working copy) @@ -0,0 +1,47 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Index: core/src/test/resources/org/apache/logging/log4j/core/appender/db/jpa/log4j2-jpa.xml =================================================================== --- core/src/test/resources/org/apache/logging/log4j/core/appender/db/jpa/log4j2-jpa.xml (revision 0) +++ core/src/test/resources/org/apache/logging/log4j/core/appender/db/jpa/log4j2-jpa.xml (working copy) @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + + + + + + + + Index: log4j12-api/src/test/java/org/apache/log4j/CategoryTest.java =================================================================== --- log4j12-api/src/test/java/org/apache/log4j/CategoryTest.java (revision 1479389) +++ log4j12-api/src/test/java/org/apache/log4j/CategoryTest.java (working copy) @@ -161,7 +161,7 @@ @Test public void testClassName() { final Category category = Category.getInstance("TestCategory"); - final Layout layout = PatternLayout.createLayout("%d %p %C{1.} [%t] %m%n", null, null, null); + final Layout layout = PatternLayout.createLayout("%d %p %C{1.} [%t] %m%n", null, null, null, null); final ListAppender appender = new ListAppender("List2", null, layout, false, false); appender.start(); category.setAdditivity(false); Index: log4j12-api/src/test/java/org/apache/log4j/LoggerTest.java =================================================================== --- log4j12-api/src/test/java/org/apache/log4j/LoggerTest.java (revision 1479389) +++ log4j12-api/src/test/java/org/apache/log4j/LoggerTest.java (working copy) @@ -457,7 +457,7 @@ @Test @SuppressWarnings("deprecation") public void testLog() { - final PatternLayout layout = PatternLayout.createLayout("%d %C %L %m", null, null, null); + final PatternLayout layout = PatternLayout.createLayout("%d %C %L %m", null, null, null, null); final ListAppender appender = new ListAppender("List", null, layout, false, false); appender.start(); final Logger root = Logger.getRootLogger(); Index: pom.xml =================================================================== --- pom.xml (revision 1479389) +++ pom.xml (working copy) @@ -289,6 +289,36 @@ 4.7 test + + org.easymock + easymock + 3.1 + + + org.hsqldb + hsqldb + 2.2.9 + + + org.hibernate + hibernate-entitymanager + 4.1.9.Final + + + org.hibernate.javax.persistence + hibernate-jpa-2.0-api + 1.0.1.Final + + + org.mongodb + mongo-java-driver + 2.11.1 + + + org.lightcouch + lightcouch + 0.0.5 + Index: src/site/site.vm =================================================================== --- src/site/site.vm (revision 1479389) +++ src/site/site.vm (working copy) @@ -1,4 +1,20 @@ + #macro ( link $href $name $target $img $position $alt $border $width $height ) #set ( $linkTitle = ' title="' + $name + '"' ) Index: src/site/site.xml =================================================================== --- src/site/site.xml (revision 1479389) +++ src/site/site.xml (working copy) @@ -74,8 +74,11 @@ + + + Index: src/site/xdoc/manual/appenders.xml =================================================================== --- src/site/xdoc/manual/appenders.xml (revision 1479389) +++ src/site/xdoc/manual/appenders.xml (working copy) @@ -20,6 +20,7 @@ Log4j 2 Appenders Ralph Goers + Nick Williams @@ -644,6 +645,118 @@ ]]> + + +

The JDBCAppender writes log events to a relational database table using standard JDBC. It can be configured + to obtain JDBC connections using the DriverManager, a JNDI DataSource or a custom factory method.

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Parameter NameTypeDescription
nameStringThe name of the Appender.
suppressExceptionsbooleanThe default is true, causing exceptions to be internally logged and then ignored. When set to false + exceptions will be percolated to the caller.
filterFilterA Filter to determine if the event should be handled by this Appender. More than one Filter may be + used by using a CompositeFilter.
bufferSizeintIf an integer greater than 0, this causes the appender to buffer log events and flush whenever the + buffer reaches this size.
connectionSourceConnectionSourceThe connections source from which database connections should be retrieved.
tableNameStringThe name of the database table to insert log events into.
columnConfigsColumnConfig[]Information about the columns that log event data should be inserted into and how to insert that data. + This is represented with multiple <Column /> elements.
+

+ Here are a few sample configurations for the JDBCAppender: + +


+
+  
+    
+      
+      
+      
+      
+      
+      
+    
+  
+  
+    
+      
+    
+  
+]]>
+ +

+
+  
+    
+      
+      
+      
+      
+      
+      
+    
+  
+  
+    
+      
+    
+  
+]]>
+ +

+
+  
+    
+      
+      
+      
+      
+      
+      
+      
+    
+  
+  
+    
+      
+    
+  
+]]>
+

+

The JMSQueueAppender sends the formatted log event to a JMS Queue.

@@ -861,6 +974,217 @@ ]]>

+
+ +

The JPAAppender writes log events to a relational database table using the Java Persistence API. + It requires the API and a provider implementation be on the classpath. It also requires a decorated entity + configured to persist to the table desired.

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Parameter NameTypeDescription
nameStringThe name of the Appender.
suppressExceptionsbooleanThe default is true, causing exceptions to be internally logged and then ignored. When set to false + exceptions will be percolated to the caller.
filterFilterA Filter to determine if the event should be handled by this Appender. More than one Filter may be + used by using a CompositeFilter.
bufferSizeintIf an integer greater than 0, this causes the appender to buffer log events and flush whenever the + buffer reaches this size.
entityClassNameStringThe fully qualified name of the concrete LogEventWrapperEntity implementation that has JPA annotations + mapping it to a database table.
persistenceUnitNameStringThe name of the JPA persistence unit that should be used for persisting log events.
+

+ Here is a sample configurations for the JPAAppender. The first XML sample is the Log4j configuration file, + the second is the persistence.xml file. Hibernate ORM is assumed here, but any JPA provider will do: + +


+
+  
+    
+  
+  
+    
+      
+    
+  
+]]>
+ +

+
+  
+    org.hibernate.ejb.HibernatePersistence
+    jdbc/ApplicationDataSource
+    com.example.logging.JpaLogEntity
+    
+      
+    
+  
+]]>
+ +
+

+
+
+ +

The NoSQLAppender writes log events to a NoSQL database using an internal lightweight provider interface. + Provider implementations currently exist for MongoDB and Apache CouchDB, and writing a custom provider is + quite simple.

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Parameter NameTypeDescription
nameStringThe name of the Appender.
suppressExceptionsbooleanThe default is true, causing exceptions to be internally logged and then ignored. When set to false + exceptions will be percolated to the caller.
filterFilterA Filter to determine if the event should be handled by this Appender. More than one Filter may be + used by using a CompositeFilter.
bufferSizeintIf an integer greater than 0, this causes the appender to buffer log events and flush whenever the + buffer reaches this size.
noSqlProviderNoSQLProvider<C extends NoSQLConnection<W, T extends NoSQLObject<W>>>The NoSQL provider that provides connections to the chosen NoSQL database.
+

+ Here are a few sample configurations for the NoSQLAppender: + +


+
+  
+    
+      
+    
+  
+  
+    
+      
+    
+  
+]]>
+ +

+
+  
+    
+      
+    
+  
+  
+    
+      
+    
+  
+]]>
+ +

+
+  
+    
+      
+    
+  
+  
+    
+      
+    
+  
+]]>
+

+
The OutputStreamAppender provides the base for many of the other Appenders such as the File and Socket