![JAR search and dependency download from the Maven repository](/logo.png)
com.ning.metrics.eventtracker.ScribeSender Maven / Gradle / Ivy
/*
* Copyright 2010-2011 Ning, Inc.
*
* Ning licenses this file to you 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.ning.metrics.eventtracker;
import com.mogwee.executors.FailsafeScheduledExecutor;
import com.ning.metrics.serialization.event.Event;
import com.ning.metrics.serialization.event.Events;
import com.ning.metrics.serialization.writer.CallbackHandler;
import org.apache.commons.codec.binary.Base64;
import org.apache.thrift.transport.TTransportException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.weakref.jmx.Managed;
import scribe.thrift.LogEntry;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
/**
* ScribeSender
*
* The class needs to be public for JMX.
*/
public class ScribeSender implements EventSender
{
private static final Logger log = LoggerFactory.getLogger(ScribeSender.class);
private static final Charset CHARSET = Charset.forName("ISO-8859-1");
private final AtomicInteger connectionRetries = new AtomicInteger(0);
private final ScribeClient scribeClient;
private final AtomicInteger messagesSuccessfullySent = new AtomicInteger(0);
private final AtomicInteger messagesSuccessfullySentSinceLastReconnection = new AtomicInteger(0);
private int messagesToSendBeforeReconnecting = 0;
private final AtomicBoolean sleeping = new AtomicBoolean(true);
private final AtomicBoolean isClosed = new AtomicBoolean(true);
public ScribeSender(final ScribeClient scribeClient, final int messagesToSendBeforeReconnecting, final int maxIdleTimeInMinutes)
{
this.scribeClient = scribeClient;
this.messagesToSendBeforeReconnecting = messagesToSendBeforeReconnecting;
// Setup a watchdog for the Scribe connection. We don't want to keep it open forever. For instance, SLB VIP
// may trigger a RST if idle more than a few minutes.
final ScheduledExecutorService executor = new FailsafeScheduledExecutor(1, "ScribeWatchdog");
executor.scheduleAtFixedRate(new Runnable()
{
@Override
public void run()
{
if (sleeping.get()) {
log.info("Idle connection to Scribe, re-opening it");
createConnection();
}
sleeping.set(true);
}
}, maxIdleTimeInMinutes, maxIdleTimeInMinutes, TimeUnit.MINUTES);
}
/**
* Re-initialize the connection with the Scribe endpoint.
*/
public synchronized void createConnection()
{
if (scribeClient != null) {
try {
connectionRetries.incrementAndGet();
scribeClient.closeLogger();
scribeClient.openLogger();
isClosed.set(false);
log.info("Connection to Scribe established");
}
catch (TTransportException e) {
log.warn("Unable to connect to Scribe: {}", e.getLocalizedMessage());
scribeClient.closeLogger();
}
}
else {
log.warn("Scribe client has not been set up correctly.");
}
}
/**
* Disconnect from Scribe for good.
*/
@Override
public synchronized void close()
{
if (scribeClient != null && !isClosed.get()) {
scribeClient.closeLogger();
isClosed.set(true);
}
}
@Override
public void send(final File file, final CallbackHandler handler)
{
if (isClosed.get()) {
createConnection();
}
// Tell the watchdog that we are doing something
sleeping.set(false);
// Parse the underlying file and generate the payload for Scribe
final List list = createScribePayload(file, handler);
if (list == null) {
// Something went wrong
return;
}
try {
scribeClient.log(list);
// Get rid of the file. We do it early, because the reconnection may fail
handler.onSuccess(file);
messagesSuccessfullySent.addAndGet(list.size());
messagesSuccessfullySentSinceLastReconnection.addAndGet(list.size());
// For load balancing capabilities, we don't want to make sticky connections to Scribe.
// After a certain threshold, force a refresh of the connection.
if (messagesSuccessfullySentSinceLastReconnection.get() > messagesToSendBeforeReconnecting) {
log.info("Recycling connection with Scribe");
messagesSuccessfullySentSinceLastReconnection.set(0);
createConnection();
}
}
catch (org.apache.thrift.TException e) {
// Connection flacky?
log.warn("Error while sending message to Scribe: {}", e.getLocalizedMessage());
createConnection();
handler.onError(new Throwable(e), file);
}
}
/**
* Give a file of events, generate LogEntry messages for Scribe
*
* @param file File containing events
* @param handler notifier for the serialization-writer library
* @return list of Scirbe-ready events on success, null otherwise
*/
private List createScribePayload(final File file, final CallbackHandler handler)
{
try {
final List events = Events.fromFile(file);
final List list = new ArrayList(events.size());
for (final Event event : events) {
final String logEntryMessage = eventToLogEntryMessage(event);
list.add(new LogEntry(event.getName(), logEntryMessage));
}
return list;
}
catch (ClassNotFoundException e) {
handler.onError(new Throwable(e), file);
return null;
}
catch (IOException e) {
handler.onError(new Throwable(e), file);
return null;
}
}
protected static String eventToLogEntryMessage(final Event event) throws IOException
{
// Has the sender specified how to send the data?
byte[] payload = event.getSerializedEvent();
// Nope, default to ObjectOutputStream
if (payload == null) {
final ByteArrayOutputStream out = new ByteArrayOutputStream();
event.writeExternal(new ObjectOutputStream(out));
payload = out.toByteArray();
// 64-bit encode the serialized object
payload = new Base64().encode(payload);
}
final String scribePayload = new String(payload, CHARSET);
// To avoid costly Thrift deserialization on the collector side, we embed the
// timestamp in the format, outside of the payload. We need it for HDFS routing.
return String.format("%s:%s", event.getEventDateTime().getMillis(), scribePayload);
}
@Managed(description = "Get the number of messages successfully sent since startup to Scribe")
public long getMessagesSuccessfullySent()
{
return messagesSuccessfullySent.get();
}
@Managed(description = "Get the number of messages successfully sent since last reconnection to Scribe")
public long getMessagesSuccessfullySentSinceLastReconnection()
{
return messagesSuccessfullySentSinceLastReconnection.get();
}
@Managed(description = "Get the number of times we retried to connect to Scribe")
public long getConnectionRetries()
{
return connectionRetries.get();
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy