JAL-3026 srcjar files for VARNA and log4j
[jalview.git] / srcjar / org / apache / log4j / net / SocketAppender.java
diff --git a/srcjar/org/apache/log4j/net/SocketAppender.java b/srcjar/org/apache/log4j/net/SocketAppender.java
new file mode 100644 (file)
index 0000000..88f0049
--- /dev/null
@@ -0,0 +1,477 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+// Contributors: Dan MacDonald <dan@redknee.com>
+
+package org.apache.log4j.net;
+
+import java.io.IOException;
+import java.io.ObjectOutputStream;
+import java.io.InterruptedIOException;
+import java.net.InetAddress;
+import java.net.Socket;
+
+import org.apache.log4j.AppenderSkeleton;
+import org.apache.log4j.helpers.LogLog;
+import org.apache.log4j.spi.ErrorCode;
+import org.apache.log4j.spi.LoggingEvent;
+
+/**
+    Sends {@link LoggingEvent} objects to a remote a log server,
+    usually a {@link SocketNode}.
+
+    <p>The SocketAppender has the following properties:
+
+    <ul>
+
+      <p><li>If sent to a {@link SocketNode}, remote logging is
+      non-intrusive as far as the log event is concerned. In other
+      words, the event will be logged with the same time stamp, {@link
+      org.apache.log4j.NDC}, location info as if it were logged locally by
+      the client.
+
+      <p><li>SocketAppenders do not use a layout. They ship a
+      serialized {@link LoggingEvent} object to the server side.
+
+      <p><li>Remote logging uses the TCP protocol. Consequently, if
+      the server is reachable, then log events will eventually arrive
+      at the server.
+
+      <p><li>If the remote server is down, the logging requests are
+      simply dropped. However, if and when the server comes back up,
+      then event transmission is resumed transparently. This
+      transparent reconneciton is performed by a <em>connector</em>
+      thread which periodically attempts to connect to the server.
+
+      <p><li>Logging events are automatically <em>buffered</em> by the
+      native TCP implementation. This means that if the link to server
+      is slow but still faster than the rate of (log) event production
+      by the client, the client will not be affected by the slow
+      network connection. However, if the network connection is slower
+      then the rate of event production, then the client can only
+      progress at the network rate. In particular, if the network link
+      to the the server is down, the client will be blocked.
+
+      <p>On the other hand, if the network link is up, but the server
+      is down, the client will not be blocked when making log requests
+      but the log events will be lost due to server unavailability.
+
+      <p><li>Even if a <code>SocketAppender</code> is no longer
+      attached to any category, it will not be garbage collected in
+      the presence of a connector thread. A connector thread exists
+      only if the connection to the server is down. To avoid this
+      garbage collection problem, you should {@link #close} the the
+      <code>SocketAppender</code> explicitly. See also next item.
+
+      <p>Long lived applications which create/destroy many
+      <code>SocketAppender</code> instances should be aware of this
+      garbage collection problem. Most other applications can safely
+      ignore it.
+
+      <p><li>If the JVM hosting the <code>SocketAppender</code> exits
+      before the <code>SocketAppender</code> is closed either
+      explicitly or subsequent to garbage collection, then there might
+      be untransmitted data in the pipe which might be lost. This is a
+      common problem on Windows based systems.
+
+      <p>To avoid lost data, it is usually sufficient to {@link
+      #close} the <code>SocketAppender</code> either explicitly or by
+      calling the {@link org.apache.log4j.LogManager#shutdown} method
+      before exiting the application.
+
+
+     </ul>
+
+    @author  Ceki G&uuml;lc&uuml;
+    @since 0.8.4 */
+
+public class SocketAppender extends AppenderSkeleton {
+
+  /**
+     The default port number of remote logging server (4560).
+     @since 1.2.15
+  */
+  static public final int DEFAULT_PORT                 = 4560;
+
+  /**
+     The default reconnection delay (30000 milliseconds or 30 seconds).
+  */
+  static final int DEFAULT_RECONNECTION_DELAY   = 30000;
+
+  /**
+     We remember host name as String in addition to the resolved
+     InetAddress so that it can be returned via getOption().
+  */
+  String remoteHost;
+
+  /**
+   * The MulticastDNS zone advertised by a SocketAppender
+   */
+  public static final String ZONE = "_log4j_obj_tcpconnect_appender.local.";
+
+  InetAddress address;
+  int port = DEFAULT_PORT;
+  ObjectOutputStream oos;
+  int reconnectionDelay = DEFAULT_RECONNECTION_DELAY;
+  boolean locationInfo = false;
+  private String application;
+
+  private Connector connector;
+
+  int counter = 0;
+
+  // reset the ObjectOutputStream every 70 calls
+  //private static final int RESET_FREQUENCY = 70;
+  private static final int RESET_FREQUENCY = 1;
+  private boolean advertiseViaMulticastDNS;
+  private ZeroConfSupport zeroConf;
+
+  public SocketAppender() {
+  }
+
+  /**
+     Connects to remote server at <code>address</code> and <code>port</code>.
+  */
+  public SocketAppender(InetAddress address, int port) {
+    this.address = address;
+    this.remoteHost = address.getHostName();
+    this.port = port;
+    connect(address, port);
+  }
+
+  /**
+     Connects to remote server at <code>host</code> and <code>port</code>.
+  */
+  public SocketAppender(String host, int port) {
+    this.port = port;
+    this.address = getAddressByName(host);
+    this.remoteHost = host;
+    connect(address, port);
+  }
+
+  /**
+     Connect to the specified <b>RemoteHost</b> and <b>Port</b>.
+  */
+  public void activateOptions() {
+    if (advertiseViaMulticastDNS) {
+      zeroConf = new ZeroConfSupport(ZONE, port, getName());
+      zeroConf.advertise();
+    }
+    connect(address, port);
+  }
+
+  /**
+   * Close this appender.  
+   *
+   * <p>This will mark the appender as closed and call then {@link
+   * #cleanUp} method.
+   * */
+  synchronized public void close() {
+    if(closed) {
+        return;
+    }
+
+    this.closed = true;
+    if (advertiseViaMulticastDNS) {
+      zeroConf.unadvertise();
+    }
+
+    cleanUp();
+  }
+
+  /**
+   * Drop the connection to the remote host and release the underlying
+   * connector thread if it has been created 
+   * */
+  public void cleanUp() {
+    if(oos != null) {
+      try {
+       oos.close();
+      } catch(IOException e) {
+          if (e instanceof InterruptedIOException) {
+              Thread.currentThread().interrupt();
+          }
+             LogLog.error("Could not close oos.", e);
+      }
+      oos = null;
+    }
+    if(connector != null) {
+      //LogLog.debug("Interrupting the connector.");
+      connector.interrupted = true;
+      connector = null;  // allow gc
+    }
+  }
+
+  void connect(InetAddress address, int port) {
+    if(this.address == null) {
+        return;
+    }
+    try {
+      // First, close the previous connection if any.
+      cleanUp();
+      oos = new ObjectOutputStream(new Socket(address, port).getOutputStream());
+    } catch(IOException e) {
+      if (e instanceof InterruptedIOException) {
+          Thread.currentThread().interrupt();
+      }
+      String msg = "Could not connect to remote log4j server at ["
+       +address.getHostName()+"].";
+      if(reconnectionDelay > 0) {
+        msg += " We will try again later.";
+       fireConnector(); // fire the connector thread
+      } else {
+          msg += " We are not retrying.";
+          errorHandler.error(msg, e, ErrorCode.GENERIC_FAILURE);
+      } 
+      LogLog.error(msg);
+    }
+  }
+
+
+  public void append(LoggingEvent event) {
+    if(event == null) {
+        return;
+    }
+
+    if(address==null) {
+      errorHandler.error("No remote host is set for SocketAppender named \""+
+                       this.name+"\".");
+      return;
+    }
+
+    if(oos != null) {
+      try {
+        
+       if(locationInfo) {
+          event.getLocationInformation();
+       }
+    if (application != null) {
+        event.setProperty("application", application);
+    }
+    event.getNDC();
+    event.getThreadName();
+    event.getMDCCopy();
+    event.getRenderedMessage();
+    event.getThrowableStrRep();
+    
+       oos.writeObject(event);
+       //LogLog.debug("=========Flushing.");
+       oos.flush();
+       if(++counter >= RESET_FREQUENCY) {
+         counter = 0;
+         // Failing to reset the object output stream every now and
+         // then creates a serious memory leak.
+         //System.err.println("Doing oos.reset()");
+         oos.reset();
+       }
+      } catch(IOException e) {
+          if (e instanceof InterruptedIOException) {
+              Thread.currentThread().interrupt();
+          }
+             oos = null;
+             LogLog.warn("Detected problem with connection: "+e);
+             if(reconnectionDelay > 0) {
+                fireConnector();
+             } else {
+                errorHandler.error("Detected problem with connection, not reconnecting.", e,
+                      ErrorCode.GENERIC_FAILURE);
+             }
+      }
+    }
+  }
+
+  public void setAdvertiseViaMulticastDNS(boolean advertiseViaMulticastDNS) {
+    this.advertiseViaMulticastDNS = advertiseViaMulticastDNS;
+  }
+
+  public boolean isAdvertiseViaMulticastDNS() {
+    return advertiseViaMulticastDNS;
+  }
+
+  void fireConnector() {
+    if(connector == null) {
+      LogLog.debug("Starting a new connector thread.");
+      connector = new Connector();
+      connector.setDaemon(true);
+      connector.setPriority(Thread.MIN_PRIORITY);
+      connector.start();
+    }
+  }
+
+  static
+  InetAddress getAddressByName(String host) {
+    try {
+      return InetAddress.getByName(host);
+    } catch(Exception e) {
+      if (e instanceof InterruptedIOException || e instanceof InterruptedException) {
+          Thread.currentThread().interrupt();
+      }
+      LogLog.error("Could not find address of ["+host+"].", e);
+      return null;
+    }
+  }
+
+  /**
+   * The SocketAppender does not use a layout. Hence, this method
+   * returns <code>false</code>.  
+   * */
+  public boolean requiresLayout() {
+    return false;
+  }
+
+  /**
+   * The <b>RemoteHost</b> option takes a string value which should be
+   * the host name of the server where a {@link SocketNode} is
+   * running.
+   * */
+  public void setRemoteHost(String host) {
+    address = getAddressByName(host);
+    remoteHost = host;
+  }
+
+  /**
+     Returns value of the <b>RemoteHost</b> option.
+   */
+  public String getRemoteHost() {
+    return remoteHost;
+  }
+
+  /**
+     The <b>Port</b> option takes a positive integer representing
+     the port where the server is waiting for connections.
+   */
+  public void setPort(int port) {
+    this.port = port;
+  }
+
+  /**
+     Returns value of the <b>Port</b> option.
+   */
+  public int getPort() {
+    return port;
+  }
+
+  /**
+     The <b>LocationInfo</b> option takes a boolean value. If true,
+     the information sent to the remote host will include location
+     information. By default no location information is sent to the server.
+   */
+  public void setLocationInfo(boolean locationInfo) {
+    this.locationInfo = locationInfo;
+  }
+
+  /**
+     Returns value of the <b>LocationInfo</b> option.
+   */
+  public boolean getLocationInfo() {
+    return locationInfo;
+  }
+
+  /**
+   * The <b>App</b> option takes a string value which should be the name of the 
+   * application getting logged.
+   * If property was already set (via system property), don't set here.
+   * @since 1.2.15
+   */
+  public void setApplication(String lapp) {
+    this.application = lapp;
+  }
+
+  /**
+   *  Returns value of the <b>Application</b> option.
+   * @since 1.2.15
+   */
+  public String getApplication() {
+    return application;
+  }
+
+  /**
+     The <b>ReconnectionDelay</b> option takes a positive integer
+     representing the number of milliseconds to wait between each
+     failed connection attempt to the server. The default value of
+     this option is 30000 which corresponds to 30 seconds.
+
+     <p>Setting this option to zero turns off reconnection
+     capability.
+   */
+  public void setReconnectionDelay(int delay) {
+    this.reconnectionDelay = delay;
+  }
+
+  /**
+     Returns value of the <b>ReconnectionDelay</b> option.
+   */
+  public int getReconnectionDelay() {
+    return reconnectionDelay;
+  }
+
+  /**
+     The Connector will reconnect when the server becomes available
+     again.  It does this by attempting to open a new connection every
+     <code>reconnectionDelay</code> milliseconds.
+
+     <p>It stops trying whenever a connection is established. It will
+     restart to try reconnect to the server when previously open
+     connection is droppped.
+
+     @author  Ceki G&uuml;lc&uuml;
+     @since 0.8.4
+  */
+  class Connector extends Thread {
+
+    boolean interrupted = false;
+
+    public
+    void run() {
+      Socket socket;
+      while(!interrupted) {
+       try {
+         sleep(reconnectionDelay);
+         LogLog.debug("Attempting connection to "+address.getHostName());
+         socket = new Socket(address, port);
+         synchronized(this) {
+           oos = new ObjectOutputStream(socket.getOutputStream());
+           connector = null;
+           LogLog.debug("Connection established. Exiting connector thread.");
+           break;
+         }
+       } catch(InterruptedException e) {
+         LogLog.debug("Connector interrupted. Leaving loop.");
+         return;
+       } catch(java.net.ConnectException e) {
+         LogLog.debug("Remote host "+address.getHostName()
+                      +" refused connection.");
+       } catch(IOException e) {
+        if (e instanceof InterruptedIOException) {
+            Thread.currentThread().interrupt();
+        }
+           LogLog.debug("Could not connect to " + address.getHostName()+
+                      ". Exception is " + e);
+       }
+      }
+      //LogLog.debug("Exiting Connector.run() method.");
+    }
+
+    /**
+       public
+       void finalize() {
+       LogLog.debug("Connector finalize() has been called.");
+       }
+    */
+  }
+
+}