/*
* Copyright 2014-2016 CyberVision, Inc.
*
* 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 org.kaaproject.kaa.server.appenders.flume.appender;
import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import org.apache.flume.Event;
import org.apache.flume.EventDeliveryException;
import org.kaaproject.kaa.common.dto.logs.LogAppenderDto;
import org.kaaproject.kaa.server.appenders.flume.appender.client.FlumeClientManager;
import org.kaaproject.kaa.server.appenders.flume.appender.client.async.AppendBatchAsyncResultPojo;
import org.kaaproject.kaa.server.appenders.flume.config.gen.FlumeConfig;
import org.kaaproject.kaa.server.common.log.shared.appender.AbstractLogAppender;
import org.kaaproject.kaa.server.common.log.shared.appender.LogDeliveryCallback;
import org.kaaproject.kaa.server.common.log.shared.appender.LogEventPack;
import org.kaaproject.kaa.server.common.log.shared.avro.gen.RecordHeader;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
public class FlumeLogAppender extends AbstractLogAppender<FlumeConfig> {
private static final Logger LOG = LoggerFactory.getLogger(FlumeLogAppender.class);
private static final int MAX_CALLBACK_THREAD_POOL_SIZE = 10;
private ExecutorService executor;
private ExecutorService callbackExecutor;
private ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
private AtomicInteger flumeSuccessLogCount = new AtomicInteger();
private AtomicInteger flumeFailureLogCount = new AtomicInteger();
private AtomicInteger inputLogCount = new AtomicInteger();
private FlumeConfig configuration;
private boolean closed = false;
private FlumeEventBuilder flumeEventBuilder;
private FlumeClientManager<?> flumeClientManager;
/**
* Instantiates a new FlumeLogAppender.
*/
public FlumeLogAppender() {
super(FlumeConfig.class);
scheduler.scheduleWithFixedDelay(new Runnable() {
@Override
public void run() {
long second = System.currentTimeMillis() / 1000;
LOG.info(
"[{}] Received {} log record count, {} success flume callbacks, "
+ "{} failure flume callbacks / second.",
second, inputLogCount.getAndSet(0), flumeSuccessLogCount.getAndSet(0),
flumeFailureLogCount.getAndSet(0));
}
}, 0L, 1L, TimeUnit.SECONDS);
}
@Override
public void doAppend(final LogEventPack logEventPack, final RecordHeader header,
final LogDeliveryCallback listener) {
if (!closed) {
if (executor == null || callbackExecutor == null || flumeClientManager == null) {
reinit();
}
if (executor == null || callbackExecutor == null || flumeClientManager == null) {
LOG.warn("Some of components haven't been initialized. Skipping append method");
listener.onInternalError();
return;
}
executor.submit(new Runnable() {
@Override
public void run() {
try {
List<Event> events = flumeEventBuilder.generateEvents(logEventPack, header,
getApplicationToken());
try {
if (events != null && !events.isEmpty()) {
if (flumeClientManager != null) {
int logCount = events.size();
inputLogCount.getAndAdd(logCount);
ListenableFuture<AppendBatchAsyncResultPojo> result = flumeClientManager
.sendEventsToFlumeAsync(events);
Futures.addCallback(result, new Callback(listener, flumeSuccessLogCount,
flumeFailureLogCount, logCount), callbackExecutor);
LOG.debug("Appended {} logs to flume", logEventPack.getEvents().size());
} else {
LOG.warn("Flume client wasn't initialized. Invoke method init before.");
listener.onInternalError();
}
} else {
LOG.warn("Unable to generate Flume events from log event pack!");
listener.onInternalError();
}
} catch (EventDeliveryException ex) {
LOG.warn("Can't send flume event. ", ex);
listener.onConnectionError();
}
} catch (Exception ex) {
LOG.warn("Got exception. Can't process log events", ex);
listener.onInternalError();
}
}
});
} else {
LOG.info("Attempted to append to closed appender named [{}].", getName());
listener.onInternalError();
}
}
@Override
protected void initFromConfiguration(LogAppenderDto appender, FlumeConfig configuration) {
LOG.debug("Initializing new instance of Flume log appender");
try {
this.configuration = configuration;
flumeEventBuilder = new FlumeAvroEventBuilder();
flumeEventBuilder.init(configuration);
int executorPoolSize = Math.min(configuration.getExecutorThreadPoolSize(),
MAX_CALLBACK_THREAD_POOL_SIZE);
int callbackPoolSize = Math.min(configuration.getCallbackThreadPoolSize(),
MAX_CALLBACK_THREAD_POOL_SIZE);
executor = Executors.newFixedThreadPool(executorPoolSize);
callbackExecutor = Executors.newFixedThreadPool(callbackPoolSize);
flumeClientManager = FlumeClientManager.getInstance(configuration);
} catch (Exception ex) {
LOG.error("Failed to init Flume log appender: ", ex);
}
}
/**
* Reinitializing Flume log appender if configuration is initialized.
*/
public void reinit() {
if (configuration == null) {
LOG.warn("Flume configuration wasn't initialized. "
+ "Invoke method init with configuration before.");
return;
}
if (flumeEventBuilder == null) {
flumeEventBuilder = new FlumeAvroEventBuilder();
flumeEventBuilder.init(configuration);
}
if (executor == null) {
int executorPoolSize = Math.min(configuration.getExecutorThreadPoolSize(),
MAX_CALLBACK_THREAD_POOL_SIZE);
executor = Executors.newFixedThreadPool(executorPoolSize);
}
if (callbackExecutor == null) {
int callbackPoolSize = Math.min(configuration.getCallbackThreadPoolSize(),
MAX_CALLBACK_THREAD_POOL_SIZE);
callbackExecutor = Executors.newFixedThreadPool(callbackPoolSize);
}
if (flumeClientManager == null) {
flumeClientManager = FlumeClientManager.getInstance(configuration);
}
}
@Override
public void close() {
if (!closed) {
closed = true;
if (flumeClientManager != null) {
flumeClientManager.cleanUp();
}
if (executor != null) {
executor.shutdownNow();
}
if (callbackExecutor != null) {
callbackExecutor.shutdownNow();
}
if (scheduler != null) {
scheduler.shutdownNow();
}
flumeClientManager = null;
}
LOG.debug("Stoped Flume log appender.");
}
private static final class Callback implements FutureCallback<AppendBatchAsyncResultPojo> {
private final LogDeliveryCallback callback;
private final AtomicInteger flumeSuccessLogCount;
private final AtomicInteger flumeFailureLogCount;
private final int size;
private Callback(LogDeliveryCallback callback, AtomicInteger flumeSuccessLogCount,
AtomicInteger flumeFailureLogCount, int size) {
this.callback = callback;
this.flumeSuccessLogCount = flumeSuccessLogCount;
this.flumeFailureLogCount = flumeFailureLogCount;
this.size = size;
}
@Override
public void onSuccess(AppendBatchAsyncResultPojo result) {
flumeSuccessLogCount.getAndAdd(size);
callback.onSuccess();
}
@Override
public void onFailure(Throwable throwable) {
flumeFailureLogCount.getAndAdd(size);
LOG.warn("Failed to store record", throwable);
if (throwable instanceof IOException) {
callback.onConnectionError();
} else if (throwable instanceof EventDeliveryException) {
callback.onRemoteError();
} else {
callback.onInternalError();
}
}
}
}