KickJava   Java API By Example, From Geeks To Geeks.

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


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 // Contributors: Dan MacDonald <dan@redknee.com>
18

19 package org.apache.log4j.net;
20
21 import java.net.InetAddress JavaDoc;
22 import java.net.Socket JavaDoc;
23 import java.io.IOException JavaDoc;
24 import java.io.ObjectOutputStream JavaDoc;
25 import java.io.ObjectOutputStream JavaDoc;
26
27 import org.apache.log4j.helpers.LogLog;
28 import org.apache.log4j.spi.LoggingEvent;
29 import org.apache.log4j.AppenderSkeleton;
30
31 /**
32     Sends {@link LoggingEvent} objects to a remote a log server,
33     usually a {@link SocketNode}.
34
35     <p>The SocketAppender has the following properties:
36
37     <ul>
38
39       <p><li>If sent to a {@link SocketNode}, remote logging is
40       non-intrusive as far as the log event is concerned. In other
41       words, the event will be logged with the same time stamp, {@link
42       org.apache.log4j.NDC}, location info as if it were logged locally by
43       the client.
44
45       <p><li>SocketAppenders do not use a layout. They ship a
46       serialized {@link LoggingEvent} object to the server side.
47
48       <p><li>Remote logging uses the TCP protocol. Consequently, if
49       the server is reachable, then log events will eventually arrive
50       at the server.
51
52       <p><li>If the remote server is down, the logging requests are
53       simply dropped. However, if and when the server comes back up,
54       then event transmission is resumed transparently. This
55       transparent reconneciton is performed by a <em>connector</em>
56       thread which periodically attempts to connect to the server.
57
58       <p><li>Logging events are automatically <em>buffered</em> by the
59       native TCP implementation. This means that if the link to server
60       is slow but still faster than the rate of (log) event production
61       by the client, the client will not be affected by the slow
62       network connection. However, if the network connection is slower
63       then the rate of event production, then the client can only
64       progress at the network rate. In particular, if the network link
65       to the the server is down, the client will be blocked.
66
67       <p>On the other hand, if the network link is up, but the server
68       is down, the client will not be blocked when making log requests
69       but the log events will be lost due to server unavailability.
70
71       <p><li>Even if a <code>SocketAppender</code> is no longer
72       attached to any category, it will not be garbage collected in
73       the presence of a connector thread. A connector thread exists
74       only if the connection to the server is down. To avoid this
75       garbage collection problem, you should {@link #close} the the
76       <code>SocketAppender</code> explicitly. See also next item.
77
78       <p>Long lived applications which create/destroy many
79       <code>SocketAppender</code> instances should be aware of this
80       garbage collection problem. Most other applications can safely
81       ignore it.
82
83       <p><li>If the JVM hosting the <code>SocketAppender</code> exits
84       before the <code>SocketAppender</code> is closed either
85       explicitly or subsequent to garbage collection, then there might
86       be untransmitted data in the pipe which might be lost. This is a
87       common problem on Windows based systems.
88
89       <p>To avoid lost data, it is usually sufficient to {@link
90       #close} the <code>SocketAppender</code> either explicitly or by
91       calling the {@link org.apache.log4j.LogManager#shutdown} method
92       before exiting the application.
93
94
95      </ul>
96
97     @author Ceki G&uuml;lc&uuml;
98     @since 0.8.4 */

99
100 public class SocketAppender extends AppenderSkeleton {
101
102   /**
103      The default port number of remote logging server (4560).
104   */

105   static final int DEFAULT_PORT = 4560;
106
107   /**
108      The default reconnection delay (30000 milliseconds or 30 seconds).
109   */

110   static final int DEFAULT_RECONNECTION_DELAY = 30000;
111
112   /**
113      We remember host name as String in addition to the resolved
114      InetAddress so that it can be returned via getOption().
115   */

116   String JavaDoc remoteHost;
117
118   InetAddress JavaDoc address;
119   int port = DEFAULT_PORT;
120   ObjectOutputStream JavaDoc oos;
121   int reconnectionDelay = DEFAULT_RECONNECTION_DELAY;
122   boolean locationInfo = false;
123
124   private Connector connector;
125
126   int counter = 0;
127
128
129   // reset the ObjectOutputStream every 70 calls
130
//private static final int RESET_FREQUENCY = 70;
131
private static final int RESET_FREQUENCY = 1;
132
133   public SocketAppender() {
134   }
135
136   /**
137      Connects to remote server at <code>address</code> and <code>port</code>.
138   */

139   public SocketAppender(InetAddress JavaDoc address, int port) {
140     this.address = address;
141     this.remoteHost = address.getHostName();
142     this.port = port;
143     connect(address, port);
144   }
145
146   /**
147      Connects to remote server at <code>host</code> and <code>port</code>.
148   */

149   public SocketAppender(String JavaDoc host, int port) {
150     this.port = port;
151     this.address = getAddressByName(host);
152     this.remoteHost = host;
153     connect(address, port);
154   }
155
156   /**
157      Connect to the specified <b>RemoteHost</b> and <b>Port</b>.
158   */

159   public void activateOptions() {
160     connect(address, port);
161   }
162
163   /**
164    * Close this appender.
165    *
166    * <p>This will mark the appender as closed and call then {@link
167    * #cleanUp} method.
168    * */

169   synchronized public void close() {
170     if(closed)
171       return;
172
173     this.closed = true;
174     cleanUp();
175   }
176
177   /**
178    * Drop the connection to the remote host and release the underlying
179    * connector thread if it has been created
180    * */

181   public void cleanUp() {
182     if(oos != null) {
183       try {
184     oos.close();
185       } catch(IOException JavaDoc e) {
186     LogLog.error("Could not close oos.", e);
187       }
188       oos = null;
189     }
190     if(connector != null) {
191       //LogLog.debug("Interrupting the connector.");
192
connector.interrupted = true;
193       connector = null; // allow gc
194
}
195   }
196
197   void connect(InetAddress JavaDoc address, int port) {
198     if(this.address == null)
199       return;
200     try {
201       // First, close the previous connection if any.
202
cleanUp();
203       oos = new ObjectOutputStream JavaDoc(new Socket JavaDoc(address, port).getOutputStream());
204     } catch(IOException JavaDoc e) {
205
206       String JavaDoc msg = "Could not connect to remote log4j server at ["
207     +address.getHostName()+"].";
208       if(reconnectionDelay > 0) {
209         msg += " We will try again later.";
210     fireConnector(); // fire the connector thread
211
}
212       LogLog.error(msg, e);
213     }
214   }
215
216
217   public void append(LoggingEvent event) {
218     if(event == null)
219       return;
220
221     if(address==null) {
222       errorHandler.error("No remote host is set for SocketAppender named \""+
223             this.name+"\".");
224       return;
225     }
226
227     if(oos != null) {
228       try {
229     if(locationInfo) {
230        event.getLocationInformation();
231     }
232     oos.writeObject(event);
233     //LogLog.debug("=========Flushing.");
234
oos.flush();
235     if(++counter >= RESET_FREQUENCY) {
236       counter = 0;
237       // Failing to reset the object output stream every now and
238
// then creates a serious memory leak.
239
//System.err.println("Doing oos.reset()");
240
oos.reset();
241     }
242       } catch(IOException JavaDoc e) {
243     oos = null;
244     LogLog.warn("Detected problem with connection: "+e);
245     if(reconnectionDelay > 0) {
246       fireConnector();
247     }
248       }
249     }
250   }
251
252   void fireConnector() {
253     if(connector == null) {
254       LogLog.debug("Starting a new connector thread.");
255       connector = new Connector();
256       connector.setDaemon(true);
257       connector.setPriority(Thread.MIN_PRIORITY);
258       connector.start();
259     }
260   }
261
262   static
263   InetAddress JavaDoc getAddressByName(String JavaDoc host) {
264     try {
265       return InetAddress.getByName(host);
266     } catch(Exception JavaDoc e) {
267       LogLog.error("Could not find address of ["+host+"].", e);
268       return null;
269     }
270   }
271
272   /**
273    * The SocketAppender does not use a layout. Hence, this method
274    * returns <code>false</code>.
275    * */

276   public boolean requiresLayout() {
277     return false;
278   }
279
280   /**
281    * The <b>RemoteHost</b> option takes a string value which should be
282    * the host name of the server where a {@link SocketNode} is
283    * running.
284    * */

285   public void setRemoteHost(String JavaDoc host) {
286     address = getAddressByName(host);
287     remoteHost = host;
288   }
289
290   /**
291      Returns value of the <b>RemoteHost</b> option.
292    */

293   public String JavaDoc getRemoteHost() {
294     return remoteHost;
295   }
296
297   /**
298      The <b>Port</b> option takes a positive integer representing
299      the port where the server is waiting for connections.
300    */

301   public void setPort(int port) {
302     this.port = port;
303   }
304
305   /**
306      Returns value of the <b>Port</b> option.
307    */

308   public int getPort() {
309     return port;
310   }
311
312   /**
313      The <b>LocationInfo</b> option takes a boolean value. If true,
314      the information sent to the remote host will include location
315      information. By default no location information is sent to the server.
316    */

317   public void setLocationInfo(boolean locationInfo) {
318     this.locationInfo = locationInfo;
319   }
320
321   /**
322      Returns value of the <b>LocationInfo</b> option.
323    */

324   public boolean getLocationInfo() {
325     return locationInfo;
326   }
327
328   /**
329      The <b>ReconnectionDelay</b> option takes a positive integer
330      representing the number of milliseconds to wait between each
331      failed connection attempt to the server. The default value of
332      this option is 30000 which corresponds to 30 seconds.
333
334      <p>Setting this option to zero turns off reconnection
335      capability.
336    */

337   public void setReconnectionDelay(int delay) {
338     this.reconnectionDelay = delay;
339   }
340
341   /**
342      Returns value of the <b>ReconnectionDelay</b> option.
343    */

344   public int getReconnectionDelay() {
345     return reconnectionDelay;
346   }
347
348   /**
349      The Connector will reconnect when the server becomes available
350      again. It does this by attempting to open a new connection every
351      <code>reconnectionDelay</code> milliseconds.
352
353      <p>It stops trying whenever a connection is established. It will
354      restart to try reconnect to the server when previpously open
355      connection is droppped.
356
357      @author Ceki G&uuml;lc&uuml;
358      @since 0.8.4
359   */

360   class Connector extends Thread JavaDoc {
361
362     boolean interrupted = false;
363
364     public
365     void run() {
366       Socket JavaDoc socket;
367       while(!interrupted) {
368     try {
369       sleep(reconnectionDelay);
370       LogLog.debug("Attempting connection to "+address.getHostName());
371       socket = new Socket JavaDoc(address, port);
372       synchronized(this) {
373         oos = new ObjectOutputStream JavaDoc(socket.getOutputStream());
374         connector = null;
375         LogLog.debug("Connection established. Exiting connector thread.");
376         break;
377       }
378     } catch(InterruptedException JavaDoc e) {
379       LogLog.debug("Connector interrupted. Leaving loop.");
380       return;
381     } catch(java.net.ConnectException JavaDoc e) {
382       LogLog.debug("Remote host "+address.getHostName()
383                +" refused connection.");
384     } catch(IOException JavaDoc e) {
385       LogLog.debug("Could not connect to " + address.getHostName()+
386                ". Exception is " + e);
387     }
388       }
389       //LogLog.debug("Exiting Connector.run() method.");
390
}
391
392     /**
393        public
394        void finalize() {
395        LogLog.debug("Connector finalize() has been called.");
396        }
397     */

398   }
399
400 }
401
Popular Tags