blob: 2e4cff81b8871cbb5a842ec6d8eeb5e1e531ba25 [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 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.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 &lt;sdeboy@apache.org&gt;
*
*/
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<String, String> 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 {
StringBuilder buf = new StringBuilder(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;
}
}