[LOG4J2-2179] The MongoDB Appender should use a keys and values for a Log4j MapMessage. [LOG4J2-2180] Add a MongoDbProvider builder for and deprecate org.apache.logging.log4j.mongodb.MongoDbProvider.createNoSqlProvider().
Project: http://git-wip-us.apache.org/repos/asf/logging-log4j2/repo Commit: http://git-wip-us.apache.org/repos/asf/logging-log4j2/commit/26fc9e07 Tree: http://git-wip-us.apache.org/repos/asf/logging-log4j2/tree/26fc9e07 Diff: http://git-wip-us.apache.org/repos/asf/logging-log4j2/diff/26fc9e07 Branch: refs/heads/master Commit: 26fc9e0794c0e3a4f5c57ba5009ef31ebea9c960 Parents: ed600f7 Author: Gary Gregory <ggreg...@apache.org> Authored: Wed Jan 10 19:19:51 2018 -0700 Committer: Gary Gregory <ggreg...@apache.org> Committed: Wed Jan 10 19:19:51 2018 -0700 ---------------------------------------------------------------------- .../log4j/core/appender/AbstractAppender.java | 11 + .../core/appender/OutputStreamManager.java | 3 +- .../appender/db/AbstractDatabaseAppender.java | 301 ++++----- .../appender/db/AbstractDatabaseManager.java | 487 +++++++------- .../appender/db/jdbc/JdbcDatabaseManager.java | 633 ++++++++++--------- .../appender/db/jpa/JpaDatabaseManager.java | 393 ++++++------ .../log4j/core/appender/mom/JmsAppender.java | 2 +- .../log4j/core/appender/mom/JmsManager.java | 2 +- .../core/appender/nosql/NoSqlAppender.java | 138 +++- .../appender/nosql/NoSqlDatabaseManager.java | 33 +- .../db/AbstractDatabaseAppenderTest.java | 4 +- .../db/AbstractDatabaseManagerTest.java | 63 +- .../nosql/NoSqlDatabaseManagerTest.java | 10 +- .../test/AvailablePortSystemPropertyRule.java | 81 --- .../AvailablePortSystemPropertyTestRule.java | 81 +++ log4j-mongodb/pom.xml | 6 + .../apache/logging/log4j/mongodb/Java8Test.java | 48 ++ .../logging/log4j/mongodb/MongoDbAuthTest.java | 40 -- .../log4j/mongodb/MongoDbAuthTestJava8.java | 52 ++ .../log4j/mongodb/MongoDbCappedTest.java | 40 -- .../log4j/mongodb/MongoDbCappedTestJava8.java | 52 ++ .../mongodb/MongoDbMapMessageTestJava8.java | 74 +++ .../logging/log4j/mongodb/MongoDbTest.java | 40 -- .../logging/log4j/mongodb/MongoDbTestJava8.java | 52 ++ .../logging/log4j/mongodb/MongoDbTestRule.java | 139 ++++ .../mongodb/MongoDbTestTestRuleTestJava8.java | 69 ++ .../resources/log4j2-mongodb-map-message.xml | 23 + src/changes/changes.xml | 6 + 28 files changed, 1734 insertions(+), 1149 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/26fc9e07/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/AbstractAppender.java ---------------------------------------------------------------------- diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/AbstractAppender.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/AbstractAppender.java index 98bb240..57974bd 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/AbstractAppender.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/AbstractAppender.java @@ -252,6 +252,17 @@ public abstract class AbstractAppender extends AbstractFilterable implements App this.handler = handler; } + /** + * Serializes the given event using the appender's layout if present. + * + * @param event + * the event to serialize. + * @return the serialized event or null if no layout is present. + */ + protected Serializable toSerializable(final LogEvent event) { + return layout != null ? layout.toSerializable(event) : null; + } + @Override public String toString() { return name; http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/26fc9e07/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/OutputStreamManager.java ---------------------------------------------------------------------- diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/OutputStreamManager.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/OutputStreamManager.java index 51ef174..0873822 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/OutputStreamManager.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/OutputStreamManager.java @@ -19,6 +19,7 @@ package org.apache.logging.log4j.core.appender; import java.io.IOException; import java.io.OutputStream; import java.io.Serializable; +import java.nio.Buffer; import java.nio.ByteBuffer; import java.util.Objects; import java.util.concurrent.TimeUnit; @@ -289,7 +290,7 @@ public class OutputStreamManager extends AbstractManager implements ByteBufferDe * @since 2.6 */ protected synchronized void flushBuffer(final ByteBuffer buf) { - buf.flip(); + ((Buffer) buf).flip(); if (buf.remaining() > 0) { writeToDestination(buf.array(), buf.arrayOffset() + buf.position(), buf.remaining()); } http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/26fc9e07/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/db/AbstractDatabaseAppender.java ---------------------------------------------------------------------- diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/db/AbstractDatabaseAppender.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/db/AbstractDatabaseAppender.java index 38f412c..09798fd 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/db/AbstractDatabaseAppender.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/db/AbstractDatabaseAppender.java @@ -1,142 +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 java.util.concurrent.TimeUnit; -import java.util.concurrent.locks.Lock; -import java.util.concurrent.locks.ReadWriteLock; -import java.util.concurrent.locks.ReentrantReadWriteLock; - -import org.apache.logging.log4j.LoggingException; -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.AppenderLoggingException; - -/** - * 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.nosql NoSQL}. - * - * @param <T> Specifies which type of {@link AbstractDatabaseManager} this Appender requires. - */ -public abstract class AbstractDatabaseAppender<T extends AbstractDatabaseManager> 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 ignoreExceptions If {@code true} exceptions encountered when appending events are logged; otherwise - * they are propagated to the caller. - * @param manager The matching {@link AbstractDatabaseManager} implementation. - */ - protected AbstractDatabaseAppender(final String name, final Filter filter, final boolean ignoreExceptions, - final T manager) { - super(name, filter, null, ignoreExceptions); - 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<LogEvent> getLayout() { - return null; - } - - /** - * Returns the underlying manager in use within this appender. - * - * @return the manager. - */ - public final T getManager() { - return this.manager; - } - - @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().startup(); - } - } - - @Override - public boolean stop(final long timeout, final TimeUnit timeUnit) { - setStopping(); - boolean stopped = super.stop(timeout, timeUnit, false); - if (this.getManager() != null) { - stopped &= this.getManager().stop(timeout, timeUnit); - } - setStopped(); - return stopped; - } - - @Override - public final void append(final LogEvent event) { - this.readLock.lock(); - try { - this.getManager().write(event); - } catch (final LoggingException e) { - LOGGER.error("Unable to write to database [{}] for appender [{}].", this.getManager().getName(), - this.getName(), e); - throw e; - } catch (final Exception e) { - LOGGER.error("Unable to write to database [{}] for appender [{}].", this.getManager().getName(), - this.getName(), e); - throw new AppenderLoggingException("Unable to write to database in appender: " + e.getMessage(), e); - } finally { - this.readLock.unlock(); - } - } - - /** - * 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.isRunning()) { - manager.startup(); - } - this.manager = manager; - old.close(); - } finally { - this.writeLock.unlock(); - } - } -} +/* + * 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 java.io.Serializable; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReadWriteLock; +import java.util.concurrent.locks.ReentrantReadWriteLock; + +import org.apache.logging.log4j.LoggingException; +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.AppenderLoggingException; + +/** + * 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.nosql NoSQL}. + * + * @param <T> Specifies which type of {@link AbstractDatabaseManager} this Appender requires. + */ +public abstract class AbstractDatabaseAppender<T extends AbstractDatabaseManager> 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 ignoreExceptions If {@code true} exceptions encountered when appending events are logged; otherwise + * they are propagated to the caller. + * @param manager The matching {@link AbstractDatabaseManager} implementation. + */ + protected AbstractDatabaseAppender(final String name, final Filter filter, final boolean ignoreExceptions, + final T manager) { + super(name, filter, null, ignoreExceptions); + this.manager = manager; + } + + /** + * Instantiates the base appender. + * + * @param name The appender name. + * @param filter The filter, if any, to use. + * @param layout The layout to use to format the event. + * @param ignoreExceptions If {@code true} exceptions encountered when appending events are logged; otherwise + * they are propagated to the caller. + * @param manager The matching {@link AbstractDatabaseManager} implementation. + */ + protected AbstractDatabaseAppender(final String name, final Filter filter, + final Layout<? extends Serializable> layout, final boolean ignoreExceptions, final T manager) { + super(name, filter, layout, ignoreExceptions); + 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<LogEvent> getLayout() { + return null; + } + + /** + * Returns the underlying manager in use within this appender. + * + * @return the manager. + */ + public final T getManager() { + return this.manager; + } + + @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().startup(); + } + } + + @Override + public boolean stop(final long timeout, final TimeUnit timeUnit) { + setStopping(); + boolean stopped = super.stop(timeout, timeUnit, false); + if (this.getManager() != null) { + stopped &= this.getManager().stop(timeout, timeUnit); + } + setStopped(); + return stopped; + } + + @Override + public final void append(final LogEvent event) { + this.readLock.lock(); + try { + this.getManager().write(event, toSerializable(event)); + } catch (final LoggingException e) { + LOGGER.error("Unable to write to database [{}] for appender [{}].", this.getManager().getName(), + this.getName(), e); + throw e; + } catch (final Exception e) { + LOGGER.error("Unable to write to database [{}] for appender [{}].", this.getManager().getName(), + this.getName(), e); + throw new AppenderLoggingException("Unable to write to database in appender: " + e.getMessage(), e); + } finally { + this.readLock.unlock(); + } + } + + /** + * 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.isRunning()) { + manager.startup(); + } + this.manager = manager; + old.close(); + } finally { + this.writeLock.unlock(); + } + } +} http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/26fc9e07/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/db/AbstractDatabaseManager.java ---------------------------------------------------------------------- diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/db/AbstractDatabaseManager.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/db/AbstractDatabaseManager.java index b8b9899..54c33d1 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/db/AbstractDatabaseManager.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/db/AbstractDatabaseManager.java @@ -1,232 +1,255 @@ -/* - * 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 java.io.Flushable; -import java.util.ArrayList; -import java.util.concurrent.TimeUnit; - -import org.apache.logging.log4j.core.LogEvent; -import org.apache.logging.log4j.core.appender.AbstractManager; -import org.apache.logging.log4j.core.appender.ManagerFactory; - -/** - * Manager that allows database appenders to have their configuration reloaded without losing events. - */ -public abstract class AbstractDatabaseManager extends AbstractManager implements Flushable { - private final ArrayList<LogEvent> buffer; - private final int bufferSize; - - private boolean running = 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(final String name, final int bufferSize) { - super(null, name); - this.bufferSize = bufferSize; - this.buffer = new ArrayList<>(bufferSize + 1); - } - - /** - * Implementations should implement this method to perform any proprietary startup operations. This method will - * never be called twice on the same instance. It is safe to throw any exceptions from this method. This method - * does not necessarily connect to the database, as it is generally unreliable to connect once and use the same - * connection for hours. - */ - protected abstract void startupInternal() throws Exception; - - /** - * This method is called within the appender when the appender is started. If it has not already been called, it - * calls {@link #startupInternal()} and catches any exceptions it might throw. - */ - public final synchronized void startup() { - if (!this.isRunning()) { - try { - this.startupInternal(); - this.running = true; - } catch (final Exception e) { - logError("Could not perform database startup operations", 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 <em>after</em> - * {@link #startupInternal()}. It is safe to throw any exceptions from this method. This method does not - * necessarily disconnect from the database for the same reasons outlined in {@link #startupInternal()}. - * @return true if all resources were closed normally, false otherwise. - */ - protected abstract boolean shutdownInternal() throws Exception; - - /** - * This method is called from the {@link #close()} method when the appender is stopped or the appender's manager - * is replaced. If it has not already been called, it calls {@link #shutdownInternal()} and catches any exceptions - * it might throw. - * @return true if all resources were closed normally, false otherwise. - */ - public final synchronized boolean shutdown() { - boolean closed = true; - this.flush(); - if (this.isRunning()) { - try { - closed &= this.shutdownInternal(); - } catch (final Exception e) { - logWarn("Caught exception while performing database shutdown operations", e); - closed = false; - } finally { - this.running = false; - } - } - return closed; - } - - /** - * Indicates whether the manager is currently connected {@link #startup()} has been called and {@link #shutdown()} - * has not been called). - * - * @return {@code true} if the manager is connected. - */ - public final boolean isRunning() { - return this.running; - } - - /** - * Connects to the database and starts a transaction (if applicable). With buffering enabled, this is called when - * flushing the buffer begins, before the first call to {@link #writeInternal}. With buffering disabled, this is - * called immediately before every invocation of {@link #writeInternal}. - */ - protected abstract void connectAndStart(); - - /** - * 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); - - /** - * Commits any active transaction (if applicable) and disconnects from the database (returns the connection to the - * connection pool). With buffering enabled, this is called when flushing the buffer completes, after the last call - * to {@link #writeInternal}. With buffering disabled, this is called immediately after every invocation of - * {@link #writeInternal}. - * @return true if all resources were closed normally, false otherwise. - */ - protected abstract boolean commitAndClose(); - - /** - * This method is called automatically when the buffer size reaches its maximum or at the beginning of a call to - * {@link #shutdown()}. It can also be called manually to flush events to the database. - */ - @Override - public final synchronized void flush() { - if (this.isRunning() && this.buffer.size() > 0) { - this.connectAndStart(); - try { - for (final LogEvent event : this.buffer) { - this.writeInternal(event); - } - } finally { - this.commitAndClose(); - // not sure if this should be done when writing the events failed - 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(final LogEvent event) { - if (this.bufferSize > 0) { - this.buffer.add(event.toImmutable()); - if (this.buffer.size() >= this.bufferSize || event.isEndOfBatch()) { - this.flush(); - } - } else { - this.connectAndStart(); - try { - this.writeInternal(event); - } finally { - this.commitAndClose(); - } - } - } - - @Override - public final boolean releaseSub(final long timeout, final TimeUnit timeUnit) { - return this.shutdown(); - } - - @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 <M> The concrete manager type. - * @param <T> The concrete {@link AbstractFactoryData} type. - * @return a new or existing manager of the specified type and name. - */ - protected static <M extends AbstractDatabaseManager, T extends AbstractFactoryData> M getManager( - final String name, final T data, final ManagerFactory<M, T> factory - ) { - 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 { - private final int bufferSize; - - /** - * Constructs the base factory data. - * - * @param bufferSize The size of the buffer. - */ - protected AbstractFactoryData(final int bufferSize) { - this.bufferSize = bufferSize; - } - - /** - * Gets the buffer size. - * - * @return the buffer size. - */ - public int getBufferSize() { - return bufferSize; - } - } -} +/* + * 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 java.io.Flushable; +import java.io.Serializable; +import java.util.ArrayList; +import java.util.concurrent.TimeUnit; + +import org.apache.logging.log4j.core.LogEvent; +import org.apache.logging.log4j.core.appender.AbstractManager; +import org.apache.logging.log4j.core.appender.ManagerFactory; + +/** + * Manager that allows database appenders to have their configuration reloaded without losing events. + */ +public abstract class AbstractDatabaseManager extends AbstractManager implements Flushable { + private final ArrayList<LogEvent> buffer; + private final int bufferSize; + + private boolean running = 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(final String name, final int bufferSize) { + super(null, name); + this.bufferSize = bufferSize; + this.buffer = new ArrayList<>(bufferSize + 1); + } + + /** + * Implementations should implement this method to perform any proprietary startup operations. This method will + * never be called twice on the same instance. It is safe to throw any exceptions from this method. This method + * does not necessarily connect to the database, as it is generally unreliable to connect once and use the same + * connection for hours. + */ + protected abstract void startupInternal() throws Exception; + + /** + * This method is called within the appender when the appender is started. If it has not already been called, it + * calls {@link #startupInternal()} and catches any exceptions it might throw. + */ + public final synchronized void startup() { + if (!this.isRunning()) { + try { + this.startupInternal(); + this.running = true; + } catch (final Exception e) { + logError("Could not perform database startup operations", 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 <em>after</em> + * {@link #startupInternal()}. It is safe to throw any exceptions from this method. This method does not + * necessarily disconnect from the database for the same reasons outlined in {@link #startupInternal()}. + * @return true if all resources were closed normally, false otherwise. + */ + protected abstract boolean shutdownInternal() throws Exception; + + /** + * This method is called from the {@link #close()} method when the appender is stopped or the appender's manager + * is replaced. If it has not already been called, it calls {@link #shutdownInternal()} and catches any exceptions + * it might throw. + * @return true if all resources were closed normally, false otherwise. + */ + public final synchronized boolean shutdown() { + boolean closed = true; + this.flush(); + if (this.isRunning()) { + try { + closed &= this.shutdownInternal(); + } catch (final Exception e) { + logWarn("Caught exception while performing database shutdown operations", e); + closed = false; + } finally { + this.running = false; + } + } + return closed; + } + + /** + * Indicates whether the manager is currently connected {@link #startup()} has been called and {@link #shutdown()} + * has not been called). + * + * @return {@code true} if the manager is connected. + */ + public final boolean isRunning() { + return this.running; + } + + /** + * Connects to the database and starts a transaction (if applicable). With buffering enabled, this is called when + * flushing the buffer begins, before the first call to {@link #writeInternal}. With buffering disabled, this is + * called immediately before every invocation of {@link #writeInternal}. + */ + protected abstract void connectAndStart(); + + /** + * Performs the actual writing of the event in an implementation-specific way. This method is called immediately + * from {@link #write(LogEvent, Serializable)} if buffering is off, or from {@link #flush()} if the buffer has reached its limit. + * + * @param event The event to write to the database. + * @deprecated Use {@link #writeInternal(LogEvent, Serializable)}. + */ + @Deprecated + protected abstract void writeInternal(LogEvent event); + + /** + * Performs the actual writing of the event in an implementation-specific way. This method is called immediately + * from {@link #write(LogEvent, Serializable)} 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, Serializable serializable); + + /** + * Commits any active transaction (if applicable) and disconnects from the database (returns the connection to the + * connection pool). With buffering enabled, this is called when flushing the buffer completes, after the last call + * to {@link #writeInternal}. With buffering disabled, this is called immediately after every invocation of + * {@link #writeInternal}. + * @return true if all resources were closed normally, false otherwise. + */ + protected abstract boolean commitAndClose(); + + /** + * This method is called automatically when the buffer size reaches its maximum or at the beginning of a call to + * {@link #shutdown()}. It can also be called manually to flush events to the database. + */ + @Override + public final synchronized void flush() { + if (this.isRunning() && this.buffer.size() > 0) { + this.connectAndStart(); + try { + for (final LogEvent event : this.buffer) { + this.writeInternal(event); + } + } finally { + this.commitAndClose(); + // not sure if this should be done when writing the events failed + this.buffer.clear(); + } + } + } + + /** + * This method manages buffering and writing of events. + * + * @param event The event to write to the database. + * @deprecated since 2.10.1 Use {@link #write(LogEvent, Serializable)}. + */ + @Deprecated + public final synchronized void write(final LogEvent event) { + write(event, null); + } + + /** + * This method manages buffering and writing of events. + * + * @param event The event to write to the database. + * @param serializable Serializable event + */ + public final synchronized void write(final LogEvent event, final Serializable serializable) { + if (this.bufferSize > 0) { + this.buffer.add(event.toImmutable()); + if (this.buffer.size() >= this.bufferSize || event.isEndOfBatch()) { + this.flush(); + } + } else { + this.connectAndStart(); + try { + this.writeInternal(event, serializable); + } finally { + this.commitAndClose(); + } + } + } + + @Override + public final boolean releaseSub(final long timeout, final TimeUnit timeUnit) { + return this.shutdown(); + } + + @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 <M> The concrete manager type. + * @param <T> The concrete {@link AbstractFactoryData} type. + * @return a new or existing manager of the specified type and name. + */ + protected static <M extends AbstractDatabaseManager, T extends AbstractFactoryData> M getManager( + final String name, final T data, final ManagerFactory<M, T> factory + ) { + 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 { + private final int bufferSize; + + /** + * Constructs the base factory data. + * + * @param bufferSize The size of the buffer. + */ + protected AbstractFactoryData(final int bufferSize) { + this.bufferSize = bufferSize; + } + + /** + * Gets the buffer size. + * + * @return the buffer size. + */ + public int getBufferSize() { + return bufferSize; + } + } +} http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/26fc9e07/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/db/jdbc/JdbcDatabaseManager.java ---------------------------------------------------------------------- diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/db/jdbc/JdbcDatabaseManager.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/db/jdbc/JdbcDatabaseManager.java index 2d80f70..a652c2c 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/db/jdbc/JdbcDatabaseManager.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/db/jdbc/JdbcDatabaseManager.java @@ -1,313 +1,320 @@ -/* - * 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.io.StringReader; -import java.sql.Clob; -import java.sql.Connection; -import java.sql.DatabaseMetaData; -import java.sql.NClob; -import java.sql.PreparedStatement; -import java.sql.SQLException; -import java.sql.Timestamp; -import java.sql.Types; -import java.util.ArrayList; -import java.util.Date; -import java.util.List; - -import org.apache.logging.log4j.core.LogEvent; -import org.apache.logging.log4j.core.appender.AppenderLoggingException; -import org.apache.logging.log4j.core.appender.ManagerFactory; -import org.apache.logging.log4j.core.appender.db.AbstractDatabaseManager; -import org.apache.logging.log4j.core.appender.db.ColumnMapping; -import org.apache.logging.log4j.core.config.plugins.convert.DateTypeConverter; -import org.apache.logging.log4j.core.config.plugins.convert.TypeConverters; -import org.apache.logging.log4j.core.util.Closer; -import org.apache.logging.log4j.spi.ThreadContextMap; -import org.apache.logging.log4j.spi.ThreadContextStack; -import org.apache.logging.log4j.util.ReadOnlyStringMap; -import org.apache.logging.log4j.util.Strings; - -/** - * An {@link AbstractDatabaseManager} implementation for relational databases accessed via JDBC. - */ -public final class JdbcDatabaseManager extends AbstractDatabaseManager { - - private static final JdbcDatabaseManagerFactory INSTANCE = new JdbcDatabaseManagerFactory(); - - // NOTE: prepared statements are prepared in this order: column mappings, then column configs - private final List<ColumnMapping> columnMappings; - private final List<ColumnConfig> columnConfigs; - private final ConnectionSource connectionSource; - private final String sqlStatement; - - private Connection connection; - private PreparedStatement statement; - private boolean isBatchSupported; - - private JdbcDatabaseManager(final String name, final int bufferSize, final ConnectionSource connectionSource, - final String sqlStatement, final List<ColumnConfig> columnConfigs, - final List<ColumnMapping> columnMappings) { - super(name, bufferSize); - this.connectionSource = connectionSource; - this.sqlStatement = sqlStatement; - this.columnConfigs = columnConfigs; - this.columnMappings = columnMappings; - } - - @Override - protected void startupInternal() throws Exception { - this.connection = this.connectionSource.getConnection(); - final DatabaseMetaData metaData = this.connection.getMetaData(); - this.isBatchSupported = metaData.supportsBatchUpdates(); - Closer.closeSilently(this.connection); - } - - @Override - protected boolean shutdownInternal() { - if (this.connection != null || this.statement != null) { - return this.commitAndClose(); - } - return true; - } - - @Override - protected void connectAndStart() { - try { - this.connection = this.connectionSource.getConnection(); - this.connection.setAutoCommit(false); - this.statement = this.connection.prepareStatement(this.sqlStatement); - } catch (final SQLException e) { - throw new AppenderLoggingException( - "Cannot write logging event or flush buffer; JDBC manager cannot connect to the database.", e - ); - } - } - - @Override - protected void writeInternal(final LogEvent event) { - StringReader reader = null; - try { - if (!this.isRunning() || this.connection == null || this.connection.isClosed() || this.statement == null - || this.statement.isClosed()) { - throw new AppenderLoggingException( - "Cannot write logging event; JDBC manager not connected to the database."); - } - - int i = 1; - for (final ColumnMapping mapping : this.columnMappings) { - if (ThreadContextMap.class.isAssignableFrom(mapping.getType()) - || ReadOnlyStringMap.class.isAssignableFrom(mapping.getType())) { - this.statement.setObject(i++, event.getContextData().toMap()); - } else if (ThreadContextStack.class.isAssignableFrom(mapping.getType())) { - this.statement.setObject(i++, event.getContextStack().asList()); - } else if (Date.class.isAssignableFrom(mapping.getType())) { - this.statement.setObject(i++, - DateTypeConverter.fromMillis(event.getTimeMillis(), mapping.getType().asSubclass(Date.class))); - } else if (Clob.class.isAssignableFrom(mapping.getType())) { - this.statement.setClob(i++, new StringReader(mapping.getLayout().toSerializable(event))); - } else if (NClob.class.isAssignableFrom(mapping.getType())) { - this.statement.setNClob(i++, new StringReader(mapping.getLayout().toSerializable(event))); - } else { - final Object value = TypeConverters.convert(mapping.getLayout().toSerializable(event), - mapping.getType(), null); - if (value == null) { - this.statement.setNull(i++, Types.NULL); - } else { - this.statement.setObject(i++, value); - } - } - } - for (final ColumnConfig column : this.columnConfigs) { - if (column.isEventTimestamp()) { - this.statement.setTimestamp(i++, new Timestamp(event.getTimeMillis())); - } else if (column.isClob()) { - reader = new StringReader(column.getLayout().toSerializable(event)); - if (column.isUnicode()) { - this.statement.setNClob(i++, reader); - } else { - this.statement.setClob(i++, reader); - } - } else if (column.isUnicode()) { - this.statement.setNString(i++, column.getLayout().toSerializable(event)); - } else { - this.statement.setString(i++, column.getLayout().toSerializable(event)); - } - } - - if (this.isBatchSupported) { - this.statement.addBatch(); - } else if (this.statement.executeUpdate() == 0) { - throw new AppenderLoggingException( - "No records inserted in database table for log event in JDBC manager."); - } - } catch (final SQLException e) { - throw new AppenderLoggingException("Failed to insert record for log event in JDBC manager: " + - e.getMessage(), e); - } finally { - Closer.closeSilently(reader); - } - } - - @Override - protected boolean commitAndClose() { - boolean closed = true; - try { - if (this.connection != null && !this.connection.isClosed()) { - if (this.isBatchSupported) { - this.statement.executeBatch(); - } - this.connection.commit(); - } - } catch (final SQLException e) { - throw new AppenderLoggingException("Failed to commit transaction logging event or flushing buffer.", e); - } finally { - try { - Closer.close(this.statement); - } catch (final Exception e) { - logWarn("Failed to close SQL statement logging event or flushing buffer", e); - closed = false; - } finally { - this.statement = null; - } - - try { - Closer.close(this.connection); - } catch (final Exception e) { - logWarn("Failed to close database connection logging event or flushing buffer", e); - closed = false; - } finally { - this.connection = null; - } - } - return closed; - } - - /** - * 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. - * @deprecated use {@link #getManager(String, int, ConnectionSource, String, ColumnConfig[], ColumnMapping[])} - */ - @Deprecated - public static JdbcDatabaseManager getJDBCDatabaseManager(final String name, final int bufferSize, - final ConnectionSource connectionSource, - final String tableName, - final ColumnConfig[] columnConfigs) { - - return getManager(name, - new FactoryData(bufferSize, connectionSource, tableName, columnConfigs, new ColumnMapping[0]), - getFactory()); - } - - /** - * Creates a JDBC manager for use within the {@link JdbcAppender}, or returns a suitable one if it already exists. - * - * @param name The name of the manager, which should include connection details and hashed passwords where possible. - * @param bufferSize The size of the log event buffer. - * @param connectionSource The source for connections to the database. - * @param tableName The name of the database table to insert log events into. - * @param columnConfigs Configuration information about the log table columns. - * @param columnMappings column mapping configuration (including type conversion). - * @return a new or existing JDBC manager as applicable. - */ - public static JdbcDatabaseManager getManager(final String name, - final int bufferSize, - final ConnectionSource connectionSource, - final String tableName, - final ColumnConfig[] columnConfigs, - final ColumnMapping[] columnMappings) { - return getManager(name, new FactoryData(bufferSize, connectionSource, tableName, columnConfigs, columnMappings), - getFactory()); - } - - private static JdbcDatabaseManagerFactory getFactory() { - return INSTANCE; - } - - /** - * Encapsulates data that {@link JdbcDatabaseManagerFactory} uses to create managers. - */ - private static final class FactoryData extends AbstractDatabaseManager.AbstractFactoryData { - private final ConnectionSource connectionSource; - private final String tableName; - private final ColumnConfig[] columnConfigs; - private final ColumnMapping[] columnMappings; - - protected FactoryData(final int bufferSize, final ConnectionSource connectionSource, final String tableName, - final ColumnConfig[] columnConfigs, final ColumnMapping[] columnMappings) { - super(bufferSize); - this.connectionSource = connectionSource; - this.tableName = tableName; - this.columnConfigs = columnConfigs; - this.columnMappings = columnMappings; - } - } - - /** - * Creates managers. - */ - private static final class JdbcDatabaseManagerFactory implements ManagerFactory<JdbcDatabaseManager, FactoryData> { - @Override - public JdbcDatabaseManager createManager(final String name, final FactoryData data) { - final StringBuilder sb = new StringBuilder("INSERT INTO ").append(data.tableName).append(" ("); - // so this gets a little more complicated now that there are two ways to configure column mappings, but - // both mappings follow the same exact pattern for the prepared statement - for (final ColumnMapping mapping : data.columnMappings) { - sb.append(mapping.getName()).append(','); - } - for (final ColumnConfig config : data.columnConfigs) { - sb.append(config.getColumnName()).append(','); - } - // at least one of those arrays is guaranteed to be non-empty - sb.setCharAt(sb.length() - 1, ')'); - sb.append(" VALUES ("); - final List<ColumnMapping> columnMappings = new ArrayList<>(data.columnMappings.length); - for (final ColumnMapping mapping : data.columnMappings) { - if (Strings.isNotEmpty(mapping.getLiteralValue())) { - sb.append(mapping.getLiteralValue()); - } else { - sb.append('?'); - columnMappings.add(mapping); - } - sb.append(','); - } - final List<ColumnConfig> columnConfigs = new ArrayList<>(data.columnConfigs.length); - for (final ColumnConfig config : data.columnConfigs) { - if (Strings.isNotEmpty(config.getLiteralValue())) { - sb.append(config.getLiteralValue()); - } else { - sb.append('?'); - columnConfigs.add(config); - } - sb.append(','); - } - // at least one of those arrays is guaranteed to be non-empty - sb.setCharAt(sb.length() - 1, ')'); - final String sqlStatement = sb.toString(); - - return new JdbcDatabaseManager(name, data.getBufferSize(), data.connectionSource, sqlStatement, - columnConfigs, columnMappings); - } - } - -} +/* + * 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.io.Serializable; +import java.io.StringReader; +import java.sql.Clob; +import java.sql.Connection; +import java.sql.DatabaseMetaData; +import java.sql.NClob; +import java.sql.PreparedStatement; +import java.sql.SQLException; +import java.sql.Timestamp; +import java.sql.Types; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; + +import org.apache.logging.log4j.core.LogEvent; +import org.apache.logging.log4j.core.appender.AppenderLoggingException; +import org.apache.logging.log4j.core.appender.ManagerFactory; +import org.apache.logging.log4j.core.appender.db.AbstractDatabaseManager; +import org.apache.logging.log4j.core.appender.db.ColumnMapping; +import org.apache.logging.log4j.core.config.plugins.convert.DateTypeConverter; +import org.apache.logging.log4j.core.config.plugins.convert.TypeConverters; +import org.apache.logging.log4j.core.util.Closer; +import org.apache.logging.log4j.spi.ThreadContextMap; +import org.apache.logging.log4j.spi.ThreadContextStack; +import org.apache.logging.log4j.util.ReadOnlyStringMap; +import org.apache.logging.log4j.util.Strings; + +/** + * An {@link AbstractDatabaseManager} implementation for relational databases accessed via JDBC. + */ +public final class JdbcDatabaseManager extends AbstractDatabaseManager { + + private static final JdbcDatabaseManagerFactory INSTANCE = new JdbcDatabaseManagerFactory(); + + // NOTE: prepared statements are prepared in this order: column mappings, then column configs + private final List<ColumnMapping> columnMappings; + private final List<ColumnConfig> columnConfigs; + private final ConnectionSource connectionSource; + private final String sqlStatement; + + private Connection connection; + private PreparedStatement statement; + private boolean isBatchSupported; + + private JdbcDatabaseManager(final String name, final int bufferSize, final ConnectionSource connectionSource, + final String sqlStatement, final List<ColumnConfig> columnConfigs, + final List<ColumnMapping> columnMappings) { + super(name, bufferSize); + this.connectionSource = connectionSource; + this.sqlStatement = sqlStatement; + this.columnConfigs = columnConfigs; + this.columnMappings = columnMappings; + } + + @Override + protected void startupInternal() throws Exception { + this.connection = this.connectionSource.getConnection(); + final DatabaseMetaData metaData = this.connection.getMetaData(); + this.isBatchSupported = metaData.supportsBatchUpdates(); + Closer.closeSilently(this.connection); + } + + @Override + protected boolean shutdownInternal() { + if (this.connection != null || this.statement != null) { + return this.commitAndClose(); + } + return true; + } + + @Override + protected void connectAndStart() { + try { + this.connection = this.connectionSource.getConnection(); + this.connection.setAutoCommit(false); + this.statement = this.connection.prepareStatement(this.sqlStatement); + } catch (final SQLException e) { + throw new AppenderLoggingException( + "Cannot write logging event or flush buffer; JDBC manager cannot connect to the database.", e + ); + } + } + + @Deprecated + @Override + protected void writeInternal(final LogEvent event) { + writeInternal(event, null); + } + + @Override + protected void writeInternal(final LogEvent event, final Serializable serializable) { + StringReader reader = null; + try { + if (!this.isRunning() || this.connection == null || this.connection.isClosed() || this.statement == null + || this.statement.isClosed()) { + throw new AppenderLoggingException( + "Cannot write logging event; JDBC manager not connected to the database."); + } + + int i = 1; + for (final ColumnMapping mapping : this.columnMappings) { + if (ThreadContextMap.class.isAssignableFrom(mapping.getType()) + || ReadOnlyStringMap.class.isAssignableFrom(mapping.getType())) { + this.statement.setObject(i++, event.getContextData().toMap()); + } else if (ThreadContextStack.class.isAssignableFrom(mapping.getType())) { + this.statement.setObject(i++, event.getContextStack().asList()); + } else if (Date.class.isAssignableFrom(mapping.getType())) { + this.statement.setObject(i++, + DateTypeConverter.fromMillis(event.getTimeMillis(), mapping.getType().asSubclass(Date.class))); + } else if (Clob.class.isAssignableFrom(mapping.getType())) { + this.statement.setClob(i++, new StringReader(mapping.getLayout().toSerializable(event))); + } else if (NClob.class.isAssignableFrom(mapping.getType())) { + this.statement.setNClob(i++, new StringReader(mapping.getLayout().toSerializable(event))); + } else { + final Object value = TypeConverters.convert(mapping.getLayout().toSerializable(event), + mapping.getType(), null); + if (value == null) { + this.statement.setNull(i++, Types.NULL); + } else { + this.statement.setObject(i++, value); + } + } + } + for (final ColumnConfig column : this.columnConfigs) { + if (column.isEventTimestamp()) { + this.statement.setTimestamp(i++, new Timestamp(event.getTimeMillis())); + } else if (column.isClob()) { + reader = new StringReader(column.getLayout().toSerializable(event)); + if (column.isUnicode()) { + this.statement.setNClob(i++, reader); + } else { + this.statement.setClob(i++, reader); + } + } else if (column.isUnicode()) { + this.statement.setNString(i++, column.getLayout().toSerializable(event)); + } else { + this.statement.setString(i++, column.getLayout().toSerializable(event)); + } + } + + if (this.isBatchSupported) { + this.statement.addBatch(); + } else if (this.statement.executeUpdate() == 0) { + throw new AppenderLoggingException( + "No records inserted in database table for log event in JDBC manager."); + } + } catch (final SQLException e) { + throw new AppenderLoggingException("Failed to insert record for log event in JDBC manager: " + + e.getMessage(), e); + } finally { + Closer.closeSilently(reader); + } + } + + @Override + protected boolean commitAndClose() { + boolean closed = true; + try { + if (this.connection != null && !this.connection.isClosed()) { + if (this.isBatchSupported) { + this.statement.executeBatch(); + } + this.connection.commit(); + } + } catch (final SQLException e) { + throw new AppenderLoggingException("Failed to commit transaction logging event or flushing buffer.", e); + } finally { + try { + Closer.close(this.statement); + } catch (final Exception e) { + logWarn("Failed to close SQL statement logging event or flushing buffer", e); + closed = false; + } finally { + this.statement = null; + } + + try { + Closer.close(this.connection); + } catch (final Exception e) { + logWarn("Failed to close database connection logging event or flushing buffer", e); + closed = false; + } finally { + this.connection = null; + } + } + return closed; + } + + /** + * 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. + * @deprecated use {@link #getManager(String, int, ConnectionSource, String, ColumnConfig[], ColumnMapping[])} + */ + @Deprecated + public static JdbcDatabaseManager getJDBCDatabaseManager(final String name, final int bufferSize, + final ConnectionSource connectionSource, + final String tableName, + final ColumnConfig[] columnConfigs) { + + return getManager(name, + new FactoryData(bufferSize, connectionSource, tableName, columnConfigs, new ColumnMapping[0]), + getFactory()); + } + + /** + * Creates a JDBC manager for use within the {@link JdbcAppender}, or returns a suitable one if it already exists. + * + * @param name The name of the manager, which should include connection details and hashed passwords where possible. + * @param bufferSize The size of the log event buffer. + * @param connectionSource The source for connections to the database. + * @param tableName The name of the database table to insert log events into. + * @param columnConfigs Configuration information about the log table columns. + * @param columnMappings column mapping configuration (including type conversion). + * @return a new or existing JDBC manager as applicable. + */ + public static JdbcDatabaseManager getManager(final String name, + final int bufferSize, + final ConnectionSource connectionSource, + final String tableName, + final ColumnConfig[] columnConfigs, + final ColumnMapping[] columnMappings) { + return getManager(name, new FactoryData(bufferSize, connectionSource, tableName, columnConfigs, columnMappings), + getFactory()); + } + + private static JdbcDatabaseManagerFactory getFactory() { + return INSTANCE; + } + + /** + * Encapsulates data that {@link JdbcDatabaseManagerFactory} uses to create managers. + */ + private static final class FactoryData extends AbstractDatabaseManager.AbstractFactoryData { + private final ConnectionSource connectionSource; + private final String tableName; + private final ColumnConfig[] columnConfigs; + private final ColumnMapping[] columnMappings; + + protected FactoryData(final int bufferSize, final ConnectionSource connectionSource, final String tableName, + final ColumnConfig[] columnConfigs, final ColumnMapping[] columnMappings) { + super(bufferSize); + this.connectionSource = connectionSource; + this.tableName = tableName; + this.columnConfigs = columnConfigs; + this.columnMappings = columnMappings; + } + } + + /** + * Creates managers. + */ + private static final class JdbcDatabaseManagerFactory implements ManagerFactory<JdbcDatabaseManager, FactoryData> { + @Override + public JdbcDatabaseManager createManager(final String name, final FactoryData data) { + final StringBuilder sb = new StringBuilder("INSERT INTO ").append(data.tableName).append(" ("); + // so this gets a little more complicated now that there are two ways to configure column mappings, but + // both mappings follow the same exact pattern for the prepared statement + for (final ColumnMapping mapping : data.columnMappings) { + sb.append(mapping.getName()).append(','); + } + for (final ColumnConfig config : data.columnConfigs) { + sb.append(config.getColumnName()).append(','); + } + // at least one of those arrays is guaranteed to be non-empty + sb.setCharAt(sb.length() - 1, ')'); + sb.append(" VALUES ("); + final List<ColumnMapping> columnMappings = new ArrayList<>(data.columnMappings.length); + for (final ColumnMapping mapping : data.columnMappings) { + if (Strings.isNotEmpty(mapping.getLiteralValue())) { + sb.append(mapping.getLiteralValue()); + } else { + sb.append('?'); + columnMappings.add(mapping); + } + sb.append(','); + } + final List<ColumnConfig> columnConfigs = new ArrayList<>(data.columnConfigs.length); + for (final ColumnConfig config : data.columnConfigs) { + if (Strings.isNotEmpty(config.getLiteralValue())) { + sb.append(config.getLiteralValue()); + } else { + sb.append('?'); + columnConfigs.add(config); + } + sb.append(','); + } + // at least one of those arrays is guaranteed to be non-empty + sb.setCharAt(sb.length() - 1, ')'); + final String sqlStatement = sb.toString(); + + return new JdbcDatabaseManager(name, data.getBufferSize(), data.connectionSource, sqlStatement, + columnConfigs, columnMappings); + } + } + +} http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/26fc9e07/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/db/jpa/JpaDatabaseManager.java ---------------------------------------------------------------------- diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/db/jpa/JpaDatabaseManager.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/db/jpa/JpaDatabaseManager.java index d5fc922..ddfa18e 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/db/jpa/JpaDatabaseManager.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/db/jpa/JpaDatabaseManager.java @@ -1,193 +1,200 @@ -/* - * 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 java.lang.reflect.Constructor; - -import javax.persistence.EntityManager; -import javax.persistence.EntityManagerFactory; -import javax.persistence.EntityTransaction; -import javax.persistence.Persistence; - -import org.apache.logging.log4j.core.LogEvent; -import org.apache.logging.log4j.core.appender.AppenderLoggingException; -import org.apache.logging.log4j.core.appender.ManagerFactory; -import org.apache.logging.log4j.core.appender.db.AbstractDatabaseManager; - -/** - * 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<? extends AbstractLogEventWrapperEntity> entityConstructor; - private final String persistenceUnitName; - - private EntityManagerFactory entityManagerFactory; - - private EntityManager entityManager; - private EntityTransaction transaction; - - private JpaDatabaseManager(final String name, final int bufferSize, - final Class<? extends AbstractLogEventWrapperEntity> entityClass, - final Constructor<? extends AbstractLogEventWrapperEntity> entityConstructor, - final String persistenceUnitName) { - super(name, bufferSize); - this.entityClassName = entityClass.getName(); - this.entityConstructor = entityConstructor; - this.persistenceUnitName = persistenceUnitName; - } - - @Override - protected void startupInternal() { - this.entityManagerFactory = Persistence.createEntityManagerFactory(this.persistenceUnitName); - } - - @Override - protected boolean shutdownInternal() { - boolean closed = true; - if (this.entityManager != null || this.transaction != null) { - closed &= this.commitAndClose(); - } - if (this.entityManagerFactory != null && this.entityManagerFactory.isOpen()) { - this.entityManagerFactory.close(); - } - return closed; - } - - @Override - protected void connectAndStart() { - try { - this.entityManager = this.entityManagerFactory.createEntityManager(); - this.transaction = this.entityManager.getTransaction(); - this.transaction.begin(); - } catch (final Exception e) { - throw new AppenderLoggingException( - "Cannot write logging event or flush buffer; manager cannot create EntityManager or transaction.", e - ); - } - } - - @Override - protected void writeInternal(final LogEvent event) { - if (!this.isRunning() || this.entityManagerFactory == null || this.entityManager == null - || this.transaction == null) { - throw new AppenderLoggingException( - "Cannot write logging event; JPA manager not connected to the database."); - } - - AbstractLogEventWrapperEntity entity; - try { - entity = this.entityConstructor.newInstance(event); - } catch (final Exception e) { - throw new AppenderLoggingException("Failed to instantiate entity class [" + this.entityClassName + "].", e); - } - - try { - this.entityManager.persist(entity); - } catch (final Exception e) { - if (this.transaction != null && this.transaction.isActive()) { - this.transaction.rollback(); - this.transaction = null; - } - throw new AppenderLoggingException("Failed to insert record for log event in JPA manager: " + - e.getMessage(), e); - } - } - - @Override - protected boolean commitAndClose() { - boolean closed = true; - try { - if (this.transaction != null && this.transaction.isActive()) { - this.transaction.commit(); - } - } catch (final Exception e) { - if (this.transaction != null && this.transaction.isActive()) { - this.transaction.rollback(); - } - } finally { - this.transaction = null; - try { - if (this.entityManager != null && this.entityManager.isOpen()) { - this.entityManager.close(); - } - } catch (final Exception e) { - logWarn("Failed to close entity manager while logging event or flushing buffer", e); - closed = false; - } finally { - this.entityManager = null; - } - } - return closed; - } - - /** - * 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 AbstractLogEventWrapperEntity} 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(final String name, final int bufferSize, - final Class<? extends AbstractLogEventWrapperEntity> - entityClass, - final Constructor<? extends AbstractLogEventWrapperEntity> - entityConstructor, - final String persistenceUnitName) { - - return AbstractDatabaseManager.getManager( - name, new FactoryData(bufferSize, entityClass, entityConstructor, persistenceUnitName), FACTORY - ); - } - - /** - * Encapsulates data that {@link JPADatabaseManagerFactory} uses to create managers. - */ - private static final class FactoryData extends AbstractDatabaseManager.AbstractFactoryData { - private final Class<? extends AbstractLogEventWrapperEntity> entityClass; - private final Constructor<? extends AbstractLogEventWrapperEntity> entityConstructor; - private final String persistenceUnitName; - - protected FactoryData(final int bufferSize, final Class<? extends AbstractLogEventWrapperEntity> entityClass, - final Constructor<? extends AbstractLogEventWrapperEntity> entityConstructor, - final String persistenceUnitName) { - super(bufferSize); - - this.entityClass = entityClass; - this.entityConstructor = entityConstructor; - this.persistenceUnitName = persistenceUnitName; - } - } - - /** - * Creates managers. - */ - private static final class JPADatabaseManagerFactory implements ManagerFactory<JpaDatabaseManager, FactoryData> { - @Override - public JpaDatabaseManager createManager(final String name, final FactoryData data) { - return new JpaDatabaseManager( - name, data.getBufferSize(), data.entityClass, data.entityConstructor, data.persistenceUnitName - ); - } - } -} +/* + * 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 java.io.Serializable; +import java.lang.reflect.Constructor; + +import javax.persistence.EntityManager; +import javax.persistence.EntityManagerFactory; +import javax.persistence.EntityTransaction; +import javax.persistence.Persistence; + +import org.apache.logging.log4j.core.LogEvent; +import org.apache.logging.log4j.core.appender.AppenderLoggingException; +import org.apache.logging.log4j.core.appender.ManagerFactory; +import org.apache.logging.log4j.core.appender.db.AbstractDatabaseManager; + +/** + * 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<? extends AbstractLogEventWrapperEntity> entityConstructor; + private final String persistenceUnitName; + + private EntityManagerFactory entityManagerFactory; + + private EntityManager entityManager; + private EntityTransaction transaction; + + private JpaDatabaseManager(final String name, final int bufferSize, + final Class<? extends AbstractLogEventWrapperEntity> entityClass, + final Constructor<? extends AbstractLogEventWrapperEntity> entityConstructor, + final String persistenceUnitName) { + super(name, bufferSize); + this.entityClassName = entityClass.getName(); + this.entityConstructor = entityConstructor; + this.persistenceUnitName = persistenceUnitName; + } + + @Override + protected void startupInternal() { + this.entityManagerFactory = Persistence.createEntityManagerFactory(this.persistenceUnitName); + } + + @Override + protected boolean shutdownInternal() { + boolean closed = true; + if (this.entityManager != null || this.transaction != null) { + closed &= this.commitAndClose(); + } + if (this.entityManagerFactory != null && this.entityManagerFactory.isOpen()) { + this.entityManagerFactory.close(); + } + return closed; + } + + @Override + protected void connectAndStart() { + try { + this.entityManager = this.entityManagerFactory.createEntityManager(); + this.transaction = this.entityManager.getTransaction(); + this.transaction.begin(); + } catch (final Exception e) { + throw new AppenderLoggingException( + "Cannot write logging event or flush buffer; manager cannot create EntityManager or transaction.", e + ); + } + } + + @Deprecated + @Override + protected void writeInternal(final LogEvent event) { + writeInternal(event, null); + } + + @Override + protected void writeInternal(final LogEvent event, final Serializable serializable) { + if (!this.isRunning() || this.entityManagerFactory == null || this.entityManager == null + || this.transaction == null) { + throw new AppenderLoggingException( + "Cannot write logging event; JPA manager not connected to the database."); + } + + AbstractLogEventWrapperEntity entity; + try { + entity = this.entityConstructor.newInstance(event); + } catch (final Exception e) { + throw new AppenderLoggingException("Failed to instantiate entity class [" + this.entityClassName + "].", e); + } + + try { + this.entityManager.persist(entity); + } catch (final Exception e) { + if (this.transaction != null && this.transaction.isActive()) { + this.transaction.rollback(); + this.transaction = null; + } + throw new AppenderLoggingException("Failed to insert record for log event in JPA manager: " + + e.getMessage(), e); + } + } + + @Override + protected boolean commitAndClose() { + boolean closed = true; + try { + if (this.transaction != null && this.transaction.isActive()) { + this.transaction.commit(); + } + } catch (final Exception e) { + if (this.transaction != null && this.transaction.isActive()) { + this.transaction.rollback(); + } + } finally { + this.transaction = null; + try { + if (this.entityManager != null && this.entityManager.isOpen()) { + this.entityManager.close(); + } + } catch (final Exception e) { + logWarn("Failed to close entity manager while logging event or flushing buffer", e); + closed = false; + } finally { + this.entityManager = null; + } + } + return closed; + } + + /** + * 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 AbstractLogEventWrapperEntity} 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(final String name, final int bufferSize, + final Class<? extends AbstractLogEventWrapperEntity> + entityClass, + final Constructor<? extends AbstractLogEventWrapperEntity> + entityConstructor, + final String persistenceUnitName) { + + return AbstractDatabaseManager.getManager( + name, new FactoryData(bufferSize, entityClass, entityConstructor, persistenceUnitName), FACTORY + ); + } + + /** + * Encapsulates data that {@link JPADatabaseManagerFactory} uses to create managers. + */ + private static final class FactoryData extends AbstractDatabaseManager.AbstractFactoryData { + private final Class<? extends AbstractLogEventWrapperEntity> entityClass; + private final Constructor<? extends AbstractLogEventWrapperEntity> entityConstructor; + private final String persistenceUnitName; + + protected FactoryData(final int bufferSize, final Class<? extends AbstractLogEventWrapperEntity> entityClass, + final Constructor<? extends AbstractLogEventWrapperEntity> entityConstructor, + final String persistenceUnitName) { + super(bufferSize); + + this.entityClass = entityClass; + this.entityConstructor = entityConstructor; + this.persistenceUnitName = persistenceUnitName; + } + } + + /** + * Creates managers. + */ + private static final class JPADatabaseManagerFactory implements ManagerFactory<JpaDatabaseManager, FactoryData> { + @Override + public JpaDatabaseManager createManager(final String name, final FactoryData data) { + return new JpaDatabaseManager( + name, data.getBufferSize(), data.entityClass, data.entityConstructor, data.persistenceUnitName + ); + } + } +} http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/26fc9e07/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/mom/JmsAppender.java ---------------------------------------------------------------------- diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/mom/JmsAppender.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/mom/JmsAppender.java index 57e8c75..1704018 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/mom/JmsAppender.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/mom/JmsAppender.java @@ -266,7 +266,7 @@ public class JmsAppender extends AbstractAppender { @Override public void append(final LogEvent event) { - this.manager.send(event, getLayout().toSerializable(event)); + this.manager.send(event, toSerializable(event)); } public JmsManager getManager() {