All Downloads are FREE. Search and download functionalities are using the official Maven repository.

biz.paluch.logging.gelf.intern.sender.GelfTCPSender Maven / Gradle / Ivy

The newest version!
package biz.paluch.logging.gelf.intern.sender;

import java.io.IOException;
import java.net.ConnectException;
import java.net.InetSocketAddress;
import java.net.SocketException;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;
import java.util.concurrent.TimeUnit;

import biz.paluch.logging.gelf.intern.Closer;
import biz.paluch.logging.gelf.intern.ErrorReporter;
import biz.paluch.logging.gelf.intern.GelfMessage;
import biz.paluch.logging.gelf.intern.GelfSender;

/**
 * @author https://github.com/t0xa/gelfj
 * @author Mark Paluch
 */
public class GelfTCPSender extends AbstractNioSender implements GelfSender {

    public static final String CONNECTION_TIMEOUT = "connectionTimeout";
    public static final String READ_TIMEOUT = "readTimeout";
    public static final String RETRIES = "deliveryAttempts";
    public static final String KEEPALIVE = "keepAlive";
    public static final String WRITE_BACKOFF_TIME = "writeBackoffTime";
    public static final String WRITE_BACKOFF_THRESHOLD = "writeBackoffThreshold";
    public static final String MAX_WRITE_BACKOFF_TIME = "maxWriteBackoffTime";

    private final int readTimeoutMs;
    private final int connectTimeoutMs;
    private final boolean keepAlive;
    private final int deliveryAttempts;

    private final int writeBackoffThreshold;
    private final BackOff backoff;

    private final Object ioLock = new Object();

    private final ThreadLocal writeBuffers = new ThreadLocal() {
        @Override
        protected ByteBuffer initialValue() {
            return ByteBuffer.allocateDirect(INITIAL_BUFFER_SIZE);
        }
    };

    /**
     * @param host the host, must not be {@literal null}.
     * @param port the port.
     * @param connectTimeoutMs connection timeout, in {@link TimeUnit#MILLISECONDS}.
     * @param readTimeoutMs read timeout, in {@link TimeUnit#MILLISECONDS}.
     * @param errorReporter the error reporter, must not be {@literal null}.
     * @throws IOException in case of I/O errors
     */
    public GelfTCPSender(String host, int port, int connectTimeoutMs, int readTimeoutMs, ErrorReporter errorReporter)
            throws IOException {
        this(host, port, connectTimeoutMs, readTimeoutMs, 1, false, errorReporter);
    }

    /**
     * @param host the host, must not be {@literal null}.
     * @param port the port.
     * @param connectTimeoutMs connection timeout, in {@link TimeUnit#MILLISECONDS}.
     * @param readTimeoutMs read timeout, in {@link TimeUnit#MILLISECONDS}.
     * @param deliveryAttempts number of delivery attempts.
     * @param keepAlive {@literal true} to enable TCP keep-alive.
     * @param errorReporter the error reporter, must not be {@literal null}.
     * @throws IOException in case of I/O errors
     */
    public GelfTCPSender(String host, int port, int connectTimeoutMs, int readTimeoutMs, int deliveryAttempts,
            boolean keepAlive, ErrorReporter errorReporter) throws IOException {

        this(host, port, connectTimeoutMs, readTimeoutMs, deliveryAttempts, keepAlive,
                new BoundedBackOff(new ConstantBackOff(50, TimeUnit.MILLISECONDS), connectTimeoutMs, TimeUnit.MILLISECONDS), 10,
                errorReporter);
    }

    /**
     * @param host the host, must not be {@literal null}.
     * @param port the port.
     * @param connectTimeoutMs connection timeout, in {@link TimeUnit#MILLISECONDS}.
     * @param readTimeoutMs read timeout, in {@link TimeUnit#MILLISECONDS}.
     * @param deliveryAttempts number of delivery attempts.
     * @param keepAlive {@literal true} to enable TCP keep-alive.
     * @param backoff Backoff strategy to activate if a socket sender buffer is full and several attempts to write to the socket are unsuccessful due to it.
     * @param writeBackoffThreshold attempts to write to a socket before a backoff will be activated.
     * @param errorReporter the error reporter, must not be {@literal null}.
     * @throws IOException in case of I/O errors
     */
    public GelfTCPSender(String host, int port, int connectTimeoutMs, int readTimeoutMs, int deliveryAttempts,
                         boolean keepAlive,
            BackOff backoff, int writeBackoffThreshold,
                         ErrorReporter errorReporter) throws IOException {

        super(errorReporter, host, port);

        this.connectTimeoutMs = connectTimeoutMs;
        this.readTimeoutMs = readTimeoutMs;
        this.keepAlive = keepAlive;
        this.deliveryAttempts = deliveryAttempts < 1 ? Integer.MAX_VALUE : deliveryAttempts;

        this.backoff = backoff;
        this.writeBackoffThreshold = writeBackoffThreshold;

        this.setChannel(createSocketChannel(readTimeoutMs, keepAlive));
    }

