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