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