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.xml.XMLLayout;
24  import org.apache.log4j.helpers.LogLog;
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 <sdeboy@apache.org>
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    // if there is something irrecoverably wrong with the settings, there is no
72    // point in sending out packeets.
73    boolean inError = false;
74    
75    public UDPAppender() {
76        super(false);
77    }
78  
79    /***
80       Sends UDP packets to the <code>address</code> and <code>port</code>.
81    */
82    public UDPAppender(final InetAddress address, final int port) {
83      super(false);
84      this.address = address;
85      this.remoteHost = address.getHostName();
86      this.port = port;
87      activateOptions();
88    }
89  
90    /***
91       Sends UDP packets to the <code>address</code> and <code>port</code>.
92    */
93    public UDPAppender(final String host, final int port) {
94      super(false);
95      this.port = port;
96      this.address = getAddressByName(host);
97      this.remoteHost = host;
98      activateOptions();
99    }
100 
101   /***
102      Open the UDP sender for the <b>RemoteHost</b> and <b>Port</b>.
103   */
104   public void activateOptions() {
105     try {
106       hostname = InetAddress.getLocalHost().getHostName();
107     } catch (UnknownHostException uhe) {
108       try {
109         hostname = InetAddress.getLocalHost().getHostAddress();
110       } catch (UnknownHostException uhe2) {
111         hostname = "unknown";
112       }
113     }
114 
115     //allow system property of application to be primary
116     if (application == null) {
117       application = System.getProperty(Constants.APPLICATION_KEY);
118     } else {
119       if (System.getProperty(Constants.APPLICATION_KEY) != null) {
120         application = application + "-" + System.getProperty(Constants.APPLICATION_KEY);
121       }
122     }
123 
124     if(remoteHost != null) {
125       address = getAddressByName(remoteHost);
126       connect(address, port);
127     } else {
128       String err = "The RemoteHost property is required for SocketAppender named "+ name;
129       LogLog.error(err);
130       throw new IllegalStateException(err);
131     }
132     super.activateOptions();
133   }
134 
135   /***
136      Close this appender.
137      <p>This will mark the appender as closed and
138      call then {@link #cleanUp} method.
139   */
140   public synchronized void close() {
141     if (closed) {
142       return;
143     }
144 
145     this.closed = true;
146     cleanUp();
147   }
148 
149   /***
150      Close the UDP 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(InetAddress address, int port) {
166     if (this.address == null) {
167       return;
168     }
169 
170     try {
171       // First, close the previous connection if any.
172       cleanUp();
173       outSocket = new DatagramSocket();
174       outSocket.connect(address, port);
175     } catch (IOException e) {
176       LogLog.error(
177         "Could not open UDP Socket for sending.", e);
178       inError = true;
179     }
180   }
181 
182   public void append(LoggingEvent event) {
183     if(inError) {
184       return;
185     }
186     
187     if (event == null) {
188       return;
189     }
190 
191     if (address == null) {
192       return;
193     }
194 
195     if (outSocket != null) {
196       event.setProperty(Constants.HOSTNAME_KEY, hostname);
197       if (application != null) {
198         event.setProperty(Constants.APPLICATION_KEY, application);
199       }
200 
201       try {
202         // TODO UDPAppender throws NullPointerException if the layout is not set
203         StringBuffer buf = new StringBuffer(layout.format(event));
204 
205         byte[] payload;
206         if(encoding == null) {
207           payload = buf.toString().getBytes();
208         } else {
209           payload = buf.toString().getBytes(encoding);
210         }
211 
212         DatagramPacket dp =
213            new DatagramPacket(payload, payload.length, address, port);
214         outSocket.send(dp);
215       } catch (IOException e) {
216         outSocket = null;
217         LogLog.warn("Detected problem with UDP connection: " + e);
218       }
219     }
220   }
221 
222   public boolean isActive() {
223     return !inError;
224   }
225   
226   InetAddress getAddressByName(String host) {
227     try {
228       return InetAddress.getByName(host);
229     } catch (Exception e) {
230       LogLog.error("Could not find address of [" + host + "].", e);
231       return null;
232     }
233   }
234 
235   /***
236      The UDPAppender uses layouts. Hence, this method returns
237      <code>true</code>.
238   */
239   public boolean requiresLayout() {
240     return true;
241   }
242 
243   /***
244      The <b>RemoteHost</b> option takes a string value which should be
245      the host name or ipaddress to send the UDP packets.
246    */
247   public void setRemoteHost(String host) {
248     remoteHost = host;
249   }
250 
251   /***
252      Returns value of the <b>RemoteHost</b> option.
253    */
254   public String getRemoteHost() {
255     return remoteHost;
256   }
257 
258   /***
259      The <b>App</b> option takes a string value which should be the name of the application getting logged.
260      If property was already set (via system property), don't set here.
261    */
262   public void setApplication(String app) {
263     this.application = app;
264   }
265 
266   /***
267      Returns value of the <b>App</b> option.
268    */
269   public String getApplication() {
270     return application;
271   }
272 
273   /***
274      The <b>Encoding</b> option specifies how the bytes are encoded.  If this option is not specified, 
275      the System encoding is used.
276    */
277   public void setEncoding(String encoding) {
278     this.encoding = encoding;
279   }
280 
281   /***
282      Returns value of the <b>Encoding</b> option.
283    */
284   public String getEncoding() {
285     return encoding;
286   }
287 
288     /***
289      The <b>Port</b> option takes a positive integer representing
290      the port where UDP packets will be sent.
291    */
292   public void setPort(int port) {
293     this.port = port;
294   }
295 
296   /***
297      Returns value of the <b>Port</b> option.
298    */
299   public int getPort() {
300     return port;
301   }
302 
303 }