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