/** * Copyright 2013 Michael K. Werle * * 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.coruscations.logback.redis; import com.lmax.disruptor.BlockingWaitStrategy; import com.lmax.disruptor.EventFactory; import com.lmax.disruptor.EventHandler; import com.lmax.disruptor.ExceptionHandler; import com.lmax.disruptor.MultiThreadedClaimStrategy; import com.lmax.disruptor.RingBuffer; import com.lmax.disruptor.dsl.Disruptor; import ch.qos.logback.core.UnsynchronizedAppenderBase; import redis.clients.jedis.Jedis; import redis.clients.jedis.JedisPool; import redis.clients.jedis.JedisPoolConfig; public abstract class RedisAppenderBase<E, M> extends UnsynchronizedAppenderBase<E> { // Buffer info private int bufferSize = 512; // Jedis info private String redisHostName = "127.0.0.1"; private int redisPort = 6379; private int redisTimeout = 5000; private String redisPassword = null; private int redisDatabase = 0; protected JedisPool pool; private Disruptor<EventWrapper<M>> disruptor; // Must be volatile for shutdown private volatile RingBuffer<EventWrapper<M>> ringBuffer; @Override public void start() { this.pool = startJedisPool(); EventFactory<EventWrapper<M>> eventFactory = new EventFactory<EventWrapper<M>>() { public EventWrapper<M> newInstance() { return new EventWrapper<M>(); } }; Disruptor<EventWrapper<M>> disruptor = createDisruptor(eventFactory); ringBuffer = disruptor.start(); this.disruptor = disruptor; super.start(); } @SuppressWarnings("unchecked") private Disruptor<EventWrapper<M>> createDisruptor( EventFactory<EventWrapper<M>> eventFactory) { Disruptor<EventWrapper<M>> disruptor = new Disruptor<EventWrapper<M>>(eventFactory, context.getExecutorService(), new MultiThreadedClaimStrategy(bufferSize), new BlockingWaitStrategy()); disruptor.handleExceptionsWith(new ExceptionHandler() { @Override public void handleEventException(Throwable ex, long sequence, Object event) { addWarn("Failed to log even to Redis.", ex); } @Override public void handleOnStartException(Throwable ex) { addError("Failed to start Redis appender.", ex); } @Override public void handleOnShutdownException(Throwable ex) { addWarn("Failed to stop Redis appender.", ex); } }); disruptor.handleEventsWith(getEventFlusher()); return disruptor; } private JedisPool startJedisPool() { JedisPoolConfig jedisPoolConfig = new JedisPoolConfig(); // TODO: Add pool configuration properties. JedisPool pool = new JedisPool(jedisPoolConfig, redisHostName, redisPort, redisTimeout, redisPassword, redisDatabase); Jedis jedis = pool.getResource(); try { jedis.select(0); } catch (Exception e) { addError("Cannot connect to " + redisHostName + ":" + redisPort, e); this.stop(); return null; } finally { pool.returnResource(jedis); } return pool; } public abstract M formatEvent(E eventObject); public abstract EventHandler<EventWrapper<M>> getEventFlusher(); @SuppressWarnings("AssignmentToNull") @Override public void stop() { // Unconditionally set the ringBuffer to null because if events are published after // the disruptor is shutdown, we will deadlock. ringBuffer = null; if (disruptor != null) { disruptor.shutdown(); disruptor = null; } if (pool != null) { pool.destroy(); pool = null; } super.stop(); } @Override protected final void append(E eventObject) { M message = formatEvent(eventObject); long index = ringBuffer.next(); ringBuffer.get(index).setMessage(message); ringBuffer.publish(index); } public int getBufferSize() { return bufferSize; } public void setBufferSize(int bufferSize) { this.bufferSize = bufferSize; } public String getRedisHostName() { return redisHostName; } public void setRedisHostName(String redisHostName) { this.redisHostName = redisHostName; } public int getRedisPort() { return redisPort; } public void setRedisPort(int redisPort) { this.redisPort = redisPort; } public int getRedisTimeout() { return redisTimeout; } public void setRedisTimeout(int redisTimeout) { this.redisTimeout = redisTimeout; } public String getRedisPassword() { return redisPassword; } public void setRedisPassword(String redisPassword) { this.redisPassword = redisPassword; } public int getRedisDatabase() { return redisDatabase; } public void setRedisDatabase(int redisDatabase) { this.redisDatabase = redisDatabase; } public final class EventWrapper<E> { private E message; public E getMessage() { return message; } public void setMessage(final E message) { this.message = message; } } }