All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.kdgregory.log4j2.aws.internal.AbstractAppender Maven / Gradle / Ivy

The newest version!
// 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.log4j2.aws.internal;

import java.lang.Thread.UncaughtExceptionHandler;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.TimeUnit;

import org.apache.logging.log4j.core.Layout;
import org.apache.logging.log4j.core.LogEvent;
import org.apache.logging.log4j.core.StringLayout;

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;


/**
 *  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. *

* Log4J2, like Logback, explicitly initializes the appender before use. The only * strangeness here is in stop(): there are two versions defined by the interface, * but current versions of Log4J call only one of them. */ public abstract class AbstractAppender < WriterConfigType extends AbstractWriterConfig, AppenderConfigType extends AbstractAppenderConfig, AppenderStatsType extends AbstractWriterStatistics, AppenderStatsMXBeanType > extends org.apache.logging.log4j.core.appender.AbstractAppender { // 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; // this object is used for synchronization of initialization and writer change private Object initializationLock = new Object(); // this is provided to us by subclass protected AppenderConfigType appenderConfig; // character set for handling header/footer; extracted from layout if possible protected Charset layoutCharset = StandardCharsets.UTF_8; // holding this separately because of Log4J's "setters? we don't need no stinkin' setters!" // approach to configuration protected DiscardAction discardAction; // the current writer protected volatile LogWriter writer; protected AbstractAppender( String name, ThreadFactory threadFactory, WriterFactory writerFactory, AppenderStatsType appenderStats, Class appenderStatsMXBeanClass, AppenderConfigType config, InternalLogger providedInternalLogger) { super(name, config.getFilter(), config.getLayout()); this.appenderConfig = config; this.threadFactory = threadFactory; this.writerFactory = writerFactory; this.appenderStats = appenderStats; this.appenderStatsMXBeanClass = appenderStatsMXBeanClass; this.internalLogger = (providedInternalLogger != null) ? providedInternalLogger : new Log4J2InternalLogger(this); discardAction = DiscardAction.lookup(config.getDiscardAction()); if (discardAction == null) { internalLogger.error("invalid discard action: " + config.getDiscardAction(), null); discardAction = DiscardAction.oldest; } Layout layout = config.getLayout(); if (layout instanceof StringLayout) { layoutCharset = ((StringLayout)layout).getCharset(); } } //---------------------------------------------------------------------------- // Other accessors //---------------------------------------------------------------------------- /** * Returns the appender statistics object. */ public AppenderStatsType getAppenderStatistics() { return appenderStats; } /** * Exposes configuration for testing. */ public AppenderConfigType getConfig() { return appenderConfig; } //---------------------------------------------------------------------------- // Appender implementation //---------------------------------------------------------------------------- @Override public void start() { synchronized (initializationLock) { if (isStarted()) { // someone else already initialized us return; } startWriter(); registerStatisticsBean(); } super.start(); } @Override public void stop() { // the framework should never call this code; this exception // is here to catch any copy-paste errors in tests, but will // also serve to identify an incorrect framework version throw new IllegalStateException("stop() called -- should never happen with Log4J > 2.8"); } @Override public boolean stop(long timeout, TimeUnit timeUnit) { synchronized (initializationLock) { if (writer != null) { stopWriter(timeout, timeUnit); } unregisterStatisticsBean(); } return super.stop(timeout, timeUnit); } @Override public void append(LogEvent event) { if (! isStarted()) { internalLogger.warn("append called before appender was started"); return; } String message = null; try { message = getConfig().getLayout().toSerializable(event); } catch (Exception ex) { internalLogger.error("unable to apply layout", ex); return; } try { internalAppend(new LogMessage(event.getTimeMillis(), 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 start a new writer running. Does not * close the old writer, if any. */ private void startWriter() { WriterConfigType writerConfig = generateWriterConfig() .setTruncateOversizeMessages(appenderConfig.getTruncateOversizeMessages()) .setSynchronousMode(appenderConfig.isSynchronous()) .setBatchDelay(appenderConfig.getBatchDelay()) .setDiscardThreshold(appenderConfig.getDiscardThreshold()) .setDiscardAction(discardAction) .setClientFactoryMethod(appenderConfig.getClientFactory()) .setAssumedRole(appenderConfig.getAssumedRole()) .setClientRegion(appenderConfig.getClientRegion()) .setClientEndpoint(appenderConfig.getClientEndpoint()) .setInitializationTimeout(appenderConfig.getInitializationTimeout()) .setEnableBatchLogging(appenderConfig.isEnableBatchLogging()); synchronized (initializationLock) { try { writer = writerFactory.newLogWriter(writerConfig, 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 (getLayout().getHeader() != null) { String header = new String(getLayout().getHeader(), layoutCharset); if (header.length() > 0) { internalAppend(new LogMessage(System.currentTimeMillis(), header)); } } } catch (Exception ex) { internalLogger.error("exception while initializing writer", ex); } } } /** * Closes the current writer and optionally waits for it to shut down. */ private void stopWriter(long timeout, TimeUnit timeUnit) { synchronized (initializationLock) { try { if (writer == null) return; if (getLayout().getFooter() != null) { String header = new String(getLayout().getFooter(), layoutCharset); if (header.length() > 0) { internalAppend(new LogMessage(System.currentTimeMillis(), header)); } } writer.stop(); if (timeUnit != null) { writer.waitUntilStopped(timeUnit.toMillis(timeout)); } } 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. */ 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. */ 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); } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy