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