PageRenderTime 20ms CodeModel.GetById 5ms app.highlight 12ms RepoModel.GetById 0ms app.codeStats 0ms

/apache-log4j-1.2.17/src/main/java/org/apache/log4j/net/SocketAppender.java

#
Java | 474 lines | 231 code | 49 blank | 194 comment | 30 complexity | c83e2fbc9e5d4851e2084bb6816c1a58 MD5 | raw file
Possible License(s): Apache-2.0
  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
 20package org.apache.log4j.net;
 21
 22import java.io.IOException;
 23import java.io.ObjectOutputStream;
 24import java.io.InterruptedIOException;
 25import java.net.InetAddress;
 26import java.net.Socket;
 27
 28import org.apache.log4j.AppenderSkeleton;
 29import org.apache.log4j.helpers.LogLog;
 30import org.apache.log4j.spi.ErrorCode;
 31import 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
102public 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    this.closed = true;
188    if (advertiseViaMulticastDNS) {
189      zeroConf.unadvertise();
190    }
191
192    cleanUp();
193  }
194
195  /**
196   * Drop the connection to the remote host and release the underlying
197   * connector thread if it has been created 
198   * */
199  public void cleanUp() {
200    if(oos != null) {
201      try {
202	oos.close();
203      } catch(IOException e) {
204          if (e instanceof InterruptedIOException) {
205              Thread.currentThread().interrupt();
206          }
207	      LogLog.error("Could not close oos.", e);
208      }
209      oos = null;
210    }
211    if(connector != null) {
212      //LogLog.debug("Interrupting the connector.");
213      connector.interrupted = true;
214      connector = null;  // allow gc
215    }
216  }
217
218  void connect(InetAddress address, int port) {
219    if(this.address == null)
220      return;
221    try {
222      // First, close the previous connection if any.
223      cleanUp();
224      oos = new ObjectOutputStream(new Socket(address, port).getOutputStream());
225    } catch(IOException e) {
226      if (e instanceof InterruptedIOException) {
227          Thread.currentThread().interrupt();
228      }
229      String msg = "Could not connect to remote log4j server at ["
230	+address.getHostName()+"].";
231      if(reconnectionDelay > 0) {
232        msg += " We will try again later.";
233	fireConnector(); // fire the connector thread
234      } else {
235          msg += " We are not retrying.";
236          errorHandler.error(msg, e, ErrorCode.GENERIC_FAILURE);
237      } 
238      LogLog.error(msg);
239    }
240  }
241
242
243  public void append(LoggingEvent event) {
244    if(event == null)
245      return;
246
247    if(address==null) {
248      errorHandler.error("No remote host is set for SocketAppender named \""+
249			this.name+"\".");
250      return;
251    }
252
253    if(oos != null) {
254      try {
255    	 
256	if(locationInfo) {
257	   event.getLocationInformation();
258	}
259    if (application != null) {
260        event.setProperty("application", application);
261    }
262    event.getNDC();
263    event.getThreadName();
264    event.getMDCCopy();
265    event.getRenderedMessage();
266    event.getThrowableStrRep();
267    
268	oos.writeObject(event);
269	//LogLog.debug("=========Flushing.");
270	oos.flush();
271	if(++counter >= RESET_FREQUENCY) {
272	  counter = 0;
273	  // Failing to reset the object output stream every now and
274	  // then creates a serious memory leak.
275	  //System.err.println("Doing oos.reset()");
276	  oos.reset();
277	}
278      } catch(IOException e) {
279          if (e instanceof InterruptedIOException) {
280              Thread.currentThread().interrupt();
281          }
282	      oos = null;
283	      LogLog.warn("Detected problem with connection: "+e);
284	      if(reconnectionDelay > 0) {
285	         fireConnector();
286	      } else {
287	         errorHandler.error("Detected problem with connection, not reconnecting.", e,
288	               ErrorCode.GENERIC_FAILURE);
289	      }
290      }
291    }
292  }
293
294  public void setAdvertiseViaMulticastDNS(boolean advertiseViaMulticastDNS) {
295    this.advertiseViaMulticastDNS = advertiseViaMulticastDNS;
296  }
297
298  public boolean isAdvertiseViaMulticastDNS() {
299    return advertiseViaMulticastDNS;
300  }
301
302  void fireConnector() {
303    if(connector == null) {
304      LogLog.debug("Starting a new connector thread.");
305      connector = new Connector();
306      connector.setDaemon(true);
307      connector.setPriority(Thread.MIN_PRIORITY);
308      connector.start();
309    }
310  }
311
312  static
313  InetAddress getAddressByName(String host) {
314    try {
315      return InetAddress.getByName(host);
316    } catch(Exception e) {
317      if (e instanceof InterruptedIOException || e instanceof InterruptedException) {
318          Thread.currentThread().interrupt();
319      }
320      LogLog.error("Could not find address of ["+host+"].", e);
321      return null;
322    }
323  }
324
325  /**
326   * The SocketAppender does not use a layout. Hence, this method
327   * returns <code>false</code>.  
328   * */
329  public boolean requiresLayout() {
330    return false;
331  }
332
333  /**
334   * The <b>RemoteHost</b> option takes a string value which should be
335   * the host name of the server where a {@link SocketNode} is
336   * running.
337   * */
338  public void setRemoteHost(String host) {
339    address = getAddressByName(host);
340    remoteHost = host;
341  }
342
343  /**
344     Returns value of the <b>RemoteHost</b> option.
345   */
346  public String getRemoteHost() {
347    return remoteHost;
348  }
349
350  /**
351     The <b>Port</b> option takes a positive integer representing
352     the port where the server is waiting for connections.
353   */
354  public void setPort(int port) {
355    this.port = port;
356  }
357
358  /**
359     Returns value of the <b>Port</b> option.
360   */
361  public int getPort() {
362    return port;
363  }
364
365  /**
366     The <b>LocationInfo</b> option takes a boolean value. If true,
367     the information sent to the remote host will include location
368     information. By default no location information is sent to the server.
369   */
370  public void setLocationInfo(boolean locationInfo) {
371    this.locationInfo = locationInfo;
372  }
373
374  /**
375     Returns value of the <b>LocationInfo</b> option.
376   */
377  public boolean getLocationInfo() {
378    return locationInfo;
379  }
380
381  /**
382   * The <b>App</b> option takes a string value which should be the name of the 
383   * application getting logged.
384   * If property was already set (via system property), don't set here.
385   * @since 1.2.15
386   */
387  public void setApplication(String lapp) {
388    this.application = lapp;
389  }
390
391  /**
392   *  Returns value of the <b>Application</b> option.
393   * @since 1.2.15
394   */
395  public String getApplication() {
396    return application;
397  }
398
399  /**
400     The <b>ReconnectionDelay</b> option takes a positive integer
401     representing the number of milliseconds to wait between each
402     failed connection attempt to the server. The default value of
403     this option is 30000 which corresponds to 30 seconds.
404
405     <p>Setting this option to zero turns off reconnection
406     capability.
407   */
408  public void setReconnectionDelay(int delay) {
409    this.reconnectionDelay = delay;
410  }
411
412  /**
413     Returns value of the <b>ReconnectionDelay</b> option.
414   */
415  public int getReconnectionDelay() {
416    return reconnectionDelay;
417  }
418
419  /**
420     The Connector will reconnect when the server becomes available
421     again.  It does this by attempting to open a new connection every
422     <code>reconnectionDelay</code> milliseconds.
423
424     <p>It stops trying whenever a connection is established. It will
425     restart to try reconnect to the server when previously open
426     connection is droppped.
427
428     @author  Ceki G&uuml;lc&uuml;
429     @since 0.8.4
430  */
431  class Connector extends Thread {
432
433    boolean interrupted = false;
434
435    public
436    void run() {
437      Socket socket;
438      while(!interrupted) {
439	try {
440	  sleep(reconnectionDelay);
441	  LogLog.debug("Attempting connection to "+address.getHostName());
442	  socket = new Socket(address, port);
443	  synchronized(this) {
444	    oos = new ObjectOutputStream(socket.getOutputStream());
445	    connector = null;
446	    LogLog.debug("Connection established. Exiting connector thread.");
447	    break;
448	  }
449	} catch(InterruptedException e) {
450	  LogLog.debug("Connector interrupted. Leaving loop.");
451	  return;
452	} catch(java.net.ConnectException e) {
453	  LogLog.debug("Remote host "+address.getHostName()
454		       +" refused connection.");
455	} catch(IOException e) {
456        if (e instanceof InterruptedIOException) {
457            Thread.currentThread().interrupt();
458        }
459	    LogLog.debug("Could not connect to " + address.getHostName()+
460		       ". Exception is " + e);
461	}
462      }
463      //LogLog.debug("Exiting Connector.run() method.");
464    }
465
466    /**
467       public
468       void finalize() {
469       LogLog.debug("Connector finalize() has been called.");
470       }
471    */
472  }
473
474}