001/*
002 * Logback: the reliable, generic, fast and flexible logging framework.
003 * Copyright (C) 1999-2026, QOS.ch. All rights reserved.
004 *
005 * This program and the accompanying materials are dual-licensed under
006 * either the terms of the Eclipse Public License v2.0 as published by
007 * the Eclipse Foundation
008 *
009 *   or (per the licensee's choosing)
010 *
011 * under the terms of the GNU Lesser General Public License version 2.1
012 * as published by the Free Software Foundation.
013 */
014// Contributors: Dan MacDonald <dan@redknee.com>
015package ch.qos.logback.core.net;
016
017import java.io.IOException;
018import java.io.Serializable;
019import java.net.ConnectException;
020import java.net.InetAddress;
021import java.net.Socket;
022import java.net.UnknownHostException;
023import java.util.concurrent.BlockingDeque;
024import java.util.concurrent.Future;
025import java.util.concurrent.TimeUnit;
026
027import javax.net.SocketFactory;
028
029import ch.qos.logback.core.AppenderBase;
030import ch.qos.logback.core.spi.DeferredProcessingAware;
031import ch.qos.logback.core.spi.PreSerializationTransformer;
032import ch.qos.logback.core.util.CloseUtil;
033import ch.qos.logback.core.util.Duration;
034
035/**
036 * An abstract base for module specific {@code SocketAppender} implementations
037 * in other logback modules.
038 * 
039 * @author Ceki G&uuml;lc&uuml;
040 * @author S&eacute;bastien Pennec
041 * @author Carl Harris
042 * @author Sebastian Gr&ouml;bler
043 */
044
045public abstract class AbstractSocketAppender<E> extends AppenderBase<E> implements SocketConnector.ExceptionHandler {
046
047    /**
048     * The default port number of remote logging server (4560).
049     */
050    public static final int DEFAULT_PORT = 4560;
051
052    /**
053     * The default reconnection delay (30000 milliseconds or 30 seconds).
054     */
055    public static final int DEFAULT_RECONNECTION_DELAY = 30000;
056
057    /**
058     * Default size of the deque used to hold logging events that are destined for
059     * the remote peer.
060     */
061    public static final int DEFAULT_QUEUE_SIZE = 128;
062
063    /**
064     * Default timeout when waiting for the remote server to accept our connection.
065     */
066    private static final int DEFAULT_ACCEPT_CONNECTION_DELAY = 5000;
067
068    /**
069     * Default timeout for how long to wait when inserting an event into the
070     * BlockingQueue.
071     */
072    private static final int DEFAULT_EVENT_DELAY_TIMEOUT = 100;
073
074    private final ObjectWriterFactory objectWriterFactory;
075    private final QueueFactory queueFactory;
076
077    private String remoteHost;
078    private int port = DEFAULT_PORT;
079    private InetAddress address;
080    private Duration reconnectionDelay = new Duration(DEFAULT_RECONNECTION_DELAY);
081    private int queueSize = DEFAULT_QUEUE_SIZE;
082    private int acceptConnectionTimeout = DEFAULT_ACCEPT_CONNECTION_DELAY;
083    private Duration eventDelayLimit = new Duration(DEFAULT_EVENT_DELAY_TIMEOUT);
084
085    private BlockingDeque<E> deque;
086    private String peerId;
087    private SocketConnector connector;
088    private Future<?> task;
089
090    private volatile Socket socket;
091
092    /**
093     * Constructs a new appender.
094     */
095    protected AbstractSocketAppender() {
096        this(new QueueFactory(), new ObjectWriterFactory());
097    }
098
099    /**
100     * Constructs a new appender using the given {@link QueueFactory} and
101     * {@link ObjectWriterFactory}.
102     */
103    AbstractSocketAppender(QueueFactory queueFactory, ObjectWriterFactory objectWriterFactory) {
104        this.objectWriterFactory = objectWriterFactory;
105        this.queueFactory = queueFactory;
106    }
107
108    /**
109     * {@inheritDoc}
110     */
111    public void start() {
112        if (isStarted())
113            return;
114        int errorCount = 0;
115        if (port <= 0) {
116            errorCount++;
117            addError("No port was configured for appender" + name
118                    + " For more information, please visit http://logback.qos.ch/codes.html#socket_no_port");
119        }
120
121        if (remoteHost == null) {
122            errorCount++;
123            addError("No remote host was configured for appender" + name
124                    + " For more information, please visit http://logback.qos.ch/codes.html#socket_no_host");
125        }
126
127        if (queueSize == 0) {
128            addWarn("Queue size of zero is deprecated, use a size of one to indicate synchronous processing");
129        }
130
131        if (queueSize < 0) {
132            errorCount++;
133            addError("Queue size must be greater than zero");
134        }
135
136        if (errorCount == 0) {
137            try {
138                address = InetAddress.getByName(remoteHost);
139            } catch (UnknownHostException ex) {
140                addError("unknown host: " + remoteHost);
141                errorCount++;
142            }
143        }
144
145        if (errorCount == 0) {
146            deque = queueFactory.newLinkedBlockingDeque(queueSize);
147            peerId = "remote peer " + remoteHost + ":" + port + ": ";
148            connector = createConnector(address, port, 0, reconnectionDelay.getMilliseconds());
149            task = getContext().getExecutorService().submit(new Runnable() {
150                @Override
151                public void run() {
152                    connectSocketAndDispatchEvents();
153                }
154            });
155            super.start();
156        }
157    }
158
159    /**
160     * {@inheritDoc}
161     */
162    @Override
163    public void stop() {
164        if (!isStarted())
165            return;
166        CloseUtil.closeQuietly(socket);
167        task.cancel(true);
168        super.stop();
169    }
170
171    /**
172     * {@inheritDoc}
173     */
174    @Override
175    protected void append(E event) {
176        if (event == null || !isStarted())
177            return;
178
179        try {
180
181            // otherwise MDC information is not transferred. See also logback/issues/1010
182            if(event instanceof DeferredProcessingAware) {
183                ((DeferredProcessingAware) event).prepareForDeferredProcessing();
184            }
185
186            final boolean inserted = deque.offer(event, eventDelayLimit.getMilliseconds(), TimeUnit.MILLISECONDS);
187            if (!inserted) {
188                addInfo("Dropping event due to timeout limit of [" + eventDelayLimit + "] being exceeded");
189            }
190        } catch (InterruptedException e) {
191            addError("Interrupted while appending event to SocketAppender", e);
192            Thread.currentThread().interrupt();
193
194        }
195    }
196
197    private void connectSocketAndDispatchEvents() {
198        try {
199            while (socketConnectionCouldBeEstablished()) {
200                try {
201                    ObjectWriter objectWriter = createObjectWriterForSocket();
202                    addInfo(peerId + "connection established");
203                    dispatchEvents(objectWriter);
204                } catch (javax.net.ssl.SSLHandshakeException she) {
205                    // FIXME
206                    Thread.sleep(DEFAULT_RECONNECTION_DELAY);
207                } catch (IOException ex) {
208                    addInfo(peerId + "connection failed: ", ex);
209                } finally {
210                    CloseUtil.closeQuietly(socket);
211                    socket = null;
212                    addInfo(peerId + "connection closed");
213                }
214            }
215        } catch (InterruptedException ex) {
216            Thread.currentThread().interrupt();
217        }
218        addInfo("shutting down");
219    }
220
221    private boolean socketConnectionCouldBeEstablished() throws InterruptedException {
222        return (socket = connector.call()) != null;
223    }
224
225    private ObjectWriter createObjectWriterForSocket() throws IOException {
226        socket.setSoTimeout(acceptConnectionTimeout);
227        ObjectWriter objectWriter = objectWriterFactory.newAutoFlushingObjectWriter(socket.getOutputStream());
228        socket.setSoTimeout(0);
229        return objectWriter;
230    }
231
232    private SocketConnector createConnector(InetAddress address, int port, int initialDelay, long retryDelay) {
233        SocketConnector connector = newConnector(address, port, initialDelay, retryDelay);
234        connector.setExceptionHandler(this);
235        connector.setSocketFactory(getSocketFactory());
236        return connector;
237    }
238
239    private void dispatchEvents(ObjectWriter objectWriter) throws InterruptedException, IOException {
240        while (true) {
241            E event = deque.takeFirst();
242            postProcessEvent(event);
243            Serializable serializableEvent = getPST().transform(event);
244            try {
245                objectWriter.write(serializableEvent);
246            } catch (IOException e) {
247                tryReAddingEventToFrontOfQueue(event);
248                throw e;
249            }
250        }
251    }
252
253    private void tryReAddingEventToFrontOfQueue(E event) {
254        final boolean wasInserted = deque.offerFirst(event);
255        if (!wasInserted) {
256            addInfo("Dropping event due to socket connection error and maxed out deque capacity");
257        }
258    }
259
260    /**
261     * {@inheritDoc}
262     */
263    public void connectionFailed(SocketConnector connector, Exception ex) {
264        if (ex instanceof InterruptedException) {
265            addInfo("connector interrupted");
266        } else if (ex instanceof ConnectException) {
267            addInfo(peerId + "connection refused");
268        } else {
269            addInfo(peerId + ex);
270        }
271    }
272
273    /**
274     * Creates a new {@link SocketConnector}.
275     * <p>
276     * The default implementation creates an instance of
277     * {@link DefaultSocketConnector}. A subclass may override to provide a
278     * different {@link SocketConnector} implementation.
279     * 
280     * @param address      target remote address
281     * @param port         target remote port
282     * @param initialDelay delay before the first connection attempt
283     * @param retryDelay   delay before a reconnection attempt
284     * @return socket connector
285     */
286    protected SocketConnector newConnector(InetAddress address, int port, long initialDelay, long retryDelay) {
287        return new DefaultSocketConnector(address, port, initialDelay, retryDelay);
288    }
289
290    /**
291     * Gets the default {@link SocketFactory} for the platform.
292     * <p>
293     * Subclasses may override to provide a custom socket factory.
294     */
295    protected SocketFactory getSocketFactory() {
296        return SocketFactory.getDefault();
297    }
298
299    /**
300     * Post-processes an event before it is serialized for delivery to the remote
301     * receiver.
302     * 
303     * @param event the event to post-process
304     */
305    protected abstract void postProcessEvent(E event);
306
307    /**
308     * Get the pre-serialization transformer that will be used to transform each
309     * event into a Serializable object before delivery to the remote receiver.
310     * 
311     * @return transformer object
312     */
313    protected abstract PreSerializationTransformer<E> getPST();
314
315    /**
316     * The <b>RemoteHost</b> property takes the name of the host where a
317     * corresponding server is running.
318     */
319    public void setRemoteHost(String host) {
320        remoteHost = host;
321    }
322
323    /**
324     * Returns value of the <b>RemoteHost</b> property.
325     */
326    public String getRemoteHost() {
327        return remoteHost;
328    }
329
330    /**
331     * The <b>Port</b> property takes a positive integer representing the port where
332     * the server is waiting for connections.
333     */
334    public void setPort(int port) {
335        this.port = port;
336    }
337
338    /**
339     * Returns value of the <b>Port</b> property.
340     */
341    public int getPort() {
342        return port;
343    }
344
345    /**
346     * The <b>reconnectionDelay</b> property takes a positive {@link Duration} value
347     * representing the time to wait between each failed connection attempt to the
348     * server. The default value of this option is to 30 seconds.
349     *
350     * <p>
351     * Setting this option to zero turns off reconnection capability.
352     */
353    public void setReconnectionDelay(Duration delay) {
354        this.reconnectionDelay = delay;
355    }
356
357    /**
358     * Returns value of the <b>reconnectionDelay</b> property.
359     */
360    public Duration getReconnectionDelay() {
361        return reconnectionDelay;
362    }
363
364    /**
365     * The <b>queueSize</b> property takes a non-negative integer representing the
366     * number of logging events to retain for delivery to the remote receiver. When
367     * the deque size is zero, event delivery to the remote receiver is synchronous.
368     * When the deque size is greater than zero, the {@link #append(Object)} method
369     * returns immediately after enqueing the event, assuming that there is space
370     * available in the deque. Using a non-zero deque length can improve performance
371     * by eliminating delays caused by transient network delays.
372     * 
373     * @param queueSize the deque size to set.
374     */
375    public void setQueueSize(int queueSize) {
376        this.queueSize = queueSize;
377    }
378
379    /**
380     * Returns the value of the <b>queueSize</b> property.
381     */
382    public int getQueueSize() {
383        return queueSize;
384    }
385
386    /**
387     * The <b>eventDelayLimit</b> takes a non-negative integer representing the
388     * number of milliseconds to allow the appender to block if the underlying
389     * BlockingQueue is full. Once this limit is reached, the event is dropped.
390     *
391     * @param eventDelayLimit the event delay limit
392     */
393    public void setEventDelayLimit(Duration eventDelayLimit) {
394        this.eventDelayLimit = eventDelayLimit;
395    }
396
397    /**
398     * Returns the value of the <b>eventDelayLimit</b> property.
399     */
400    public Duration getEventDelayLimit() {
401        return eventDelayLimit;
402    }
403
404    /**
405     * Sets the timeout that controls how long we'll wait for the remote peer to
406     * accept our connection attempt.
407     * <p>
408     * This property is configurable primarily to support instrumentation for unit
409     * testing.
410     * 
411     * @param acceptConnectionTimeout timeout value in milliseconds
412     */
413    void setAcceptConnectionTimeout(int acceptConnectionTimeout) {
414        this.acceptConnectionTimeout = acceptConnectionTimeout;
415    }
416
417}