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

io.bitsensor.plugins.shaded.org.springframework.amqp.rabbit.logback.AmqpAppender Maven / Gradle / Ivy

The newest version!
/*
 * Copyright 2014-2016 the original author or authors.
 *
 * 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 io.bitsensor.plugins.shaded.io.bitsensor.plugins.shaded.org.springframework.amqp.rabbit.logback;

import java.io.UnsupportedEncodingException;
import java.util.Calendar;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.Timer;
import java.util.TimerTask;
import java.util.UUID;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.atomic.AtomicInteger;

import io.bitsensor.plugins.shaded.io.bitsensor.plugins.shaded.org.springframework.amqp.AmqpException;
import io.bitsensor.plugins.shaded.io.bitsensor.plugins.shaded.org.springframework.amqp.core.DirectExchange;
import io.bitsensor.plugins.shaded.io.bitsensor.plugins.shaded.org.springframework.amqp.core.Exchange;
import io.bitsensor.plugins.shaded.io.bitsensor.plugins.shaded.org.springframework.amqp.core.FanoutExchange;
import io.bitsensor.plugins.shaded.io.bitsensor.plugins.shaded.org.springframework.amqp.core.HeadersExchange;
import io.bitsensor.plugins.shaded.io.bitsensor.plugins.shaded.org.springframework.amqp.core.Message;
import io.bitsensor.plugins.shaded.io.bitsensor.plugins.shaded.org.springframework.amqp.core.MessageDeliveryMode;
import io.bitsensor.plugins.shaded.io.bitsensor.plugins.shaded.org.springframework.amqp.core.MessageProperties;
import io.bitsensor.plugins.shaded.io.bitsensor.plugins.shaded.org.springframework.amqp.core.TopicExchange;
import io.bitsensor.plugins.shaded.io.bitsensor.plugins.shaded.org.springframework.amqp.rabbit.connection.AbstractConnectionFactory;
import io.bitsensor.plugins.shaded.io.bitsensor.plugins.shaded.org.springframework.amqp.rabbit.connection.CachingConnectionFactory;
import io.bitsensor.plugins.shaded.io.bitsensor.plugins.shaded.org.springframework.amqp.rabbit.core.DeclareExchangeConnectionListener;
import io.bitsensor.plugins.shaded.io.bitsensor.plugins.shaded.org.springframework.amqp.rabbit.core.RabbitAdmin;
import io.bitsensor.plugins.shaded.io.bitsensor.plugins.shaded.org.springframework.amqp.rabbit.core.RabbitTemplate;
import io.bitsensor.plugins.shaded.io.bitsensor.plugins.shaded.org.springframework.amqp.rabbit.support.LogAppenderUtils;

import ch.qos.logback.classic.Level;
import ch.qos.logback.classic.PatternLayout;
import ch.qos.logback.classic.pattern.TargetLengthBasedClassNameAbbreviator;
import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.classic.spi.LoggingEvent;
import ch.qos.logback.core.AppenderBase;
import ch.qos.logback.core.Layout;

/**
 * A Lockback appender that publishes logging events to an AMQP Exchange.
 * 

* A fully-configured AmqpAppender, with every option set to their defaults, would look like this: *

 * {@code
 * 
 *     
 *        %n ]]>
 *     
 *      
 *     AmqpAppenderTest
 *     %property{applicationId}.%c.%p
 *     true
 *     UTF-8
 *     false
 *     NON_PERSISTENT
 * 
 * }
 * 
* * @author Artem Bilan * @author Gary Russell * @since 1.4 */ public class AmqpAppender extends AppenderBase { /** * Key name for the application id (if there is one set via the appender config) in the message properties. */ public static final String APPLICATION_ID = "applicationId"; /** * Key name for the logger category name in the message properties */ public static final String CATEGORY_NAME = "categoryName"; /** * Key name for the logger level name in the message properties */ public static final String CATEGORY_LEVEL = "level"; /** * Name of the exchange to publish log events to. */ private String exchangeName = "logs"; /** * Type of the exchange to publish log events to. */ private String exchangeType = "topic"; private final PatternLayout locationLayout = new PatternLayout(); { this.locationLayout.setPattern("%nopex%class|%method|%line"); } /** * Logback Layout to use to generate routing key. */ private final PatternLayout routingKeyLayout = new PatternLayout(); { this.routingKeyLayout.setPattern("%nopex%c.%p"); } /** * Configuration arbitrary application ID. */ private String applicationId = null; /** * Where LoggingEvents are queued to send. */ private final LinkedBlockingQueue events = new LinkedBlockingQueue(); /** * The pool of senders. */ private ExecutorService senderPool = null; /** * How many senders to use at once. Use more senders if you have lots of log output going through this appender. */ private int senderPoolSize = 2; /** * How many times to retry sending a message if the broker is unavailable or there is some other error. */ private int maxSenderRetries = 30; /** * Retries are delayed like: N ^ log(N), where N is the retry number. */ private final Timer retryTimer = new Timer("log-event-retry-delay", true); /** * RabbitMQ ConnectionFactory. */ private AbstractConnectionFactory connectionFactory; /** * Additional client connection properties added to the rabbit connection, with the form * {@code key:value[,key:value]...}. */ private String clientConnectionProperties; /** * A comma-delimited list of broker addresses: host:port[,host:port]* * @since 1.5.6 */ private String addresses; /** * RabbitMQ host to connect to. */ private String host = "localhost"; /** * RabbitMQ virtual host to connect to. */ private String virtualHost = "/"; /** * RabbitMQ port to connect to. */ private int port = 5672; /** * RabbitMQ user to connect as. */ private String username = "guest"; /** * RabbitMQ password for this user. */ private String password = "guest"; /** * Default content-type of log messages. */ private String contentType = "text/plain"; /** * Default content-encoding of log messages. */ private String contentEncoding = null; /** * Whether or not to try and declare the configured exchange when this appender starts. */ private boolean declareExchange = false; /** * charset to use when converting String to byte[], default null (system default charset used). * If the charset is unsupported on the current platform, we fall back to using * the system charset. */ private String charset; private boolean durable = true; private MessageDeliveryMode deliveryMode = MessageDeliveryMode.PERSISTENT; private boolean autoDelete = false; /** * Used to determine whether {@link MessageProperties#setMessageId(String)} is set. */ private boolean generateId = false; private Layout layout; private TargetLengthBasedClassNameAbbreviator abbreviator; private boolean includeCallerData; public void setRoutingKeyPattern(String routingKeyPattern) { this.routingKeyLayout.setPattern("%nopex{}" + routingKeyPattern); } public String getHost() { return this.host; } public void setHost(String host) { this.host = host; } public int getPort() { return this.port; } public void setPort(int port) { this.port = port; } public void setAddresses(String addresses) { this.addresses = addresses; } public String getAddresses() { return this.addresses; } public String getVirtualHost() { return this.virtualHost; } public void setVirtualHost(String virtualHost) { this.virtualHost = virtualHost; } public String getUsername() { return this.username; } public void setUsername(String username) { this.username = username; } public String getPassword() { return this.password; } public void setPassword(String password) { this.password = password; } public String getExchangeName() { return this.exchangeName; } public void setExchangeName(String exchangeName) { this.exchangeName = exchangeName; } public String getExchangeType() { return this.exchangeType; } public void setExchangeType(String exchangeType) { this.exchangeType = exchangeType; } public String getRoutingKeyPattern() { return this.routingKeyLayout.getPattern(); } public boolean isDeclareExchange() { return this.declareExchange; } public void setDeclareExchange(boolean declareExchange) { this.declareExchange = declareExchange; } public String getContentType() { return this.contentType; } public void setContentType(String contentType) { this.contentType = contentType; } public String getContentEncoding() { return this.contentEncoding; } public void setContentEncoding(String contentEncoding) { this.contentEncoding = contentEncoding; } public String getApplicationId() { return this.applicationId; } public void setApplicationId(String applicationId) { this.applicationId = applicationId; } public int getSenderPoolSize() { return this.senderPoolSize; } public void setSenderPoolSize(int senderPoolSize) { this.senderPoolSize = senderPoolSize; } public int getMaxSenderRetries() { return this.maxSenderRetries; } public void setMaxSenderRetries(int maxSenderRetries) { this.maxSenderRetries = maxSenderRetries; } public boolean isDurable() { return this.durable; } public void setDurable(boolean durable) { this.durable = durable; } public String getDeliveryMode() { return this.deliveryMode.toString(); } public void setDeliveryMode(String deliveryMode) { this.deliveryMode = MessageDeliveryMode.valueOf(deliveryMode); } public boolean isAutoDelete() { return this.autoDelete; } public void setAutoDelete(boolean autoDelete) { this.autoDelete = autoDelete; } public boolean isGenerateId() { return this.generateId; } public void setGenerateId(boolean generateId) { this.generateId = generateId; } public String getCharset() { return this.charset; } public void setCharset(String charset) { this.charset = charset; } public void setLayout(Layout layout) { this.layout = layout; } public void setAbbreviation(int len) { this.abbreviator = new TargetLengthBasedClassNameAbbreviator(len); } /** * Set additional client connection properties to be added to the rabbit connection, * with the form {@code key:value[,key:value]...}. * @param clientConnectionProperties the properties. * @since 1.5.6 */ public void setClientConnectionProperties(String clientConnectionProperties) { this.clientConnectionProperties = clientConnectionProperties; } public boolean isIncludeCallerData() { return this.includeCallerData; } /** * If true, the caller data will be available in the target AMQP message. * By default no caller data is sent to the RabbitMQ. * @param includeCallerData include or on caller data * @since 1.7.1 * @see ILoggingEvent#getCallerData() */ public void setIncludeCallerData(boolean includeCallerData) { this.includeCallerData = includeCallerData; } @Override public void start() { super.start(); this.routingKeyLayout.setPattern(this.routingKeyLayout.getPattern() .replaceAll("%property\\{applicationId\\}", this.applicationId)); this.routingKeyLayout.setContext(getContext()); this.routingKeyLayout.start(); this.locationLayout.setContext(getContext()); this.locationLayout.start(); this.connectionFactory = new CachingConnectionFactory(); this.connectionFactory.setHost(this.host); this.connectionFactory.setPort(this.port); if (this.addresses != null) { this.connectionFactory.setAddresses(this.addresses); } this.connectionFactory.setUsername(this.username); this.connectionFactory.setPassword(this.password); this.connectionFactory.setVirtualHost(this.virtualHost); LogAppenderUtils.updateClientConnectionProperties(this.connectionFactory, this.clientConnectionProperties); updateConnectionClientProperties(this.connectionFactory.getRabbitConnectionFactory().getClientProperties()); setUpExchangeDeclaration(); this.senderPool = Executors.newCachedThreadPool(); for (int i = 0; i < this.senderPoolSize; i++) { this.senderPool.submit(new EventSender()); } if (this.layout == null) { addError("A layout is required"); } } /** * Subclasses can override this method to add properties to the connection client * properties. * @param clientProperties the client properties. * @since 1.5.6 */ protected void updateConnectionClientProperties(Map clientProperties) { } @Override public void stop() { super.stop(); if (null != this.senderPool) { this.senderPool.shutdownNow(); this.senderPool = null; } if (null != this.connectionFactory) { this.connectionFactory.destroy(); } this.retryTimer.cancel(); this.routingKeyLayout.stop(); } @Override protected void append(ILoggingEvent event) { if (isIncludeCallerData()) { event.getCallerData(); } this.events.add(new Event(event)); } /** * @deprecated - use {@link #setUpExchangeDeclaration()} */ @Deprecated protected void maybeDeclareExchange() { setUpExchangeDeclaration(); } protected void setUpExchangeDeclaration() { RabbitAdmin admin = new RabbitAdmin(this.connectionFactory); if (this.declareExchange) { Exchange x; if ("topic".equals(this.exchangeType)) { x = new TopicExchange(this.exchangeName, this.durable, this.autoDelete); } else if ("direct".equals(this.exchangeType)) { x = new DirectExchange(this.exchangeName, this.durable, this.autoDelete); } else if ("fanout".equals(this.exchangeType)) { x = new FanoutExchange(this.exchangeName, this.durable, this.autoDelete); } else if ("headers".equals(this.exchangeType)) { x = new HeadersExchange(this.exchangeType, this.durable, this.autoDelete); } else { x = new TopicExchange(this.exchangeName, this.durable, this.autoDelete); } this.connectionFactory.addConnectionListener(new DeclareExchangeConnectionListener(x, admin)); } } /** * Subclasses may modify the final message before sending. * @param message The message. * @param event The event. * @return The modified message. * @since 1.4 */ public Message postProcessMessageBeforeSend(Message message, Event event) { return message; } /** * Helper class to actually send LoggingEvents asynchronously. */ protected class EventSender implements Runnable { @Override public void run() { try { RabbitTemplate rabbitTemplate = new RabbitTemplate(AmqpAppender.this.connectionFactory); while (true) { final Event event = AmqpAppender.this.events.take(); ILoggingEvent logEvent = event.getEvent(); String name = logEvent.getLoggerName(); Level level = logEvent.getLevel(); MessageProperties amqpProps = new MessageProperties(); amqpProps.setDeliveryMode(AmqpAppender.this.deliveryMode); amqpProps.setContentType(AmqpAppender.this.contentType); if (null != AmqpAppender.this.contentEncoding) { amqpProps.setContentEncoding(AmqpAppender.this.contentEncoding); } amqpProps.setHeader(CATEGORY_NAME, name); amqpProps.setHeader(CATEGORY_LEVEL, level.toString()); if (AmqpAppender.this.generateId) { amqpProps.setMessageId(UUID.randomUUID().toString()); } // Set timestamp Calendar tstamp = Calendar.getInstance(); tstamp.setTimeInMillis(logEvent.getTimeStamp()); amqpProps.setTimestamp(tstamp.getTime()); // Copy properties in from MDC Map props = event.getProperties(); Set> entrySet = props.entrySet(); for (Entry entry : entrySet) { amqpProps.setHeader(entry.getKey(), entry.getValue()); } String[] location = AmqpAppender.this.locationLayout.doLayout(logEvent).split("\\|"); if (!"?".equals(location[0])) { amqpProps.setHeader( "location", String.format("%s.%s()[%s]", location[0], location[1], location[2])); } String msgBody; String routingKey = AmqpAppender.this.routingKeyLayout.doLayout(logEvent); // Set applicationId, if we're using one if (AmqpAppender.this.applicationId != null) { amqpProps.setAppId(AmqpAppender.this.applicationId); } if (AmqpAppender.this.abbreviator != null && logEvent instanceof LoggingEvent) { ((LoggingEvent) logEvent).setLoggerName(AmqpAppender.this.abbreviator.abbreviate(name)); msgBody = AmqpAppender.this.layout.doLayout(logEvent); ((LoggingEvent) logEvent).setLoggerName(name); } else { msgBody = AmqpAppender.this.layout.doLayout(logEvent); } // Send a message try { Message message = null; if (AmqpAppender.this.charset != null) { try { message = new Message(msgBody.getBytes(AmqpAppender.this.charset), amqpProps); } catch (UnsupportedEncodingException e) { message = new Message(msgBody.getBytes(), amqpProps); //NOSONAR (default charset) } } else { message = new Message(msgBody.getBytes(), amqpProps); //NOSONAR (default charset) } message = postProcessMessageBeforeSend(message, event); rabbitTemplate.send(AmqpAppender.this.exchangeName, routingKey, message); } catch (AmqpException e) { int retries = event.incrementRetries(); if (retries < AmqpAppender.this.maxSenderRetries) { // Schedule a retry based on the number of times I've tried to re-send this AmqpAppender.this.retryTimer.schedule(new TimerTask() { @Override public void run() { AmqpAppender.this.events.add(event); } }, (long) (Math.pow(retries, Math.log(retries)) * 1000)); } else { addError("Could not send log message " + logEvent.getMessage() + " after " + AmqpAppender.this.maxSenderRetries + " retries", e); } } } } catch (InterruptedException e) { Thread.currentThread().interrupt(); } } } /** * Small helper class to encapsulate a LoggingEvent, its MDC properties, and the number of retries. */ protected static class Event { private final ILoggingEvent event; private final Map properties; private final AtomicInteger retries = new AtomicInteger(0); public Event(ILoggingEvent event) { this.event = event; this.properties = this.event.getMDCPropertyMap(); } public ILoggingEvent getEvent() { return this.event; } public Map getProperties() { return this.properties; } public int incrementRetries() { return this.retries.incrementAndGet(); } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy