JAL-3026 major update 1/2
[jalview.git] / srcjar_unused / org / apache / log4j / net / SocketAppender.java
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 // Contributors: Dan MacDonald <dan@redknee.com>
19
20 package org.apache.log4j.net;
21
22 import java.io.IOException;
23 import java.io.ObjectOutputStream;
24 import java.io.InterruptedIOException;
25 import java.net.InetAddress;
26 import java.net.Socket;
27
28 import org.apache.log4j.AppenderSkeleton;
29 import org.apache.log4j.helpers.LogLog;
30 import org.apache.log4j.spi.ErrorCode;
31 import org.apache.log4j.spi.LoggingEvent;
32
33 /**
34     Sends {@link LoggingEvent} objects to a remote a log server,
35     usually a {@link SocketNode}.
36
37     <p>The SocketAppender has the following properties:
38
39     <ul>
40
41       <p><li>If sent to a {@link SocketNode}, remote logging is
42       non-intrusive as far as the log event is concerned. In other
43       words, the event will be logged with the same time stamp, {@link
44       org.apache.log4j.NDC}, location info as if it were logged locally by
45       the client.
46
47       <p><li>SocketAppenders do not use a layout. They ship a
48       serialized {@link LoggingEvent} object to the server side.
49
50       <p><li>Remote logging uses the TCP protocol. Consequently, if
51       the server is reachable, then log events will eventually arrive
52       at the server.
53
54       <p><li>If the remote server is down, the logging requests are
55       simply dropped. However, if and when the server comes back up,
56       then event transmission is resumed transparently. This
57       transparent reconneciton is performed by a <em>connector</em>
58       thread which periodically attempts to connect to the server.
59
60       <p><li>Logging events are automatically <em>buffered</em> by the
61       native TCP implementation. This means that if the link to server
62       is slow but still faster than the rate of (log) event production
63       by the client, the client will not be affected by the slow
64       network connection. However, if the network connection is slower
65       then the rate of event production, then the client can only
66       progress at the network rate. In particular, if the network link
67       to the the server is down, the client will be blocked.
68
69       <p>On the other hand, if the network link is up, but the server
70       is down, the client will not be blocked when making log requests
71       but the log events will be lost due to server unavailability.
72
73       <p><li>Even if a <code>SocketAppender</code> is no longer
74       attached to any category, it will not be garbage collected in
75       the presence of a connector thread. A connector thread exists
76       only if the connection to the server is down. To avoid this
77       garbage collection problem, you should {@link #close} the the
78       <code>SocketAppender</code> explicitly. See also next item.
79
80       <p>Long lived applications which create/destroy many
81       <code>SocketAppender</code> instances should be aware of this
82       garbage collection problem. Most other applications can safely
83       ignore it.
84
85       <p><li>If the JVM hosting the <code>SocketAppender</code> exits
86       before the <code>SocketAppender</code> is closed either
87       explicitly or subsequent to garbage collection, then there might
88       be untransmitted data in the pipe which might be lost. This is a
89       common problem on Windows based systems.
90
91       <p>To avoid lost data, it is usually sufficient to {@link
92       #close} the <code>SocketAppender</code> either explicitly or by
93       calling the {@link org.apache.log4j.LogManager#shutdown} method
94       before exiting the application.
95
96
97      </ul>
98
99     @author  Ceki G&uuml;lc&uuml;
100     @since 0.8.4 */
101
102 public class SocketAppender extends AppenderSkeleton {
103
104   /**
105      The default port number of remote logging server (4560).
106      @since 1.2.15
107   */
108   static public final int DEFAULT_PORT                 = 4560;
109
110   /**
111      The default reconnection delay (30000 milliseconds or 30 seconds).
112   */
113   static final int DEFAULT_RECONNECTION_DELAY   = 30000;
114
115   /**
116      We remember host name as String in addition to the resolved
117      InetAddress so that it can be returned via getOption().
118   */
119   String remoteHost;
120
121   /**
122    * The MulticastDNS zone advertised by a SocketAppender
123    */
124   public static final String ZONE = "_log4j_obj_tcpconnect_appender.local.";
125
126   InetAddress address;
127   int port = DEFAULT_PORT;
128   ObjectOutputStream oos;
129   int reconnectionDelay = DEFAULT_RECONNECTION_DELAY;
130   boolean locationInfo = false;
131   private String application;
132
133   private Connector connector;
134
135   int counter = 0;
136
137   // reset the ObjectOutputStream every 70 calls
138   //private static final int RESET_FREQUENCY = 70;
139   private static final int RESET_FREQUENCY = 1;
140   private boolean advertiseViaMulticastDNS;
141   private ZeroConfSupport zeroConf;
142
143   public SocketAppender() {
144   }
145
146   /**
147      Connects to remote server at <code>address</code> and <code>port</code>.
148   */
149   public SocketAppender(InetAddress address, int port) {
150     this.address = address;
151     this.remoteHost = address.getHostName();
152     this.port = port;
153     connect(address, port);
154   }
155
156   /**
157      Connects to remote server at <code>host</code> and <code>port</code>.
158   */
159   public SocketAppender(String host, int port) {
160     this.port = port;
161     this.address = getAddressByName(host);
162     this.remoteHost = host;
163     connect(address, port);
164   }
165
166   /**
167      Connect to the specified <b>RemoteHost</b> and <b>Port</b>.
168   */
169   public void activateOptions() {
170     if (advertiseViaMulticastDNS) {
171       zeroConf = new ZeroConfSupport(ZONE, port, getName());
172       zeroConf.advertise();
173     }
174     connect(address, port);
175   }
176
177   /**
178    * Close this appender.  
179    *
180    * <p>This will mark the appender as closed and call then {@link
181    * #cleanUp} method.
182    * */
183   synchronized public void close() {
184     if(closed) {
185         return;
186     }
187
188     this.closed = true;
189     if (advertiseViaMulticastDNS) {
190       zeroConf.unadvertise();
191     }
192
193     cleanUp();
194   }
195
196   /**
197    * Drop the connection to the remote host and release the underlying
198    * connector thread if it has been created 
199    * */
200   public void cleanUp() {
201     if(oos != null) {
202       try {
203         oos.close();
204       } catch(IOException e) {
205           if (e instanceof InterruptedIOException) {
206               Thread.currentThread().interrupt();
207           }
208               LogLog.error("Could not close oos.", e);
209       }
210       oos = null;
211     }
212     if(connector != null) {
213       //LogLog.debug("Interrupting the connector.");
214       connector.interrupted = true;
215       connector = null;  // allow gc
216     }
217   }
218
219   void connect(InetAddress address, int port) {
220     if(this.address == null) {
221         return;
222     }
223     try {
224       // First, close the previous connection if any.
225       cleanUp();
226       oos = new ObjectOutputStream(new Socket(address, port).getOutputStream());
227     } catch(IOException e) {
228       if (e instanceof InterruptedIOException) {
229           Thread.currentThread().interrupt();
230       }
231       String msg = "Could not connect to remote log4j server at ["
232         +address.getHostName()+"].";
233       if(reconnectionDelay > 0) {
234         msg += " We will try again later.";
235         fireConnector(); // fire the connector thread
236       } else {
237           msg += " We are not retrying.";
238           errorHandler.error(msg, e, ErrorCode.GENERIC_FAILURE);
239       } 
240       LogLog.error(msg);
241     }
242   }
243
244
245   public void append(LoggingEvent event) {
246     if(event == null) {
247         return;
248     }
249
250     if(address==null) {
251       errorHandler.error("No remote host is set for SocketAppender named \""+
252                         this.name+"\".");
253       return;
254     }
255
256     if(oos != null) {
257       try {
258          
259         if(locationInfo) {
260            event.getLocationInformation();
261         }
262     if (application != null) {
263         event.setProperty("application", application);
264     }
265     event.getNDC();
266     event.getThreadName();
267     event.getMDCCopy();
268     event.getRenderedMessage();
269     event.getThrowableStrRep();
270     
271         oos.writeObject(event);
272         //LogLog.debug("=========Flushing.");
273         oos.flush();
274         if(++counter >= RESET_FREQUENCY) {
275           counter = 0;
276           // Failing to reset the object output stream every now and
277           // then creates a serious memory leak.
278           //System.err.println("Doing oos.reset()");
279           oos.reset();
280         }
281       } catch(IOException e) {
282           if (e instanceof InterruptedIOException) {
283               Thread.currentThread().interrupt();
284           }
285               oos = null;
286               LogLog.warn("Detected problem with connection: "+e);
287               if(reconnectionDelay > 0) {
288                  fireConnector();
289               } else {
290                  errorHandler.error("Detected problem with connection, not reconnecting.", e,
291                        ErrorCode.GENERIC_FAILURE);
292               }
293       }
294     }
295   }
296
297   public void setAdvertiseViaMulticastDNS(boolean advertiseViaMulticastDNS) {
298     this.advertiseViaMulticastDNS = advertiseViaMulticastDNS;
299   }
300
301   public boolean isAdvertiseViaMulticastDNS() {
302     return advertiseViaMulticastDNS;
303   }
304
305   void fireConnector() {
306     if(connector == null) {
307       LogLog.debug("Starting a new connector thread.");
308       connector = new Connector();
309       connector.setDaemon(true);
310       connector.setPriority(Thread.MIN_PRIORITY);
311       connector.start();
312     }
313   }
314
315   static
316   InetAddress getAddressByName(String host) {
317     try {
318       return InetAddress.getByName(host);
319     } catch(Exception e) {
320       if (e instanceof InterruptedIOException || e instanceof InterruptedException) {
321           Thread.currentThread().interrupt();
322       }
323       LogLog.error("Could not find address of ["+host+"].", e);
324       return null;
325     }
326   }
327
328   /**
329    * The SocketAppender does not use a layout. Hence, this method
330    * returns <code>false</code>.  
331    * */
332   public boolean requiresLayout() {
333     return false;
334   }
335
336   /**
337    * The <b>RemoteHost</b> option takes a string value which should be
338    * the host name of the server where a {@link SocketNode} is
339    * running.
340    * */
341   public void setRemoteHost(String host) {
342     address = getAddressByName(host);
343     remoteHost = host;
344   }
345
346   /**
347      Returns value of the <b>RemoteHost</b> option.
348    */
349   public String getRemoteHost() {
350     return remoteHost;
351   }
352
353   /**
354      The <b>Port</b> option takes a positive integer representing
355      the port where the server is waiting for connections.
356    */
357   public void setPort(int port) {
358     this.port = port;
359   }
360
361   /**
362      Returns value of the <b>Port</b> option.
363    */
364   public int getPort() {
365     return port;
366   }
367
368   /**
369      The <b>LocationInfo</b> option takes a boolean value. If true,
370      the information sent to the remote host will include location
371      information. By default no location information is sent to the server.
372    */
373   public void setLocationInfo(boolean locationInfo) {
374     this.locationInfo = locationInfo;
375   }
376
377   /**
378      Returns value of the <b>LocationInfo</b> option.
379    */
380   public boolean getLocationInfo() {
381     return locationInfo;
382   }
383
384   /**
385    * The <b>App</b> option takes a string value which should be the name of the 
386    * application getting logged.
387    * If property was already set (via system property), don't set here.
388    * @since 1.2.15
389    */
390   public void setApplication(String lapp) {
391     this.application = lapp;
392   }
393
394   /**
395    *  Returns value of the <b>Application</b> option.
396    * @since 1.2.15
397    */
398   public String getApplication() {
399     return application;
400   }
401
402   /**
403      The <b>ReconnectionDelay</b> option takes a positive integer
404      representing the number of milliseconds to wait between each
405      failed connection attempt to the server. The default value of
406      this option is 30000 which corresponds to 30 seconds.
407
408      <p>Setting this option to zero turns off reconnection
409      capability.
410    */
411   public void setReconnectionDelay(int delay) {
412     this.reconnectionDelay = delay;
413   }
414
415   /**
416      Returns value of the <b>ReconnectionDelay</b> option.
417    */
418   public int getReconnectionDelay() {
419     return reconnectionDelay;
420   }
421
422   /**
423      The Connector will reconnect when the server becomes available
424      again.  It does this by attempting to open a new connection every
425      <code>reconnectionDelay</code> milliseconds.
426
427      <p>It stops trying whenever a connection is established. It will
428      restart to try reconnect to the server when previously open
429      connection is droppped.
430
431      @author  Ceki G&uuml;lc&uuml;
432      @since 0.8.4
433   */
434   class Connector extends Thread {
435
436     boolean interrupted = false;
437
438     public
439     void run() {
440       Socket socket;
441       while(!interrupted) {
442         try {
443           sleep(reconnectionDelay);
444           LogLog.debug("Attempting connection to "+address.getHostName());
445           socket = new Socket(address, port);
446           synchronized(this) {
447             oos = new ObjectOutputStream(socket.getOutputStream());
448             connector = null;
449             LogLog.debug("Connection established. Exiting connector thread.");
450             break;
451           }
452         } catch(InterruptedException e) {
453           LogLog.debug("Connector interrupted. Leaving loop.");
454           return;
455         } catch(java.net.ConnectException e) {
456           LogLog.debug("Remote host "+address.getHostName()
457                        +" refused connection.");
458         } catch(IOException e) {
459         if (e instanceof InterruptedIOException) {
460             Thread.currentThread().interrupt();
461         }
462             LogLog.debug("Could not connect to " + address.getHostName()+
463                        ". Exception is " + e);
464         }
465       }
466       //LogLog.debug("Exiting Connector.run() method.");
467     }
468
469     /**
470        public
471        void finalize() {
472        LogLog.debug("Connector finalize() has been called.");
473        }
474     */
475   }
476
477 }