View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    * 
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   * 
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  
18  package org.apache.log4j.net;
19  
20  import java.io.IOException;
21  import java.net.DatagramPacket;
22  import java.net.InetAddress;
23  import java.net.MulticastSocket;
24  import java.net.UnknownHostException;
25  import java.util.HashMap;
26  import java.util.Hashtable;
27  import java.util.Map;
28  
29  import org.apache.log4j.AppenderSkeleton;
30  import org.apache.log4j.helpers.Constants;
31  import org.apache.log4j.spi.LoggingEvent;
32  import org.apache.log4j.helpers.LogLog;
33  import org.apache.log4j.xml.XMLLayout;
34  
35  
36  /**
37   *  Multicast-based Appender.  Works in conjunction with the MulticastReceiver, which expects
38   *  a LoggingEvent encoded using XMLLayout. 
39   * 
40   *  Sends log information as a multicast datagrams.
41   *
42   *  <p>Messages are not sent as LoggingEvent objects but as text after
43   *  applying XMLLayout.
44   *
45   *  <p>The port and remoteHost properties can be set in configuration properties.
46   *  By setting the remoteHost to a broadcast address any number of clients can
47   *  listen for log messages.
48   *
49   *  <p>This was inspired and really extended/copied from {@link SocketAppender}.  Please
50   *  see the docs for the proper credit to the authors of that class.
51   *
52   *  @author  <a href="mailto:kbrown@versatilesolutions.com">Kevin Brown</a>
53   *  @author Scott Deboy &lt;sdeboy@apache.org&gt;
54   * 
55   */
56  public class MulticastAppender extends AppenderSkeleton implements PortBased {
57    /**
58       The default port number for the multicast packets. (9991).
59    */
60    static final int DEFAULT_PORT = 9991;
61  
62    /**
63     * The MulticastDNS zone advertised by a MulticastAppender
64     * the MulticastAppender also adds a 'multicastAddress' property with the multicast address value as a string
65     */
66    public static final String ZONE = "_log4j_xml_mcast_appender.local.";
67  
68    /**
69       We remember host name as String in addition to the resolved
70       InetAddress so that it can be returned via getOption().
71    */
72    String hostname;
73    String remoteHost;
74    String application;
75    int timeToLive;
76    InetAddress address;
77    int port = DEFAULT_PORT;
78    MulticastSocket outSocket;
79    private String encoding;
80  
81    private boolean locationInfo = false;
82    private boolean advertiseViaMulticastDNS;
83    private ZeroConfSupport zeroConf;
84  
85    public MulticastAppender() {
86       super(false);
87    }
88  
89    /**
90       Open the multicast sender for the <b>RemoteHost</b> and <b>Port</b>.
91    */
92    public void activateOptions() {
93      try {
94        hostname = InetAddress.getLocalHost().getHostName();
95      } catch (UnknownHostException uhe) {
96        try {
97          hostname = InetAddress.getLocalHost().getHostAddress();
98        } catch (UnknownHostException uhe2) {
99          hostname = "unknown";
100       }
101     }
102 
103     //allow system property of application to be primary
104     if (application == null) {
105       application = System.getProperty(Constants.APPLICATION_KEY);
106     } else {
107       if (System.getProperty(Constants.APPLICATION_KEY) != null) {
108         application = application + "-" + System.getProperty(Constants.APPLICATION_KEY);
109       }
110     }
111 
112     if(remoteHost != null) {
113       address = getAddressByName(remoteHost);
114     } else {
115       String err = "The RemoteHost property is required for MulticastAppender named "+ name;
116       LogLog.error(err);
117       throw new IllegalStateException(err);
118     }
119 
120     if (layout == null) {
121         layout = new XMLLayout();
122     }
123       
124     if (advertiseViaMulticastDNS) {
125         Map properties = new HashMap();
126         properties.put("multicastAddress", remoteHost);
127         zeroConf = new ZeroConfSupport(ZONE, port, getName(), properties);
128         zeroConf.advertise();
129     }
130     connect();
131     super.activateOptions();
132   }
133 
134   /**
135      Close this appender.
136      <p>This will mark the appender as closed and
137      call then {@link #cleanUp} method.
138   */
139   public synchronized void close() {
140     if (closed) {
141       return;
142     }
143 
144     this.closed = true;
145     if (advertiseViaMulticastDNS) {
146         zeroConf.unadvertise();
147     }
148     cleanUp();
149   }
150 
151   /**
152      Close the Socket and release the underlying
153      connector thread if it has been created
154    */
155   public void cleanUp() {
156     if (outSocket != null) {
157       try {
158         outSocket.close();
159       } catch (Exception e) {
160         LogLog.error("Could not close outSocket.", e);
161       }
162 
163       outSocket = null;
164     }
165   }
166 
167   void connect() {
168     if (this.address == null) {
169       return;
170     }
171 
172     try {
173       // First, close the previous connection if any.
174       cleanUp();
175       outSocket = new MulticastSocket();
176       outSocket.setTimeToLive(timeToLive);
177     } catch (IOException e) {
178       LogLog.error("Error in connect method of MulticastAppender named "+name, e);
179     }
180   }
181 
182   public void append(LoggingEvent event) {
183     if (event == null) {
184       return;
185     }
186 
187     if(locationInfo) {
188 	   event.getLocationInformation();
189 	}
190 
191     if (outSocket != null) {
192         event.setProperty(Constants.HOSTNAME_KEY, hostname);
193 
194         if (application != null) {
195           event.setProperty(Constants.APPLICATION_KEY, application);
196         }
197       
198 		if(locationInfo) {
199 		   event.getLocationInformation();
200 		}
201 
202 
203       try {
204         StringBuffer buf = new StringBuffer(layout.format(event));
205 
206         byte[] payload;
207         if(encoding == null) {
208           payload = buf.toString().getBytes();
209         } else {
210           payload = buf.toString().getBytes(encoding);
211         }
212 
213         DatagramPacket dp =
214            new DatagramPacket(payload, payload.length, address, port);
215         outSocket.send(dp);
216       } catch (IOException e) {
217         outSocket = null;
218         LogLog.warn("Detected problem with Multicast connection: " + e);
219       }
220     }
221   }
222 
223   InetAddress getAddressByName(String host) {
224     try {
225       return InetAddress.getByName(host);
226     } catch (Exception e) {
227       LogLog.error("Could not find address of [" + host + "].", e);
228       return null;
229     }
230   }
231 
232   /**
233      The <b>RemoteHost</b> option takes a string value which should be
234      the host name or ipaddress to send the multicast packets.
235    */
236   public void setRemoteHost(String host) {
237     remoteHost = host;
238   }
239 
240   /**
241      Returns value of the <b>RemoteHost</b> option.
242    */
243   public String getRemoteHost() {
244     return remoteHost;
245   }
246 
247   /**
248   The <b>LocationInfo</b> option takes a boolean value. If true,
249   the information sent to the remote host will include location
250   information. By default no location information is sent to the server.
251    */
252   public void setLocationInfo(boolean locationInfo) {
253 	  this.locationInfo = locationInfo;
254   }
255 
256   /**
257    * Returns value of the <b>LocationInfo</b> option.
258    */
259   public boolean getLocationInfo() {
260 	  return locationInfo;
261   }
262 
263   /**
264       The <b>Encoding</b> option specifies how the bytes are encoded.  If this option is not specified, 
265       the System encoding is used.
266     */
267    public void setEncoding(String encoding) {
268      this.encoding = encoding;
269    }
270 
271    /**
272       Returns value of the <b>Encoding</b> option.
273     */
274    public String getEncoding() {
275      return encoding;
276    }
277   /**
278      The <b>App</b> option takes a string value which should be the name of the application getting logged.
279      If property was already set (via system property), don't set here.
280    */
281   public void setApplication(String app) {
282     this.application = app;
283   }
284 
285   /**
286      Returns value of the <b>App</b> option.
287    */
288   public String getApplication() {
289     return application;
290   }
291 
292   /**
293      The <b>Time to live</b> option takes a positive integer representing
294      the time to live value.
295    */
296   public void setTimeToLive(int timeToLive) {
297     this.timeToLive = timeToLive;
298   }
299 
300   /**
301      Returns value of the <b>Time to Live</b> option.
302    */
303   public int getTimeToLive() {
304     return timeToLive;
305   }
306 
307   /**
308      The <b>Port</b> option takes a positive integer representing
309      the port where multicast packets will be sent.
310    */
311   public void setPort(int port) {
312     this.port = port;
313   }
314 
315   /**
316      Returns value of the <b>Port</b> option.
317    */
318   public int getPort() {
319     return port;
320   }
321 
322   /* (non-Javadoc)
323    * @see org.apache.log4j.net.NetworkBased#isActive()
324    */
325   public boolean isActive() {
326     // TODO handle active/inactive
327     return true;
328   }
329 
330     /**
331      * Gets whether appender requires a layout.
332      * @return false
333      */
334   public boolean requiresLayout() {
335       return true;
336   }
337 
338   public boolean isAdvertiseViaMulticastDNS() {
339       return advertiseViaMulticastDNS;
340   }
341 
342   public void setAdvertiseViaMulticastDNS(boolean advertiseViaMulticastDNS) {
343       this.advertiseViaMulticastDNS = advertiseViaMulticastDNS;
344   }
345 }