Java tutorial
/** * Copyright (C) 2010-2013 Andrei Pozolotin <Andrei.Pozolotin@gmail.com> * * All rights reserved. Licensed under the OSI BSD License. * * http://www.opensource.org/licenses/bsd-license.php */ package com.carrotgarden.log4j.aws.sns; import java.io.File; import java.util.List; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.SynchronousQueue; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; import org.apache.log4j.AppenderSkeleton; import org.apache.log4j.Layout; import org.apache.log4j.PatternLayout; import org.apache.log4j.helpers.LogLog; import org.apache.log4j.helpers.OptionConverter; import org.apache.log4j.spi.LoggingEvent; import org.codehaus.jackson.annotate.JsonProperty; import org.codehaus.jackson.map.ObjectMapper; import org.codehaus.jackson.map.SerializationConfig.Feature; import com.amazonaws.auth.AWSCredentials; import com.amazonaws.auth.PropertiesCredentials; import com.amazonaws.services.sns.AmazonSNSAsync; import com.amazonaws.services.sns.AmazonSNSAsyncClient; import com.amazonaws.services.sns.model.ListTopicsResult; import com.amazonaws.services.sns.model.PublishRequest; import com.amazonaws.services.sns.model.Topic; /** * AWS SNS appender * * original idea from * * https://github.com/apetresc/amazon-sns-log4j-appender * * https://github.com/insula/log4j-sns * */ public class Appender extends AppenderSkeleton { public static final int DEFAULT_POOL_MIN = 0; public static final int DEFAULT_POOL_MAX = 10; // /** log4j config option; amazon credentials file; must exist */ @JsonProperty protected String credentials; /** log4j config option; SNS topic name; must exist */ @JsonProperty protected String topicName; /** * log4j config option; SNS topic subject; use for instance identity; * optional */ @JsonProperty protected String topicSubject; /** log4j config option; minimum thread pool size; optional */ @JsonProperty protected int poolMin = DEFAULT_POOL_MIN; /** log4j config option; maximum thread pool size; optional */ @JsonProperty protected int poolMax = DEFAULT_POOL_MAX; /** log4j config option; layout class name; optional */ @JsonProperty public Layout getLaoyut() { return super.getLayout(); } /** render for {@link #toString()} */ @JsonProperty public String getLayoutClassName() { final Layout layout = getLayout(); return layout == null ? null : layout.getClass().getName(); } /** log4j config option; layout class name; optional */ public void setLayoutClassName(final String layoutClassName) { final Layout defaultLayout = new PatternLayout(); layout = (Layout) OptionConverter.instantiateByClassName( // layoutClassName, // Layout.class, // defaultLayout // ); } /** render for {@link #toString()} */ @JsonProperty public String getEvaluatorClassName() { final Evaluator evaluator = getEvaluator(); return evaluator == null ? null : evaluator.getClass().getName(); } /** log4j config option; evaluator class name; optional */ public void setEvaluatorClassName(final String evaluatorClassName) { final Evaluator defaultEvaluator = new EvaluatorThrottler(); evaluator = (Evaluator) OptionConverter.instantiateByClassName( // evaluatorClassName, // Evaluator.class, // defaultEvaluator // ); } /** evaluator configured for this appender */ @JsonProperty protected String evaluatorProperties; // /** evaluator configured for this appender */ @JsonProperty protected Evaluator evaluator; /** * topic ARN resolved from existing amazon topic name * * http://aws.amazon.com/sns/faqs/#10 */ @JsonProperty protected String topicARN; /** AWS SNS client thread pool */ protected ExecutorService service; /** AWS SNS client dedicated to the appender */ protected AmazonSNSAsync amazonClient; /** appender activation status */ @JsonProperty protected volatile boolean isActive; // public boolean isActive() { return isActive; } public boolean isTriggering(final LoggingEvent event) { return isActive && evaluator.isTriggeringEvent(event); } public boolean hasCredentials() { return credentials != null; } public boolean hasTopicName() { return topicName != null; } public boolean hasTopicSubject() { return topicSubject != null; } public boolean hasEvaluator() { return evaluator != null; } public boolean hasLayout() { return layout != null; } public boolean hasTopicARN() { return topicARN != null; } public boolean hasAmazonClient() { return amazonClient != null; } /** provide amazon login credentials from file */ protected boolean ensureCredentials() { if (hasCredentials()) { final File file = new File(getCredentials()); if (file.exists() && file.isFile() && file.canRead()) { return true; } } LogLog.error("sns: ivalid option", new IllegalArgumentException("Credentials")); return false; } /** amazon topic name is required option */ protected boolean ensureTopicName() { if (hasTopicName()) { return true; } else { LogLog.error("sns: ivalid option", new IllegalArgumentException("TopicName")); return false; } } /** instantiate amazon client */ protected boolean ensureAmazonClient() { try { final File file = new File(getCredentials()); final AWSCredentials creds = new PropertiesCredentials(file); amazonClient = new AmazonSNSAsyncClient(creds, service); return true; } catch (final Exception e) { LogLog.error("sns: amazon client init failure", e); return false; } } /** resolve topic ARN from topic name */ protected boolean ensureTopicARN() { try { final ListTopicsResult result = amazonClient.listTopics(); final List<Topic> topicList = result.getTopics(); for (final Topic entry : topicList) { final String arn = entry.getTopicArn(); final String name = Util.topicNameFromARN(arn); if (getTopicName().equals(name)) { topicARN = arn; return true; } } LogLog.error("sns: unknown topic name", new IllegalArgumentException(getTopicName())); return false; } catch (final Exception e) { LogLog.error("sns: amazon topic lookup failure", e); return false; } } /** provide default throttling evaluator */ protected boolean ensureEvaluator() { try { if (!hasEvaluator()) { setEvaluator(new EvaluatorThrottler()); } getEvaluator().setProperties(getEvaluatorProperties()); return true; } catch (final Exception e) { LogLog.error("sns: evaluator init falure", e); return false; } } /** provide default JSON event layout renderer */ protected boolean ensureLayout() { try { if (!hasLayout()) { setLayout(new LayoutJSON()); } return true; } catch (final Exception e) { LogLog.error("sns: layout init failure", e); return false; } } /** provide AWS SNS thread pool */ protected boolean ensureService() { try { service = new ThreadPoolExecutor(// poolMin, // poolMax, // 60L, // TimeUnit.SECONDS, // new SynchronousQueue<Runnable>(), // new ThreadFactoryAWS() // ); return true; } catch (final Exception e) { LogLog.warn("sns: failed to init service; using default", e); service = Executors.newCachedThreadPool(); return true; } } @Override public synchronized void activateOptions() { isActive = true // && ensureLayout() // && ensureEvaluator() // && ensureService() // && ensureCredentials() // && ensureAmazonClient() // && ensureTopicName() // && ensureTopicARN() // ; LogLog.warn("sns: appender activate : " + getClass().getName() + "\n" + this); if (!isActive()) { LogLog.error("sns: appender is disabled due to invalid configration : " + getClass().getName()); } } /** */ @Override public synchronized void close() { isActive = false; LogLog.warn("sns: appender deactivate : " + getClass().getName()); if (hasAmazonClient()) { service.shutdown(); service = null; amazonClient.shutdown(); amazonClient = null; } } /** will used json layout by default */ @Override public boolean requiresLayout() { return true; } @Override public void append(final LoggingEvent event) { // LogLog.warn("event=" + event.getMessage()); if (!isTriggering(event)) { return; } // LogLog.warn("event=" + event.getLoggerName()); String message; if (hasLayout()) { message = getLayout().format(event); } else { message = event.getRenderedMessage(); } message = Util.forceByteLimit(message, Util.MESSAGE_LIMIT); String subject; if (hasTopicSubject()) { subject = getTopicSubject(); subject = Util.forceByteLimit(subject, Util.SUBJECT_LIMIT); } else { subject = null; } publish(message, subject); } protected void publish(final String message, final String subject) { try { final PublishRequest request = new PublishRequest(// topicARN, message, subject); amazonClient.publishAsync(request); } catch (final Exception e) { LogLog.error("sns: publish failure", e); } } public String getCredentials() { return credentials; } public void setCredentials(final String credentials) { this.credentials = credentials; } public String getTopicName() { return topicName; } public void setTopicName(final String topicName) { this.topicName = topicName; } public String getTopicSubject() { return topicSubject; } public void setTopicSubject(final String topicSubject) { this.topicSubject = topicSubject; } public Evaluator getEvaluator() { return evaluator; } public void setEvaluator(final Evaluator evaluator) { this.evaluator = evaluator; } public int getPoolMin() { return poolMin; } public void setPoolMin(final int poolMin) { this.poolMin = poolMin; } public void setPoolMin(final String poolMinText) { this.poolMin = Util.getIntValue(poolMinText, DEFAULT_POOL_MIN); } public int getPoolMax() { return poolMax; } public void setPoolMax(final int poolMax) { this.poolMax = poolMax; } public void setPoolMax(final String poolMaxText) { this.poolMax = Util.getIntValue(poolMaxText, DEFAULT_POOL_MAX); } public String getEvaluatorProperties() { return evaluatorProperties; } public void setEvaluatorProperties(final String evaluatorProperties) { this.evaluatorProperties = evaluatorProperties; } /** render as JSON; use only @JsonProperty annotated fields */ @Override public String toString() { try { final ObjectMapper mapper = new ObjectMapper(); mapper.configure(Feature.INDENT_OUTPUT, true); mapper.configure(Feature.USE_ANNOTATIONS, true); mapper.configure(Feature.AUTO_DETECT_FIELDS, false); mapper.configure(Feature.AUTO_DETECT_GETTERS, false); mapper.configure(Feature.AUTO_DETECT_IS_GETTERS, false); mapper.configure(Feature.FAIL_ON_EMPTY_BEANS, false); return mapper.writeValueAsString(this); } catch (final Exception e) { LogLog.error("sns: ", e); return "{}"; } } }