Properly track resources during shutdown. Detect if an a problem occurred during shutdown.
Project: http://git-wip-us.apache.org/repos/asf/logging-log4j2/repo Commit: http://git-wip-us.apache.org/repos/asf/logging-log4j2/commit/78fe32c0 Tree: http://git-wip-us.apache.org/repos/asf/logging-log4j2/tree/78fe32c0 Diff: http://git-wip-us.apache.org/repos/asf/logging-log4j2/diff/78fe32c0 Branch: refs/heads/LOG4J2-1349-gcfree-threadcontext Commit: 78fe32c0d4643e6692bbcdc3ed754be90f647183 Parents: 97e45ed Author: ggregory <ggreg...@us-l-gg02.rocketsoftware.com> Authored: Wed Sep 7 13:37:42 2016 -0400 Committer: ggregory <ggreg...@us-l-gg02.rocketsoftware.com> Committed: Wed Sep 7 13:37:42 2016 -0400 ---------------------------------------------------------------------- .../log4j/core/appender/AbstractManager.java | 11 +- .../appender/AbstractOutputStreamAppender.java | 2 +- .../core/appender/AbstractWriterAppender.java | 252 +-- .../core/appender/OutputStreamManager.java | 4 +- .../core/appender/RollingFileAppender.java | 748 ++++---- .../log4j/core/appender/WriterManager.java | 301 ++-- .../appender/db/AbstractDatabaseAppender.java | 284 +-- .../appender/db/AbstractDatabaseManager.java | 458 ++--- .../appender/db/jdbc/JdbcDatabaseManager.java | 515 +++--- .../appender/db/jpa/JpaDatabaseManager.java | 381 ++-- .../log4j/core/appender/mom/JmsAppender.java | 430 ++--- .../log4j/core/appender/mom/JmsManager.java | 351 ++-- .../appender/mom/jeromq/JeroMqAppender.java | 370 ++-- .../core/appender/mom/jeromq/JeroMqManager.java | 443 ++--- .../core/appender/mom/kafka/KafkaAppender.java | 240 +-- .../core/appender/mom/kafka/KafkaManager.java | 159 +- .../appender/rolling/RollingFileManager.java | 958 +++++----- .../logging/log4j/core/net/JndiManager.java | 288 +-- .../log4j/core/net/server/JmsServer.java | 295 +-- .../logging/log4j/core/util/JndiCloser.java | 118 +- .../log4j/flume/appender/FlumeAppender.java | 6 +- .../log4j/flume/appender/FlumeAvroManager.java | 670 +++---- .../flume/appender/FlumeEmbeddedManager.java | 555 +++--- .../flume/appender/FlumePersistentManager.java | 1699 +++++++++--------- .../nosql/appender/NoSqlDatabaseManager.java | 440 ++--- 25 files changed, 5008 insertions(+), 4970 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/78fe32c0/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/AbstractManager.java ---------------------------------------------------------------------- diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/AbstractManager.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/AbstractManager.java index acd2759..a08ec75 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/AbstractManager.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/AbstractManager.java @@ -74,18 +74,21 @@ public abstract class AbstractManager implements AutoCloseable { stop(AbstractLifeCycle.DEFAULT_STOP_TIMEOUT, AbstractLifeCycle.DEFAULT_STOP_TIMEUNIT); } - public void stop(final long timeout, final TimeUnit timeUnit) { + public boolean stop(final long timeout, final TimeUnit timeUnit) { + boolean stopped = true; LOCK.lock(); try { --count; if (count <= 0) { MAP.remove(name); LOGGER.debug("Shutting down {} {}", this.getClass().getSimpleName(), getName()); - releaseSub(timeout, timeUnit); + stopped = releaseSub(timeout, timeUnit); + LOGGER.debug("Shut down {} {}, all resources released: {}", this.getClass().getSimpleName(), getName(), stopped); } } finally { LOCK.unlock(); } + return stopped; } /** @@ -143,9 +146,11 @@ public abstract class AbstractManager implements AutoCloseable { * lock is held. A timeout is passed for implementors to use as they see fit. * @param timeout timeout * @param timeUnit timeout time unit + * @return true if all resources were closed normally, false otherwise. */ - protected void releaseSub(final long timeout, final TimeUnit timeUnit) { + protected boolean releaseSub(final long timeout, final TimeUnit timeUnit) { // This default implementation does nothing. + return true; } protected int getCount() { http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/78fe32c0/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/AbstractOutputStreamAppender.java ---------------------------------------------------------------------- diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/AbstractOutputStreamAppender.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/AbstractOutputStreamAppender.java index d4ad3f7..f7e86b4 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/AbstractOutputStreamAppender.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/AbstractOutputStreamAppender.java @@ -139,7 +139,7 @@ public abstract class AbstractOutputStreamAppender<M extends OutputStreamManager @Override protected boolean stop(final long timeout, final TimeUnit timeUnit, final boolean changeLifeCycleState) { boolean stopped = super.stop(timeout, timeUnit, changeLifeCycleState); - manager.stop(timeout, timeUnit); + stopped &= manager.stop(timeout, timeUnit); if (changeLifeCycleState) { setStopped(); } http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/78fe32c0/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/AbstractWriterAppender.java ---------------------------------------------------------------------- diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/AbstractWriterAppender.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/AbstractWriterAppender.java index 7e1d46b..17a0bb4 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/AbstractWriterAppender.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/AbstractWriterAppender.java @@ -1,126 +1,126 @@ -/* - * 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; - -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.core.Filter; -import org.apache.logging.log4j.core.LogEvent; -import org.apache.logging.log4j.core.StringLayout; - -/** - * Appends log events as strings to a writer. - * - * @param <M> - * The kind of {@link WriterManager} under management - */ -public abstract class AbstractWriterAppender<M extends WriterManager> extends AbstractAppender { - - /** - * Immediate flush means that the underlying writer will be flushed at the - * end of each append operation. Immediate flush is slower but ensures that - * each append request is actually written. If <code>immediateFlush</code> - * is set to {@code false}, then there is a good chance that the last few - * logs events are not actually written to persistent media if and when the - * application crashes. - */ - protected final boolean immediateFlush; - private final M manager; - private final ReadWriteLock readWriteLock = new ReentrantReadWriteLock(); - private final Lock readLock = readWriteLock.readLock(); - - /** - * Instantiates. - * - * @param name - * The name of the Appender. - * @param layout - * The layout to format the message. - * @param manager - * The OutputStreamManager. - */ - protected AbstractWriterAppender(final String name, final StringLayout layout, final Filter filter, - final boolean ignoreExceptions, final boolean immediateFlush, final M manager) { - super(name, filter, layout, ignoreExceptions); - this.manager = manager; - this.immediateFlush = immediateFlush; - } - - /** - * Actual writing occurs here. - * <p> - * Most subclasses will need to override this method. - * </p> - * - * @param event - * The LogEvent. - */ - @Override - public void append(final LogEvent event) { - readLock.lock(); - try { - final String str = getStringLayout().toSerializable(event); - if (str.length() > 0) { - manager.write(str); - if (this.immediateFlush || event.isEndOfBatch()) { - manager.flush(); - } - } - } catch (final AppenderLoggingException ex) { - error("Unable to write " + manager.getName() + " for appender " + getName() + ": " + ex); - throw ex; - } finally { - readLock.unlock(); - } - } - - /** - * Gets the manager. - * - * @return the manager. - */ - public M getManager() { - return manager; - } - - public StringLayout getStringLayout() { - return (StringLayout) getLayout(); - } - - @Override - public void start() { - if (getLayout() == null) { - LOGGER.error("No layout set for the appender named [{}].", getName()); - } - if (manager == null) { - LOGGER.error("No OutputStreamManager set for the appender named [{}].", getName()); - } - super.start(); - } - - @Override - public boolean stop(final long timeout, final TimeUnit timeUnit) { - setStopping(); - super.stop(timeout, timeUnit, false); - manager.stop(timeout, timeUnit); - setStopped(); - return true; - } -} +/* + * 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; + +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.core.Filter; +import org.apache.logging.log4j.core.LogEvent; +import org.apache.logging.log4j.core.StringLayout; + +/** + * Appends log events as strings to a writer. + * + * @param <M> + * The kind of {@link WriterManager} under management + */ +public abstract class AbstractWriterAppender<M extends WriterManager> extends AbstractAppender { + + /** + * Immediate flush means that the underlying writer will be flushed at the + * end of each append operation. Immediate flush is slower but ensures that + * each append request is actually written. If <code>immediateFlush</code> + * is set to {@code false}, then there is a good chance that the last few + * logs events are not actually written to persistent media if and when the + * application crashes. + */ + protected final boolean immediateFlush; + private final M manager; + private final ReadWriteLock readWriteLock = new ReentrantReadWriteLock(); + private final Lock readLock = readWriteLock.readLock(); + + /** + * Instantiates. + * + * @param name + * The name of the Appender. + * @param layout + * The layout to format the message. + * @param manager + * The OutputStreamManager. + */ + protected AbstractWriterAppender(final String name, final StringLayout layout, final Filter filter, + final boolean ignoreExceptions, final boolean immediateFlush, final M manager) { + super(name, filter, layout, ignoreExceptions); + this.manager = manager; + this.immediateFlush = immediateFlush; + } + + /** + * Actual writing occurs here. + * <p> + * Most subclasses will need to override this method. + * </p> + * + * @param event + * The LogEvent. + */ + @Override + public void append(final LogEvent event) { + readLock.lock(); + try { + final String str = getStringLayout().toSerializable(event); + if (str.length() > 0) { + manager.write(str); + if (this.immediateFlush || event.isEndOfBatch()) { + manager.flush(); + } + } + } catch (final AppenderLoggingException ex) { + error("Unable to write " + manager.getName() + " for appender " + getName() + ": " + ex); + throw ex; + } finally { + readLock.unlock(); + } + } + + /** + * Gets the manager. + * + * @return the manager. + */ + public M getManager() { + return manager; + } + + public StringLayout getStringLayout() { + return (StringLayout) getLayout(); + } + + @Override + public void start() { + if (getLayout() == null) { + LOGGER.error("No layout set for the appender named [{}].", getName()); + } + if (manager == null) { + LOGGER.error("No OutputStreamManager set for the appender named [{}].", getName()); + } + super.start(); + } + + @Override + public boolean stop(final long timeout, final TimeUnit timeUnit) { + setStopping(); + boolean stopped = super.stop(timeout, timeUnit, false); + stopped &= manager.stop(timeout, timeUnit); + setStopped(); + return stopped; + } +} http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/78fe32c0/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 c5b7a45..d7a2627 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 @@ -131,9 +131,9 @@ public class OutputStreamManager extends AbstractManager implements ByteBufferDe * Default hook to write footer during close. */ @Override - public void releaseSub(final long timeout, final TimeUnit timeUnit) { + public boolean releaseSub(final long timeout, final TimeUnit timeUnit) { writeFooter(); - closeOutputStream(); + return closeOutputStream(); } /** http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/78fe32c0/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/RollingFileAppender.java ---------------------------------------------------------------------- diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/RollingFileAppender.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/RollingFileAppender.java index 726c2d2..b799d1c 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/RollingFileAppender.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/RollingFileAppender.java @@ -1,374 +1,374 @@ -/* - * 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; - -import java.io.Serializable; -import java.util.HashMap; -import java.util.Map; -import java.util.concurrent.TimeUnit; -import java.util.zip.Deflater; - -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.rolling.DefaultRolloverStrategy; -import org.apache.logging.log4j.core.appender.rolling.RollingFileManager; -import org.apache.logging.log4j.core.appender.rolling.RolloverStrategy; -import org.apache.logging.log4j.core.appender.rolling.TriggeringPolicy; -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.PluginBuilderAttribute; -import org.apache.logging.log4j.core.config.plugins.PluginBuilderFactory; -import org.apache.logging.log4j.core.config.plugins.PluginConfiguration; -import org.apache.logging.log4j.core.config.plugins.PluginElement; -import org.apache.logging.log4j.core.config.plugins.validation.constraints.Required; -import org.apache.logging.log4j.core.net.Advertiser; -import org.apache.logging.log4j.core.util.Booleans; -import org.apache.logging.log4j.core.util.Integers; - -/** - * An appender that writes to files and can roll over at intervals. - */ -@Plugin(name = "RollingFile", category = "Core", elementType = "appender", printObject = true) -public final class RollingFileAppender extends AbstractOutputStreamAppender<RollingFileManager> { - - /** - * Builds FileAppender instances. - * - * @param <B> - * This builder class - */ - public static class Builder<B extends Builder<B>> extends AbstractOutputStreamAppender.Builder<B> - implements org.apache.logging.log4j.core.util.Builder<RollingFileAppender> { - - @PluginBuilderAttribute - @Required - private String fileName; - - @PluginBuilderAttribute - @Required - private String filePattern; - - @PluginBuilderAttribute - private boolean append = true; - - @PluginBuilderAttribute - private boolean locking; - - @PluginElement("Policy") - @Required - private TriggeringPolicy policy; - - @PluginElement("Strategy") - private RolloverStrategy strategy; - - @PluginBuilderAttribute - private boolean advertise; - - @PluginBuilderAttribute - private String advertiseUri; - - @PluginBuilderAttribute - private boolean createOnDemand; - - @PluginConfiguration - private Configuration configuration; - - @Override - public RollingFileAppender build() { - // Even though some variables may be annotated with @Required, we must still perform validation here for - // call sites that build builders programmatically. - final boolean isBufferedIo = isBufferedIo(); - final int bufferSize = getBufferSize(); - if (getName() == null) { - LOGGER.error("RollingFileAppender '{}': No name provided.", getName()); - return null; - } - - if (!isBufferedIo && bufferSize > 0) { - LOGGER.warn("RollingFileAppender '{}': The bufferSize is set to {} but bufferedIO is not true", getName(), bufferSize); - } - - if (fileName == null) { - LOGGER.error("RollingFileAppender '{}': No file name provided.", getName()); - return null; - } - - if (filePattern == null) { - LOGGER.error("RollingFileAppender '{}': No file name pattern provided.", getName()); - return null; - } - - if (policy == null) { - LOGGER.error("RollingFileAppender '{}': No TriggeringPolicy provided.", getName()); - return null; - } - - if (strategy == null) { - strategy = DefaultRolloverStrategy.createStrategy(null, null, null, - String.valueOf(Deflater.DEFAULT_COMPRESSION), null, true, configuration); - } - - if (strategy == null) { - strategy = DefaultRolloverStrategy.createStrategy(null, null, null, - String.valueOf(Deflater.DEFAULT_COMPRESSION), null, true, configuration); - } - - final RollingFileManager manager = RollingFileManager.getFileManager(fileName, filePattern, append, - isBufferedIo, policy, strategy, advertiseUri, getLayout(), bufferSize, isImmediateFlush(), - createOnDemand, configuration); - if (manager == null) { - return null; - } - - manager.initialize(); - - return new RollingFileAppender(getName(), getLayout(), getFilter(), manager, fileName, filePattern, - isIgnoreExceptions(), isImmediateFlush(), advertise ? configuration.getAdvertiser() : null); - } - - public String getAdvertiseUri() { - return advertiseUri; - } - - public Configuration getConfiguration() { - return configuration; - } - - public String getFileName() { - return fileName; - } - - public boolean isAdvertise() { - return advertise; - } - - public boolean isAppend() { - return append; - } - - public boolean isCreateOnDemand() { - return createOnDemand; - } - - public boolean isLocking() { - return locking; - } - - public B withAdvertise(final boolean advertise) { - this.advertise = advertise; - return asBuilder(); - } - - public B withAdvertiseUri(final String advertiseUri) { - this.advertiseUri = advertiseUri; - return asBuilder(); - } - - public B withAppend(final boolean append) { - this.append = append; - return asBuilder(); - } - - public B withConfiguration(final Configuration config) { - this.configuration = config; - return asBuilder(); - } - - public B withFileName(final String fileName) { - this.fileName = fileName; - return asBuilder(); - } - - public B withCreateOnDemand(final boolean createOnDemand) { - this.createOnDemand = createOnDemand; - return asBuilder(); - } - - public B withLocking(final boolean locking) { - this.locking = locking; - return asBuilder(); - } - - public String getFilePattern() { - return filePattern; - } - - public TriggeringPolicy getPolicy() { - return policy; - } - - public RolloverStrategy getStrategy() { - return strategy; - } - - public B withFilePattern(final String filePattern) { - this.filePattern = filePattern; - return asBuilder(); - } - - public B withPolicy(final TriggeringPolicy policy) { - this.policy = policy; - return asBuilder(); - } - - public B withStrategy(final RolloverStrategy strategy) { - this.strategy = strategy; - return asBuilder(); - } - - } - - private static final int DEFAULT_BUFFER_SIZE = 8192; - - private final String fileName; - private final String filePattern; - private Object advertisement; - private final Advertiser advertiser; - - private RollingFileAppender(final String name, final Layout<? extends Serializable> layout, final Filter filter, - final RollingFileManager manager, final String fileName, final String filePattern, - final boolean ignoreExceptions, final boolean immediateFlush, final Advertiser advertiser) { - super(name, layout, filter, ignoreExceptions, immediateFlush, manager); - if (advertiser != null) { - final Map<String, String> configuration = new HashMap<>(layout.getContentFormat()); - configuration.put("contentType", layout.getContentType()); - configuration.put("name", name); - advertisement = advertiser.advertise(configuration); - } - this.fileName = fileName; - this.filePattern = filePattern; - this.advertiser = advertiser; - } - - @Override - public boolean stop(final long timeout, final TimeUnit timeUnit) { - setStopping(); - super.stop(timeout, timeUnit, false); - if (advertiser != null) { - advertiser.unadvertise(advertisement); - } - setStopped(); - return true; - } - - /** - * Writes the log entry rolling over the file when required. - - * @param event The LogEvent. - */ - @Override - public void append(final LogEvent event) { - getManager().checkRollover(event); - super.append(event); - } - - /** - * Returns the File name for the Appender. - * @return The file name. - */ - public String getFileName() { - return fileName; - } - - /** - * Returns the file pattern used when rolling over. - * @return The file pattern. - */ - public String getFilePattern() { - return filePattern; - } - - /** - * Returns the triggering policy. - * @param <T> TriggeringPolicy type - * @return The TriggeringPolicy - */ - public <T extends TriggeringPolicy> T getTriggeringPolicy() { - return getManager().getTriggeringPolicy(); - } - - /** - * Creates a RollingFileAppender. - * @param fileName The name of the file that is actively written to. (required). - * @param filePattern The pattern of the file name to use on rollover. (required). - * @param append If true, events are appended to the file. If false, the file - * is overwritten when opened. Defaults to "true" - * @param name The name of the Appender (required). - * @param bufferedIO When true, I/O will be buffered. Defaults to "true". - * @param bufferSizeStr buffer size for buffered IO (default is 8192). - * @param immediateFlush When true, events are immediately flushed. Defaults to "true". - * @param policy The triggering policy. (required). - * @param strategy The rollover strategy. Defaults to DefaultRolloverStrategy. - * @param layout The layout to use (defaults to the default PatternLayout). - * @param filter The Filter or null. - * @param ignore If {@code "true"} (default) exceptions encountered when appending events are logged; otherwise - * they are propagated to the caller. - * @param advertise "true" if the appender configuration should be advertised, "false" otherwise. - * @param advertiseUri The advertised URI which can be used to retrieve the file contents. - * @param config The Configuration. - * @return A RollingFileAppender. - * @deprecated Use {@link #newBuilder()}. - */ - @Deprecated - public static RollingFileAppender createAppender( - // @formatter:off - final String fileName, - final String filePattern, - final String append, - final String name, - final String bufferedIO, - final String bufferSizeStr, - final String immediateFlush, - final TriggeringPolicy policy, - final RolloverStrategy strategy, - final Layout<? extends Serializable> layout, - final Filter filter, - final String ignore, - final String advertise, - final String advertiseUri, - final Configuration config) { - // @formatter:on - final int bufferSize = Integers.parseInt(bufferSizeStr, DEFAULT_BUFFER_SIZE); - // @formatter:off - return newBuilder() - .withAdvertise(Boolean.parseBoolean(advertise)) - .withAdvertiseUri(advertiseUri) - .withAppend(Booleans.parseBoolean(append, true)) - .withBufferedIo(Booleans.parseBoolean(bufferedIO, true)) - .withBufferSize(bufferSize) - .withConfiguration(config) - .withFileName(fileName) - .withFilePattern(filePattern) - .withFilter(filter) - .withIgnoreExceptions(Booleans.parseBoolean(ignore, true)) - .withImmediateFlush(Booleans.parseBoolean(immediateFlush, true)) - .withLayout(layout) - .withCreateOnDemand(false) - .withLocking(false) - .withName(name) - .withPolicy(policy) - .withStrategy(strategy) - .build(); - // @formatter:on - } - - @PluginBuilderFactory - public static <B extends Builder<B>> B newBuilder() { - return new Builder<B>().asBuilder(); - } -} +/* + * 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; + +import java.io.Serializable; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.TimeUnit; +import java.util.zip.Deflater; + +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.rolling.DefaultRolloverStrategy; +import org.apache.logging.log4j.core.appender.rolling.RollingFileManager; +import org.apache.logging.log4j.core.appender.rolling.RolloverStrategy; +import org.apache.logging.log4j.core.appender.rolling.TriggeringPolicy; +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.PluginBuilderAttribute; +import org.apache.logging.log4j.core.config.plugins.PluginBuilderFactory; +import org.apache.logging.log4j.core.config.plugins.PluginConfiguration; +import org.apache.logging.log4j.core.config.plugins.PluginElement; +import org.apache.logging.log4j.core.config.plugins.validation.constraints.Required; +import org.apache.logging.log4j.core.net.Advertiser; +import org.apache.logging.log4j.core.util.Booleans; +import org.apache.logging.log4j.core.util.Integers; + +/** + * An appender that writes to files and can roll over at intervals. + */ +@Plugin(name = "RollingFile", category = "Core", elementType = "appender", printObject = true) +public final class RollingFileAppender extends AbstractOutputStreamAppender<RollingFileManager> { + + /** + * Builds FileAppender instances. + * + * @param <B> + * This builder class + */ + public static class Builder<B extends Builder<B>> extends AbstractOutputStreamAppender.Builder<B> + implements org.apache.logging.log4j.core.util.Builder<RollingFileAppender> { + + @PluginBuilderAttribute + @Required + private String fileName; + + @PluginBuilderAttribute + @Required + private String filePattern; + + @PluginBuilderAttribute + private boolean append = true; + + @PluginBuilderAttribute + private boolean locking; + + @PluginElement("Policy") + @Required + private TriggeringPolicy policy; + + @PluginElement("Strategy") + private RolloverStrategy strategy; + + @PluginBuilderAttribute + private boolean advertise; + + @PluginBuilderAttribute + private String advertiseUri; + + @PluginBuilderAttribute + private boolean createOnDemand; + + @PluginConfiguration + private Configuration configuration; + + @Override + public RollingFileAppender build() { + // Even though some variables may be annotated with @Required, we must still perform validation here for + // call sites that build builders programmatically. + final boolean isBufferedIo = isBufferedIo(); + final int bufferSize = getBufferSize(); + if (getName() == null) { + LOGGER.error("RollingFileAppender '{}': No name provided.", getName()); + return null; + } + + if (!isBufferedIo && bufferSize > 0) { + LOGGER.warn("RollingFileAppender '{}': The bufferSize is set to {} but bufferedIO is not true", getName(), bufferSize); + } + + if (fileName == null) { + LOGGER.error("RollingFileAppender '{}': No file name provided.", getName()); + return null; + } + + if (filePattern == null) { + LOGGER.error("RollingFileAppender '{}': No file name pattern provided.", getName()); + return null; + } + + if (policy == null) { + LOGGER.error("RollingFileAppender '{}': No TriggeringPolicy provided.", getName()); + return null; + } + + if (strategy == null) { + strategy = DefaultRolloverStrategy.createStrategy(null, null, null, + String.valueOf(Deflater.DEFAULT_COMPRESSION), null, true, configuration); + } + + if (strategy == null) { + strategy = DefaultRolloverStrategy.createStrategy(null, null, null, + String.valueOf(Deflater.DEFAULT_COMPRESSION), null, true, configuration); + } + + final RollingFileManager manager = RollingFileManager.getFileManager(fileName, filePattern, append, + isBufferedIo, policy, strategy, advertiseUri, getLayout(), bufferSize, isImmediateFlush(), + createOnDemand, configuration); + if (manager == null) { + return null; + } + + manager.initialize(); + + return new RollingFileAppender(getName(), getLayout(), getFilter(), manager, fileName, filePattern, + isIgnoreExceptions(), isImmediateFlush(), advertise ? configuration.getAdvertiser() : null); + } + + public String getAdvertiseUri() { + return advertiseUri; + } + + public Configuration getConfiguration() { + return configuration; + } + + public String getFileName() { + return fileName; + } + + public boolean isAdvertise() { + return advertise; + } + + public boolean isAppend() { + return append; + } + + public boolean isCreateOnDemand() { + return createOnDemand; + } + + public boolean isLocking() { + return locking; + } + + public B withAdvertise(final boolean advertise) { + this.advertise = advertise; + return asBuilder(); + } + + public B withAdvertiseUri(final String advertiseUri) { + this.advertiseUri = advertiseUri; + return asBuilder(); + } + + public B withAppend(final boolean append) { + this.append = append; + return asBuilder(); + } + + public B withConfiguration(final Configuration config) { + this.configuration = config; + return asBuilder(); + } + + public B withFileName(final String fileName) { + this.fileName = fileName; + return asBuilder(); + } + + public B withCreateOnDemand(final boolean createOnDemand) { + this.createOnDemand = createOnDemand; + return asBuilder(); + } + + public B withLocking(final boolean locking) { + this.locking = locking; + return asBuilder(); + } + + public String getFilePattern() { + return filePattern; + } + + public TriggeringPolicy getPolicy() { + return policy; + } + + public RolloverStrategy getStrategy() { + return strategy; + } + + public B withFilePattern(final String filePattern) { + this.filePattern = filePattern; + return asBuilder(); + } + + public B withPolicy(final TriggeringPolicy policy) { + this.policy = policy; + return asBuilder(); + } + + public B withStrategy(final RolloverStrategy strategy) { + this.strategy = strategy; + return asBuilder(); + } + + } + + private static final int DEFAULT_BUFFER_SIZE = 8192; + + private final String fileName; + private final String filePattern; + private Object advertisement; + private final Advertiser advertiser; + + private RollingFileAppender(final String name, final Layout<? extends Serializable> layout, final Filter filter, + final RollingFileManager manager, final String fileName, final String filePattern, + final boolean ignoreExceptions, final boolean immediateFlush, final Advertiser advertiser) { + super(name, layout, filter, ignoreExceptions, immediateFlush, manager); + if (advertiser != null) { + final Map<String, String> configuration = new HashMap<>(layout.getContentFormat()); + configuration.put("contentType", layout.getContentType()); + configuration.put("name", name); + advertisement = advertiser.advertise(configuration); + } + this.fileName = fileName; + this.filePattern = filePattern; + this.advertiser = advertiser; + } + + @Override + public boolean stop(final long timeout, final TimeUnit timeUnit) { + setStopping(); + final boolean stopped = super.stop(timeout, timeUnit, false); + if (advertiser != null) { + advertiser.unadvertise(advertisement); + } + setStopped(); + return stopped; + } + + /** + * Writes the log entry rolling over the file when required. + + * @param event The LogEvent. + */ + @Override + public void append(final LogEvent event) { + getManager().checkRollover(event); + super.append(event); + } + + /** + * Returns the File name for the Appender. + * @return The file name. + */ + public String getFileName() { + return fileName; + } + + /** + * Returns the file pattern used when rolling over. + * @return The file pattern. + */ + public String getFilePattern() { + return filePattern; + } + + /** + * Returns the triggering policy. + * @param <T> TriggeringPolicy type + * @return The TriggeringPolicy + */ + public <T extends TriggeringPolicy> T getTriggeringPolicy() { + return getManager().getTriggeringPolicy(); + } + + /** + * Creates a RollingFileAppender. + * @param fileName The name of the file that is actively written to. (required). + * @param filePattern The pattern of the file name to use on rollover. (required). + * @param append If true, events are appended to the file. If false, the file + * is overwritten when opened. Defaults to "true" + * @param name The name of the Appender (required). + * @param bufferedIO When true, I/O will be buffered. Defaults to "true". + * @param bufferSizeStr buffer size for buffered IO (default is 8192). + * @param immediateFlush When true, events are immediately flushed. Defaults to "true". + * @param policy The triggering policy. (required). + * @param strategy The rollover strategy. Defaults to DefaultRolloverStrategy. + * @param layout The layout to use (defaults to the default PatternLayout). + * @param filter The Filter or null. + * @param ignore If {@code "true"} (default) exceptions encountered when appending events are logged; otherwise + * they are propagated to the caller. + * @param advertise "true" if the appender configuration should be advertised, "false" otherwise. + * @param advertiseUri The advertised URI which can be used to retrieve the file contents. + * @param config The Configuration. + * @return A RollingFileAppender. + * @deprecated Use {@link #newBuilder()}. + */ + @Deprecated + public static RollingFileAppender createAppender( + // @formatter:off + final String fileName, + final String filePattern, + final String append, + final String name, + final String bufferedIO, + final String bufferSizeStr, + final String immediateFlush, + final TriggeringPolicy policy, + final RolloverStrategy strategy, + final Layout<? extends Serializable> layout, + final Filter filter, + final String ignore, + final String advertise, + final String advertiseUri, + final Configuration config) { + // @formatter:on + final int bufferSize = Integers.parseInt(bufferSizeStr, DEFAULT_BUFFER_SIZE); + // @formatter:off + return newBuilder() + .withAdvertise(Boolean.parseBoolean(advertise)) + .withAdvertiseUri(advertiseUri) + .withAppend(Booleans.parseBoolean(append, true)) + .withBufferedIo(Booleans.parseBoolean(bufferedIO, true)) + .withBufferSize(bufferSize) + .withConfiguration(config) + .withFileName(fileName) + .withFilePattern(filePattern) + .withFilter(filter) + .withIgnoreExceptions(Booleans.parseBoolean(ignore, true)) + .withImmediateFlush(Booleans.parseBoolean(immediateFlush, true)) + .withLayout(layout) + .withCreateOnDemand(false) + .withLocking(false) + .withName(name) + .withPolicy(policy) + .withStrategy(strategy) + .build(); + // @formatter:on + } + + @PluginBuilderFactory + public static <B extends Builder<B>> B newBuilder() { + return new Builder<B>().asBuilder(); + } +} http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/78fe32c0/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/WriterManager.java ---------------------------------------------------------------------- diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/WriterManager.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/WriterManager.java index 323a9fe..c2e7572 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/WriterManager.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/WriterManager.java @@ -1,150 +1,151 @@ -/* - * 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; - -import java.io.IOException; -import java.io.Writer; -import java.util.concurrent.TimeUnit; - -import org.apache.logging.log4j.core.StringLayout; - -/** - * Manages a Writer so that it can be shared by multiple Appenders and will - * allow appenders to reconfigure without requiring a new writer. - */ -public class WriterManager extends AbstractManager { - - /** - * Creates a Manager. - * - * @param name The name of the stream to manage. - * @param data The data to pass to the Manager. - * @param factory The factory to use to create the Manager. - * @param <T> The type of the WriterManager. - * @return A WriterManager. - */ - public static <T> WriterManager getManager(final String name, final T data, - final ManagerFactory<? extends WriterManager, T> factory) { - return AbstractManager.getManager(name, factory, data); - } - protected final StringLayout layout; - - private volatile Writer writer; - - public WriterManager(final Writer writer, final String streamName, final StringLayout layout, - final boolean writeHeader) { - super(null, streamName); - this.writer = writer; - this.layout = layout; - if (writeHeader && layout != null) { - final byte[] header = layout.getHeader(); - if (header != null) { - try { - this.writer.write(new String(header, layout.getCharset())); - } catch (final IOException e) { - logError("Unable to write header", e); - } - } - } - } - - protected synchronized void closeWriter() { - final Writer w = writer; // access volatile field only once per method - try { - w.close(); - } catch (final IOException ex) { - logError("Unable to close stream", ex); - } - } - - /** - * Flushes any buffers. - */ - public synchronized void flush() { - try { - writer.flush(); - } catch (final IOException ex) { - final String msg = "Error flushing stream " + getName(); - throw new AppenderLoggingException(msg, ex); - } - } - - protected Writer getWriter() { - return writer; - } - - /** - * Returns the status of the stream. - * @return true if the stream is open, false if it is not. - */ - public boolean isOpen() { - return getCount() > 0; - } - - /** - * Default hook to write footer during close. - */ - @Override - public void releaseSub(final long timeout, final TimeUnit timeUnit) { - writeFooter(); - closeWriter(); - } - - protected void setWriter(final Writer writer) { - final byte[] header = layout.getHeader(); - if (header != null) { - try { - writer.write(new String(header, layout.getCharset())); - this.writer = writer; // only update field if writer.write() succeeded - } catch (final IOException ioe) { - logError("Unable to write header", ioe); - } - } else { - this.writer = writer; - } - } - - /** - * Some output streams synchronize writes while others do not. Synchronizing here insures that - * log events won't be intertwined. - * @param bytes The serialized Log event. - * @param offset The offset into the byte array. - * @param length The number of bytes to write. - * @throws AppenderLoggingException if an error occurs. - */ - protected synchronized void write(final String str) { - try { - writer.write(str); - } catch (final IOException ex) { - final String msg = "Error writing to stream " + getName(); - throw new AppenderLoggingException(msg, ex); - } - } - - /** - * Writes the footer. - */ - protected void writeFooter() { - if (layout == null) { - return; - } - final byte[] footer = layout.getFooter(); - if (footer != null && footer.length > 0) { - write(new String(footer, layout.getCharset())); - } - } -} +/* + * 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; + +import java.io.IOException; +import java.io.Writer; +import java.util.concurrent.TimeUnit; + +import org.apache.logging.log4j.core.StringLayout; + +/** + * Manages a Writer so that it can be shared by multiple Appenders and will + * allow appenders to reconfigure without requiring a new writer. + */ +public class WriterManager extends AbstractManager { + + /** + * Creates a Manager. + * + * @param name The name of the stream to manage. + * @param data The data to pass to the Manager. + * @param factory The factory to use to create the Manager. + * @param <T> The type of the WriterManager. + * @return A WriterManager. + */ + public static <T> WriterManager getManager(final String name, final T data, + final ManagerFactory<? extends WriterManager, T> factory) { + return AbstractManager.getManager(name, factory, data); + } + protected final StringLayout layout; + + private volatile Writer writer; + + public WriterManager(final Writer writer, final String streamName, final StringLayout layout, + final boolean writeHeader) { + super(null, streamName); + this.writer = writer; + this.layout = layout; + if (writeHeader && layout != null) { + final byte[] header = layout.getHeader(); + if (header != null) { + try { + this.writer.write(new String(header, layout.getCharset())); + } catch (final IOException e) { + logError("Unable to write header", e); + } + } + } + } + + protected synchronized void closeWriter() { + final Writer w = writer; // access volatile field only once per method + try { + w.close(); + } catch (final IOException ex) { + logError("Unable to close stream", ex); + } + } + + /** + * Flushes any buffers. + */ + public synchronized void flush() { + try { + writer.flush(); + } catch (final IOException ex) { + final String msg = "Error flushing stream " + getName(); + throw new AppenderLoggingException(msg, ex); + } + } + + protected Writer getWriter() { + return writer; + } + + /** + * Returns the status of the stream. + * @return true if the stream is open, false if it is not. + */ + public boolean isOpen() { + return getCount() > 0; + } + + /** + * Default hook to write footer during close. + */ + @Override + public boolean releaseSub(final long timeout, final TimeUnit timeUnit) { + writeFooter(); + closeWriter(); + return true; + } + + protected void setWriter(final Writer writer) { + final byte[] header = layout.getHeader(); + if (header != null) { + try { + writer.write(new String(header, layout.getCharset())); + this.writer = writer; // only update field if writer.write() succeeded + } catch (final IOException ioe) { + logError("Unable to write header", ioe); + } + } else { + this.writer = writer; + } + } + + /** + * Some output streams synchronize writes while others do not. Synchronizing here insures that + * log events won't be intertwined. + * @param bytes The serialized Log event. + * @param offset The offset into the byte array. + * @param length The number of bytes to write. + * @throws AppenderLoggingException if an error occurs. + */ + protected synchronized void write(final String str) { + try { + writer.write(str); + } catch (final IOException ex) { + final String msg = "Error writing to stream " + getName(); + throw new AppenderLoggingException(msg, ex); + } + } + + /** + * Writes the footer. + */ + protected void writeFooter() { + if (layout == null) { + return; + } + final byte[] footer = layout.getFooter(); + if (footer != null && footer.length > 0) { + write(new String(footer, layout.getCharset())); + } + } +} http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/78fe32c0/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 85a3a67..538b644 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,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; - -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 <a href="/log4j/2.x/log4j-nosql/apidocs/">NoSQL</a>. - * - * @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(); - super.stop(timeout, timeUnit, false); - if (this.getManager() != null) { - this.getManager().stop(timeout, timeUnit); - } - setStopped(); - return true; - } - - @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.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 <a href="/log4j/2.x/log4j-nosql/apidocs/">NoSQL</a>. + * + * @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(); + } + } +} http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/78fe32c0/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 5f46bf4..8400adb 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,226 +1,232 @@ -/* - * 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()}. - */ - protected abstract void 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. - */ - public final synchronized void shutdown() { - this.flush(); - if (this.isRunning()) { - try { - this.shutdownInternal(); - } catch (final Exception e) { - logWarn("Caught exception while performing database shutdown operations", e); - } finally { - this.running = false; - } - } - } - - /** - * 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}. - */ - protected abstract void 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); - if (this.buffer.size() >= this.bufferSize || event.isEndOfBatch()) { - this.flush(); - } - } else { - this.connectAndStart(); - try { - this.writeInternal(event); - } finally { - this.commitAndClose(); - } - } - } - - @Override - public final void releaseSub(final long timeout, final TimeUnit timeUnit) { - 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.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 { + 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); + 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; + } + } +}