Package net.logstash.logback.appender

Source Code of net.logstash.logback.appender.LogstashTcpSocketAppender

/**
* 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 net.logstash.logback.appender;

import java.io.BufferedOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.net.ConnectException;
import java.net.InetAddress;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.TimeUnit;

import javax.net.SocketFactory;

import ch.qos.logback.classic.ClassicConstants;
import ch.qos.logback.classic.net.LoggingEventPreSerializationTransformer;
import ch.qos.logback.classic.net.SocketAppender;
import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.core.AppenderBase;
import ch.qos.logback.core.CoreConstants;
import ch.qos.logback.core.encoder.Encoder;
import ch.qos.logback.core.net.DefaultSocketConnector;
import ch.qos.logback.core.net.SocketConnector;
import ch.qos.logback.core.spi.PreSerializationTransformer;
import ch.qos.logback.core.status.ErrorStatus;
import ch.qos.logback.core.util.CloseUtil;
import ch.qos.logback.core.util.Duration;

/**
* This class is a modification of {@link SocketAppender}. The queue type and
* the dispatch method is different than the original version. <br/>
* The connection thread is going to be closed only if an event with the marker {@link ClassicConstants.FINALIZE_SESSION_MARKER} is sent. <br/>
* <br/>
* For example:<br/>
* <code>logger.info(ClassicConstants.FINALIZE_SESSION_MARKER, "About to end the job");</code>
*
*
* @author <a href="mailto:mirko.bernardoni@gmail.com">Mirko Bernardoni</a>
* @since 11 Jun 2014 (creation date)
*/
public class LogstashTcpSocketAppender extends AppenderBase<ILoggingEvent>
        implements Runnable, SocketConnector.ExceptionHandler {
   
    private static final PreSerializationTransformer<ILoggingEvent> PST = new LoggingEventPreSerializationTransformer();
   
    /**
     * The default port number of remote logging server (4560).
     */
    public static final int DEFAULT_PORT = 4560;
   
    /**
     * The default reconnection delay (30000 milliseconds or 30 seconds).
     */
    public static final int DEFAULT_RECONNECTION_DELAY = 30000;
   
    /**
     * Default size of the queue used to hold logging events that are destined
     * for the remote peer.
     * Assuming an average log entry to take 1k, this would result in the application
     * using about 10MB additional memory if the queue is full
     */
    public static final int DEFAULT_QUEUE_SIZE = 10000;
   
    /**
     * Default timeout when waiting for the remote server to accept our
     * connection.
     */
    private static final int DEFAULT_ACCEPT_CONNECTION_DELAY = 5000;
   
    /**
     * Default timeout for how long to wait when inserting an event into the
     * BlockingQueue.
     */
    private static final int DEFAULT_EVENT_DELAY_TIMEOUT = 100;
   
    private String remoteHost;
   
    private int port = DEFAULT_PORT;
   
    private InetAddress address;
   
    private Duration reconnectionDelay = new Duration(
            DEFAULT_RECONNECTION_DELAY);
   
    private int acceptConnectionTimeout = DEFAULT_ACCEPT_CONNECTION_DELAY;
   
    private Duration eventDelayLimit = new Duration(DEFAULT_EVENT_DELAY_TIMEOUT);

    private int queueSize = DEFAULT_QUEUE_SIZE;
   
    private BlockingQueue<ILoggingEvent> queue;
   
    private String peerId;
   
    private Future<?> task;
   
    private Future<Socket> connectorTask;
   
    private volatile Socket socket;
   
    /**
     * It is the encoder which is ultimately responsible for writing the event
     * to an {@link OutputStream}.
     */
    protected Encoder<ILoggingEvent> encoder;
   
    /**
     * @return the encoder
     */
    public Encoder<ILoggingEvent> getEncoder() {
        return encoder;
    }
   
    /**
     * @param encoder
     *            the encoder to set
     */
    public void setEncoder(Encoder<ILoggingEvent> encoder) {
        this.encoder = encoder;
    }
   
    /**
     * Get the pre-serialization transformer that will be used to transform each
     * event into a Serializable object before delivery to the remote receiver.
     *
     * @return transformer object
     */
    public PreSerializationTransformer<ILoggingEvent> getPST() {
        return PST;
    }
   
    protected void encoderInit(OutputStream outputStream) {
        if (encoder != null && outputStream != null) {
            try {
                encoder.init(outputStream);
            } catch (IOException ioe) {
                this.started = false;
                addStatus(new ErrorStatus(
                        "Failed to initialize encoder for appender named ["
                                + name + "].", this, ioe));
            }
        }
    }
   
    protected void encoderClose(OutputStream outputStream) {
        if (encoder != null && outputStream != null) {
            try {
                encoder.close();
            } catch (IOException ioe) {
                this.started = false;
                addStatus(new ErrorStatus(
                        "Failed to write footer for appender named [" + name
                                + "].", this, ioe));
            }
        }
    }
   
    /**
     * {@inheritDoc}
     */
    public void start() {
        if (isStarted())
            return;
        int errorCount = 0;
        if (encoder == null) {
            errorCount++;
            addError("No encoder was configured for appender " + name + ".");
        }
        if (port <= 0) {
            errorCount++;
            addError("No port was configured for appender "
                    + name
                    + ". For more information, please visit http://logback.qos.ch/codes.html#socket_no_port");
        }
       
        if (remoteHost == null) {
            errorCount++;
            addError("No remote host was configured for appender "
                    + name
                    + ". For more information, please visit http://logback.qos.ch/codes.html#socket_no_host");
        }
       
        if (errorCount == 0) {
            try {
                address = InetAddress.getByName(remoteHost);
            } catch (UnknownHostException ex) {
                addError("unknown host: " + remoteHost);
                errorCount++;
            }
        }
       
        if (errorCount == 0) {
            encoder.start();
            queue = new LinkedBlockingQueue<ILoggingEvent>(queueSize);
            peerId = "remote peer " + remoteHost + ":" + port + ": ";
            task = getContext().getExecutorService().submit(this);
            super.start();
        }
    }
   
    /**
     * {@inheritDoc}
     */
    @Override
    public void stop() {
        if (!isStarted())
            return;
        CloseUtil.closeQuietly(socket);
        task.cancel(true);
        if (connectorTask != null)
            connectorTask.cancel(true);
        super.stop();
    }
   
    /**
     * {@inheritDoc}
     */
    @Override
    protected void append(ILoggingEvent event) {
        if (event == null || !isStarted())
            return;
       
        try {
            final boolean inserted = queue.offer(event,
                    eventDelayLimit.getMilliseconds(), TimeUnit.MILLISECONDS);
            if (!inserted) {
                addInfo("Dropping event due to timeout limit of ["
                        + eventDelayLimit + "] being exceeded");
            }
        } catch (InterruptedException e) {
            addError("Interrupted while appending event to SocketAppender", e);
        }
    }
   
    /**
     * {@inheritDoc}
     */
    public final void run() {
        try {
            while (!Thread.currentThread().isInterrupted()) {
                SocketConnector connector = createConnector(address, port, 0,
                        reconnectionDelay.getMilliseconds());
               
                connectorTask = activateConnector(connector);
                if (connectorTask == null)
                    break;
               
                socket = waitForConnectorToReturnASocket();
                if (socket == null)
                    break;
                dispatchEvents();
            }
        } catch (InterruptedException ex) {
            assert true; // ok... we'll exit now
        }
        addInfo("shutting down");
    }
   
    private SocketConnector createConnector(InetAddress address, int port,
            int initialDelay, long retryDelay) {
        SocketConnector connector = newConnector(address, port, initialDelay,
                retryDelay);
        connector.setExceptionHandler(this);
        connector.setSocketFactory(getSocketFactory());
        return connector;
    }
   
    private Future<Socket> activateConnector(SocketConnector connector) {
        try {
            return getContext().getExecutorService().submit(connector);
        } catch (RejectedExecutionException ex) {
            return null;
        }
    }
   
    private Socket waitForConnectorToReturnASocket()
            throws InterruptedException {
        try {
            Socket s = connectorTask.get();
            connectorTask = null;
            return s;
        } catch (ExecutionException e) {
            return null;
        }
    }
   
    /**
     * Inifinte loop that send the messages from the queue to the remote host
     *
     * @throws InterruptedException
     */
    private void dispatchEvents() throws InterruptedException {
        OutputStream outputStream = null;
        try {
            socket.setSoTimeout(acceptConnectionTimeout);
            outputStream = new BufferedOutputStream(socket.getOutputStream());
            encoderInit(outputStream);
            socket.setSoTimeout(0);
            addInfo(peerId + "connection established");
            int counter = 0;
            while (true) {
                ILoggingEvent event = queue.take();
                this.encoder.doEncode(event);
                outputStream.flush();
                if (++counter >= CoreConstants.OOS_RESET_FREQUENCY) {
                    // Failing to reset the object output stream every now and
                    // then creates a serious memory leak.
                    outputStream.flush();
                    counter = 0;
                }
            }
        } catch (IOException ex) {
            addInfo(peerId + "connection failed: " + ex);
        } finally {
            if (outputStream != null) {
                encoderClose(outputStream);
            }
            CloseUtil.closeQuietly(socket);
            socket = null;
            addInfo(peerId + "connection closed");
        }
    }
   
    /**
     * {@inheritDoc}
     */
    public void connectionFailed(SocketConnector connector, Exception ex) {
        if (ex instanceof InterruptedException) {
            addInfo("connector interrupted");
        } else if (ex instanceof ConnectException) {
            addInfo(peerId + "connection refused");
        } else {
            addInfo(peerId + ex);
        }
    }
   
    /**
     * Creates a new {@link SocketConnector}.
     * <p>
     * The default implementation creates an instance of {@link DefaultSocketConnector}. A subclass may override to provide a different {@link SocketConnector} implementation.
     *
     * @param address
     *            target remote address
     * @param port
     *            target remote port
     * @param initialDelay
     *            delay before the first connection attempt
     * @param retryDelay
     *            delay before a reconnection attempt
     * @return socket connector
     */
    protected SocketConnector newConnector(InetAddress address, int port,
            long initialDelay, long retryDelay) {
        return new DefaultSocketConnector(address, port, initialDelay,
                retryDelay);
    }
   
    /**
     * Gets the default {@link SocketFactory} for the platform.
     * <p>
     * Subclasses may override to provide a custom socket factory.
     */
    protected SocketFactory getSocketFactory() {
        return SocketFactory.getDefault();
    }
   
    /**
     * The <b>RemoteHost</b> property takes the name of of the host where a
     * corresponding server is running.
     */
    public void setRemoteHost(String host) {
        remoteHost = host;
    }
   
    /**
     * Returns value of the <b>RemoteHost</b> property.
     */
    public String getRemoteHost() {
        return remoteHost;
    }
   
    /**
     * The <b>Port</b> property takes a positive integer representing the port
     * where the server is waiting for connections.
     */
    public void setPort(int port) {
        this.port = port;
    }
   
    /**
     * Returns value of the <b>Port</b> property.
     */
    public int getPort() {
        return port;
    }
   
    /**
     * The <b>reconnectionDelay</b> property takes a positive {@link Duration} value representing the time to wait between each failed connection
     * attempt to the server. The default value of this option is to 30 seconds.
     *
     * <p>
     * Setting this option to zero turns off reconnection capability.
     */
    public void setReconnectionDelay(Duration delay) {
        this.reconnectionDelay = delay;
    }
   
    /**
     * Returns value of the <b>reconnectionDelay</b> property.
     */
    public Duration getReconnectionDelay() {
        return reconnectionDelay;
    }
   
    /**
     * The <b>eventDelayLimit</b> takes a non-negative integer representing the
     * number of milliseconds to allow the appender to block if the underlying
     * BlockingQueue is full. Once this limit is reached, the event is dropped.
     *
     * @param eventDelayLimit
     *            the event delay limit
     */
    public void setEventDelayLimit(Duration eventDelayLimit) {
        this.eventDelayLimit = eventDelayLimit;
    }
   
    /**
     * Returns the value of the <b>eventDelayLimit</b> property.
     */
    public Duration getEventDelayLimit() {
        return eventDelayLimit;
    }
   
    /**
     * Sets the timeout that controls how long we'll wait for the remote peer to
     * accept our connection attempt.
     * <p>
     * This property is configurable primarily to support instrumentation for unit testing.
     *
     * @param acceptConnectionTimeout
     *            timeout value in milliseconds
     */
    void setAcceptConnectionTimeout(int acceptConnectionTimeout) {
        this.acceptConnectionTimeout = acceptConnectionTimeout;
    }

    /**
     * Returns the value of the <b>queueSize</b> property.
     */
    public int getQueueSize() {
        return queueSize;
    }

    /**
     * Sets the maximum number of entries in the queue. Once the queue is full additional entries will be dropped
     * if in the time given by the <b>eventDelayLimit</b> no space becomes available.
     *
     * @param queueSize the maximum number of entries in the queue
     */
    public void setQueueSize(int queueSize) {
        if (queue != null) {
            throw new IllegalStateException("Queue size must be set before initialization");
        }
        this.queueSize = queueSize;
    }
}
TOP

Related Classes of net.logstash.logback.appender.LogstashTcpSocketAppender

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.