-/*
- * 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ülcü
- @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ülcü
- @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.");
- }
- */
- }
-
-}