KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > apache > log4j > net > SocketHubAppender


1 /*
2  * Copyright 1999-2005 The Apache Software Foundation.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */

16
17 package org.apache.log4j.net;
18
19 import java.util.Vector JavaDoc;
20 import java.net.Socket JavaDoc;
21 import java.net.ServerSocket JavaDoc;
22 import java.net.SocketException JavaDoc;
23 import java.io.ObjectOutputStream JavaDoc;
24 import java.io.IOException JavaDoc;
25 import java.io.InterruptedIOException JavaDoc;
26 import java.net.InetAddress JavaDoc;
27
28 import org.apache.log4j.helpers.LogLog;
29 import org.apache.log4j.spi.LoggingEvent;
30 import org.apache.log4j.AppenderSkeleton;
31
32 /**
33   Sends {@link LoggingEvent} objects to a set of remote log servers,
34   usually a {@link SocketNode SocketNodes}.
35     
36   <p>Acts just like {@link SocketAppender} except that instead of
37   connecting to a given remote log server,
38   <code>SocketHubAppender</code> accepts connections from the remote
39   log servers as clients. It can accept more than one connection.
40   When a log event is received, the event is sent to the set of
41   currently connected remote log servers. Implemented this way it does
42   not require any update to the configuration file to send data to
43   another remote log server. The remote log server simply connects to
44   the host and port the <code>SocketHubAppender</code> is running on.
45   
46   <p>The <code>SocketHubAppender</code> does not store events such
47   that the remote side will events that arrived after the
48   establishment of its connection. Once connected, events arrive in
49   order as guaranteed by the TCP protocol.
50
51   <p>This implementation borrows heavily from the {@link
52   SocketAppender}.
53
54   <p>The SocketHubAppender has the following characteristics:
55   
56   <ul>
57   
58   <p><li>If sent to a {@link SocketNode}, logging is non-intrusive as
59   far as the log event is concerned. In other words, the event will be
60   logged with the same time stamp, {@link org.apache.log4j.NDC},
61   location info as if it were logged locally.
62   
63   <p><li><code>SocketHubAppender</code> does not use a layout. It
64   ships a serialized {@link LoggingEvent} object to the remote side.
65   
66   <p><li><code>SocketHubAppender</code> relies on the TCP
67   protocol. Consequently, if the remote side is reachable, then log
68   events will eventually arrive at remote client.
69   
70   <p><li>If no remote clients are attached, the logging requests are
71   simply dropped.
72   
73   <p><li>Logging events are automatically <em>buffered</em> by the
74   native TCP implementation. This means that if the link to remote
75   client is slow but still faster than the rate of (log) event
76   production, the application will not be affected by the slow network
77   connection. However, if the network connection is slower then the
78   rate of event production, then the local application can only
79   progress at the network rate. In particular, if the network link to
80   the the remote client is down, the application will be blocked.
81   
82   <p>On the other hand, if the network link is up, but the remote
83   client is down, the client will not be blocked when making log
84   requests but the log events will be lost due to client
85   unavailability.
86
87   <p>The single remote client case extends to multiple clients
88   connections. The rate of logging will be determined by the slowest
89   link.
90     
91   <p><li>If the JVM hosting the <code>SocketHubAppender</code> exits
92   before the <code>SocketHubAppender</code> is closed either
93   explicitly or subsequent to garbage collection, then there might
94   be untransmitted data in the pipe which might be lost. This is a
95   common problem on Windows based systems.
96   
97   <p>To avoid lost data, it is usually sufficient to {@link #close}
98   the <code>SocketHubAppender</code> either explicitly or by calling
99   the {@link org.apache.log4j.LogManager#shutdown} method before
100   exiting the application.
101   
102   </ul>
103      
104   @author Mark Womack */

