com.kdgregory.log4j2.aws.SNSAppender Maven / Gradle / Ivy
Show all versions of log4j2-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.log4j2.aws;
import java.util.Date;
import org.apache.logging.log4j.core.Appender;
import org.apache.logging.log4j.core.Core;
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.validation.constraints.Required;
import org.apache.logging.log4j.core.lookup.StrSubstitutor;
import com.kdgregory.log4j2.aws.internal.AbstractAppender;
import com.kdgregory.log4j2.aws.internal.AbstractAppenderBuilder;
import com.kdgregory.log4j2.aws.internal.SNSAppenderConfig;
import com.kdgregory.logging.aws.sns.SNSWriterConfig;
import com.kdgregory.logging.aws.sns.SNSWriterFactory;
import com.kdgregory.logging.aws.sns.SNSWriterStatistics;
import com.kdgregory.logging.aws.sns.SNSWriterStatisticsMXBean;
import com.kdgregory.logging.aws.common.Substitutions;
import com.kdgregory.logging.common.factories.DefaultThreadFactory;
import com.kdgregory.logging.common.util.InternalLogger;
/**
* An appender that writes to an SNS topic.
*
* This appender supports the following configuration parameters:
*
*
*
* topicName
* The name of the destination SNS topic; substitutions are allowed.
*
* Must refer to a topic in the current region; if not, and you do not
* enable auto-create, initialization fails.
*
* If you specify both topicName
and topicArn
,
* the latter takes precedence.
*
*
* topicArn
* The ARN of the destination SNS topic; substitutions are allowed.
*
* Must refer to a topic in the current region; if not, initialization
* fails.
*
* If you specify both topicName
and topicArn
,
* the latter takes precedence.
*
*
* autoCreate
* If true, and the topic is specified by name, the appender will create
* the topic if it does not already exist. If false, a missing topic
* will be reported as an error and the appender will be disabled.
*
* Default is false
.
*
*
* subject
* (optional) The subject for messages that are delivered via email. This
* is constrained by the SNS API to be less than 100 characters, ASCII
* only, and not start with whitespace.
*
*
* discardThreshold
* The number of unsent messages that will trigger message discard. A
* high value is useful when network connectivity is intermittent and/or
* overall AWS communication is causing throttling. However, a value that
* is too high may cause out-of-memory errors.
*
* The default, 10,000, is based on the assumptions that (1) each message
* will be 1k or less, and (2) any app that uses remote logging can afford
* 10MB.
*
*
* discardAction
* The action to take when the number of unsent messages exceeds the
* discard threshold. Values are "none" (retain all messages), "oldest"
* (discard oldest messages), and "newest" (discard most recent messages).
*
* The default is "oldest". Attempting to set an incorrect value will throw
* a configuration error.
*
*
* assumedRole
* Specifies role name or ARN that will be assumed by this appender. Useful
* for cross-account logging. If the appender does not have permission to
* assume this role, initialization will fail.
*
*
* clientFactory
* The fully-qualified name of a static method to create the correct AWS
* client, which will be called instead of the writer's internal client
* factory. This is useful if you need non-default configuration, such as
* using a proxy server.
*
* The passed string is of the form com.example.Classname.methodName
.
* If this does not reference a class/method on the classpath then writer
* initialization will fail.
*
*
* clientRegion
* Specifies a non-default service region. This setting is ignored if you
* use a client factory.
*
* Note that the region must be supported by the current SDK version.
*
*
* clientEndpoint
* Specifies a non-default service endpoint. This is intended for use with
* older AWS SDK versions that do not provide client factories and default
* to us-east-1 for constructed clients, although it can be used for newer
* releases when you want to override the default region provider. This
* setting is ignored if you use a client factory.
*
*
* useShutdownHook
* Controls whether the appender uses a shutdown hook to attempt to process
* outstanding messages when the JVM exits. This is true by default; set to
* false to disable.
*
*
* @see Appender documentation
*/
@Plugin(name = "SNSAppender", category = Core.CATEGORY_NAME, elementType = Appender.ELEMENT_TYPE)
public class SNSAppender
extends AbstractAppender
{
//----------------------------------------------------------------------------
// Builder
//----------------------------------------------------------------------------
@PluginBuilderFactory
public static SNSAppenderBuilder newBuilder() {
return new SNSAppenderBuilder();
}
public static class SNSAppenderBuilder
extends AbstractAppenderBuilder
implements SNSAppenderConfig, org.apache.logging.log4j.core.util.Builder
{
// this appender has different defaults than others, and while I could
// have created an arg-taking constructor for AbstractAppenderBuilder,
// I'm a little worred that Log4J might try to do something bizarre
public SNSAppenderBuilder()
{
setDiscardThreshold(1000);
}
@PluginBuilderAttribute("name")
@Required(message = "SNSAppender: no name provided")
private String name;
public SNSAppenderBuilder setName(String value)
{
this.name = value;
return this;
}
@Override
public String getName()
{
return name;
}
@PluginBuilderAttribute("topicName")
private String topicName;
/**
* Sets the topicName
configuration property.
*/
public SNSAppenderBuilder setTopicName(String value)
{
this.topicName = value;
return this;
}
/**
* Returns the topicName
configuration property, null
* if the appender was configured via ARN.
*/
@Override
public String getTopicName()
{
return topicName;
}
@PluginBuilderAttribute("topicArn")
private String topicArn;
/**
* Sets the topicArn
configuration property.
*/
public SNSAppenderBuilder setTopicArn(String value)
{
this.topicArn = value;
return this;
}
/**
* Returns the topicArn
configuration property, null
* if the appender was configured via name.
*/
@Override
public String getTopicArn()
{
return topicArn;
}
@PluginBuilderAttribute("autoCreate")
private boolean autoCreate;
/**
* Sets the autoCreate
configuration property.
*/
public SNSAppenderBuilder setAutoCreate(boolean value)
{
this.autoCreate = value;
return this;
}
/**
* Returns the autoCreate
configuration property.
*/
@Override
public boolean isAutoCreate()
{
return autoCreate;
}
@PluginBuilderAttribute("subject")
private String subject;
/**
* Sets the subject
configuration property.
*/
public SNSAppenderBuilder setSubject(String value)
{
this.subject = value;
return this;
}
/**
* Returns the subject
configuration property.
*/
@Override
public String getSubject()
{
return subject;
}
@Override
public long getBatchDelay()
{
// we want a different default than other appenders, and we can't prevent
// Log4J from changing it (because it bypasses the setter), so we'll return
// a constant ... unless the super returns 0, which means synchronous mode
long value = super.getBatchDelay();
return (value == 0) ? 0 : 1;
}
@Override
public SNSAppender build()
{
return new SNSAppender(name, this, null);
}
}
//----------------------------------------------------------------------------
// Appender
//----------------------------------------------------------------------------
// this is extracted from config so that we can validate
protected Integer retentionPeriod;
protected SNSAppender(String name, SNSAppenderConfig config, InternalLogger internalLogger)
{
super(
name,
new DefaultThreadFactory("log4j2-kinesis"),
new SNSWriterFactory(),
new SNSWriterStatistics(),
SNSWriterStatisticsMXBean.class,
config,
internalLogger);
}
//----------------------------------------------------------------------------
// Additional public API
//----------------------------------------------------------------------------
//----------------------------------------------------------------------------
// Internals
//----------------------------------------------------------------------------
@Override
protected SNSWriterConfig generateWriterConfig()
{
StrSubstitutor l4jsubs = config.getConfiguration().getStrSubstitutor();
Substitutions subs = new Substitutions(new Date(), sequence.get());
String actualTopicName = subs.perform(l4jsubs.replace(config.getTopicName()));
String actualTopicArn = subs.perform(l4jsubs.replace(config.getTopicArn()));
String actualSubject = subs.perform(l4jsubs.replace(config.getSubject()));
return new SNSWriterConfig(
actualTopicName, actualTopicArn, actualSubject, config.isAutoCreate(),
config.getDiscardThreshold(), discardAction,
config.getClientFactory(), config.getAssumedRole(), config.getClientRegion(), config.getClientEndpoint());
}
@Override
protected boolean shouldRotate(long now)
{
return false;
}
}