/*
* Copyright (c) 2013 Mike Heath. All rights reserved.
*
* 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 loggregator;
import com.google.protobuf.ByteString;
import com.google.protobuf.MessageLite;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.DatagramChannel;
import java.util.UUID;
/**
* Builder class for creating instances of {@link loggregator.Emitter}
*
* @author Mike Heath <elcapo@gmail.com>
*/
public class EmitterBuilder {
public static final int MAX_MESSAGE_BYTE_SIZE = (8 * 1024) - 512;
private static final ByteString TRUNCATED_STRING = ByteString.copyFromUtf8("TRUNCATED");
private final InetSocketAddress address;
private final String secret;
private boolean blocking = false;
private String sourceName = "UNKNOWN";
private String sourceId = "0";
/**
* Creates a new {@code EmitterBuilder} for emitting messages to the provided host.
*
* @param host the Loggregator host that events are sent to
* @param port the port the Loggregator host is listening for events on
* @param secret the shared secret used for authenticating logging events
*/
public EmitterBuilder(String host, int port, String secret) {
this(new InetSocketAddress(host, port), secret);
}
/**
* Creates a new {@code EmitterBuilder} for emitting messages to the provided host.
*
* @param address the socket adddress of the Loggregator host that events are sent to
* @param secret the shared secret used for authenticating logging events
*/
public EmitterBuilder(InetSocketAddress address, String secret) {
this.address = address;
this.secret = secret;
}
/**
* Indicates whether the emitter should wait until the logging event has been sent over the network or not.
*
* @param blocking {@code true} if the emitter should block until the logging event has been sent, {@code false} if
* the client should not wait. The default is {@code false}.
* @return this builder instance
*/
public EmitterBuilder blocking(boolean blocking) {
this.blocking = blocking;
return this;
}
/**
* The source name attached to logging events.
*
* @param sourceName The name of the source attached to logging events.
* @return this builder instance
*/
public EmitterBuilder sourceName(String sourceName) {
this.sourceName = sourceName;
return this;
}
/**
* The source's instance id.
*
* @param sourceId the instance id of the source.
* @return this builder instance
*/
public EmitterBuilder sourceId(String sourceId) {
this.sourceId = sourceId;
return this;
}
public Emitter build() {
return new DefaultEmitter(this);
}
private static class DefaultEmitter implements Emitter {
private final DatagramChannel channel;
private final MessageSigner signer;
private final String sourceName;
private final String sourceId;
public DefaultEmitter(EmitterBuilder builder) {
try {
channel = DatagramChannel.open();
} catch (IOException e) {
throw new RuntimeException(e);
}
try {
channel.connect(builder.address);
channel.configureBlocking(builder.blocking);
} catch (IOException e) {
try {
channel.close();
} catch (IOException e1) {
// Ignore any error closing the channel.
}
throw new RuntimeException(e);
}
signer = new MessageSigner(builder.secret);
this.sourceName = builder.sourceName;
this.sourceId = builder.sourceId;
}
@Override
public void emit(String appId, String message) {
sendMessage(appId, message, Messages.LogMessage.MessageType.OUT);
}
@Override
public void emit(UUID appId, String message) {
sendMessage(appId.toString(), message, Messages.LogMessage.MessageType.OUT);
}
@Override
public void emitError(String appId, String message) {
sendMessage(appId, message, Messages.LogMessage.MessageType.ERR);
}
@Override
public void emitError(UUID appId, String message) {
sendMessage(appId.toString(), message, Messages.LogMessage.MessageType.ERR);
}
@Override
public void close() {
try {
channel.close();
} catch (IOException e) {
throw new LoggregatorException(e);
}
}
private void sendMessage(String appId, String message, Messages.LogMessage.MessageType type) {
final Messages.LogMessage logMessage = buildLogMessage(appId, message, type);
final Messages.LogEnvelope envelope = Messages.LogEnvelope.newBuilder()
.setRoutingKey(appId)
.setLogMessage(logMessage)
.setSignature(ByteString.copyFrom(signer.sign(logMessage)))
.build();
writeMessage(envelope);
}
private Messages.LogMessage buildLogMessage(String appId, String message, Messages.LogMessage.MessageType type) {
ByteString messageBytes = ByteString.copyFromUtf8(message);
if (messageBytes.size() > MAX_MESSAGE_BYTE_SIZE) {
messageBytes = messageBytes.substring(0, MAX_MESSAGE_BYTE_SIZE - TRUNCATED_STRING.size()).concat(TRUNCATED_STRING);
}
final Messages.LogMessage.Builder builder = Messages.LogMessage.newBuilder()
.setAppId(appId)
.setMessage(messageBytes)
.setMessageType(type)
.setTimestamp(System.currentTimeMillis() * 1000000);
if (sourceId != null) {
builder.setSourceId(sourceId);
}
if (sourceName != null) {
builder.setSourceName(sourceName);
}
return builder.build();
}
private void writeMessage(MessageLite message) {
final byte[] bytes = message.toByteArray();
final ByteBuffer buffer = ByteBuffer.wrap(bytes);
try {
channel.write(buffer);
} catch (IOException e) {
throw new LoggregatorException(e);
}
}
}
}