| /* |
| * 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 <sdeboy@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; |
| } |
| } |