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