    protected SocketChannel createSocketChannel(int readTimeoutMs, boolean keepAlive) throws IOException {
        SocketChannel socketChannel = SocketChannel.open();
        socketChannel.configureBlocking(false);
        socketChannel.socket().setKeepAlive(keepAlive);
        socketChannel.socket().setSoTimeout(readTimeoutMs);
        return socketChannel;
    }

    /**
     * @param message the message
     * @return {@literal true} if message was sent.
     */
    public boolean sendMessage(GelfMessage message) {

        if (isShutdown()) {
            return false;
        }

        IOException exception = null;

        for (int i = 0; i < deliveryAttempts; i++) {
            try {

                // (re)-connect if necessary
                if (!isConnected()) {
                    synchronized (ioLock) {
                        connect();
                    }
                }
                ByteBuffer buffer;
                if (INITIAL_BUFFER_SIZE == 0) {
                    buffer = message.toTCPBuffer();
                } else {
                    buffer = GelfBuffers.toTCPBuffer(message, writeBuffers);
                }

                try {
                    synchronized (ioLock) {
                        write(buffer);
                    }
                } catch (InterruptedException e) {
                    reportError(e.getMessage(), new IOException("Cannot send data to " + getHost() + ":" + getPort(), e));
                    Thread.currentThread().interrupt();
                    return false;
                }

                return true;
            } catch (IOException e) {
                Closer.close(channel());
                exception = e;
            }
        }

        if (exception != null) {
            reportError(exception.getMessage(),
                    new IOException("Cannot send data to " + getHost() + ":" + getPort(), exception));
        }

        return false;
    }

    protected void write(ByteBuffer buffer) throws IOException, InterruptedException {

        int nothingWrittenTimesInRow = 0;
        BackOffExecution backoffExecution = null;

        while (buffer.hasRemaining()) {
            int written = channel().write(buffer);

            if (written < 0 || !isConnected()) {
                // indicator the socket was closed
                Closer.close(channel());
                throw new SocketException("Cannot write buffer to channel");
            }
            if (written == 0) {
                if (backoffExecution == null) {
                    backoffExecution = backoff.start();
                }
                nothingWrittenTimesInRow++;
                if (nothingWrittenTimesInRow > writeBackoffThreshold) {
                    long toSleep = backoffExecution.nextBackOff();
                    if (toSleep == BackOffExecution.STOP) {
                        Closer.close(channel());
                        throw new SocketException("Cannot write buffer to channel, no progress in writing");
                    }
                    Thread.sleep(toSleep);
                }
            } else { // written > 0
                nothingWrittenTimesInRow = 0;
            }
        }
    }

    protected boolean connect() throws IOException {

        if (isConnected()) {
            return false;
        }

        Closer.close(channel());
        setChannel(createSocketChannel(readTimeoutMs, keepAlive));

        InetSocketAddress inetSocketAddress = new InetSocketAddress(getHost(), getPort());
        if (channel().connect(inetSocketAddress)) {
            return true;
        }

        long connectTimeoutLeft = TimeUnit.MILLISECONDS.toNanos(connectTimeoutMs);
        long waitTimeoutMs = 10;
        long waitTimeoutNs = TimeUnit.MILLISECONDS.toNanos(waitTimeoutMs);
        boolean connected;
        try {
            while (!(connected = channel().finishConnect())) {
                Thread.sleep(waitTimeoutMs);
                connectTimeoutLeft -= waitTimeoutNs;

                if (connectTimeoutLeft <= 0) {
                    break;
                }
            }

            if (!connected) {
                throw new ConnectException("Connection timed out. Cannot connect to " + inetSocketAddress + " within "
                        + TimeUnit.NANOSECONDS.toMillis(connectTimeoutLeft) + "ms");
            }

            return connected;
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            throw new IOException("Connection interrupted", e);
        }
    }

    @Override
    protected boolean isConnected(SocketChannel channel) {
        return channel.isConnected();
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy