/*
* Copyright © 2014-2015 Cask Data, 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 co.cask.cdap.logging.save;
import ch.qos.logback.classic.spi.ILoggingEvent;
import co.cask.cdap.api.metrics.MetricsContext;
import co.cask.cdap.common.conf.Constants;
import co.cask.cdap.common.logging.LoggingContext;
import co.cask.cdap.logging.appender.kafka.LoggingEventSerializer;
import co.cask.cdap.logging.context.LoggingContextHelper;
import co.cask.cdap.logging.kafka.KafkaLogEvent;
import org.apache.avro.generic.GenericRecord;
import org.apache.twill.kafka.client.FetchedMessage;
import org.apache.twill.kafka.client.KafkaConsumer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Iterator;
import java.util.Set;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
/**
* Kafka Message callback that fetches log from kafka and calls the process method for all the plugins configured.
*/
public class KafkaMessageCallback implements KafkaConsumer.MessageCallback {
private static final Logger LOG = LoggerFactory.getLogger(KafkaMessageCallback.class);
private final Set<KafkaLogProcessor> kafkaLogProcessors;
private final LoggingEventSerializer serializer;
private final CountDownLatch stopLatch;
private final MetricsContext metricsContext;
public KafkaMessageCallback(CountDownLatch stopLatch,
Set<KafkaLogProcessor> kafkaLogProcessors,
MetricsContext metricsContext) throws Exception {
this.kafkaLogProcessors = kafkaLogProcessors;
this.serializer = new LoggingEventSerializer();
this.stopLatch = stopLatch;
this.metricsContext = metricsContext;
}
@Override
public void onReceived(Iterator<FetchedMessage> messages) {
try {
if (stopLatch.await(50, TimeUnit.MICROSECONDS)) {
// if count down occurred return
LOG.info("Returning since callback is cancelled.");
return;
}
} catch (InterruptedException e) {
LOG.error("Exception: ", e);
Thread.currentThread().interrupt();
return;
}
int count = 0;
long oldestProcessed = Long.MAX_VALUE;
while (messages.hasNext()) {
FetchedMessage message = messages.next();
try {
GenericRecord genericRecord = serializer.toGenericRecord(message.getPayload());
ILoggingEvent event = serializer.fromGenericRecord(genericRecord);
LoggingContext loggingContext = LoggingContextHelper.getLoggingContext(event.getMDCPropertyMap());
KafkaLogEvent logEvent = new KafkaLogEvent(genericRecord, event, loggingContext,
message.getTopicPartition().getPartition(),
message.getNextOffset());
for (KafkaLogProcessor processor : kafkaLogProcessors) {
try {
processor.process(logEvent);
} catch (Throwable th) {
LOG.error("Error processing kafka log event in processor {}",
processor.getClass().getSimpleName());
}
}
if (event.getTimeStamp() < oldestProcessed) {
oldestProcessed = event.getTimeStamp();
}
} catch (Throwable th) {
LOG.error("Error processing message at topic {} parition {}",
message.getTopicPartition().getTopic(),
message.getTopicPartition().getPartition());
}
count++;
}
if (count > 0) {
// todo: use hostogram when available (CDAP-3120)
metricsContext.gauge(Constants.Metrics.Name.Log.PROCESS_DELAY, System.currentTimeMillis() - oldestProcessed);
metricsContext.increment(Constants.Metrics.Name.Log.PROCESS_MESSAGES_COUNT, count);
}
LOG.trace("Got {} messages from kafka", count);
}
@Override
public void finished() {
LOG.info("KafkaMessageCallback finished.");
}
}