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 java.util.Vector;
21  import java.net.Socket;
22  import java.net.ServerSocket;
23  import java.net.SocketException;
24  import java.io.ObjectOutputStream;
25  import java.io.IOException;
26  import java.io.InterruptedIOException;
27  import java.net.InetAddress;
28  
29  import org.apache.log4j.helpers.LogLog;
30  import org.apache.log4j.spi.LoggingEvent;
31  import org.apache.log4j.AppenderSkeleton;
32  
33  /***
34    Sends {@link LoggingEvent} objects to a set of remote log servers,
35    usually a {@link SocketNode SocketNodes}.
36      
37    <p>Acts just like {@link SocketAppender} except that instead of
38    connecting to a given remote log server,
39    <code>SocketHubAppender</code> accepts connections from the remote
40    log servers as clients.  It can accept more than one connection.
41    When a log event is received, the event is sent to the set of
42    currently connected remote log servers. Implemented this way it does
43    not require any update to the configuration file to send data to
44    another remote log server. The remote log server simply connects to
45    the host and port the <code>SocketHubAppender</code> is running on.
46    
47    <p>The <code>SocketHubAppender</code> does not store events such
48    that the remote side will events that arrived after the
49    establishment of its connection. Once connected, events arrive in
50    order as guaranteed by the TCP protocol.
51  
52    <p>This implementation borrows heavily from the {@link
53    SocketAppender}.
54  
55    <p>The SocketHubAppender has the following characteristics:
56    
57    <ul>
58    
59    <p><li>If sent to a {@link SocketNode}, logging is non-intrusive as
60    far as the log event is concerned. In other words, the event will be
61    logged with the same time stamp, {@link org.apache.log4j.NDC},
62    location info as if it were logged locally.
63    
64    <p><li><code>SocketHubAppender</code> does not use a layout. It
65    ships a serialized {@link LoggingEvent} object to the remote side.
66    
67    <p><li><code>SocketHubAppender</code> relies on the TCP
68    protocol. Consequently, if the remote side is reachable, then log
69    events will eventually arrive at remote client.
70    
71    <p><li>If no remote clients are attached, the logging requests are
72    simply dropped.
73    
74    <p><li>Logging events are automatically <em>buffered</em> by the
75    native TCP implementation. This means that if the link to remote
76    client is slow but still faster than the rate of (log) event
77    production, the application will not be affected by the slow network
78    connection. However, if the network connection is slower then the
79    rate of event production, then the local application can only
80    progress at the network rate. In particular, if the network link to
81    the the remote client is down, the application will be blocked.
82    
83    <p>On the other hand, if the network link is up, but the remote
84    client is down, the client will not be blocked when making log
85    requests but the log events will be lost due to client
86    unavailability. 
87  
88    <p>The single remote client case extends to multiple clients
89    connections. The rate of logging will be determined by the slowest
90    link.
91      
92    <p><li>If the JVM hosting the <code>SocketHubAppender</code> exits
93    before the <code>SocketHubAppender</code> is closed either
94    explicitly or subsequent to garbage collection, then there might
95    be untransmitted data in the pipe which might be lost. This is a
96    common problem on Windows based systems.
97    
98    <p>To avoid lost data, it is usually sufficient to {@link #close}
99    the <code>SocketHubAppender</code> either explicitly or by calling
100   the {@link org.apache.log4j.LogManager#shutdown} method before
101   exiting the application.
102   
103   </ul>
104      
105   @author Mark Womack */
106 
107 public class SocketHubAppender extends AppenderSkeleton {
108 
109   /***
110      The default port number of the ServerSocket will be created on. */
111   static final int DEFAULT_PORT = 4560;
112   
113   private int port = DEFAULT_PORT;
114   private Vector oosList = new Vector();
115   private ServerMonitor serverMonitor = null;
116   private boolean locationInfo = false;
117   
118   public SocketHubAppender() { }
119 
120   /***
121      Connects to remote server at <code>address</code> and <code>port</code>. */
122   public
123   SocketHubAppender(int _port) {
124     port = _port;
125     startServer();
126   }
127 
128   /***
129      Set up the socket server on the specified port.  */
130   public
131   void activateOptions() {
132     startServer();
133   }
134 
135   /***
136      Close this appender. 
137      <p>This will mark the appender as closed and
138      call then {@link #cleanUp} method. */
139   synchronized
140   public
141   void close() {
142     if(closed)
143       return;
144 
145 	LogLog.debug("closing SocketHubAppender " + getName());
146     this.closed = true;
147     cleanUp();
148 	LogLog.debug("SocketHubAppender " + getName() + " closed");
149   }
150 
151   /***
152      Release the underlying ServerMonitor thread, and drop the connections
153      to all connected remote servers. */
154   public 
155   void cleanUp() {
156     // stop the monitor thread
157 	LogLog.debug("stopping ServerSocket");
158     serverMonitor.stopMonitor();
159     serverMonitor = null;
160     
161     // close all of the connections
162 	LogLog.debug("closing client connections");
163     while (oosList.size() != 0) {
164       ObjectOutputStream oos = (ObjectOutputStream)oosList.elementAt(0);
165       if(oos != null) {
166         try {
167         	oos.close();
168         }
169         catch(IOException e) {
170         	LogLog.error("could not close oos.", e);
171         }
172         
173         oosList.removeElementAt(0);     
174       }
175     }
176   }
177 
178   /***
179     Append an event to all of current connections. */
180   public
181   void append(LoggingEvent event) {
182 	// if no event or no open connections, exit now
183     if(event == null || oosList.size() == 0)
184       return;
185 
186     // set up location info if requested
187     if (locationInfo) {
188     	event.getLocationInformation();	
189     } 
190 
191 	// loop through the current set of open connections, appending the event to each
192     for (int streamCount = 0; streamCount < oosList.size(); streamCount++) {    	
193 
194       ObjectOutputStream oos = null;
195       try {
196         oos = (ObjectOutputStream)oosList.elementAt(streamCount);
197       }
198       catch (ArrayIndexOutOfBoundsException e) {
199         // catch this, but just don't assign a value
200         // this should not really occur as this method is
201         // the only one that can remove oos's (besides cleanUp).
202       }
203       
204       // list size changed unexpectedly? Just exit the append.
205       if (oos == null)
206         break;
207         
208       try {
209       	oos.writeObject(event);
210       	oos.flush();
211     	// Failing to reset the object output stream every now and
212     	// then creates a serious memory leak.
213     	// right now we always reset. TODO - set up frequency counter per oos?
214     	oos.reset();
215       }
216       catch(IOException e) {
217       	// there was an io exception so just drop the connection
218       	oosList.removeElementAt(streamCount);
219       	LogLog.debug("dropped connection");
220       	
221       	// decrement to keep the counter in place (for loop always increments)
222       	streamCount--;
223       }
224     }
225   }
226   
227   /***
228      The SocketHubAppender does not use a layout. Hence, this method returns
229      <code>false</code>. */
230   public
231   boolean requiresLayout() {
232     return false;
233   }
234   
235   /***
236      The <b>Port</b> option takes a positive integer representing
237      the port where the server is waiting for connections. */
238   public
239   void setPort(int _port) {
240     port = _port;
241   }
242   
243   /***
244      Returns value of the <b>Port</b> option. */
245   public
246   int getPort() {
247     return port;
248   }
249   
250   /***
251      The <b>LocationInfo</b> option takes a boolean value. If true,
252      the information sent to the remote host will include location
253      information. By default no location information is sent to the server. */
254   public
255   void setLocationInfo(boolean _locationInfo) {
256     locationInfo = _locationInfo;
257   }
258   
259   /***
260      Returns value of the <b>LocationInfo</b> option. */
261   public
262   boolean getLocationInfo() {
263     return locationInfo;
264   }
265   
266   /***
267     Start the ServerMonitor thread. */
268   private
269   void startServer() {
270     serverMonitor = new ServerMonitor(port, oosList);
271   }
272   
273   /***
274     This class is used internally to monitor a ServerSocket
275     and register new connections in a vector passed in the
276     constructor. */
277   private
278   class ServerMonitor implements Runnable {
279     private int port;
280     private Vector oosList;
281     private boolean keepRunning;
282     private Thread monitorThread;
283     
284     /***
285       Create a thread and start the monitor. */
286     public
287     ServerMonitor(int _port, Vector _oosList) {
288       port = _port;
289       oosList = _oosList;
290       keepRunning = true;
291       monitorThread = new Thread(this);
292       monitorThread.setDaemon(true);
293       monitorThread.start();
294     }
295     
296     /***
297       Stops the monitor. This method will not return until
298       the thread has finished executing. */
299     public
300     synchronized
301     void stopMonitor() {
302       if (keepRunning) {
303     	LogLog.debug("server monitor thread shutting down");
304         keepRunning = false;
305         try {
306           monitorThread.join();
307         }
308         catch (InterruptedException e) {
309           // do nothing?
310         }
311         
312         // release the thread
313         monitorThread = null;
314     	LogLog.debug("server monitor thread shut down");
315       }
316     }
317     
318     /***
319       Method that runs, monitoring the ServerSocket and adding connections as
320       they connect to the socket. */
321     public
322     void run() {
323       ServerSocket serverSocket = null;
324       try {
325         serverSocket = new ServerSocket(port);
326         serverSocket.setSoTimeout(1000);
327       }
328       catch (Exception e) {
329         LogLog.error("exception setting timeout, shutting down server socket.", e);
330         keepRunning = false;
331         return;
332       }
333 
334       try {
335     	try {
336         	serverSocket.setSoTimeout(1000);
337     	}
338     	catch (SocketException e) {
339           LogLog.error("exception setting timeout, shutting down server socket.", e);
340           return;
341     	}
342       
343     	while (keepRunning) {
344           Socket socket = null;
345           try {
346             socket = serverSocket.accept();
347           }
348           catch (InterruptedIOException e) {
349             // timeout occurred, so just loop
350           }
351           catch (SocketException e) {
352             LogLog.error("exception accepting socket, shutting down server socket.", e);
353             keepRunning = false;
354           }
355           catch (IOException e) {
356             LogLog.error("exception accepting socket.", e);
357           }
358 	        
359           // if there was a socket accepted
360           if (socket != null) {
361             try {
362               InetAddress remoteAddress = socket.getInetAddress();
363               LogLog.debug("accepting connection from " + remoteAddress.getHostName() 
364 			   + " (" + remoteAddress.getHostAddress() + ")");
365 	        	
366               // create an ObjectOutputStream
367               ObjectOutputStream oos = new ObjectOutputStream(socket.getOutputStream());
368 	            
369               // add it to the oosList.  OK since Vector is synchronized.
370               oosList.addElement(oos);
371             }
372             catch (IOException e) {
373               LogLog.error("exception creating output stream on socket.", e);
374             }
375           }
376         }
377       }
378       finally {
379     	// close the socket
380     	try {
381     		serverSocket.close();
382     	}
383     	catch (IOException e) {
384     		// do nothing with it?
385     	}
386       }
387     }
388   }
389 }
390