
com.kdgregory.logback.aws.internal.AbstractAppender Maven / Gradle / Ivy
Show all versions of logback-aws-appenders Show documentation
// Copyright (c) Keith D Gregory
//
// Licensed 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 com.kdgregory.logback.aws.internal;
import java.lang.Thread.UncaughtExceptionHandler;
import com.kdgregory.logging.aws.internal.AbstractWriterConfig;
import com.kdgregory.logging.aws.internal.AbstractWriterStatistics;
import com.kdgregory.logging.common.LogMessage;
import com.kdgregory.logging.common.LogWriter;
import com.kdgregory.logging.common.util.InternalLogger;
import com.kdgregory.logging.common.util.MessageQueue.DiscardAction;
import com.kdgregory.logging.common.util.ThreadFactory;
import com.kdgregory.logging.common.util.WriterFactory;
import ch.qos.logback.access.spi.IAccessEvent;
import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.core.Layout;
import ch.qos.logback.core.UnsynchronizedAppenderBase;
/**
* Common implementation code that's shared between appenders.
*
* For the most part, appenders have the same behavior: they initialize, transform
* log messages, and shut down. Most of the code to do that lives here, with a few
* hooks that are implemented in the appender proper.
*
* Most of the member variables defined by this class are protected. This is intended
* to support testing. If you decide to subclass and access those variables, remember
* that this is an internal class: they may go away.
*
* Note: this appender is built on UnsynchronizedAppenderBase
, which is
* perhaps unnecessary (if you're worried about contention you shouldn't be doing a
* lot of logging). However, there are some critical sections within {@link #append},
* and these are protected by internal lock objects.
*/
public abstract class AbstractAppender
<
WriterConfigType extends AbstractWriterConfig,
AppenderStatsType extends AbstractWriterStatistics,
AppenderStatsMXBeanType,
LogbackEventType
>
extends UnsynchronizedAppenderBase
{
// factories for creating writer and thread
// note: these must be explicitly assigned in subclass constructor, because they
// will almost certainly be inner classes that require access to the outer
// instance, and you can't create those in a super() call
protected ThreadFactory threadFactory;
protected WriterFactory writerFactory;
// used for internal logging: we manage this and expose it to our subclasses
protected InternalLogger internalLogger;
// the appender stats object; we keep the reference because we call writer factory
protected AppenderStatsType appenderStats;
// the MX bean type for the appender stats object
private Class appenderStatsMXBeanClass;
// the current writer; initialized on first append, changed after rotation or error
protected volatile LogWriter writer;
// layout is managed by this class, not superclass
protected Layout layout;
// this object is used for synchronization of initialization and writer change
private Object initializationLock = new Object();
// appender configuration; subclass passes instance to constructor, clones for writer
protected WriterConfigType appenderConfig;
public AbstractAppender(
WriterConfigType appenderConfig,
ThreadFactory threadFactory,
WriterFactory writerFactory,
AppenderStatsType appenderStats,
Class appenderStatsMXBeanClass)
{
this.appenderConfig = appenderConfig;
this.threadFactory = threadFactory;
this.writerFactory = writerFactory;
this.appenderStats = appenderStats;
this.appenderStatsMXBeanClass = appenderStatsMXBeanClass;
this.internalLogger = new LogbackInternalLogger(this);
}
//----------------------------------------------------------------------------
// Configuration
//----------------------------------------------------------------------------
@Override
public void setName(String name)
{
super.setName(name);
}
/**
* Sets the layout manager for this appender. Note that we do not use an
* Encoder
; it is a knob that doesn't need to be turned.
*/
public void setLayout(Layout layout)
{
this.layout = layout;
}
/**
* Sets the synchronous
configuration property. This can only
* be done at the time of configuration, not once the appender is operating.
*/
public void setSynchronous(boolean value)
{
if (writer != null)
throw new IllegalStateException("can not set synchronous mode once writer created");
appenderConfig.setSynchronousMode(value);
}
/**
* Returns the synchronous
configuration property.
*/
public boolean getSynchronous()
{
return appenderConfig.getSynchronousMode();
}
/**
* Sets the batchDelay
configuration property.
*/
public void setBatchDelay(long value)
{
appenderConfig.setBatchDelay(value);
if (writer != null)
{
writer.setBatchDelay(value);
}
}
/**
* Returns the batchDelay
configuration property.
*/
public long getBatchDelay()
{
return appenderConfig.getBatchDelay();
}
/**
* Sets the truncateOversizeMessages
configuration property.
*/
public void setTruncateOversizeMessages(boolean value)
{
appenderConfig.setTruncateOversizeMessages(value);
}
/**
* Returns the truncateOversizeMessages
configuration property.
*/
public boolean getTruncateOversizeMessages()
{
return appenderConfig.getTruncateOversizeMessages();
}
/**
* Sets the discardThreshold
configuration property.
*/
public void setDiscardThreshold(int value)
{
appenderConfig.setDiscardThreshold(value);
if (writer != null)
{
writer.setDiscardThreshold(value);
}
}
/**
* Returns the discardThreshold
configuration property.
*/
public int getDiscardThreshold()
{
return appenderConfig.getDiscardThreshold();
}
/**
* Sets the discardAction
configuration property.
*/
public void setDiscardAction(String value)
{
DiscardAction tmpDiscardAction = DiscardAction.lookup(value);
if (tmpDiscardAction == null)
{
internalLogger.error("invalid discard action: " + value, null);
return;
}
appenderConfig.setDiscardAction(tmpDiscardAction);
if (writer != null)
{
writer.setDiscardAction(tmpDiscardAction);
}
}
/**
* Returns the discardAction
configuration property.
*/
public String getDiscardAction()
{
return appenderConfig.getDiscardAction().toString();
}
/**
* Sets the assumedRole
configuration property.
*
* Calling this method after the writer has been initialized will have no
* effect until the next log rotation.
*/
public void setAssumedRole(String value)
{
appenderConfig.setAssumedRole(value);
}
/**
* Returns the assumedRole
configuration property.
*/
public String getAssumedRole()
{
return appenderConfig.getAssumedRole();
}
/**
* Sets the discardAction
configuration property.
*
* Calling this method after the writer has been initialized will have no
* effect until the next log rotation.
*/
public void setClientFactory(String value)
{
appenderConfig.setClientFactoryMethod(value);
}
/**
* Returns the clientFactory
configuration property.
*/
public String getClientFactory()
{
return appenderConfig.getClientFactoryMethod();
}
/**
* Sets the clientRegion
configuration property.
*/
public void setClientRegion(String value)
{
appenderConfig.setClientRegion(value);
}
/**
* Returns the clientRegion
configuration property.
*/
public String getClientRegion()
{
return appenderConfig.getClientRegion();
}
/**
* Sets the clientEndpoint
configuration property.
*/
public void setClientEndpoint(String value)
{
appenderConfig.setClientEndpoint(value);
}
/**
* Returns the clientEndpoint
configuration property.
*/
public String getClientEndpoint()
{
return appenderConfig.getClientEndpoint();
}
/**
* Sets the useShutdownHook
configuration property.
*/
public void setUseShutdownHook(boolean value)
{
appenderConfig.setUseShutdownHook(value);
}
/**
* Returns the useShutdownHook
configuration property.
*/
public boolean getUseShutdownHook()
{
return appenderConfig.getUseShutdownHook();
}
/**
* Sets the initializationTimeout
configuration property.
*/
public void setInitializationTimeout(long value)
{
appenderConfig.setInitializationTimeout(value);
}
/**
* Returns the initializationTimeout
configuration property.
*/
public long getInitializationTimeout()
{
return appenderConfig.getInitializationTimeout();
}
/**
* Sets the enableBatchLogging
configuration property.
*/
public void setEnableBatchLogging(boolean value)
{
appenderConfig.setEnableBatchLogging(value);
}
/**
* Returns the enableBatchLogging
configuration property.
*/
public boolean getEnableBatchLogging()
{
return appenderConfig.getEnableBatchLogging();
}
//----------------------------------------------------------------------------
// Other accessors
//----------------------------------------------------------------------------
/**
* Returns the appender statistics object.
*/
public AppenderStatsType getAppenderStatistics()
{
return appenderStats;
}
//----------------------------------------------------------------------------
// Appender implementation
//----------------------------------------------------------------------------
@Override
public void start()
{
synchronized (initializationLock)
{
if (isStarted())
{
// someone else already initialized us
return;
}
startWriter();
registerStatisticsBean();
}
super.start();
}
@Override
public void stop()
{
synchronized (initializationLock)
{
if (writer != null)
{
stopWriter();
}
unregisterStatisticsBean();
}
super.stop();
}
@Override
protected void append(LogbackEventType event)
{
if (! isStarted())
{
internalLogger.warn("append called before appender was started");
return;
}
// it would be nice if Logback events had a shared superinterface,
// but they don't so we need to get ugly to get timestamp
long timestamp = (event instanceof ILoggingEvent) ? ((ILoggingEvent)event).getTimeStamp()
: (event instanceof IAccessEvent) ? ((IAccessEvent)event).getTimeStamp()
: System.currentTimeMillis();
String message = null;
try
{
message = layout.doLayout(event);
}
catch (Exception ex)
{
internalLogger.error("unable to apply layout", ex);
return;
}
try
{
internalAppend(new LogMessage(timestamp, message));
}
catch (Exception ex)
{
internalLogger.error("unable to append event", ex);
}
}
//----------------------------------------------------------------------------
// Subclass hooks
//----------------------------------------------------------------------------
/**
* Called as part of initialization. Subclass should provide a config
* object that is populated with everything the subclass controls, and
* with all substitutions applied. The abstract class will populate
* with everything that it controls (eg, connection info).
*/
protected abstract WriterConfigType generateWriterConfig();
//----------------------------------------------------------------------------
// Internals
//----------------------------------------------------------------------------
/**
* Called by {@link #initialize} to create the writer.
*/
private void startWriter()
{
WriterConfigType actualConfig = generateWriterConfig();
synchronized (initializationLock)
{
try
{
writer = writerFactory.newLogWriter(actualConfig, appenderStats, internalLogger);
threadFactory.startWriterThread(writer, new UncaughtExceptionHandler()
{
@Override
public void uncaughtException(Thread t, Throwable ex)
{
internalLogger.error("unhandled exception in writer", ex);
appenderStats.setLastError(null, ex);
writer = null;
}
});
if (layout.getFileHeader() != null)
{
internalAppend(new LogMessage(System.currentTimeMillis(), layout.getFileHeader()));
}
}
catch (Exception ex)
{
internalLogger.error("exception while initializing writer", ex);
}
}
}
/**
* Closes the current writer.
*/
private void stopWriter()
{
synchronized (initializationLock)
{
try
{
if (writer == null)
return;
if (layout.getFileFooter() != null)
{
internalAppend(new LogMessage(System.currentTimeMillis(), layout.getFileFooter()));
}
writer.stop();
writer.waitUntilStopped(appenderConfig.getBatchDelay() * 2);
}
catch (Exception ex)
{
internalLogger.error("exception while shutting down writer", ex);
}
writer = null;
}
}
/**
* Registers the appender statistics with JMX. Logs but otherwise ignores failure.
*
* The name for the bean is consistent with the Log4J LayoutDynamicMBean
,
* so that it will appear in the hierarchy under the appender.
*
* Note: this method is protected so that it can be avoided during unit tests.
*/
protected void registerStatisticsBean()
{
JMXManager.getInstance().addStatsBean(getName(), appenderStats, appenderStatsMXBeanClass);
}
/**
* Unregisters the appender statistics from JMX. This is called when the appender
* is closed. Logs but otherwise ignores failure.
*
* Note: this method is protected so that it can be avoided during unit tests.
*/
protected void unregisterStatisticsBean()
{
JMXManager.getInstance().removeStatsBean(getName());
}
private void internalAppend(LogMessage message)
{
if (message == null)
{
internalLogger.error("internal error: message was null", null);
return;
}
writer.addMessage(message);
}
}