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
9 * http://www.apache.org/licenses/LICENSE-2.0
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.
18 // Contributors: Dan MacDonald <dan@redknee.com>
20 package org.apache.log4j.net;
22 import java.io.IOException;
23 import java.io.ObjectOutputStream;
24 import java.io.InterruptedIOException;
25 import java.net.InetAddress;
26 import java.net.Socket;
28 import org.apache.log4j.AppenderSkeleton;
29 import org.apache.log4j.helpers.LogLog;
30 import org.apache.log4j.spi.ErrorCode;
31 import org.apache.log4j.spi.LoggingEvent;
34 Sends {@link LoggingEvent} objects to a remote a log server,
35 usually a {@link SocketNode}.
37 <p>The SocketAppender has the following properties:
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
47 <p><li>SocketAppenders do not use a layout. They ship a
48 serialized {@link LoggingEvent} object to the server side.
50 <p><li>Remote logging uses the TCP protocol. Consequently, if
51 the server is reachable, then log events will eventually arrive
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.
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.
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.
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.
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
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.
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.
99 @author Ceki Gülcü
102 public class SocketAppender extends AppenderSkeleton {
105 The default port number of remote logging server (4560).
108 static public final int DEFAULT_PORT = 4560;
111 The default reconnection delay (30000 milliseconds or 30 seconds).
113 static final int DEFAULT_RECONNECTION_DELAY = 30000;
116 We remember host name as String in addition to the resolved
117 InetAddress so that it can be returned via getOption().
122 * The MulticastDNS zone advertised by a SocketAppender
124 public static final String ZONE = "_log4j_obj_tcpconnect_appender.local.";
127 int port = DEFAULT_PORT;
128 ObjectOutputStream oos;
129 int reconnectionDelay = DEFAULT_RECONNECTION_DELAY;
130 boolean locationInfo = false;
131 private String application;
133 private Connector connector;
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;
143 public SocketAppender() {
147 Connects to remote server at <code>address</code> and <code>port</code>.
149 public SocketAppender(InetAddress address, int port) {
150 this.address = address;
151 this.remoteHost = address.getHostName();
153 connect(address, port);
157 Connects to remote server at <code>host</code> and <code>port</code>.
159 public SocketAppender(String host, int port) {
161 this.address = getAddressByName(host);
162 this.remoteHost = host;
163 connect(address, port);
167 Connect to the specified <b>RemoteHost</b> and <b>Port</b>.
169 public void activateOptions() {
170 if (advertiseViaMulticastDNS) {
171 zeroConf = new ZeroConfSupport(ZONE, port, getName());
172 zeroConf.advertise();
174 connect(address, port);
178 * Close this appender.
180 * <p>This will mark the appender as closed and call then {@link
183 synchronized public void close() {
189 if (advertiseViaMulticastDNS) {
190 zeroConf.unadvertise();
197 * Drop the connection to the remote host and release the underlying
198 * connector thread if it has been created
200 public void cleanUp() {
204 } catch(IOException e) {
205 if (e instanceof InterruptedIOException) {
206 Thread.currentThread().interrupt();
208 LogLog.error("Could not close oos.", e);
212 if(connector != null) {
213 //LogLog.debug("Interrupting the connector.");
214 connector.interrupted = true;
215 connector = null; // allow gc
219 void connect(InetAddress address, int port) {
220 if(this.address == null) {
224 // First, close the previous connection if any.
226 oos = new ObjectOutputStream(new Socket(address, port).getOutputStream());
227 } catch(IOException e) {
228 if (e instanceof InterruptedIOException) {
229 Thread.currentThread().interrupt();
231 String msg = "Could not connect to remote log4j server at ["
232 +address.getHostName()+"].";
233 if(reconnectionDelay > 0) {
234 msg += " We will try again later.";
235 fireConnector(); // fire the connector thread
237 msg += " We are not retrying.";
238 errorHandler.error(msg, e, ErrorCode.GENERIC_FAILURE);
245 public void append(LoggingEvent event) {
251 errorHandler.error("No remote host is set for SocketAppender named \""+
260 event.getLocationInformation();
262 if (application != null) {
263 event.setProperty("application", application);
266 event.getThreadName();
268 event.getRenderedMessage();
269 event.getThrowableStrRep();
271 oos.writeObject(event);
272 //LogLog.debug("=========Flushing.");
274 if(++counter >= RESET_FREQUENCY) {
276 // Failing to reset the object output stream every now and
277 // then creates a serious memory leak.
278 //System.err.println("Doing oos.reset()");
281 } catch(IOException e) {
282 if (e instanceof InterruptedIOException) {
283 Thread.currentThread().interrupt();
286 LogLog.warn("Detected problem with connection: "+e);
287 if(reconnectionDelay > 0) {
290 errorHandler.error("Detected problem with connection, not reconnecting.", e,
291 ErrorCode.GENERIC_FAILURE);
297 public void setAdvertiseViaMulticastDNS(boolean advertiseViaMulticastDNS) {
298 this.advertiseViaMulticastDNS = advertiseViaMulticastDNS;
301 public boolean isAdvertiseViaMulticastDNS() {
302 return advertiseViaMulticastDNS;
305 void fireConnector() {
306 if(connector == null) {
307 LogLog.debug("Starting a new connector thread.");
308 connector = new Connector();
309 connector.setDaemon(true);
310 connector.setPriority(Thread.MIN_PRIORITY);
316 InetAddress getAddressByName(String host) {
318 return InetAddress.getByName(host);
319 } catch(Exception e) {
320 if (e instanceof InterruptedIOException || e instanceof InterruptedException) {
321 Thread.currentThread().interrupt();
323 LogLog.error("Could not find address of ["+host+"].", e);
329 * The SocketAppender does not use a layout. Hence, this method
330 * returns <code>false</code>.
332 public boolean requiresLayout() {
337 * The <b>RemoteHost</b> option takes a string value which should be
338 * the host name of the server where a {@link SocketNode} is
341 public void setRemoteHost(String host) {
342 address = getAddressByName(host);
347 Returns value of the <b>RemoteHost</b> option.
349 public String getRemoteHost() {
354 The <b>Port</b> option takes a positive integer representing
355 the port where the server is waiting for connections.
357 public void setPort(int port) {
362 Returns value of the <b>Port</b> option.
364 public int getPort() {
369 The <b>LocationInfo</b> option takes a boolean value. If true,
370 the information sent to the remote host will include location
371 information. By default no location information is sent to the server.
373 public void setLocationInfo(boolean locationInfo) {
374 this.locationInfo = locationInfo;
378 Returns value of the <b>LocationInfo</b> option.
380 public boolean getLocationInfo() {
385 * The <b>App</b> option takes a string value which should be the name of the
386 * application getting logged.
387 * If property was already set (via system property), don't set here.
390 public void setApplication(String lapp) {
391 this.application = lapp;
395 * Returns value of the <b>Application</b> option.
398 public String getApplication() {
403 The <b>ReconnectionDelay</b> option takes a positive integer
404 representing the number of milliseconds to wait between each
405 failed connection attempt to the server. The default value of
406 this option is 30000 which corresponds to 30 seconds.
408 <p>Setting this option to zero turns off reconnection
411 public void setReconnectionDelay(int delay) {
412 this.reconnectionDelay = delay;
416 Returns value of the <b>ReconnectionDelay</b> option.
418 public int getReconnectionDelay() {
419 return reconnectionDelay;
423 The Connector will reconnect when the server becomes available
424 again. It does this by attempting to open a new connection every
425 <code>reconnectionDelay</code> milliseconds.
427 <p>It stops trying whenever a connection is established. It will
428 restart to try reconnect to the server when previously open
429 connection is droppped.
431 @author Ceki Gülcü
434 class Connector extends Thread {
436 boolean interrupted = false;
441 while(!interrupted) {
443 sleep(reconnectionDelay);
444 LogLog.debug("Attempting connection to "+address.getHostName());
445 socket = new Socket(address, port);
447 oos = new ObjectOutputStream(socket.getOutputStream());
449 LogLog.debug("Connection established. Exiting connector thread.");
452 } catch(InterruptedException e) {
453 LogLog.debug("Connector interrupted. Leaving loop.");
455 } catch(java.net.ConnectException e) {
456 LogLog.debug("Remote host "+address.getHostName()
457 +" refused connection.");
458 } catch(IOException e) {
459 if (e instanceof InterruptedIOException) {
460 Thread.currentThread().interrupt();
462 LogLog.debug("Could not connect to " + address.getHostName()+
463 ". Exception is " + e);
466 //LogLog.debug("Exiting Connector.run() method.");
472 LogLog.debug("Connector finalize() has been called.");