View Javadoc

1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  
18  package org.apache.log4j.net;
19  
20  import org.apache.log4j.plugins.Plugin;
21  import org.apache.log4j.plugins.Receiver;
22  import org.apache.log4j.spi.LoggerRepository;
23  
24  import java.io.IOException;
25  import java.net.Socket;
26  import java.util.ArrayList;
27  import java.util.Collections;
28  import java.util.Iterator;
29  import java.util.List;
30  
31  /***
32    SocketHubReceiver receives a remote logging event on a configured
33    socket and "posts" it to a LoggerRepository as if the event was
34    generated locally. This class is designed to receive events from
35    the SocketHubAppender class (or classes that send compatible events).
36  
37    <p>Once the event has been "posted", it will be handled by the
38    appenders currently configured in the LoggerRespository.
39  
40    @author Mark Womack
41    @author Ceki G&uuml;lc&uuml;
42    @author Paul Smith (psmith@apache.org)
43  */
44  public class SocketHubReceiver
45  extends Receiver implements SocketNodeEventListener, PortBased {
46  
47      /***
48       * Default reconnection delay.
49       */
50    static final int DEFAULT_RECONNECTION_DELAY   = 30000;
51  
52      /***
53       * Host.
54       */
55    protected String host;
56  
57      /***
58       * Port.
59       */
60    protected int port;
61      /***
62       * Reconnection delay.
63       */
64    protected int reconnectionDelay = DEFAULT_RECONNECTION_DELAY;
65  
66      /***
67       * Active.
68       */
69    protected boolean active = false;
70  
71      /***
72       * Connector.
73       */
74    protected Connector connector;
75  
76      /***
77       * Socket.
78       */
79    protected Socket socket;
80  
81      /***
82       * Listener list.
83       */
84    private List listenerList = Collections.synchronizedList(new ArrayList());
85  
86      /***
87       * Create new instance.
88       */
89    public SocketHubReceiver() {
90       super();
91    }
92  
93      /***
94       * Create new instance.
95       * @param h host
96       * @param p port
97       */
98    public SocketHubReceiver(final String h,
99                             final int p) {
100     super();
101     host = h;
102     port = p;
103   }
104 
105     /***
106      * Create new instance.
107      * @param h host
108      * @param p port
109      * @param repo logger repository
110      */
111   public SocketHubReceiver(final String h,
112                            final int p,
113                            final LoggerRepository repo) {
114     super();
115     host = h;
116     port = p;
117     repository = repo;
118   }
119 
120   /***
121    * Adds a SocketNodeEventListener to this receiver to be notified
122    * of SocketNode events.
123    * @param l listener
124    */
125   public void addSocketNodeEventListener(final SocketNodeEventListener l) {
126     listenerList.add(l);
127   }
128 
129   /***
130    * Removes a specific SocketNodeEventListener from this instance
131    * so that it will no  longer be notified of SocketNode events.
132    * @param l listener
133    */
134   public void removeSocketNodeEventListener(
135           final SocketNodeEventListener l) {
136     listenerList.remove(l);
137   }
138 
139   /***
140     Get the remote host to connect to for logging events.
141     @return host
142    */
143   public String getHost() {
144     return host;
145   }
146 
147   /***
148    * Configures the Host property, this will require activateOptions
149    * to be called for this to take effect.
150    * @param remoteHost address of remote host.
151    */
152   public void setHost(final String remoteHost) {
153     this.host = remoteHost;
154   }
155   /***
156     Set the remote host to connect to for logging events.
157    Equivalent to setHost.
158    @param remoteHost address of remote host.
159    */
160   public void setPort(final String remoteHost) {
161     host = remoteHost;
162   }
163 
164   /***
165     Get the remote port to connect to for logging events.
166    @return port
167    */
168   public int getPort() {
169     return port;
170   }
171 
172   /***
173     Set the remote port to connect to for logging events.
174     @param p port
175    */
176   public void setPort(final int p) {
177     this.port = p;
178   }
179 
180   /***
181      The <b>ReconnectionDelay</b> option takes a positive integer
182      representing the number of milliseconds to wait between each
183      failed connection attempt to the server. The default value of
184      this option is 30000 which corresponds to 30 seconds.
185 
186      <p>Setting this option to zero turns off reconnection
187      capability.
188    @param delay milliseconds to wait or zero to not reconnect.
189    */
190   public void setReconnectionDelay(final int delay) {
191     int oldValue = this.reconnectionDelay;
192     this.reconnectionDelay = delay;
193     firePropertyChange("reconnectionDelay", oldValue, this.reconnectionDelay);
194   }
195 
196   /***
197      Returns value of the <b>ReconnectionDelay</b> option.
198    @return value of reconnection delay option.
199    */
200   public int getReconnectionDelay() {
201     return reconnectionDelay;
202   }
203 
204   /***
205    * Returns true if the receiver is the same class and they are
206    * configured for the same properties, and super class also considers
207    * them to be equivalent. This is used by PluginRegistry when determining
208    * if the a similarly configured receiver is being started.
209    *
210    * @param testPlugin The plugin to test equivalency against.
211    * @return boolean True if the testPlugin is equivalent to this plugin.
212    */
213   public boolean isEquivalent(final Plugin testPlugin) {
214     if (testPlugin != null && testPlugin instanceof SocketHubReceiver) {
215       SocketHubReceiver sReceiver = (SocketHubReceiver) testPlugin;
216 
217       return (port == sReceiver.getPort()
218               && host.equals(sReceiver.getHost())
219               && reconnectionDelay == sReceiver.getReconnectionDelay()
220               && super.isEquivalent(testPlugin));
221     }
222     return false;
223   }
224 
225   /***
226     Returns true if this receiver is active.
227    @return true if receiver is active
228    */
229   public synchronized boolean isActive() {
230     return active;
231   }
232 
233   /***
234     Sets the flag to indicate if receiver is active or not.
235    @param b new value
236    */
237   protected synchronized void setActive(final boolean b) {
238     active = b;
239   }
240 
241   /***
242     Starts the SocketReceiver with the current options. */
243   public void activateOptions() {
244     if (!isActive()) {
245       setActive(true);
246       fireConnector(false);
247     }
248   }
249 
250   /***
251     Called when the receiver should be stopped. Closes the socket */
252   public synchronized void shutdown() {
253     // mark this as no longer running
254     active = false;
255 
256     // close the socket
257     try {
258       if (socket != null) {
259         socket.close();
260       }
261     } catch (Exception e) {
262       // ignore for now
263     }
264     socket = null;
265 
266     // stop the connector
267     if (connector != null) {
268       connector.interrupted = true;
269       connector = null;  // allow gc
270     }
271   }
272 
273   /***
274     Listen for a socketClosedEvent from the SocketNode. Reopen the
275     socket if this receiver is still active.
276    @param e exception not used.
277    */
278   public void socketClosedEvent(final Exception e) {
279     // we clear the connector object here
280     // so that it actually does reconnect if the
281     // remote socket dies.
282     connector = null;
283     fireConnector(true);
284   }
285 
286     /***
287      * Fire connectors.
288      * @param isReconnect true if reconnect.
289      */
290   private synchronized void fireConnector(final boolean isReconnect) {
291     if (active && connector == null) {
292       getLogger().debug("Starting a new connector thread.");
293       connector = new Connector(isReconnect);
294       connector.setDaemon(true);
295       connector.setPriority(Thread.MIN_PRIORITY);
296       connector.start();
297     }
298   }
299 
300     /***
301      * Set socket.
302      * @param newSocket new value for socket.
303      */
304   private synchronized void setSocket(final Socket newSocket) {
305     connector = null;
306     socket = newSocket;
307     SocketNode13 node = new SocketNode13(socket, this);
308     node.addSocketNodeEventListener(this);
309 
310     synchronized (listenerList) {
311         for (Iterator iter = listenerList.iterator(); iter.hasNext();) {
312             SocketNodeEventListener listener =
313                     (SocketNodeEventListener) iter.next();
314             node.addSocketNodeEventListener(listener);
315         }
316     }
317     new Thread(node).start();
318   }
319 
320   /***
321    The Connector will reconnect when the server becomes available
322    again.  It does this by attempting to open a new connection every
323    <code>reconnectionDelay</code> milliseconds.
324 
325    <p>It stops trying whenever a connection is established. It will
326    restart to try reconnect to the server when previpously open
327    connection is droppped.
328 
329    @author  Ceki G&uuml;lc&uuml;
330    */
331   private final class Connector extends Thread {
332 
333       /***
334        * Interruption status.
335        */
336     boolean interrupted = false;
337       /***
338        * If true, then delay on next iteration.
339        */
340     boolean doDelay;
341 
342       /***
343        * Create new instance.
344        * @param isReconnect true if reconnecting.
345        */
346     public Connector(final boolean isReconnect) {
347       super();
348       doDelay = isReconnect;
349     }
350 
351       /***
352        * Attempt to connect until interrupted.
353        */
354     public void run() {
355       while (!interrupted) {
356         try {
357           if (doDelay) {
358             getLogger().debug("waiting for " + reconnectionDelay
359               + " milliseconds before reconnecting.");
360             sleep(reconnectionDelay);
361           }
362           doDelay = true;
363           getLogger().debug("Attempting connection to " + host);
364           Socket s = new Socket(host, port);
365           setSocket(s);
366           getLogger().debug(
367                   "Connection established. Exiting connector thread.");
368           break;
369         } catch (InterruptedException e) {
370           getLogger().debug("Connector interrupted. Leaving loop.");
371           return;
372         } catch (java.net.ConnectException e) {
373           getLogger().debug("Remote host {} refused connection.", host);
374         } catch (IOException e) {
375           getLogger().debug("Could not connect to {}. Exception is {}.",
376                   host, e);
377         }
378       }
379     }
380   }
381 
382     /***
383      * This method does nothing.
384      * @param remoteInfo remote info.
385      */
386   public void socketOpened(final String remoteInfo) {
387 
388     // This method does nothing.
389   }
390 }