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 package org.apache.log4j.net;
20 import java.io.IOException;
21 import java.io.InterruptedIOException;
22 import java.io.ObjectOutputStream;
23 import java.net.InetAddress;
24 import java.net.ServerSocket;
25 import java.net.Socket;
26 import java.net.SocketException;
27 import java.util.Vector;
29 import org.apache.log4j.AppenderSkeleton;
30 import org.apache.log4j.helpers.CyclicBuffer;
31 import org.apache.log4j.helpers.LogLog;
32 import org.apache.log4j.spi.LoggingEvent;
35 Sends {@link LoggingEvent} objects to a set of remote log servers,
36 usually a {@link SocketNode SocketNodes}.
38 <p>Acts just like {@link SocketAppender} except that instead of
39 connecting to a given remote log server,
40 <code>SocketHubAppender</code> accepts connections from the remote
41 log servers as clients. It can accept more than one connection.
42 When a log event is received, the event is sent to the set of
43 currently connected remote log servers. Implemented this way it does
44 not require any update to the configuration file to send data to
45 another remote log server. The remote log server simply connects to
46 the host and port the <code>SocketHubAppender</code> is running on.
48 <p>The <code>SocketHubAppender</code> does not store events such
49 that the remote side will events that arrived after the
50 establishment of its connection. Once connected, events arrive in
51 order as guaranteed by the TCP protocol.
53 <p>This implementation borrows heavily from the {@link
56 <p>The SocketHubAppender has the following characteristics:
60 <p><li>If sent to a {@link SocketNode}, logging is non-intrusive as
61 far as the log event is concerned. In other words, the event will be
62 logged with the same time stamp, {@link org.apache.log4j.NDC},
63 location info as if it were logged locally.
65 <p><li><code>SocketHubAppender</code> does not use a layout. It
66 ships a serialized {@link LoggingEvent} object to the remote side.
68 <p><li><code>SocketHubAppender</code> relies on the TCP
69 protocol. Consequently, if the remote side is reachable, then log
70 events will eventually arrive at remote client.
72 <p><li>If no remote clients are attached, the logging requests are
75 <p><li>Logging events are automatically <em>buffered</em> by the
76 native TCP implementation. This means that if the link to remote
77 client is slow but still faster than the rate of (log) event
78 production, the application will not be affected by the slow network
79 connection. However, if the network connection is slower then the
80 rate of event production, then the local application can only
81 progress at the network rate. In particular, if the network link to
82 the the remote client is down, the application will be blocked.
84 <p>On the other hand, if the network link is up, but the remote
85 client is down, the client will not be blocked when making log
86 requests but the log events will be lost due to client
89 <p>The single remote client case extends to multiple clients
90 connections. The rate of logging will be determined by the slowest
93 <p><li>If the JVM hosting the <code>SocketHubAppender</code> exits
94 before the <code>SocketHubAppender</code> is closed either
95 explicitly or subsequent to garbage collection, then there might
96 be untransmitted data in the pipe which might be lost. This is a
97 common problem on Windows based systems.
99 <p>To avoid lost data, it is usually sufficient to {@link #close}
100 the <code>SocketHubAppender</code> either explicitly or by calling
101 the {@link org.apache.log4j.LogManager#shutdown} method before
102 exiting the application.
106 @author Mark Womack */
108 public class SocketHubAppender extends AppenderSkeleton {
111 The default port number of the ServerSocket will be created on. */
112 static final int DEFAULT_PORT = 4560;
114 private int port = DEFAULT_PORT;
115 private Vector oosList = new Vector();
116 private ServerMonitor serverMonitor = null;
117 private boolean locationInfo = false;
118 private CyclicBuffer buffer = null;
119 private String application;
120 private boolean advertiseViaMulticastDNS;
121 private ZeroConfSupport zeroConf;
124 * The MulticastDNS zone advertised by a SocketHubAppender
126 public static final String ZONE = "_log4j_obj_tcpaccept_appender.local.";
127 private ServerSocket serverSocket;
130 public SocketHubAppender() { }
133 Connects to remote server at <code>address</code> and <code>port</code>. */
135 SocketHubAppender(int _port) {
141 Set up the socket server on the specified port. */
143 void activateOptions() {
144 if (advertiseViaMulticastDNS) {
145 zeroConf = new ZeroConfSupport(ZONE, port, getName());
146 zeroConf.advertise();
153 <p>This will mark the appender as closed and
154 call then {@link #cleanUp} method. */
162 LogLog.debug("closing SocketHubAppender " + getName());
164 if (advertiseViaMulticastDNS) {
165 zeroConf.unadvertise();
169 LogLog.debug("SocketHubAppender " + getName() + " closed");
173 Release the underlying ServerMonitor thread, and drop the connections
174 to all connected remote servers. */
177 // stop the monitor thread
178 LogLog.debug("stopping ServerSocket");
179 serverMonitor.stopMonitor();
180 serverMonitor = null;
182 // close all of the connections
183 LogLog.debug("closing client connections");
184 while (oosList.size() != 0) {
185 ObjectOutputStream oos = (ObjectOutputStream)oosList.elementAt(0);
189 } catch(InterruptedIOException e) {
190 Thread.currentThread().interrupt();
191 LogLog.error("could not close oos.", e);
192 } catch(IOException e) {
193 LogLog.error("could not close oos.", e);
196 oosList.removeElementAt(0);
202 Append an event to all of current connections. */
204 void append(LoggingEvent event) {
206 // set up location info if requested
208 event.getLocationInformation();
210 if (application != null) {
211 event.setProperty("application", application);
214 event.getThreadName();
216 event.getRenderedMessage();
217 event.getThrowableStrRep();
219 if (buffer != null) {
224 // if no event or no open connections, exit now
225 if ((event == null) || (oosList.size() == 0)) {
229 // loop through the current set of open connections, appending the event to each
230 for (int streamCount = 0; streamCount < oosList.size(); streamCount++) {
232 ObjectOutputStream oos = null;
234 oos = (ObjectOutputStream)oosList.elementAt(streamCount);
236 catch (ArrayIndexOutOfBoundsException e) {
237 // catch this, but just don't assign a value
238 // this should not really occur as this method is
239 // the only one that can remove oos's (besides cleanUp).
242 // list size changed unexpectedly? Just exit the append.
248 oos.writeObject(event);
250 // Failing to reset the object output stream every now and
251 // then creates a serious memory leak.
252 // right now we always reset. TODO - set up frequency counter per oos?
255 catch(IOException e) {
256 if (e instanceof InterruptedIOException) {
257 Thread.currentThread().interrupt();
259 // there was an io exception so just drop the connection
260 oosList.removeElementAt(streamCount);
261 LogLog.debug("dropped connection");
263 // decrement to keep the counter in place (for loop always increments)
270 The SocketHubAppender does not use a layout. Hence, this method returns
271 <code>false</code>. */
273 boolean requiresLayout() {
278 The <b>Port</b> option takes a positive integer representing
279 the port where the server is waiting for connections. */
281 void setPort(int _port) {
286 * The <b>App</b> option takes a string value which should be the name of the application getting logged. If property was already set (via system
287 * property), don't set here.
290 void setApplication(String lapp) {
291 this.application = lapp;
295 * Returns value of the <b>Application</b> option.
298 String getApplication() {
303 Returns value of the <b>Port</b> option. */
310 * The <b>BufferSize</b> option takes a positive integer representing the number of events this appender will buffer and send to newly connected
314 void setBufferSize(int _bufferSize) {
315 buffer = new CyclicBuffer(_bufferSize);
319 * Returns value of the <b>bufferSize</b> option.
322 int getBufferSize() {
323 if (buffer == null) {
326 return buffer.getMaxSize();
331 The <b>LocationInfo</b> option takes a boolean value. If true,
332 the information sent to the remote host will include location
333 information. By default no location information is sent to the server. */
335 void setLocationInfo(boolean _locationInfo) {
336 locationInfo = _locationInfo;
340 Returns value of the <b>LocationInfo</b> option. */
342 boolean getLocationInfo() {
346 public void setAdvertiseViaMulticastDNS(boolean advertiseViaMulticastDNS) {
347 this.advertiseViaMulticastDNS = advertiseViaMulticastDNS;
350 public boolean isAdvertiseViaMulticastDNS() {
351 return advertiseViaMulticastDNS;
355 Start the ServerMonitor thread. */
358 serverMonitor = new ServerMonitor(port, oosList);
362 * Creates a server socket to accept connections.
363 * @param socketPort port on which the socket should listen, may be zero.
364 * @return new socket.
365 * @throws IOException IO error when opening the socket.
367 protected ServerSocket createServerSocket(final int socketPort) throws IOException {
368 return new ServerSocket(socketPort);
372 This class is used internally to monitor a ServerSocket
373 and register new connections in a vector passed in the
375 private class ServerMonitor implements Runnable {
377 private Vector oosList;
378 private boolean keepRunning;
379 private Thread monitorThread;
382 Create a thread and start the monitor. */
384 ServerMonitor(int _port, Vector _oosList) {
388 monitorThread = new Thread(this);
389 monitorThread.setDaemon(true);
390 monitorThread.setName("SocketHubAppender-Monitor-" + port);
391 monitorThread.start();
395 Stops the monitor. This method will not return until
396 the thread has finished executing. */
397 public synchronized void stopMonitor() {
399 LogLog.debug("server monitor thread shutting down");
402 if (serverSocket != null) {
403 serverSocket.close();
406 } catch (IOException ioe) {}
409 monitorThread.join();
411 catch (InterruptedException e) {
412 Thread.currentThread().interrupt();
416 // release the thread
417 monitorThread = null;
418 LogLog.debug("server monitor thread shut down");
423 void sendCachedEvents(ObjectOutputStream stream) throws IOException {
424 if (buffer != null) {
425 for (int i = 0; i < buffer.length(); i++) {
426 stream.writeObject(buffer.get(i));
434 Method that runs, monitoring the ServerSocket and adding connections as
435 they connect to the socket. */
440 serverSocket = createServerSocket(port);
441 serverSocket.setSoTimeout(1000);
443 catch (Exception e) {
444 if (e instanceof InterruptedIOException || e instanceof InterruptedException) {
445 Thread.currentThread().interrupt();
447 LogLog.error("exception setting timeout, shutting down server socket.", e);
454 serverSocket.setSoTimeout(1000);
456 catch (SocketException e) {
457 LogLog.error("exception setting timeout, shutting down server socket.", e);
461 while (keepRunning) {
462 Socket socket = null;
464 socket = serverSocket.accept();
466 catch (InterruptedIOException e) {
467 // timeout occurred, so just loop
469 catch (SocketException e) {
470 LogLog.error("exception accepting socket, shutting down server socket.", e);
473 catch (IOException e) {
474 LogLog.error("exception accepting socket.", e);
477 // if there was a socket accepted
478 if (socket != null) {
480 InetAddress remoteAddress = socket.getInetAddress();
481 LogLog.debug("accepting connection from " + remoteAddress.getHostName()
482 + " (" + remoteAddress.getHostAddress() + ")");
484 // create an ObjectOutputStream
485 ObjectOutputStream oos = new ObjectOutputStream(socket.getOutputStream());
486 if (buffer != null && buffer.length() > 0) {
487 sendCachedEvents(oos);
490 // add it to the oosList. OK since Vector is synchronized.
491 oosList.addElement(oos);
492 } catch (IOException e) {
493 if (e instanceof InterruptedIOException) {
494 Thread.currentThread().interrupt();
496 LogLog.error("exception creating output stream on socket.", e);
504 serverSocket.close();
505 } catch(InterruptedIOException e) {
506 Thread.currentThread().interrupt();
507 } catch (IOException e) {
508 // do nothing with it?