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ülcü 040 * @author Sébastien Pennec 041 * @author Carl Harris 042 * @author Sebastian Grö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}