View Javadoc
1   /**
2    * Logback: the reliable, generic, fast and flexible logging framework.
3    * Copyright (C) 1999-2015, QOS.ch. All rights reserved.
4    *
5    * This program and the accompanying materials are dual-licensed under
6    * either the terms of the Eclipse Public License v1.0 as published by
7    * the Eclipse Foundation
8    *
9    *   or (per the licensee's choosing)
10   *
11   * under the terms of the GNU Lesser General Public License version 2.1
12   * as published by the Free Software Foundation.
13   */
14  package ch.qos.logback.classic.net;
15  
16  import java.io.EOFException;
17  import java.io.IOException;
18  import java.io.ObjectInputStream;
19  import java.net.ConnectException;
20  import java.net.InetAddress;
21  import java.net.Socket;
22  import java.net.UnknownHostException;
23  import java.util.concurrent.ExecutionException;
24  import java.util.concurrent.Future;
25  import java.util.concurrent.RejectedExecutionException;
26  
27  import javax.net.SocketFactory;
28  
29  import ch.qos.logback.classic.Logger;
30  import ch.qos.logback.classic.LoggerContext;
31  import ch.qos.logback.classic.net.server.HardenedLoggingEventInputStream;
32  import ch.qos.logback.classic.spi.ILoggingEvent;
33  import ch.qos.logback.core.net.DefaultSocketConnector;
34  import ch.qos.logback.core.net.AbstractSocketAppender;
35  import ch.qos.logback.core.net.SocketConnector;
36  import ch.qos.logback.core.util.CloseUtil;
37  
38  /**
39   * A component that receives serialized {@link ILoggingEvent} objects from a
40   * remote appender over a {@link Socket}.
41   *
42   * @author Carl Harris
43   */
44  public class SocketReceiver extends ReceiverBase implements Runnable, SocketConnector.ExceptionHandler {
45  
46      private static final int DEFAULT_ACCEPT_CONNECTION_DELAY = 5000;
47  
48      private String remoteHost;
49      private InetAddress address;
50      private int port;
51      private int reconnectionDelay;
52      private int acceptConnectionTimeout = DEFAULT_ACCEPT_CONNECTION_DELAY;
53  
54      private String receiverId;
55      private volatile Socket socket;
56      private Future<Socket> connectorTask;
57  
58      /**
59       * {@inheritDoc}
60       */
61      protected boolean shouldStart() {
62          int errorCount = 0;
63          if (port == 0) {
64              errorCount++;
65              addError("No port was configured for receiver. " + "For more information, please visit http://logback.qos.ch/codes.html#receiver_no_port");
66          }
67  
68          if (remoteHost == null) {
69              errorCount++;
70              addError("No host name or address was configured for receiver. "
71                              + "For more information, please visit http://logback.qos.ch/codes.html#receiver_no_host");
72          }
73  
74          if (reconnectionDelay == 0) {
75              reconnectionDelay = AbstractSocketAppender.DEFAULT_RECONNECTION_DELAY;
76          }
77  
78          if (errorCount == 0) {
79              try {
80                  address = InetAddress.getByName(remoteHost);
81              } catch (UnknownHostException ex) {
82                  addError("unknown host: " + remoteHost);
83                  errorCount++;
84              }
85          }
86  
87          if (errorCount == 0) {
88              receiverId = "receiver " + remoteHost + ":" + port + ": ";
89          }
90  
91          return errorCount == 0;
92      }
93  
94      /**
95       * {@inheritDoc}
96       */
97      protected void onStop() {
98          if (socket != null) {
99              CloseUtil.closeQuietly(socket);
100         }
101     }
102 
103     @Override
104     protected Runnable getRunnableTask() {
105         return this;
106     }
107 
108     /**
109      * {@inheritDoc}
110      */
111     public void run() {
112         try {
113             LoggerContext lc = (LoggerContext) getContext();
114             while (!Thread.currentThread().isInterrupted()) {
115                 SocketConnector connector = createConnector(address, port, 0, reconnectionDelay);
116                 connectorTask = activateConnector(connector);
117                 if (connectorTask == null) {
118                     break;
119                 }
120                 socket = waitForConnectorToReturnASocket();
121                 if (socket == null)
122                     break;
123                 dispatchEvents(lc);
124             }
125         } catch (InterruptedException ex) {
126             assert true; // ok... we'll exit now
127         }
128         addInfo("shutting down");
129     }
130 
131     private SocketConnector createConnector(InetAddress address, int port, int initialDelay, int retryDelay) {
132         SocketConnector connector = newConnector(address, port, initialDelay, retryDelay);
133         connector.setExceptionHandler(this);
134         connector.setSocketFactory(getSocketFactory());
135         return connector;
136     }
137 
138     private Future<Socket> activateConnector(SocketConnector connector) {
139         try {
140             return getContext().getScheduledExecutorService().submit(connector);
141         } catch (RejectedExecutionException ex) {
142             return null;
143         }
144     }
145 
146     private Socket waitForConnectorToReturnASocket() throws InterruptedException {
147         try {
148             Socket s = connectorTask.get();
149             connectorTask = null;
150             return s;
151         } catch (ExecutionException e) {
152             return null;
153         }
154     }
155 
156     private void dispatchEvents(LoggerContext lc) {
157         ObjectInputStream ois = null;
158         try {
159             socket.setSoTimeout(acceptConnectionTimeout);
160             ois = new HardenedLoggingEventInputStream(socket.getInputStream());
161             socket.setSoTimeout(0);
162             addInfo(receiverId + "connection established");
163             while (true) {
164                 ILoggingEvent event = (ILoggingEvent) ois.readObject();
165                 Logger remoteLogger = lc.getLogger(event.getLoggerName());
166                 if (remoteLogger.isEnabledFor(event.getLevel())) {
167                     remoteLogger.callAppenders(event);
168                 }
169             }
170         } catch (EOFException ex) {
171             addInfo(receiverId + "end-of-stream detected");
172         } catch (IOException ex) {
173             addInfo(receiverId + "connection failed: " + ex);
174         } catch (ClassNotFoundException ex) {
175             addInfo(receiverId + "unknown event class: " + ex);
176         } finally {
177             CloseUtil.closeQuietly(ois);
178             CloseUtil.closeQuietly(socket);
179             socket = null;
180             addInfo(receiverId + "connection closed");
181         }
182     }
183 
184     /**
185      * {@inheritDoc}
186      */
187     public void connectionFailed(SocketConnector connector, Exception ex) {
188         if (ex instanceof InterruptedException) {
189             addInfo("connector interrupted");
190         } else if (ex instanceof ConnectException) {
191             addInfo(receiverId + "connection refused");
192         } else {
193             addInfo(receiverId + ex);
194         }
195     }
196 
197     protected SocketConnector newConnector(InetAddress address, int port, int initialDelay, int retryDelay) {
198         return new DefaultSocketConnector(address, port, initialDelay, retryDelay);
199     }
200 
201     protected SocketFactory getSocketFactory() {
202         return SocketFactory.getDefault();
203     }
204 
205     public void setRemoteHost(String remoteHost) {
206         this.remoteHost = remoteHost;
207     }
208 
209     public void setPort(int port) {
210         this.port = port;
211     }
212 
213     public void setReconnectionDelay(int reconnectionDelay) {
214         this.reconnectionDelay = reconnectionDelay;
215     }
216 
217     public void setAcceptConnectionTimeout(int acceptConnectionTimeout) {
218         this.acceptConnectionTimeout = acceptConnectionTimeout;
219     }
220 
221 }