/* * 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. */ package org.apache.log4j.receivers.net; import org.apache.log4j.component.plugins.Plugin; import org.apache.log4j.component.plugins.Receiver; import org.apache.log4j.net.ZeroConfSupport; import org.apache.log4j.spi.LoggerRepository; import java.io.IOException; import java.net.Socket; import java.util.ArrayList; import java.util.Collections; import java.util.Iterator; import java.util.List; /** SocketHubReceiver receives a remote logging event on a configured socket and "posts" it to a LoggerRepository as if the event was generated locally. This class is designed to receive events from the SocketHubAppender class (or classes that send compatible events). <p>Once the event has been "posted", it will be handled by the appenders currently configured in the LoggerRespository. @author Mark Womack @author Ceki Gülcü @author Paul Smith (psmith@apache.org) */ public class SocketHubReceiver extends Receiver implements SocketNodeEventListener, PortBased { /** * Default reconnection delay. */ static final int DEFAULT_RECONNECTION_DELAY = 30000; /** * Host. */ protected String host; /** * Port. */ protected int port; /** * Reconnection delay. */ protected int reconnectionDelay = DEFAULT_RECONNECTION_DELAY; /** * The MulticastDNS zone advertised by a SocketHubReceiver */ public static final String ZONE = "_log4j_obj_tcpconnect_receiver.local."; /** * Active. */ protected boolean active = false; /** * Connector. */ protected Connector connector; /** * Socket. */ protected SocketNode13 socketNode; /** * Listener list. */ private List listenerList = Collections.synchronizedList(new ArrayList()); private boolean advertiseViaMulticastDNS; private ZeroConfSupport zeroConf; /** * Create new instance. */ public SocketHubReceiver() { super(); } /** * Create new instance. * @param h host * @param p port */ public SocketHubReceiver(final String h, final int p) { super(); host = h; port = p; } /** * Create new instance. * @param h host * @param p port * @param repo logger repository */ public SocketHubReceiver(final String h, final int p, final LoggerRepository repo) { super(); host = h; port = p; repository = repo; } /** * Adds a SocketNodeEventListener to this receiver to be notified * of SocketNode events. * @param l listener */ public void addSocketNodeEventListener(final SocketNodeEventListener l) { listenerList.add(l); } /** * Removes a specific SocketNodeEventListener from this instance * so that it will no longer be notified of SocketNode events. * @param l listener */ public void removeSocketNodeEventListener( final SocketNodeEventListener l) { listenerList.remove(l); } /** Get the remote host to connect to for logging events. @return host */ public String getHost() { return host; } /** * Configures the Host property, this will require activateOptions * to be called for this to take effect. * @param remoteHost address of remote host. */ public void setHost(final String remoteHost) { this.host = remoteHost; } /** Set the remote host to connect to for logging events. Equivalent to setHost. @param remoteHost address of remote host. */ public void setPort(final String remoteHost) { host = remoteHost; } /** Get the remote port to connect to for logging events. @return port */ public int getPort() { return port; } /** Set the remote port to connect to for logging events. @param p port */ public void setPort(final int p) { this.port = p; } /** 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. @param delay milliseconds to wait or zero to not reconnect. */ public void setReconnectionDelay(final int delay) { int oldValue = this.reconnectionDelay; this.reconnectionDelay = delay; firePropertyChange("reconnectionDelay", oldValue, this.reconnectionDelay); } /** Returns value of the <b>ReconnectionDelay</b> option. @return value of reconnection delay option. */ public int getReconnectionDelay() { return reconnectionDelay; } /** * Returns true if the receiver is the same class and they are * configured for the same properties, and super class also considers * them to be equivalent. This is used by PluginRegistry when determining * if the a similarly configured receiver is being started. * * @param testPlugin The plugin to test equivalency against. * @return boolean True if the testPlugin is equivalent to this plugin. */ public boolean isEquivalent(final Plugin testPlugin) { if (testPlugin != null && testPlugin instanceof SocketHubReceiver) { SocketHubReceiver sReceiver = (SocketHubReceiver) testPlugin; return (port == sReceiver.getPort() && host.equals(sReceiver.getHost()) && reconnectionDelay == sReceiver.getReconnectionDelay() && super.isEquivalent(testPlugin)); } return false; } /** Sets the flag to indicate if receiver is active or not. @param b new value */ protected synchronized void setActive(final boolean b) { active = b; } /** Starts the SocketReceiver with the current options. */ public void activateOptions() { if (!isActive()) { setActive(true); if (advertiseViaMulticastDNS) { zeroConf = new ZeroConfSupport(ZONE, port, getName()); zeroConf.advertise(); } fireConnector(false); } } /** Called when the receiver should be stopped. Closes the socket */ public synchronized void shutdown() { // mark this as no longer running active = false; // close the socket try { if (socketNode != null) { socketNode.close(); socketNode = null; } } catch (Exception e) { getLogger().info("Excpetion closing socket", e); // ignore for now } // stop the connector if (connector != null) { connector.interrupted = true; connector = null; // allow gc } if (advertiseViaMulticastDNS) { zeroConf.unadvertise(); } } /** Listen for a socketClosedEvent from the SocketNode. Reopen the socket if this receiver is still active. @param e exception not used. */ public void socketClosedEvent(final Exception e) { // if it is a non-normal closed event // we clear the connector object here // so that it actually does reconnect if the // remote socket dies. if (e != null) { connector = null; fireConnector(true); } } /** * Fire connectors. * @param isReconnect true if reconnect. */ private synchronized void fireConnector(final boolean isReconnect) { if (active && connector == null) { getLogger().debug("Starting a new connector thread."); connector = new Connector(isReconnect); connector.setDaemon(true); connector.setPriority(Thread.MIN_PRIORITY); connector.start(); } } /** * Set socket. * @param newSocket new value for socket. */ private synchronized void setSocket(final Socket newSocket) { connector = null; socketNode = new SocketNode13(newSocket, this); socketNode.addSocketNodeEventListener(this); synchronized (listenerList) { for (Iterator iter = listenerList.iterator(); iter.hasNext();) { SocketNodeEventListener listener = (SocketNodeEventListener) iter.next(); socketNode.addSocketNodeEventListener(listener); } } new Thread(socketNode).start(); } public void setAdvertiseViaMulticastDNS(boolean advertiseViaMulticastDNS) { this.advertiseViaMulticastDNS = advertiseViaMulticastDNS; } public boolean isAdvertiseViaMulticastDNS() { return advertiseViaMulticastDNS; } /** 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 previpously open connection is droppped. @author Ceki Gülcü */ private final class Connector extends Thread { /** * Interruption status. */ boolean interrupted = false; /** * If true, then delay on next iteration. */ boolean doDelay; /** * Create new instance. * @param isReconnect true if reconnecting. */ public Connector(final boolean isReconnect) { super(); doDelay = isReconnect; } /** * Attempt to connect until interrupted. */ public void run() { while (!interrupted) { try { if (doDelay) { getLogger().debug("waiting for " + reconnectionDelay + " milliseconds before reconnecting."); sleep(reconnectionDelay); } doDelay = true; getLogger().debug("Attempting connection to " + host); Socket s = new Socket(host, port); setSocket(s); getLogger().debug( "Connection established. Exiting connector thread."); break; } catch (InterruptedException e) { getLogger().debug("Connector interrupted. Leaving loop."); return; } catch (java.net.ConnectException e) { getLogger().debug("Remote host {} refused connection.", host); } catch (IOException e) { getLogger().debug("Could not connect to {}. Exception is {}.", host, e); } } } } /** * This method does nothing. * @param remoteInfo remote info. */ public void socketOpened(final String remoteInfo) { // This method does nothing. } }