blob: 5bef1e11f7d4130652f80e5ff574fedf8d822998 [file] [log] [blame]
/*
* 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.net;
import org.apache.log4j.plugins.Plugin;
import org.apache.log4j.plugins.Receiver;
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.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&uuml;lc&uuml;
@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 final 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 (Object aListenerList : listenerList) {
SocketNodeEventListener listener =
(SocketNodeEventListener) aListenerList;
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&uuml;lc&uuml;
*/
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.
}
}