You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@logging.apache.org by rg...@apache.org on 2017/05/30 23:49:13 UTC
[36/50] [abbrv] logging-chainsaw git commit: Moved component and
receivers companion sources to the Chainsaw source tree - those companions
won't be released.
http://git-wip-us.apache.org/repos/asf/logging-chainsaw/blob/08c7be5c/src/main/java/org/apache/log4j/net/JMSReceiverBeanInfo.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/log4j/net/JMSReceiverBeanInfo.java b/src/main/java/org/apache/log4j/net/JMSReceiverBeanInfo.java
new file mode 100644
index 0000000..eec19d3
--- /dev/null
+++ b/src/main/java/org/apache/log4j/net/JMSReceiverBeanInfo.java
@@ -0,0 +1,52 @@
+/*
+ * 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 java.beans.PropertyDescriptor;
+import java.beans.SimpleBeanInfo;
+
+
+/**
+ * BeanInfo class for the JMSReceiver.
+ *
+ * @author Paul Smith <ps...@apache.org>
+ *
+ */
+public class JMSReceiverBeanInfo extends SimpleBeanInfo {
+
+ /* (non-Javadoc)
+ * @see java.beans.BeanInfo#getPropertyDescriptors()
+ */
+ public PropertyDescriptor[] getPropertyDescriptors() {
+
+ try {
+
+ return new PropertyDescriptor[] {
+ new PropertyDescriptor("name", JMSReceiver.class),
+ new PropertyDescriptor("topicFactoryName", JMSReceiver.class),
+ new PropertyDescriptor("topicName", JMSReceiver.class),
+ new PropertyDescriptor("threshold", JMSReceiver.class),
+ new PropertyDescriptor("jndiPath", JMSReceiver.class),
+ new PropertyDescriptor("userId",
+ JMSReceiver.class),
+ };
+ } catch (Exception e) {
+ }
+
+ return null;
+ }
+}
http://git-wip-us.apache.org/repos/asf/logging-chainsaw/blob/08c7be5c/src/main/java/org/apache/log4j/net/MulticastAppender.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/log4j/net/MulticastAppender.java b/src/main/java/org/apache/log4j/net/MulticastAppender.java
new file mode 100644
index 0000000..de002c5
--- /dev/null
+++ b/src/main/java/org/apache/log4j/net/MulticastAppender.java
@@ -0,0 +1,345 @@
+/*
+ * 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 java.io.IOException;
+import java.net.DatagramPacket;
+import java.net.InetAddress;
+import java.net.MulticastSocket;
+import java.net.UnknownHostException;
+import java.util.HashMap;
+import java.util.Hashtable;
+import java.util.Map;
+
+import org.apache.log4j.AppenderSkeleton;
+import org.apache.log4j.helpers.Constants;
+import org.apache.log4j.spi.LoggingEvent;
+import org.apache.log4j.helpers.LogLog;
+import org.apache.log4j.xml.XMLLayout;
+
+
+/**
+ * Multicast-based Appender. Works in conjunction with the MulticastReceiver, which expects
+ * a LoggingEvent encoded using XMLLayout.
+ *
+ * Sends log information as a multicast datagrams.
+ *
+ * <p>Messages are not sent as LoggingEvent objects but as text after
+ * applying XMLLayout.
+ *
+ * <p>The port and remoteHost properties can be set in configuration properties.
+ * By setting the remoteHost to a broadcast address any number of clients can
+ * listen for log messages.
+ *
+ * <p>This was inspired and really extended/copied from {@link SocketAppender}. Please
+ * see the docs for the proper credit to the authors of that class.
+ *
+ * @author <a href="mailto:kbrown@versatilesolutions.com">Kevin Brown</a>
+ * @author Scott Deboy <sd...@apache.org>
+ *
+ */
+public class MulticastAppender extends AppenderSkeleton implements PortBased {
+ /**
+ The default port number for the multicast packets. (9991).
+ */
+ static final int DEFAULT_PORT = 9991;
+
+ /**
+ * The MulticastDNS zone advertised by a MulticastAppender
+ * the MulticastAppender also adds a 'multicastAddress' property with the multicast address value as a string
+ */
+ public static final String ZONE = "_log4j_xml_mcast_appender.local.";
+
+ /**
+ We remember host name as String in addition to the resolved
+ InetAddress so that it can be returned via getOption().
+ */
+ String hostname;
+ String remoteHost;
+ String application;
+ int timeToLive;
+ InetAddress address;
+ int port = DEFAULT_PORT;
+ MulticastSocket outSocket;
+ private String encoding;
+
+ private boolean locationInfo = false;
+ private boolean advertiseViaMulticastDNS;
+ private ZeroConfSupport zeroConf;
+
+ public MulticastAppender() {
+ super(false);
+ }
+
+ /**
+ Open the multicast sender for the <b>RemoteHost</b> and <b>Port</b>.
+ */
+ public void activateOptions() {
+ try {
+ hostname = InetAddress.getLocalHost().getHostName();
+ } catch (UnknownHostException uhe) {
+ try {
+ hostname = InetAddress.getLocalHost().getHostAddress();
+ } catch (UnknownHostException uhe2) {
+ hostname = "unknown";
+ }
+ }
+
+ //allow system property of application to be primary
+ if (application == null) {
+ application = System.getProperty(Constants.APPLICATION_KEY);
+ } else {
+ if (System.getProperty(Constants.APPLICATION_KEY) != null) {
+ application = application + "-" + System.getProperty(Constants.APPLICATION_KEY);
+ }
+ }
+
+ if(remoteHost != null) {
+ address = getAddressByName(remoteHost);
+ } else {
+ String err = "The RemoteHost property is required for MulticastAppender named "+ name;
+ LogLog.error(err);
+ throw new IllegalStateException(err);
+ }
+
+ if (layout == null) {
+ layout = new XMLLayout();
+ }
+
+ if (advertiseViaMulticastDNS) {
+ Map properties = new HashMap();
+ properties.put("multicastAddress", remoteHost);
+ zeroConf = new ZeroConfSupport(ZONE, port, getName(), properties);
+ zeroConf.advertise();
+ }
+ connect();
+ super.activateOptions();
+ }
+
+ /**
+ Close this appender.
+ <p>This will mark the appender as closed and
+ call then {@link #cleanUp} method.
+ */
+ public synchronized void close() {
+ if (closed) {
+ return;
+ }
+
+ this.closed = true;
+ if (advertiseViaMulticastDNS) {
+ zeroConf.unadvertise();
+ }
+ cleanUp();
+ }
+
+ /**
+ Close the Socket and release the underlying
+ connector thread if it has been created
+ */
+ public void cleanUp() {
+ if (outSocket != null) {
+ try {
+ outSocket.close();
+ } catch (Exception e) {
+ LogLog.error("Could not close outSocket.", e);
+ }
+
+ outSocket = null;
+ }
+ }
+
+ void connect() {
+ if (this.address == null) {
+ return;
+ }
+
+ try {
+ // First, close the previous connection if any.
+ cleanUp();
+ outSocket = new MulticastSocket();
+ outSocket.setTimeToLive(timeToLive);
+ } catch (IOException e) {
+ LogLog.error("Error in connect method of MulticastAppender named "+name, e);
+ }
+ }
+
+ public void append(LoggingEvent event) {
+ if (event == null) {
+ return;
+ }
+
+ if(locationInfo) {
+ event.getLocationInformation();
+ }
+
+ if (outSocket != null) {
+ event.setProperty(Constants.HOSTNAME_KEY, hostname);
+
+ if (application != null) {
+ event.setProperty(Constants.APPLICATION_KEY, application);
+ }
+
+ if(locationInfo) {
+ event.getLocationInformation();
+ }
+
+
+ try {
+ StringBuffer buf = new StringBuffer(layout.format(event));
+
+ byte[] payload;
+ if(encoding == null) {
+ payload = buf.toString().getBytes();
+ } else {
+ payload = buf.toString().getBytes(encoding);
+ }
+
+ DatagramPacket dp =
+ new DatagramPacket(payload, payload.length, address, port);
+ outSocket.send(dp);
+ } catch (IOException e) {
+ outSocket = null;
+ LogLog.warn("Detected problem with Multicast connection: " + e);
+ }
+ }
+ }
+
+ InetAddress getAddressByName(String host) {
+ try {
+ return InetAddress.getByName(host);
+ } catch (Exception e) {
+ LogLog.error("Could not find address of [" + host + "].", e);
+ return null;
+ }
+ }
+
+ /**
+ The <b>RemoteHost</b> option takes a string value which should be
+ the host name or ipaddress to send the multicast packets.
+ */
+ public void setRemoteHost(String host) {
+ remoteHost = host;
+ }
+
+ /**
+ Returns value of the <b>RemoteHost</b> option.
+ */
+ public String getRemoteHost() {
+ return remoteHost;
+ }
+
+ /**
+ 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>Encoding</b> option specifies how the bytes are encoded. If this option is not specified,
+ the System encoding is used.
+ */
+ public void setEncoding(String encoding) {
+ this.encoding = encoding;
+ }
+
+ /**
+ Returns value of the <b>Encoding</b> option.
+ */
+ public String getEncoding() {
+ return encoding;
+ }
+ /**
+ 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.
+ */
+ public void setApplication(String app) {
+ this.application = app;
+ }
+
+ /**
+ Returns value of the <b>App</b> option.
+ */
+ public String getApplication() {
+ return application;
+ }
+
+ /**
+ The <b>Time to live</b> option takes a positive integer representing
+ the time to live value.
+ */
+ public void setTimeToLive(int timeToLive) {
+ this.timeToLive = timeToLive;
+ }
+
+ /**
+ Returns value of the <b>Time to Live</b> option.
+ */
+ public int getTimeToLive() {
+ return timeToLive;
+ }
+
+ /**
+ The <b>Port</b> option takes a positive integer representing
+ the port where multicast packets will be sent.
+ */
+ public void setPort(int port) {
+ this.port = port;
+ }
+
+ /**
+ Returns value of the <b>Port</b> option.
+ */
+ public int getPort() {
+ return port;
+ }
+
+ /* (non-Javadoc)
+ * @see org.apache.log4j.net.NetworkBased#isActive()
+ */
+ public boolean isActive() {
+ // TODO handle active/inactive
+ return true;
+ }
+
+ /**
+ * Gets whether appender requires a layout.
+ * @return false
+ */
+ public boolean requiresLayout() {
+ return true;
+ }
+
+ public boolean isAdvertiseViaMulticastDNS() {
+ return advertiseViaMulticastDNS;
+ }
+
+ public void setAdvertiseViaMulticastDNS(boolean advertiseViaMulticastDNS) {
+ this.advertiseViaMulticastDNS = advertiseViaMulticastDNS;
+ }
+}
http://git-wip-us.apache.org/repos/asf/logging-chainsaw/blob/08c7be5c/src/main/java/org/apache/log4j/net/MulticastReceiver.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/log4j/net/MulticastReceiver.java b/src/main/java/org/apache/log4j/net/MulticastReceiver.java
new file mode 100644
index 0000000..2dfcec2
--- /dev/null
+++ b/src/main/java/org/apache/log4j/net/MulticastReceiver.java
@@ -0,0 +1,276 @@
+/*
+ * 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 java.io.IOException;
+import java.net.DatagramPacket;
+import java.net.InetAddress;
+import java.net.MulticastSocket;
+import java.net.SocketException;
+import java.net.UnknownHostException;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+import org.apache.log4j.plugins.Pauseable;
+import org.apache.log4j.plugins.Receiver;
+import org.apache.log4j.spi.Decoder;
+import org.apache.log4j.spi.LoggingEvent;
+
+
+/**
+ * Multicast-based receiver. Accepts LoggingEvents encoded using
+ * MulticastAppender and XMLLayout. The the XML data is converted
+ * back to a LoggingEvent and is posted.
+ *
+ * @author Scott Deboy <sd...@apache.org>
+ *
+ */
+public class MulticastReceiver extends Receiver implements PortBased,
+ AddressBased, Pauseable {
+ private static final int PACKET_LENGTH = 16384;
+ private int port;
+ private String address;
+ private String encoding;
+ private MulticastSocket socket = null;
+
+ //default to log4j xml decoder
+ private String decoder = "org.apache.log4j.xml.XMLDecoder";
+ private Decoder decoderImpl;
+ private MulticastHandlerThread handlerThread;
+ private MulticastReceiverThread receiverThread;
+ private boolean paused;
+ private boolean advertiseViaMulticastDNS;
+ private ZeroConfSupport zeroConf;
+
+ /**
+ * The MulticastDNS zone advertised by a MulticastReceiver
+ */
+ public static final String ZONE = "_log4j_xml_mcast_receiver.local.";
+
+ public String getDecoder() {
+ return decoder;
+ }
+
+ public void setDecoder(String decoder) {
+ this.decoder = decoder;
+ }
+
+ public int getPort() {
+ return port;
+ }
+
+ public void setPort(int port) {
+ this.port = port;
+ }
+
+ public String getAddress() {
+ return address;
+ }
+
+ /**
+ The <b>Encoding</b> option specifies how the bytes are encoded. If this option is not specified,
+ the system encoding will be used.
+ */
+ public void setEncoding(String encoding) {
+ this.encoding = encoding;
+ }
+
+ /**
+ Returns value of the <b>Encoding</b> option.
+ */
+ public String getEncoding() {
+ return encoding;
+ }
+
+ public synchronized void shutdown() {
+ active = false;
+ if (advertiseViaMulticastDNS) {
+ zeroConf.unadvertise();
+ }
+ if (handlerThread != null) {
+ handlerThread.interrupt();
+ }
+ if (receiverThread != null) {
+ receiverThread.interrupt();
+ }
+ if (socket != null) {
+ socket.close();
+ }
+ }
+
+ public void setAddress(String address) {
+ this.address = address;
+ }
+
+ public boolean isPaused() {
+ return paused;
+ }
+
+ public void setPaused(boolean b) {
+ paused = b;
+ }
+
+ public void activateOptions() {
+ InetAddress addr = null;
+
+ try {
+ Class c = Class.forName(decoder);
+ Object o = c.newInstance();
+
+ if (o instanceof Decoder) {
+ this.decoderImpl = (Decoder) o;
+ }
+ } catch (ClassNotFoundException cnfe) {
+ getLogger().warn("Unable to find decoder", cnfe);
+ } catch (IllegalAccessException iae) {
+ getLogger().warn("Could not construct decoder", iae);
+ } catch (InstantiationException ie) {
+ getLogger().warn("Could not construct decoder", ie);
+ }
+
+ try {
+ addr = InetAddress.getByName(address);
+ } catch (UnknownHostException uhe) {
+ uhe.printStackTrace();
+ }
+
+ try {
+ active = true;
+ socket = new MulticastSocket(port);
+ socket.joinGroup(addr);
+ receiverThread = new MulticastReceiverThread();
+ receiverThread.start();
+ handlerThread = new MulticastHandlerThread();
+ handlerThread.start();
+ if (advertiseViaMulticastDNS) {
+ zeroConf = new ZeroConfSupport(ZONE, port, getName());
+ zeroConf.advertise();
+ }
+
+ } catch (IOException ioe) {
+ ioe.printStackTrace();
+ }
+ }
+
+ public void setAdvertiseViaMulticastDNS(boolean advertiseViaMulticastDNS) {
+ this.advertiseViaMulticastDNS = advertiseViaMulticastDNS;
+ }
+
+ public boolean isAdvertiseViaMulticastDNS() {
+ return advertiseViaMulticastDNS;
+ }
+
+ class MulticastHandlerThread extends Thread {
+ private List list = new ArrayList();
+
+ public MulticastHandlerThread() {
+ setDaemon(true);
+ }
+
+ public void append(String data) {
+ synchronized (list) {
+ list.add(data);
+ list.notify();
+ }
+ }
+
+ public void run() {
+ ArrayList list2 = new ArrayList();
+
+ while (isAlive()) {
+ synchronized (list) {
+ try {
+ while (list.size() == 0) {
+ list.wait();
+ }
+
+ if (list.size() > 0) {
+ list2.addAll(list);
+ list.clear();
+ }
+ } catch (InterruptedException ie) {
+ }
+ }
+
+ if (list2.size() > 0) {
+ Iterator iter = list2.iterator();
+
+ while (iter.hasNext()) {
+ String data = (String) iter.next();
+ List v = decoderImpl.decodeEvents(data.trim());
+
+ if (v != null) {
+ Iterator eventIter = v.iterator();
+
+ while (eventIter.hasNext()) {
+ if (!isPaused()) {
+ doPost((LoggingEvent) eventIter.next());
+ }
+ }
+ }
+ }
+
+ list2.clear();
+ } else {
+ try {
+ synchronized (this) {
+ wait(1000);
+ }
+ } catch (InterruptedException ie) {
+ }
+ }
+ }
+ }
+ }
+
+ class MulticastReceiverThread extends Thread {
+ public MulticastReceiverThread() {
+ setDaemon(true);
+ }
+
+ public void run() {
+ active = true;
+
+ byte[] b = new byte[PACKET_LENGTH];
+ DatagramPacket p = new DatagramPacket(b, b.length);
+
+ while (active) {
+ try {
+ socket.receive(p);
+
+ //this string constructor which accepts a charset throws an exception if it is
+ //null
+ if (encoding == null) {
+ handlerThread.append(
+ new String(p.getData(), 0, p.getLength()));
+ } else {
+ handlerThread.append(
+ new String(p.getData(), 0, p.getLength(), encoding));
+ }
+ } catch (SocketException se) {
+ //disconnected
+ } catch (IOException ioe) {
+ ioe.printStackTrace();
+ }
+ }
+
+ getLogger().debug("{}'s thread is ending.", MulticastReceiver.this.getName());
+ }
+ }
+}
http://git-wip-us.apache.org/repos/asf/logging-chainsaw/blob/08c7be5c/src/main/java/org/apache/log4j/net/MulticastReceiverBeanInfo.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/log4j/net/MulticastReceiverBeanInfo.java b/src/main/java/org/apache/log4j/net/MulticastReceiverBeanInfo.java
new file mode 100644
index 0000000..4dec14c
--- /dev/null
+++ b/src/main/java/org/apache/log4j/net/MulticastReceiverBeanInfo.java
@@ -0,0 +1,51 @@
+/*
+ * 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 java.beans.PropertyDescriptor;
+import java.beans.SimpleBeanInfo;
+
+
+/**
+ * BeanInfo class for the meta-data of the MulticastReceiver.
+ *
+ * @author Paul Smith <ps...@apache.org>
+ *
+ */
+public class MulticastReceiverBeanInfo extends SimpleBeanInfo {
+
+ /* (non-Javadoc)
+ * @see java.beans.BeanInfo#getPropertyDescriptors()
+ */
+ public PropertyDescriptor[] getPropertyDescriptors() {
+
+ try {
+
+ return new PropertyDescriptor[] {
+ new PropertyDescriptor("name", MulticastReceiver.class),
+ new PropertyDescriptor("address", MulticastReceiver.class),
+ new PropertyDescriptor("port", MulticastReceiver.class),
+ new PropertyDescriptor("threshold", MulticastReceiver.class),
+ new PropertyDescriptor("decoder", MulticastReceiver.class),
+ new PropertyDescriptor("advertiseViaMulticastDNS", MulticastReceiver.class),
+ };
+ } catch (Exception e) {
+ }
+
+ return null;
+ }
+}
http://git-wip-us.apache.org/repos/asf/logging-chainsaw/blob/08c7be5c/src/main/java/org/apache/log4j/net/NetworkBased.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/log4j/net/NetworkBased.java b/src/main/java/org/apache/log4j/net/NetworkBased.java
new file mode 100644
index 0000000..9c5153f
--- /dev/null
+++ b/src/main/java/org/apache/log4j/net/NetworkBased.java
@@ -0,0 +1,39 @@
+/*
+ * 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;
+
+/**
+ * The parent of all the Network based interfaces.
+ *
+ * @author Paul Smith (psmith@apache.org)
+ *
+ */
+public interface NetworkBased {
+
+ /**
+ * Get name.
+ * @return name.
+ */
+ String getName();
+
+ /**
+ * Get if item is active.
+ * @return if true, item is active.
+ */
+ boolean isActive();
+}
http://git-wip-us.apache.org/repos/asf/logging-chainsaw/blob/08c7be5c/src/main/java/org/apache/log4j/net/PortBased.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/log4j/net/PortBased.java b/src/main/java/org/apache/log4j/net/PortBased.java
new file mode 100644
index 0000000..c7c1f97
--- /dev/null
+++ b/src/main/java/org/apache/log4j/net/PortBased.java
@@ -0,0 +1,34 @@
+/*
+ * 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;
+
+
+/**
+ * Net based entities that 'work with' a Port should consider implementing this
+ * interface so that they can be treated generically.
+ *
+ * @author Paul Smith (psmith@apache.org)
+ *
+ */
+public interface PortBased extends NetworkBased {
+ /**
+ * Returns the Port # that this net based thing is using.
+ * @return int port number
+ */
+ int getPort();
+}
http://git-wip-us.apache.org/repos/asf/logging-chainsaw/blob/08c7be5c/src/main/java/org/apache/log4j/net/SocketHubReceiver.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/log4j/net/SocketHubReceiver.java b/src/main/java/org/apache/log4j/net/SocketHubReceiver.java
new file mode 100644
index 0000000..85058e8
--- /dev/null
+++ b/src/main/java/org/apache/log4j/net/SocketHubReceiver.java
@@ -0,0 +1,409 @@
+/*
+ * 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.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.
+ }
+}
http://git-wip-us.apache.org/repos/asf/logging-chainsaw/blob/08c7be5c/src/main/java/org/apache/log4j/net/SocketNode13.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/log4j/net/SocketNode13.java b/src/main/java/org/apache/log4j/net/SocketNode13.java
new file mode 100644
index 0000000..e27c68e
--- /dev/null
+++ b/src/main/java/org/apache/log4j/net/SocketNode13.java
@@ -0,0 +1,299 @@
+/*
+ * 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 java.io.BufferedInputStream;
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.net.Socket;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+
+import org.apache.log4j.Logger;
+import org.apache.log4j.helpers.Constants;
+import org.apache.log4j.plugins.Pauseable;
+import org.apache.log4j.plugins.Receiver;
+import org.apache.log4j.spi.ComponentBase;
+import org.apache.log4j.spi.LoggerRepository;
+import org.apache.log4j.spi.LoggingEvent;
+
+
+// Contributors: Moses Hohman <mm...@rainbow.uchicago.edu>
+
+/**
+ Read {@link LoggingEvent} objects sent from a remote client using
+ Sockets (TCP). These logging events are logged according to local
+ policy, as if they were generated locally.
+
+ <p>For example, the socket node might decide to log events to a
+ local file and also resent them to a second socket node.
+
+ Implementation lifted from org.apache.log4j.net.SocketNode
+ in log4j 1.3 and renamed to prevent collision with
+ log4j 1.2 implementation.
+
+ @author Ceki Gülcü
+ @author Paul Smith (psmith@apache.org)
+
+
+*/
+public class SocketNode13 extends ComponentBase implements Runnable, Pauseable {
+
+ /**
+ * Paused state.
+ */
+ private boolean paused;
+ /**
+ * Closed state.
+ */
+ private boolean closed;
+ /**
+ * Socket.
+ */
+ private Socket socket;
+ /**
+ * Receiver.
+ */
+ private Receiver receiver;
+ /**
+ * List of listeners.
+ */
+ private List listenerList = Collections.synchronizedList(new ArrayList());
+
+
+
+ /**
+ Constructor for socket and logger repository.
+ @param s socket
+ @param hierarchy logger repository
+ */
+ public SocketNode13(final Socket s,
+ final LoggerRepository hierarchy) {
+ super();
+ this.socket = s;
+ this.repository = hierarchy;
+ }
+
+ /**
+ Constructor for socket and receiver.
+ @param s socket
+ @param r receiver
+ */
+ public SocketNode13(final Socket s, final Receiver r) {
+ super();
+ this.socket = s;
+ this.receiver = r;
+ }
+
+ /**
+ * Set the event listener on this node.
+ *
+ * @deprecated Now supports mutliple listeners, this method
+ * simply invokes the removeSocketNodeEventListener() to remove
+ * the listener, and then readds it.
+ * @param l listener
+ */
+ public void setListener(final SocketNodeEventListener l) {
+ removeSocketNodeEventListener(l);
+ addSocketNodeEventListener(l);
+ }
+
+ /**
+ * Adds the listener to the list of listeners to be notified of the
+ * respective event.
+ * @param listener the listener to add to the list
+ */
+ public void addSocketNodeEventListener(
+ final SocketNodeEventListener listener) {
+ listenerList.add(listener);
+ }
+
+ /**
+ * Removes the registered Listener from this instances list of
+ * listeners. If the listener has not been registered, then invoking
+ * this method has no effect.
+ *
+ * @param listener the SocketNodeEventListener to remove
+ */
+ public void removeSocketNodeEventListener(
+ final SocketNodeEventListener listener) {
+ listenerList.remove(listener);
+ }
+
+
+ /**
+ * Deserialize events from socket until interrupted.
+ */
+ public void run() {
+ LoggingEvent event;
+ Logger remoteLogger;
+ Exception listenerException = null;
+ ObjectInputStream ois = null;
+
+ try {
+ ois =
+ new ObjectInputStream(
+ new BufferedInputStream(socket.getInputStream()));
+ } catch (Exception e) {
+ ois = null;
+ listenerException = e;
+ getLogger().error("Exception opening ObjectInputStream to " + socket, e);
+ }
+
+ if (ois != null) {
+
+ String hostName = socket.getInetAddress().getHostName();
+ String remoteInfo = hostName + ":" + socket.getPort();
+
+ /**
+ * notify the listener that the socket has been
+ * opened and this SocketNode is ready and waiting
+ */
+ fireSocketOpened(remoteInfo);
+
+ try {
+ while (!isClosed()) {
+ // read an event from the wire
+ event = (LoggingEvent) ois.readObject();
+ event.setProperty(Constants.HOSTNAME_KEY, hostName);
+ // store the known remote info in an event property
+ event.setProperty("log4j.remoteSourceInfo", remoteInfo);
+
+ // if configured with a receiver, tell it to post the event
+ if (!isPaused() && !isClosed()) {
+ if ((receiver != null)) {
+ receiver.doPost(event);
+
+ // else post it via the hierarchy
+ } else {
+ // get a logger from the hierarchy. The name of the logger
+ // is taken to be the name contained in the event.
+ remoteLogger = repository.getLogger(event.getLoggerName());
+
+ //event.logger = remoteLogger;
+ // apply the logger-level filter
+ if (event
+ .getLevel()
+ .isGreaterOrEqual(remoteLogger.getEffectiveLevel())) {
+ // finally log the event as if was generated locally
+ remoteLogger.callAppenders(event);
+ }
+ }
+ } else {
+ //we simply discard this event.
+ }
+ }
+ } catch (java.io.EOFException e) {
+ getLogger().info("Caught java.io.EOFException closing connection.");
+ listenerException = e;
+ } catch (java.net.SocketException e) {
+ getLogger().info("Caught java.net.SocketException closing connection.");
+ listenerException = e;
+ } catch (IOException e) {
+ getLogger().info("Caught java.io.IOException: " + e);
+ getLogger().info("Closing connection.");
+ listenerException = e;
+ } catch (Exception e) {
+ getLogger().error("Unexpected exception. Closing connection.", e);
+ listenerException = e;
+ }
+ }
+
+ // close the socket
+ try {
+ if (ois != null) {
+ ois.close();
+ }
+ } catch (Exception e) {
+ //getLogger().info("Could not close connection.", e);
+ }
+
+ // send event to listener, if configured
+ if (listenerList.size() > 0 && !isClosed()) {
+ fireSocketClosedEvent(listenerException);
+ }
+ }
+
+ /**
+ * Notifies all registered listeners regarding the closing of the Socket.
+ * @param listenerException listener exception
+ */
+ private void fireSocketClosedEvent(final Exception listenerException) {
+ synchronized (listenerList) {
+ for (Iterator iter = listenerList.iterator(); iter.hasNext();) {
+ SocketNodeEventListener snel =
+ (SocketNodeEventListener) iter.next();
+ if (snel != null) {
+ snel.socketClosedEvent(listenerException);
+ }
+ }
+ }
+ }
+
+ /**
+ * Notifies all registered listeners regarding the opening of a Socket.
+ * @param remoteInfo remote info
+ */
+ private void fireSocketOpened(final String remoteInfo) {
+ synchronized (listenerList) {
+ for (Iterator iter = listenerList.iterator(); iter.hasNext();) {
+ SocketNodeEventListener snel =
+ (SocketNodeEventListener) iter.next();
+ if (snel != null) {
+ snel.socketOpened(remoteInfo);
+ }
+ }
+ }
+ }
+
+ /**
+ * Sets if node is paused.
+ * @param b new value
+ */
+ public void setPaused(final boolean b) {
+ this.paused = b;
+ }
+
+ /**
+ * Get if node is paused.
+ * @return true if pause.
+ */
+ public boolean isPaused() {
+ return this.paused;
+ }
+
+ /**
+ * Close the node and underlying socket
+ */
+ public void close() throws IOException {
+ getLogger().debug("closing socket");
+ this.closed = true;
+ socket.close();
+ fireSocketClosedEvent(null);
+ }
+
+ /**
+ * Get if node is closed.
+ * @return true if closed.
+ */
+ public boolean isClosed() {
+ return this.closed;
+ }
+}
http://git-wip-us.apache.org/repos/asf/logging-chainsaw/blob/08c7be5c/src/main/java/org/apache/log4j/net/SocketNodeEventListener.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/log4j/net/SocketNodeEventListener.java b/src/main/java/org/apache/log4j/net/SocketNodeEventListener.java
new file mode 100644
index 0000000..6d17602
--- /dev/null
+++ b/src/main/java/org/apache/log4j/net/SocketNodeEventListener.java
@@ -0,0 +1,43 @@
+/*
+ * 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 java.util.EventListener;
+
+/**
+ Interface used to listen for {@link SocketNode} related
+ events. Clients register an instance of the interface and the
+ instance is called back when the various events occur.
+
+ @author Mark Womack
+ @author Paul Smith (psmith@apache.org)
+*/
+public interface SocketNodeEventListener extends EventListener {
+
+ /**
+ * Called when the SocketNode is created and begins awaiting data.
+ * @param remoteInfo remote info
+ */
+ void socketOpened(String remoteInfo);
+
+ /**
+ Called when the socket the node was given has been closed.
+ @param e exception
+ */
+ void socketClosedEvent(Exception e);
+}
http://git-wip-us.apache.org/repos/asf/logging-chainsaw/blob/08c7be5c/src/main/java/org/apache/log4j/net/SocketReceiver.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/log4j/net/SocketReceiver.java b/src/main/java/org/apache/log4j/net/SocketReceiver.java
new file mode 100644
index 0000000..9d4aac9
--- /dev/null
+++ b/src/main/java/org/apache/log4j/net/SocketReceiver.java
@@ -0,0 +1,479 @@
+/*
+ * 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 java.io.IOException;
+import java.net.ServerSocket;
+import java.net.Socket;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Vector;
+
+import org.apache.log4j.plugins.Pauseable;
+import org.apache.log4j.plugins.Plugin;
+import org.apache.log4j.plugins.Receiver;
+import org.apache.log4j.spi.LoggerRepository;
+import org.apache.log4j.spi.LoggingEvent;
+
+
+/**
+ SocketReceiver 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 SocketAppender 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 Scott Deboy (sdeboy@apache.org)
+ @author Paul Smith (psmith@apache.org)
+*/
+public class SocketReceiver extends Receiver implements Runnable, PortBased,
+ Pauseable {
+ /**
+ * socket map.
+ */
+ private Map socketMap = new HashMap();
+ /**
+ * Paused.
+ */
+ private boolean paused;
+ /**
+ * Thread.
+ */
+ private Thread rThread;
+ /**
+ * Port.
+ */
+ protected int port;
+ /**
+ * Server socket.
+ */
+ private ServerSocket serverSocket;
+ /**
+ * Socket list.
+ */
+ private Vector socketList = new Vector();
+
+ /**
+ * The MulticastDNS zone advertised by a SocketReceiver
+ */
+ public static final String ZONE = "_log4j_obj_tcpaccept_receiver.local.";
+
+ /**
+ * Listener.
+ */
+ private SocketNodeEventListener listener = null;
+ /**
+ * Listeners.
+ */
+ private List listenerList = Collections.synchronizedList(new ArrayList());
+ private boolean advertiseViaMulticastDNS;
+ private ZeroConfSupport zeroConf;
+
+ /**
+ * Create new instance.
+ */
+ public SocketReceiver() {
+ super();
+ }
+
+ /**
+ * Create new instance.
+ * @param p port
+ */
+ public SocketReceiver(final int p) {
+ super();
+ port = p;
+ }
+
+ /**
+ * Create new instance.
+ * @param p port
+ * @param repo logger repository
+ */
+ public SocketReceiver(final int p, final LoggerRepository repo) {
+ super();
+ this.port = p;
+ repository = repo;
+ }
+
+ /** {@inheritDoc} */
+ public int getPort() {
+ return port;
+ }
+
+ /** {@inheritDoc} */
+ public void setPort(final int p) {
+ port = p;
+ }
+
+ /**
+ * 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 SocketReceiver) {
+ SocketReceiver sReceiver = (SocketReceiver) testPlugin;
+
+ return (port == sReceiver.getPort() && super.isEquivalent(testPlugin));
+ }
+
+ return false;
+ }
+
+ /**
+ Starts the SocketReceiver with the current options. */
+ public void activateOptions() {
+ if (!isActive()) {
+ // shutdown();
+ rThread = new Thread(this);
+ rThread.setDaemon(true);
+ rThread.start();
+ if (advertiseViaMulticastDNS) {
+ zeroConf = new ZeroConfSupport(ZONE, port, getName());
+ zeroConf.advertise();
+ }
+
+ active = true;
+ }
+ }
+
+ /**
+ * Called when the receiver should be stopped. Closes the
+ * server socket and all of the open sockets.
+ */
+ public synchronized void shutdown() {
+ getLogger().debug(getName() + " received shutdown request");
+
+ // mark this as no longer running
+ active = false;
+
+ if (rThread != null) {
+ rThread.interrupt();
+ rThread = null;
+ }
+ if (advertiseViaMulticastDNS) {
+ zeroConf.unadvertise();
+ }
+
+ doShutdown();
+ }
+
+ /**
+ * Does the actual shutting down by closing the server socket
+ * and any connected sockets that have been created.
+ */
+ private synchronized void doShutdown() {
+ active = false;
+
+ getLogger().debug(getName() + " doShutdown called");
+
+ // close the server socket
+ closeServerSocket();
+
+ // close all of the accepted sockets
+ closeAllAcceptedSockets();
+ }
+
+ /**
+ * Closes the server socket, if created.
+ */
+ private void closeServerSocket() {
+ getLogger().debug("{} closing server socket", getName());
+
+ try {
+ if (serverSocket != null) {
+ serverSocket.close();
+ }
+ } catch (Exception e) {
+ // ignore for now
+ }
+
+ serverSocket = null;
+ }
+
+ /**
+ * Closes all the connected sockets in the List.
+ */
+ private synchronized void closeAllAcceptedSockets() {
+ for (int x = 0; x < socketList.size(); x++) {
+ try {
+ ((Socket) socketList.get(x)).close();
+ } catch (Exception e) {
+ // ignore for now
+ }
+ }
+
+ // clear member variables
+ socketMap.clear();
+ socketList.clear();
+ }
+
+ /**
+ Sets the flag to indicate if receiver is active or not.
+ @param b new value
+ */
+ protected synchronized void setActive(final boolean b) {
+ active = b;
+ }
+
+ public void setAdvertiseViaMulticastDNS(boolean advertiseViaMulticastDNS) {
+ this.advertiseViaMulticastDNS = advertiseViaMulticastDNS;
+ }
+
+ public boolean isAdvertiseViaMulticastDNS() {
+ return advertiseViaMulticastDNS;
+ }
+
+ /**
+ Loop, accepting new socket connections. */
+ public void run() {
+ /**
+ * Ensure we start fresh.
+ */
+ closeServerSocket();
+ closeAllAcceptedSockets();
+
+ // start the server socket
+ try {
+ serverSocket = new ServerSocket(port);
+ } catch (Exception e) {
+ getLogger().error(
+ "error starting SocketReceiver (" + this.getName()
+ + "), receiver did not start", e);
+ active = false;
+
+ return;
+ }
+
+ Socket socket = null;
+
+ try {
+ getLogger().debug("in run-about to enter while not interrupted loop");
+
+ active = true;
+
+ while (!rThread.isInterrupted()) {
+ // if we have a socket, start watching it
+ if (socket != null) {
+ getLogger().debug(
+ "socket not null - creating and starting socketnode");
+ socketList.add(socket);
+
+ SocketNode13 node = new SocketNode13(socket, this);
+ synchronized (listenerList) {
+ for (Iterator iter = listenerList.iterator();
+ iter.hasNext();) {
+ SocketNodeEventListener l =
+ (SocketNodeEventListener) iter.next();
+ node.addSocketNodeEventListener(l);
+ }
+ }
+ socketMap.put(socket, node);
+ new Thread(node).start();
+ socket = null;
+ }
+
+ getLogger().debug("waiting to accept socket");
+
+ // wait for a socket to open, then loop to start it
+ socket = serverSocket.accept();
+ getLogger().debug("accepted socket");
+ }
+ } catch (Exception e) {
+ getLogger().warn(
+ "exception while watching socket server in SocketReceiver ("
+ + this.getName() + "), stopping");
+ }
+
+ getLogger().debug("{} has exited the not interrupted loop", getName());
+
+ // socket not watched because we a no longer running
+ // so close it now.
+ if (socket != null) {
+ try {
+ socket.close();
+ } catch (IOException e1) {
+ getLogger().warn("socket exception caught - socket closed");
+ }
+ }
+
+ getLogger().debug("{} is exiting main run loop", getName());
+ }
+
+ /**
+ * Returns a Vector of SocketDetail representing the IP/Domain name
+ * of the currently connected sockets that this receiver has
+ * been responsible for creating.
+ * @return Vector of SocketDetails
+ */
+ public Vector getConnectedSocketDetails() {
+ Vector details = new Vector(socketList.size());
+
+ for (Enumeration enumeration = socketList.elements();
+ enumeration.hasMoreElements();
+ ) {
+ Socket socket = (Socket) enumeration.nextElement();
+ details.add(
+ new SocketDetail(socket, (SocketNode13) socketMap.get(socket)));
+ }
+
+ return details;
+ }
+
+ /**
+ * Returns the currently configured SocketNodeEventListener that
+ * will be automatically set for each SocketNode created.
+ * @return SocketNodeEventListener currently configured
+ *
+ * @deprecated This receiver now supports multiple listeners
+ */
+ public SocketNodeEventListener getListener() {
+ return listener;
+ }
+
+ /**
+ * Adds the listener to the list of listeners to be notified of the
+ * respective event.
+ * @param l the listener to add to the list
+ */
+ public void addSocketNodeEventListener(
+ final SocketNodeEventListener l) {
+ listenerList.add(l);
+ }
+
+ /**
+ * Removes the registered Listener from this instances list of
+ * listeners. If the listener has not been registered, then invoking
+ * this method has no effect.
+ *
+ * @param l the SocketNodeEventListener to remove
+ */
+ public void removeSocketNodeEventListener(
+ final SocketNodeEventListener l) {
+ listenerList.remove(l);
+ }
+
+ /**
+ * Sets the SocketNodeEventListener that will be used for each
+ * created SocketNode.
+ * @param l the listener to set on each creation of a SocketNode
+ * @deprecated This receiver now supports multiple listeners and
+ * so this method simply removes the listener (if there already)
+ * and readds it to the list.
+ *
+ * The passed listener will also be returned via the getListener()
+ * method still, but this is also deprecated
+ */
+ public void setListener(final SocketNodeEventListener l) {
+ removeSocketNodeEventListener(l);
+ addSocketNodeEventListener(l);
+ this.listener = l;
+ }
+
+ /** {@inheritDoc} */
+ public boolean isPaused() {
+ return paused;
+ }
+
+ /** {@inheritDoc} */
+ public void setPaused(final boolean b) {
+ paused = b;
+ }
+
+ /**
+ * Socket detail.
+ */
+ private static final class SocketDetail implements AddressBased, PortBased,
+ Pauseable {
+ /**
+ * Address.
+ */
+ private String address;
+ /**
+ * Port.
+ */
+ private int port;
+ /**
+ * Socket node.
+ */
+ private SocketNode13 socketNode;
+
+ /**
+ * Create new instance.
+ * @param socket socket
+ * @param node socket node
+ */
+ private SocketDetail(final Socket socket,
+ final SocketNode13 node) {
+ super();
+ this.address = socket.getInetAddress().getHostName();
+ this.port = socket.getPort();
+ this.socketNode = node;
+ }
+
+ /** {@inheritDoc} */
+ public String getAddress() {
+ return address;
+ }
+
+ /** {@inheritDoc} */
+ public int getPort() {
+ return port;
+ }
+
+ /** {@inheritDoc} */
+ public String getName() {
+ return "Socket";
+ }
+
+ /** {@inheritDoc} */
+ public boolean isActive() {
+ return true;
+ }
+
+ /** {@inheritDoc} */
+ public boolean isPaused() {
+ return socketNode.isPaused();
+ }
+
+ /** {@inheritDoc} */
+ public void setPaused(final boolean b) {
+ socketNode.setPaused(b);
+ }
+ }
+ /** {@inheritDoc} */
+ public void doPost(final LoggingEvent event) {
+ if (!isPaused()) {
+ super.doPost(event);
+ }
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/logging-chainsaw/blob/08c7be5c/src/main/java/org/apache/log4j/net/UDPAppender.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/log4j/net/UDPAppender.java b/src/main/java/org/apache/log4j/net/UDPAppender.java
new file mode 100644
index 0000000..55bc6f1
--- /dev/null
+++ b/src/main/java/org/apache/log4j/net/UDPAppender.java
@@ -0,0 +1,330 @@
+/*
+ * 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.AppenderSkeleton;
+import org.apache.log4j.helpers.Constants;
+import org.apache.log4j.spi.LoggingEvent;
+import org.apache.log4j.helpers.LogLog;
+import org.apache.log4j.xml.XMLLayout;
+
+import java.io.IOException;
+import java.net.DatagramPacket;
+import java.net.DatagramSocket;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+
+
+
+/**
+ * Sends log information as a UDP datagrams.
+ *
+ * <p>The UDPAppender is meant to be used as a diagnostic logging tool
+ * so that logging can be monitored by a simple UDP client.
+ *
+ * <p>Messages are not sent as LoggingEvent objects but as text after
+ * applying the designated Layout.
+ *
+ * <p>The port and remoteHost properties can be set in configuration properties.
+ * By setting the remoteHost to a broadcast address any number of clients can
+ * listen for log messages.
+ *
+ * <p>This was inspired and really extended/copied from {@link SocketAppender}.
+ * Please see the docs for the proper credit to the authors of that class.
+ *
+ * @author <a href="mailto:kbrown@versatilesolutions.com">Kevin Brown</a>
+ * @author Scott Deboy <sd...@apache.org>
+ */
+public class UDPAppender extends AppenderSkeleton implements PortBased{
+ /**
+ * The default port number for the UDP packets, 9991.
+ */
+ public static final int DEFAULT_PORT = 9991;
+
+ /**
+ We remember host name as String in addition to the resolved
+ InetAddress so that it can be returned via getOption().
+ */
+ String hostname;
+ String remoteHost;
+ String application;
+ String encoding;
+ InetAddress address;
+ int port = DEFAULT_PORT;
+ DatagramSocket outSocket;
+
+ /**
+ * The MulticastDNS zone advertised by a UDPAppender
+ */
+ public static final String ZONE = "_log4j_xml_udp_appender.local.";
+
+ // if there is something irrecoverably wrong with the settings, there is no
+ // point in sending out packeets.
+ boolean inError = false;
+ private boolean advertiseViaMulticastDNS;
+ private ZeroConfSupport zeroConf;
+
+ public UDPAppender() {
+ super(false);
+ }
+
+ /**
+ Sends UDP packets to the <code>address</code> and <code>port</code>.
+ */
+ public UDPAppender(final InetAddress address, final int port) {
+ super(false);
+ this.address = address;
+ this.remoteHost = address.getHostName();
+ this.port = port;
+ activateOptions();
+ }
+
+ /**
+ Sends UDP packets to the <code>address</code> and <code>port</code>.
+ */
+ public UDPAppender(final String host, final int port) {
+ super(false);
+ this.port = port;
+ this.address = getAddressByName(host);
+ this.remoteHost = host;
+ activateOptions();
+ }
+
+ /**
+ Open the UDP sender for the <b>RemoteHost</b> and <b>Port</b>.
+ */
+ public void activateOptions() {
+ try {
+ hostname = InetAddress.getLocalHost().getHostName();
+ } catch (UnknownHostException uhe) {
+ try {
+ hostname = InetAddress.getLocalHost().getHostAddress();
+ } catch (UnknownHostException uhe2) {
+ hostname = "unknown";
+ }
+ }
+
+ //allow system property of application to be primary
+ if (application == null) {
+ application = System.getProperty(Constants.APPLICATION_KEY);
+ } else {
+ if (System.getProperty(Constants.APPLICATION_KEY) != null) {
+ application = application + "-" + System.getProperty(Constants.APPLICATION_KEY);
+ }
+ }
+
+ if(remoteHost != null) {
+ address = getAddressByName(remoteHost);
+ connect(address, port);
+ } else {
+ String err = "The RemoteHost property is required for SocketAppender named "+ name;
+ LogLog.error(err);
+ throw new IllegalStateException(err);
+ }
+
+ if (layout == null) {
+ layout = new XMLLayout();
+ }
+
+ if (advertiseViaMulticastDNS) {
+ zeroConf = new ZeroConfSupport(ZONE, port, getName());
+ zeroConf.advertise();
+ }
+
+ super.activateOptions();
+ }
+
+ /**
+ Close this appender.
+ <p>This will mark the appender as closed and
+ call then {@link #cleanUp} method.
+ */
+ public synchronized void close() {
+ if (closed) {
+ return;
+ }
+
+ if (advertiseViaMulticastDNS) {
+ zeroConf.unadvertise();
+ }
+
+ this.closed = true;
+ cleanUp();
+ }
+
+ /**
+ Close the UDP Socket and release the underlying
+ connector thread if it has been created
+ */
+ public void cleanUp() {
+ if (outSocket != null) {
+ try {
+ outSocket.close();
+ } catch (Exception e) {
+ LogLog.error("Could not close outSocket.", e);
+ }
+
+ outSocket = null;
+ }
+ }
+
+ void connect(InetAddress address, int port) {
+ if (this.address == null) {
+ return;
+ }
+
+ try {
+ // First, close the previous connection if any.
+ cleanUp();
+ outSocket = new DatagramSocket();
+ outSocket.connect(address, port);
+ } catch (IOException e) {
+ LogLog.error(
+ "Could not open UDP Socket for sending.", e);
+ inError = true;
+ }
+ }
+
+ public void append(LoggingEvent event) {
+ if(inError) {
+ return;
+ }
+
+ if (event == null) {
+ return;
+ }
+
+ if (address == null) {
+ return;
+ }
+
+ if (outSocket != null) {
+ event.setProperty(Constants.HOSTNAME_KEY, hostname);
+ if (application != null) {
+ event.setProperty(Constants.APPLICATION_KEY, application);
+ }
+
+ try {
+ StringBuffer buf = new StringBuffer(layout.format(event));
+
+ byte[] payload;
+ if(encoding == null) {
+ payload = buf.toString().getBytes();
+ } else {
+ payload = buf.toString().getBytes(encoding);
+ }
+
+ DatagramPacket dp =
+ new DatagramPacket(payload, payload.length, address, port);
+ outSocket.send(dp);
+ } catch (IOException e) {
+ outSocket = null;
+ LogLog.warn("Detected problem with UDP connection: " + e);
+ }
+ }
+ }
+
+ public boolean isActive() {
+ return !inError;
+ }
+
+ InetAddress getAddressByName(String host) {
+ try {
+ return InetAddress.getByName(host);
+ } catch (Exception e) {
+ LogLog.error("Could not find address of [" + host + "].", e);
+ return null;
+ }
+ }
+
+ /**
+ The UDPAppender uses layouts. Hence, this method returns
+ <code>true</code>.
+ */
+ public boolean requiresLayout() {
+ return true;
+ }
+
+ /**
+ The <b>RemoteHost</b> option takes a string value which should be
+ the host name or ipaddress to send the UDP packets.
+ */
+ public void setRemoteHost(String host) {
+ remoteHost = host;
+ }
+
+ /**
+ Returns value of the <b>RemoteHost</b> option.
+ */
+ public String getRemoteHost() {
+ return remoteHost;
+ }
+
+ /**
+ 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.
+ */
+ public void setApplication(String app) {
+ this.application = app;
+ }
+
+ /**
+ Returns value of the <b>App</b> option.
+ */
+ public String getApplication() {
+ return application;
+ }
+
+ /**
+ The <b>Encoding</b> option specifies how the bytes are encoded. If this option is not specified,
+ the System encoding is used.
+ */
+ public void setEncoding(String encoding) {
+ this.encoding = encoding;
+ }
+
+ /**
+ Returns value of the <b>Encoding</b> option.
+ */
+ public String getEncoding() {
+ return encoding;
+ }
+
+ /**
+ The <b>Port</b> option takes a positive integer representing
+ the port where UDP packets will be sent.
+ */
+ public void setPort(int port) {
+ this.port = port;
+ }
+
+ /**
+ Returns value of the <b>Port</b> option.
+ */
+ public int getPort() {
+ return port;
+ }
+
+ public void setAdvertiseViaMulticastDNS(boolean advertiseViaMulticastDNS) {
+ this.advertiseViaMulticastDNS = advertiseViaMulticastDNS;
+ }
+
+ public boolean isAdvertiseViaMulticastDNS() {
+ return advertiseViaMulticastDNS;
+ }
+}
http://git-wip-us.apache.org/repos/asf/logging-chainsaw/blob/08c7be5c/src/main/java/org/apache/log4j/net/UDPReceiver.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/log4j/net/UDPReceiver.java b/src/main/java/org/apache/log4j/net/UDPReceiver.java
new file mode 100644
index 0000000..a8c7375
--- /dev/null
+++ b/src/main/java/org/apache/log4j/net/UDPReceiver.java
@@ -0,0 +1,281 @@
+/*
+ * 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 java.io.IOException;
+import java.net.DatagramPacket;
+import java.net.DatagramSocket;
+import java.net.SocketException;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+import org.apache.log4j.plugins.Pauseable;
+import org.apache.log4j.plugins.Receiver;
+import org.apache.log4j.spi.Decoder;
+import org.apache.log4j.spi.LoggingEvent;
+
+
+/**
+ * Receive LoggingEvents encoded with an XMLLayout, convert the XML data to a
+ * LoggingEvent and post the LoggingEvent.
+ *
+ * @author Scott Deboy <sd...@apache.org>
+ *
+ */
+public class UDPReceiver extends Receiver implements PortBased, Pauseable {
+ private static final int PACKET_LENGTH = 16384;
+ private UDPReceiverThread receiverThread;
+ private String encoding;
+
+ //default to log4j xml decoder
+ private String decoder = "org.apache.log4j.xml.XMLDecoder";
+ private Decoder decoderImpl;
+ protected boolean paused;
+ private transient boolean closed = false;
+ private int port;
+ private DatagramSocket socket;
+ UDPHandlerThread handlerThread;
+ private boolean advertiseViaMulticastDNS;
+ private ZeroConfSupport zeroConf;
+
+ /**
+ * The MulticastDNS zone advertised by a UDPReceiver
+ */
+ public static final String ZONE = "_log4j_xml_udp_receiver.local.";
+
+
+ public int getPort() {
+ return port;
+ }
+
+ public void setPort(int port) {
+ this.port = port;
+ }
+
+ /**
+ * The <b>Encoding</b> option specifies how the bytes are encoded. If this
+ * option is not specified, the system encoding will be used.
+ * */
+ public void setEncoding(String encoding) {
+ this.encoding = encoding;
+ }
+
+ /**
+ * Returns value of the <b>Encoding</b> option.
+ */
+ public String getEncoding() {
+ return encoding;
+ }
+
+ public String getDecoder() {
+ return decoder;
+ }
+
+ public void setDecoder(String decoder) {
+ this.decoder = decoder;
+ }
+
+ public boolean isPaused() {
+ return paused;
+ }
+
+ public void setPaused(boolean b) {
+ paused = b;
+ }
+
+ public void setAdvertiseViaMulticastDNS(boolean advertiseViaMulticastDNS) {
+ this.advertiseViaMulticastDNS = advertiseViaMulticastDNS;
+ }
+
+ public boolean isAdvertiseViaMulticastDNS() {
+ return advertiseViaMulticastDNS;
+ }
+
+ public synchronized void shutdown() {
+ if(closed == true) {
+ return;
+ }
+ closed = true;
+ active = false;
+ // Closing the datagram socket will unblock the UDPReceiverThread if it is
+ // was waiting to receive data from the socket.
+ if (socket != null) {
+ socket.close();
+ }
+
+ if (advertiseViaMulticastDNS) {
+ zeroConf.unadvertise();
+ }
+
+ try {
+ if(handlerThread != null) {
+ handlerThread.close();
+ handlerThread.join();
+ }
+ if(receiverThread != null) {
+ receiverThread.join();
+ }
+ } catch(InterruptedException ie) {
+ }
+ }
+
+ /**
+ Returns true if this receiver is active. */
+// public synchronized boolean isActive() {
+// return isActive;
+//}
+
+ public void activateOptions() {
+ try {
+ Class c = Class.forName(decoder);
+ Object o = c.newInstance();
+
+ if (o instanceof Decoder) {
+ this.decoderImpl = (Decoder) o;
+ }
+ } catch (ClassNotFoundException cnfe) {
+ getLogger().warn("Unable to find decoder", cnfe);
+ } catch (IllegalAccessException iae) {
+ getLogger().warn("Could not construct decoder", iae);
+ } catch (InstantiationException ie) {
+ getLogger().warn("Could not construct decoder", ie);
+ }
+
+ try {
+ socket = new DatagramSocket(port);
+ receiverThread = new UDPReceiverThread();
+ receiverThread.start();
+ handlerThread = new UDPHandlerThread();
+ handlerThread.start();
+ if (advertiseViaMulticastDNS) {
+ zeroConf = new ZeroConfSupport(ZONE, port, getName());
+ zeroConf.advertise();
+ }
+ active = true;
+ } catch (IOException ioe) {
+ ioe.printStackTrace();
+ }
+ }
+
+ class UDPHandlerThread extends Thread {
+ private List list = new ArrayList();
+
+ public UDPHandlerThread() {
+ setDaemon(true);
+ }
+
+ public void append(String data) {
+ synchronized (list) {
+ list.add(data);
+ list.notify();
+ }
+ }
+
+ /**
+ * Allow the UDPHandlerThread to wakeup and exit gracefully.
+ */
+ void close() {
+ synchronized(list) {
+ list.notify();
+ }
+ }
+
+ public void run() {
+ ArrayList list2 = new ArrayList();
+
+ while (!UDPReceiver.this.closed) {
+ synchronized (list) {
+ try {
+ while (!UDPReceiver.this.closed && list.size() == 0) {
+ list.wait(300);
+ }
+
+ if (list.size() > 0) {
+ list2.addAll(list);
+ list.clear();
+ }
+ } catch (InterruptedException ie) {
+ }
+ }
+
+ if (list2.size() > 0) {
+ Iterator iter = list2.iterator();
+
+ while (iter.hasNext()) {
+ String data = (String) iter.next();
+ List v = decoderImpl.decodeEvents(data);
+
+ if (v != null) {
+ Iterator eventIter = v.iterator();
+
+ while (eventIter.hasNext()) {
+ if (!isPaused()) {
+ doPost((LoggingEvent) eventIter.next());
+ }
+ }
+ }
+ }
+
+ list2.clear();
+ } else {
+ try {
+ synchronized (this) {
+ wait(1000);
+ }
+ } catch (InterruptedException ie) {
+ }
+ }
+ } // while
+ getLogger().debug(UDPReceiver.this.getName()+ "'s handler thread is exiting");
+ } // run
+ } // UDPHandlerThread
+
+ class UDPReceiverThread extends Thread {
+ public UDPReceiverThread() {
+ setDaemon(true);
+ }
+
+ public void run() {
+ byte[] b = new byte[PACKET_LENGTH];
+ DatagramPacket p = new DatagramPacket(b, b.length);
+
+ while (!UDPReceiver.this.closed) {
+ try {
+ socket.receive(p);
+
+ //this string constructor which accepts a charset throws an exception if it is
+ //null
+ if (encoding == null) {
+ handlerThread.append(
+ new String(p.getData(), 0, p.getLength()));
+ } else {
+ handlerThread.append(
+ new String(p.getData(), 0, p.getLength(), encoding));
+ }
+ } catch (SocketException se) {
+ //disconnected
+ } catch (IOException ioe) {
+ ioe.printStackTrace();
+ }
+ }
+
+ //LogLog.debug(UDPReceiver.this.getName() + "'s thread is ending.");
+ }
+ }
+}
http://git-wip-us.apache.org/repos/asf/logging-chainsaw/blob/08c7be5c/src/main/java/org/apache/log4j/net/XMLSocketNode.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/log4j/net/XMLSocketNode.java b/src/main/java/org/apache/log4j/net/XMLSocketNode.java
new file mode 100644
index 0000000..95ab638
--- /dev/null
+++ b/src/main/java/org/apache/log4j/net/XMLSocketNode.java
@@ -0,0 +1,205 @@
+/*
+ * 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.*;
+import org.apache.log4j.helpers.Constants;
+import org.apache.log4j.plugins.Receiver;
+import org.apache.log4j.spi.*;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+import java.net.Socket;
+
+import java.util.Iterator;
+import java.util.List;
+
+
+/**
+ Read {@link LoggingEvent} objects sent from a remote client using XML over
+ Sockets (TCP). These logging events are logged according to local
+ policy, as if they were generated locally.
+
+ <p>For example, the socket node might decide to log events to a
+ local file and also resent them to a second socket node.
+
+ @author Scott Deboy <sd...@apache.org>;
+
+ @since 0.8.4
+*/
+public class XMLSocketNode extends ComponentBase implements Runnable {
+ Socket socket;
+ Receiver receiver;
+ Decoder decoder;
+ SocketNodeEventListener listener;
+
+ /**
+ Constructor for socket and logger repository. */
+ public XMLSocketNode(
+ String decoder, Socket socket, LoggerRepository hierarchy) {
+ this.repository = hierarchy;
+ try {
+ Class c = Class.forName(decoder);
+ Object o = c.newInstance();
+
+ if (o instanceof Decoder) {
+ this.decoder = (Decoder) o;
+ }
+ } catch (ClassNotFoundException cnfe) {
+ getLogger().warn("Unable to find decoder", cnfe);
+ } catch (IllegalAccessException iae) {
+ getLogger().warn("Unable to construct decoder", iae);
+ } catch (InstantiationException ie) {
+ getLogger().warn("Unable to construct decoder", ie);
+ }
+
+ this.socket = socket;
+ }
+
+ /**
+ Constructor for socket and reciever. */
+ public XMLSocketNode(String decoder, Socket socket, Receiver receiver) {
+ try {
+ Class c = Class.forName(decoder);
+ Object o = c.newInstance();
+
+ if (o instanceof Decoder) {
+ this.decoder = (Decoder) o;
+ }
+ } catch (ClassNotFoundException cnfe) {
+ getLogger().warn("Unable to find decoder", cnfe);
+ } catch (IllegalAccessException iae) {
+ getLogger().warn("Unable to construct decoder", iae);
+ } catch (InstantiationException ie) {
+ getLogger().warn("Unable to construct decoder", ie);
+ }
+
+ this.socket = socket;
+ this.receiver = receiver;
+ }
+
+ /**
+ Set the event listener on this node. */
+ public void setListener(SocketNodeEventListener _listener) {
+ listener = _listener;
+ }
+
+ public void run() {
+ Logger remoteLogger;
+ Exception listenerException = null;
+ InputStream is = null;
+
+ if ((this.receiver == null) || (this.decoder == null)) {
+ is = null;
+ listenerException =
+ new Exception(
+ "No receiver or decoder provided. Cannot process xml socket events");
+ getLogger().error(
+ "Exception constructing XML Socket Receiver", listenerException);
+ }
+
+ try {
+ is = socket.getInputStream();
+ } catch (Exception e) {
+ is = null;
+ listenerException = e;
+ getLogger().error("Exception opening ObjectInputStream to " + socket, e);
+ }
+
+ if (is != null) {
+ String hostName = socket.getInetAddress().getHostName();
+ String remoteInfo = hostName + ":" + socket.getPort();
+
+ try {
+ //read data from the socket
+ //it's up to the individual decoder to handle incomplete event data
+ while (true) {
+ byte[] b = new byte[1024];
+ int length = is.read(b);
+ if (length == -1) {
+ getLogger().info(
+ "no bytes read from stream - closing connection.");
+ break;
+ }
+ List v = decoder.decodeEvents(new String(b, 0, length));
+
+ if (v != null) {
+ Iterator iter = v.iterator();
+
+ while (iter.hasNext()) {
+ LoggingEvent e = (LoggingEvent) iter.next();
+ e.setProperty(Constants.HOSTNAME_KEY, hostName);
+
+ // store the known remote info in an event property
+ e.setProperty("log4j.remoteSourceInfo", remoteInfo);
+
+ // if configured with a receiver, tell it to post the event
+ if (receiver != null) {
+ receiver.doPost(e);
+
+ // else post it via the hierarchy
+ } else {
+ // get a logger from the hierarchy. The name of the logger
+ // is taken to be the name contained in the event.
+ remoteLogger = repository.getLogger(e.getLoggerName());
+
+ //event.logger = remoteLogger;
+ // apply the logger-level filter
+ if (
+ e.getLevel().isGreaterOrEqual(
+ remoteLogger.getEffectiveLevel())) {
+ // finally log the event as if was generated locally
+ remoteLogger.callAppenders(e);
+ }
+ }
+ }
+ }
+ }
+ } catch (java.io.EOFException e) {
+ getLogger().info("Caught java.io.EOFException closing connection.");
+ listenerException = e;
+ } catch (java.net.SocketException e) {
+ getLogger().info(
+ "Caught java.net.SocketException closing connection.");
+ listenerException = e;
+ } catch (IOException e) {
+ getLogger().info("Caught java.io.IOException: " + e);
+ getLogger().info("Closing connection.");
+ listenerException = e;
+ } catch (Exception e) {
+ getLogger().error("Unexpected exception. Closing connection.", e);
+ listenerException = e;
+ }
+ }
+
+ // close the socket
+ try {
+ if (is != null) {
+ is.close();
+ }
+ } catch (Exception e) {
+ //logger.info("Could not close connection.", e);
+ }
+
+ // send event to listener, if configured
+ if (listener != null) {
+ listener.socketClosedEvent(listenerException);
+ }
+ }
+}