105
106 public class SocketHubAppender extends AppenderSkeleton {
107
108   /**
109      The default port number of the ServerSocket will be created on. */

110   static final int DEFAULT_PORT = 4560;
111   
112   private int port = DEFAULT_PORT;
113   private Vector JavaDoc oosList = new Vector JavaDoc();
114   private ServerMonitor serverMonitor = null;
115   private boolean locationInfo = false;
116   
117   public SocketHubAppender() { }
118
119   /**
120      Connects to remote server at <code>address</code> and <code>port</code>. */

121   public
122   SocketHubAppender(int _port) {
123     port = _port;
124     startServer();
125   }
126
127   /**
128      Set up the socket server on the specified port. */

129   public
130   void activateOptions() {
131     startServer();
132   }
133
134   /**
135      Close this appender.
136      <p>This will mark the appender as closed and
137      call then {@link #cleanUp} method. */

138   synchronized
139   public
140   void close() {
141     if(closed)
142       return;
143
144     LogLog.debug("closing SocketHubAppender " + getName());
145     this.closed = true;
146     cleanUp();
147     LogLog.debug("SocketHubAppender " + getName() + " closed");
148   }
149
150   /**
151      Release the underlying ServerMonitor thread, and drop the connections
152      to all connected remote servers. */

153   public
154   void cleanUp() {
155     // stop the monitor thread
156
LogLog.debug("stopping ServerSocket");
157     serverMonitor.stopMonitor();
158     serverMonitor = null;
159     
160     // close all of the connections
161
LogLog.debug("closing client connections");
162     while (oosList.size() != 0) {
163       ObjectOutputStream JavaDoc oos = (ObjectOutputStream JavaDoc)oosList.elementAt(0);
164       if(oos != null) {
165         try {
166             oos.close();
167         }
168         catch(IOException JavaDoc e) {
169             LogLog.error("could not close oos.", e);
170         }
171         
172         oosList.removeElementAt(0);
173       }
174     }
175   }
176
177   /**
178     Append an event to all of current connections. */

179   public
180   void append(LoggingEvent event) {
181     // if no event or no open connections, exit now
182
if(event == null || oosList.size() == 0)
183       return;
184
185     // set up location info if requested
186
if (locationInfo) {
187         event.getLocationInformation();
188     }
189
190     // loop through the current set of open connections, appending the event to each
191
for (int streamCount = 0; streamCount < oosList.size(); streamCount++) {
192
193       ObjectOutputStream JavaDoc oos = null;
194       try {
195         oos = (ObjectOutputStream JavaDoc)oosList.elementAt(streamCount);
196       }
197       catch (ArrayIndexOutOfBoundsException JavaDoc e) {
198         // catch this, but just don't assign a value
199
// this should not really occur as this method is
200
// the only one that can remove oos's (besides cleanUp).
201
}
202       
203       // list size changed unexpectedly? Just exit the append.
204
if (oos == null)
205         break;
206         
207       try {
208         oos.writeObject(event);
209         oos.flush();
210         // Failing to reset the object output stream every now and
211
// then creates a serious memory leak.
212
// right now we always reset. TODO - set up frequency counter per oos?
213
oos.reset();
214       }
215       catch(IOException JavaDoc e) {
216         // there was an io exception so just drop the connection
217
oosList.removeElementAt(streamCount);
218         LogLog.debug("dropped connection");
219         
220         // decrement to keep the counter in place (for loop always increments)
221
streamCount--;
222       }
223     }
224   }
225   
226   /**
227      The SocketHubAppender does not use a layout. Hence, this method returns
228      <code>false</code>. */

229   public
230   boolean requiresLayout() {
231     return false;
232   }
233   
234   /**
235      The <b>Port</b> option takes a positive integer representing
236      the port where the server is waiting for connections. */

237   public
238   void setPort(int _port) {
239     port = _port;
240   }
241   
242   /**
243      Returns value of the <b>Port</b> option. */

244   public
245   int getPort() {
246     return port;
247   }
248   
249   /**
250      The <b>LocationInfo</b> option takes a boolean value. If true,
251      the information sent to the remote host will include location
252      information. By default no location information is sent to the server. */

253   public
254   void setLocationInfo(boolean _locationInfo) {
255     locationInfo = _locationInfo;
256   }
257   
258   /**
259      Returns value of the <b>LocationInfo</b> option. */

260   public
261   boolean getLocationInfo() {
262     return locationInfo;
263   }
264   
265   /**
266     Start the ServerMonitor thread. */

267   private
268   void startServer() {
269     serverMonitor = new ServerMonitor(port, oosList);
270   }
271   
272   /**
273     This class is used internally to monitor a ServerSocket
274     and register new connections in a vector passed in the
275     constructor. */

276   private
277   class ServerMonitor implements Runnable JavaDoc {
278     private int port;
279     private Vector JavaDoc oosList;
280     private boolean keepRunning;
281     private Thread JavaDoc monitorThread;
282     
283     /**
284       Create a thread and start the monitor. */

285     public
286     ServerMonitor(int _port, Vector JavaDoc _oosList) {
287       port = _port;
288       oosList = _oosList;
289       keepRunning = true;
290       monitorThread = new Thread JavaDoc(this);
291       monitorThread.setDaemon(true);
292       monitorThread.start();
293     }
294     
295     /**
296       Stops the monitor. This method will not return until
297       the thread has finished executing. */

298     public
299     synchronized
300     void stopMonitor() {
301       if (keepRunning) {
302         LogLog.debug("server monitor thread shutting down");
303         keepRunning = false;
304         try {
305           monitorThread.join();
306         }
307         catch (InterruptedException JavaDoc e) {
308           // do nothing?
309
}
310         
311         // release the thread
312
monitorThread = null;
313         LogLog.debug("server monitor thread shut down");
314       }
315     }
316     
317     /**
318       Method that runs, monitoring the ServerSocket and adding connections as
319       they connect to the socket. */

320     public
321     void run() {
322       ServerSocket JavaDoc serverSocket = null;
323       try {
324         serverSocket = new ServerSocket JavaDoc(port);
325         serverSocket.setSoTimeout(1000);
326       }
327       catch (Exception JavaDoc e) {
328         LogLog.error("exception setting timeout, shutting down server socket.", e);
329         keepRunning = false;
330         return;
331       }
332
333       try {
334         try {
335             serverSocket.setSoTimeout(1000);
336         }
337         catch (SocketException JavaDoc e) {
338           LogLog.error("exception setting timeout, shutting down server socket.", e);
339           return;
340         }
341       
342         while (keepRunning) {
343           Socket JavaDoc socket = null;
344           try {
345             socket = serverSocket.accept();
346           }
347           catch (InterruptedIOException JavaDoc e) {
348             // timeout occurred, so just loop
349
}
350           catch (SocketException JavaDoc e) {
351             LogLog.error("exception accepting socket, shutting down server socket.", e);
352             keepRunning = false;
353           }
354           catch (IOException JavaDoc e) {
355             LogLog.error("exception accepting socket.", e);
356           }
357             
358           // if there was a socket accepted
359
if (socket != null) {
360             try {
361               InetAddress JavaDoc remoteAddress = socket.getInetAddress();
362               LogLog.debug("accepting connection from " + remoteAddress.getHostName()
363                + " (" + remoteAddress.getHostAddress() + ")");
364                 
365               // create an ObjectOutputStream
366
ObjectOutputStream JavaDoc oos = new ObjectOutputStream JavaDoc(socket.getOutputStream());
367                 
368               // add it to the oosList. OK since Vector is synchronized.
369
oosList.addElement(oos);
370             }
371             catch (IOException JavaDoc e) {
372               LogLog.error("exception creating output stream on socket.", e);
373             }
374           }
375         }
376       }
377       finally {
378         // close the socket
379
try {
380             serverSocket.close();
381         }
382         catch (IOException JavaDoc e) {
383             // do nothing with it?
384
}
385       }
386     }
387   }
388 }
389
390
Popular Tags