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