1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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
72
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
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
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
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 }