/apache-log4j-1.2.17/src/main/java/org/apache/log4j/net/SocketAppender.java
Java | 474 lines | 231 code | 49 blank | 194 comment | 30 complexity | c83e2fbc9e5d4851e2084bb6816c1a58 MD5 | raw file
Possible License(s): Apache-2.0
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// Contributors: Dan MacDonald <dan@redknee.com>
19
20package org.apache.log4j.net;
21
22import java.io.IOException;
23import java.io.ObjectOutputStream;
24import java.io.InterruptedIOException;
25import java.net.InetAddress;
26import java.net.Socket;
27
28import org.apache.log4j.AppenderSkeleton;
29import org.apache.log4j.helpers.LogLog;
30import org.apache.log4j.spi.ErrorCode;
31import org.apache.log4j.spi.LoggingEvent;
32
33/**
34 Sends {@link LoggingEvent} objects to a remote a log server,
35 usually a {@link SocketNode}.
36
37 <p>The SocketAppender has the following properties:
38
39 <ul>
40
41 <p><li>If sent to a {@link SocketNode}, remote logging is
42 non-intrusive as far as the log event is concerned. In other
43 words, the event will be logged with the same time stamp, {@link
44 org.apache.log4j.NDC}, location info as if it were logged locally by
45 the client.
46
47 <p><li>SocketAppenders do not use a layout. They ship a
48 serialized {@link LoggingEvent} object to the server side.
49
50 <p><li>Remote logging uses the TCP protocol. Consequently, if
51 the server is reachable, then log events will eventually arrive
52 at the server.
53
54 <p><li>If the remote server is down, the logging requests are
55 simply dropped. However, if and when the server comes back up,
56 then event transmission is resumed transparently. This
57 transparent reconneciton is performed by a <em>connector</em>
58 thread which periodically attempts to connect to the server.
59
60 <p><li>Logging events are automatically <em>buffered</em> by the
61 native TCP implementation. This means that if the link to server
62 is slow but still faster than the rate of (log) event production
63 by the client, the client will not be affected by the slow
64 network connection. However, if the network connection is slower
65 then the rate of event production, then the client can only
66 progress at the network rate. In particular, if the network link
67 to the the server is down, the client will be blocked.
68
69 <p>On the other hand, if the network link is up, but the server
70 is down, the client will not be blocked when making log requests
71 but the log events will be lost due to server unavailability.
72
73 <p><li>Even if a <code>SocketAppender</code> is no longer
74 attached to any category, it will not be garbage collected in
75 the presence of a connector thread. A connector thread exists
76 only if the connection to the server is down. To avoid this
77 garbage collection problem, you should {@link #close} the the
78 <code>SocketAppender</code> explicitly. See also next item.
79
80 <p>Long lived applications which create/destroy many
81 <code>SocketAppender</code> instances should be aware of this
82 garbage collection problem. Most other applications can safely
83 ignore it.
84
85 <p><li>If the JVM hosting the <code>SocketAppender</code> exits
86 before the <code>SocketAppender</code> is closed either
87 explicitly or subsequent to garbage collection, then there might
88 be untransmitted data in the pipe which might be lost. This is a
89 common problem on Windows based systems.
90
91 <p>To avoid lost data, it is usually sufficient to {@link
92 #close} the <code>SocketAppender</code> either explicitly or by
93 calling the {@link org.apache.log4j.LogManager#shutdown} method
94 before exiting the application.
95
96
97 </ul>
98
99 @author Ceki Gülcü
100 @since 0.8.4 */
101
102public class SocketAppender extends AppenderSkeleton {
103
104 /**
105 The default port number of remote logging server (4560).
106 @since 1.2.15
107 */
108 static public final int DEFAULT_PORT = 4560;
109
110 /**
111 The default reconnection delay (30000 milliseconds or 30 seconds).
112 */
113 static final int DEFAULT_RECONNECTION_DELAY = 30000;
114
115 /**
116 We remember host name as String in addition to the resolved
117 InetAddress so that it can be returned via getOption().
118 */
119 String remoteHost;
120
121 /**
122 * The MulticastDNS zone advertised by a SocketAppender
123 */
124 public static final String ZONE = "_log4j_obj_tcpconnect_appender.local.";
125
126 InetAddress address;
127 int port = DEFAULT_PORT;
128 ObjectOutputStream oos;
129 int reconnectionDelay = DEFAULT_RECONNECTION_DELAY;
130 boolean locationInfo = false;
131 private String application;
132
133 private Connector connector;
134
135 int counter = 0;
136
137 // reset the ObjectOutputStream every 70 calls
138 //private static final int RESET_FREQUENCY = 70;
139 private static final int RESET_FREQUENCY = 1;
140 private boolean advertiseViaMulticastDNS;
141 private ZeroConfSupport zeroConf;
142
143 public SocketAppender() {
144 }
145
146 /**
147 Connects to remote server at <code>address</code> and <code>port</code>.
148 */
149 public SocketAppender(InetAddress address, int port) {
150 this.address = address;
151 this.remoteHost = address.getHostName();
152 this.port = port;
153 connect(address, port);
154 }
155
156 /**
157 Connects to remote server at <code>host</code> and <code>port</code>.
158 */
159 public SocketAppender(String host, int port) {
160 this.port = port;
161 this.address = getAddressByName(host);
162 this.remoteHost = host;
163 connect(address, port);
164 }
165
166 /**
167 Connect to the specified <b>RemoteHost</b> and <b>Port</b>.
168 */
169 public void activateOptions() {
170 if (advertiseViaMulticastDNS) {
171 zeroConf = new ZeroConfSupport(ZONE, port, getName());
172 zeroConf.advertise();
173 }
174 connect(address, port);
175 }
176
177 /**
178 * Close this appender.
179 *
180 * <p>This will mark the appender as closed and call then {@link
181 * #cleanUp} method.
182 * */
183 synchronized public void close() {
184 if(closed)
185 return;
186
187 this.closed = true;
188 if (advertiseViaMulticastDNS) {
189 zeroConf.unadvertise();
190 }
191
192 cleanUp();
193 }
194
195 /**
196 * Drop the connection to the remote host and release the underlying
197 * connector thread if it has been created
198 * */
199 public void cleanUp() {
200 if(oos != null) {
201 try {
202 oos.close();
203 } catch(IOException e) {
204 if (e instanceof InterruptedIOException) {
205 Thread.currentThread().interrupt();
206 }
207 LogLog.error("Could not close oos.", e);
208 }
209 oos = null;
210 }
211 if(connector != null) {
212 //LogLog.debug("Interrupting the connector.");
213 connector.interrupted = true;
214 connector = null; // allow gc
215 }
216 }
217
218 void connect(InetAddress address, int port) {
219 if(this.address == null)
220 return;
221 try {
222 // First, close the previous connection if any.
223 cleanUp();
224 oos = new ObjectOutputStream(new Socket(address, port).getOutputStream());
225 } catch(IOException e) {
226 if (e instanceof InterruptedIOException) {
227 Thread.currentThread().interrupt();
228 }
229 String msg = "Could not connect to remote log4j server at ["
230 +address.getHostName()+"].";
231 if(reconnectionDelay > 0) {
232 msg += " We will try again later.";
233 fireConnector(); // fire the connector thread
234 } else {
235 msg += " We are not retrying.";
236 errorHandler.error(msg, e, ErrorCode.GENERIC_FAILURE);
237 }
238 LogLog.error(msg);
239 }
240 }
241
242
243 public void append(LoggingEvent event) {
244 if(event == null)
245 return;
246
247 if(address==null) {
248 errorHandler.error("No remote host is set for SocketAppender named \""+
249 this.name+"\".");
250 return;
251 }
252
253 if(oos != null) {
254 try {
255
256 if(locationInfo) {
257 event.getLocationInformation();
258 }
259 if (application != null) {
260 event.setProperty("application", application);
261 }
262 event.getNDC();
263 event.getThreadName();
264 event.getMDCCopy();
265 event.getRenderedMessage();
266 event.getThrowableStrRep();
267
268 oos.writeObject(event);
269 //LogLog.debug("=========Flushing.");
270 oos.flush();
271 if(++counter >= RESET_FREQUENCY) {
272 counter = 0;
273 // Failing to reset the object output stream every now and
274 // then creates a serious memory leak.
275 //System.err.println("Doing oos.reset()");
276 oos.reset();
277 }
278 } catch(IOException e) {
279 if (e instanceof InterruptedIOException) {
280 Thread.currentThread().interrupt();
281 }
282 oos = null;
283 LogLog.warn("Detected problem with connection: "+e);
284 if(reconnectionDelay > 0) {
285 fireConnector();
286 } else {
287 errorHandler.error("Detected problem with connection, not reconnecting.", e,
288 ErrorCode.GENERIC_FAILURE);
289 }
290 }
291 }
292 }
293
294 public void setAdvertiseViaMulticastDNS(boolean advertiseViaMulticastDNS) {
295 this.advertiseViaMulticastDNS = advertiseViaMulticastDNS;
296 }
297
298 public boolean isAdvertiseViaMulticastDNS() {
299 return advertiseViaMulticastDNS;
300 }
301
302 void fireConnector() {
303 if(connector == null) {
304 LogLog.debug("Starting a new connector thread.");
305 connector = new Connector();
306 connector.setDaemon(true);
307 connector.setPriority(Thread.MIN_PRIORITY);
308 connector.start();
309 }
310 }
311
312 static
313 InetAddress getAddressByName(String host) {
314 try {
315 return InetAddress.getByName(host);
316 } catch(Exception e) {
317 if (e instanceof InterruptedIOException || e instanceof InterruptedException) {
318 Thread.currentThread().interrupt();
319 }
320 LogLog.error("Could not find address of ["+host+"].", e);
321 return null;
322 }
323 }
324
325 /**
326 * The SocketAppender does not use a layout. Hence, this method
327 * returns <code>false</code>.
328 * */
329 public boolean requiresLayout() {
330 return false;
331 }
332
333 /**
334 * The <b>RemoteHost</b> option takes a string value which should be
335 * the host name of the server where a {@link SocketNode} is
336 * running.
337 * */
338 public void setRemoteHost(String host) {
339 address = getAddressByName(host);
340 remoteHost = host;
341 }
342
343 /**
344 Returns value of the <b>RemoteHost</b> option.
345 */
346 public String getRemoteHost() {
347 return remoteHost;
348 }
349
350 /**
351 The <b>Port</b> option takes a positive integer representing
352 the port where the server is waiting for connections.
353 */
354 public void setPort(int port) {
355 this.port = port;
356 }
357
358 /**
359 Returns value of the <b>Port</b> option.
360 */
361 public int getPort() {
362 return port;
363 }
364
365 /**
366 The <b>LocationInfo</b> option takes a boolean value. If true,
367 the information sent to the remote host will include location
368 information. By default no location information is sent to the server.
369 */
370 public void setLocationInfo(boolean locationInfo) {
371 this.locationInfo = locationInfo;
372 }
373
374 /**
375 Returns value of the <b>LocationInfo</b> option.
376 */
377 public boolean getLocationInfo() {
378 return locationInfo;
379 }
380
381 /**
382 * The <b>App</b> option takes a string value which should be the name of the
383 * application getting logged.
384 * If property was already set (via system property), don't set here.
385 * @since 1.2.15
386 */
387 public void setApplication(String lapp) {
388 this.application = lapp;
389 }
390
391 /**
392 * Returns value of the <b>Application</b> option.
393 * @since 1.2.15
394 */
395 public String getApplication() {
396 return application;
397 }
398
399 /**
400 The <b>ReconnectionDelay</b> option takes a positive integer
401 representing the number of milliseconds to wait between each
402 failed connection attempt to the server. The default value of
403 this option is 30000 which corresponds to 30 seconds.
404
405 <p>Setting this option to zero turns off reconnection
406 capability.
407 */
408 public void setReconnectionDelay(int delay) {
409 this.reconnectionDelay = delay;
410 }
411
412 /**
413 Returns value of the <b>ReconnectionDelay</b> option.
414 */
415 public int getReconnectionDelay() {
416 return reconnectionDelay;
417 }
418
419 /**
420 The Connector will reconnect when the server becomes available
421 again. It does this by attempting to open a new connection every
422 <code>reconnectionDelay</code> milliseconds.
423
424 <p>It stops trying whenever a connection is established. It will
425 restart to try reconnect to the server when previously open
426 connection is droppped.
427
428 @author Ceki Gülcü
429 @since 0.8.4
430 */
431 class Connector extends Thread {
432
433 boolean interrupted = false;
434
435 public
436 void run() {
437 Socket socket;
438 while(!interrupted) {
439 try {
440 sleep(reconnectionDelay);
441 LogLog.debug("Attempting connection to "+address.getHostName());
442 socket = new Socket(address, port);
443 synchronized(this) {
444 oos = new ObjectOutputStream(socket.getOutputStream());
445 connector = null;
446 LogLog.debug("Connection established. Exiting connector thread.");
447 break;
448 }
449 } catch(InterruptedException e) {
450 LogLog.debug("Connector interrupted. Leaving loop.");
451 return;
452 } catch(java.net.ConnectException e) {
453 LogLog.debug("Remote host "+address.getHostName()
454 +" refused connection.");
455 } catch(IOException e) {
456 if (e instanceof InterruptedIOException) {
457 Thread.currentThread().interrupt();
458 }
459 LogLog.debug("Could not connect to " + address.getHostName()+
460 ". Exception is " + e);
461 }
462 }
463 //LogLog.debug("Exiting Connector.run() method.");
464 }
465
466 /**
467 public
468 void finalize() {
469 LogLog.debug("Connector finalize() has been called.");
470 }
471 */
472 }
473
474}