+++ /dev/null
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.apache.log4j.net;
-
-import java.io.IOException;
-import java.io.InterruptedIOException;
-import java.io.ObjectOutputStream;
-import java.net.InetAddress;
-import java.net.ServerSocket;
-import java.net.Socket;
-import java.net.SocketException;
-import java.util.Vector;
-
-import org.apache.log4j.AppenderSkeleton;
-import org.apache.log4j.helpers.CyclicBuffer;
-import org.apache.log4j.helpers.LogLog;
-import org.apache.log4j.spi.LoggingEvent;
-
-/**
- Sends {@link LoggingEvent} objects to a set of remote log servers,
- usually a {@link SocketNode SocketNodes}.
-
- <p>Acts just like {@link SocketAppender} except that instead of
- connecting to a given remote log server,
- <code>SocketHubAppender</code> accepts connections from the remote
- log servers as clients. It can accept more than one connection.
- When a log event is received, the event is sent to the set of
- currently connected remote log servers. Implemented this way it does
- not require any update to the configuration file to send data to
- another remote log server. The remote log server simply connects to
- the host and port the <code>SocketHubAppender</code> is running on.
-
- <p>The <code>SocketHubAppender</code> does not store events such
- that the remote side will events that arrived after the
- establishment of its connection. Once connected, events arrive in
- order as guaranteed by the TCP protocol.
-
- <p>This implementation borrows heavily from the {@link
- SocketAppender}.
-
- <p>The SocketHubAppender has the following characteristics:
-
- <ul>
-
- <p><li>If sent to a {@link SocketNode}, logging is non-intrusive as
- far as the log event is concerned. In other words, the event will be
- logged with the same time stamp, {@link org.apache.log4j.NDC},
- location info as if it were logged locally.
-
- <p><li><code>SocketHubAppender</code> does not use a layout. It
- ships a serialized {@link LoggingEvent} object to the remote side.
-
- <p><li><code>SocketHubAppender</code> relies on the TCP
- protocol. Consequently, if the remote side is reachable, then log
- events will eventually arrive at remote client.
-
- <p><li>If no remote clients are attached, the logging requests are
- simply dropped.
-
- <p><li>Logging events are automatically <em>buffered</em> by the
- native TCP implementation. This means that if the link to remote
- client is slow but still faster than the rate of (log) event
- production, the application will not be affected by the slow network
- connection. However, if the network connection is slower then the
- rate of event production, then the local application can only
- progress at the network rate. In particular, if the network link to
- the the remote client is down, the application will be blocked.
-
- <p>On the other hand, if the network link is up, but the remote
- client is down, the client will not be blocked when making log
- requests but the log events will be lost due to client
- unavailability.
-
- <p>The single remote client case extends to multiple clients
- connections. The rate of logging will be determined by the slowest
- link.
-
- <p><li>If the JVM hosting the <code>SocketHubAppender</code> exits
- before the <code>SocketHubAppender</code> is closed either
- explicitly or subsequent to garbage collection, then there might
- be untransmitted data in the pipe which might be lost. This is a
- common problem on Windows based systems.
-
- <p>To avoid lost data, it is usually sufficient to {@link #close}
- the <code>SocketHubAppender</code> either explicitly or by calling
- the {@link org.apache.log4j.LogManager#shutdown} method before
- exiting the application.
-
- </ul>
-
- @author Mark Womack */
-
-public class SocketHubAppender extends AppenderSkeleton {
-
- /**
- The default port number of the ServerSocket will be created on. */
- static final int DEFAULT_PORT = 4560;
-
- private int port = DEFAULT_PORT;
- private Vector oosList = new Vector();
- private ServerMonitor serverMonitor = null;
- private boolean locationInfo = false;
- private CyclicBuffer buffer = null;
- private String application;
- private boolean advertiseViaMulticastDNS;
- private ZeroConfSupport zeroConf;
-
- /**
- * The MulticastDNS zone advertised by a SocketHubAppender
- */
- public static final String ZONE = "_log4j_obj_tcpaccept_appender.local.";
- private ServerSocket serverSocket;
-
-
- public SocketHubAppender() { }
-
- /**
- Connects to remote server at <code>address</code> and <code>port</code>. */
- public
- SocketHubAppender(int _port) {
- port = _port;
- startServer();
- }
-
- /**
- Set up the socket server on the specified port. */
- public
- void activateOptions() {
- if (advertiseViaMulticastDNS) {
- zeroConf = new ZeroConfSupport(ZONE, port, getName());
- zeroConf.advertise();
- }
- startServer();
- }
-
- /**
- Close this appender.
- <p>This will mark the appender as closed and
- call then {@link #cleanUp} method. */
- synchronized
- public
- void close() {
- if(closed) {
- return;
- }
-
- LogLog.debug("closing SocketHubAppender " + getName());
- this.closed = true;
- if (advertiseViaMulticastDNS) {
- zeroConf.unadvertise();
- }
- cleanUp();
-
- LogLog.debug("SocketHubAppender " + getName() + " closed");
- }
-
- /**
- Release the underlying ServerMonitor thread, and drop the connections
- to all connected remote servers. */
- public
- void cleanUp() {
- // stop the monitor thread
- LogLog.debug("stopping ServerSocket");
- serverMonitor.stopMonitor();
- serverMonitor = null;
-
- // close all of the connections
- LogLog.debug("closing client connections");
- while (oosList.size() != 0) {
- ObjectOutputStream oos = (ObjectOutputStream)oosList.elementAt(0);
- if(oos != null) {
- try {
- oos.close();
- } catch(InterruptedIOException e) {
- Thread.currentThread().interrupt();
- LogLog.error("could not close oos.", e);
- } catch(IOException e) {
- LogLog.error("could not close oos.", e);
- }
-
- oosList.removeElementAt(0);
- }
- }
- }
-
- /**
- Append an event to all of current connections. */
- public
- void append(LoggingEvent event) {
- if (event != null) {
- // set up location info if requested
- if (locationInfo) {
- event.getLocationInformation();
- }
- if (application != null) {
- event.setProperty("application", application);
- }
- event.getNDC();
- event.getThreadName();
- event.getMDCCopy();
- event.getRenderedMessage();
- event.getThrowableStrRep();
-
- if (buffer != null) {
- buffer.add(event);
- }
- }
-
- // if no event or no open connections, exit now
- if ((event == null) || (oosList.size() == 0)) {
- return;
- }
-
- // loop through the current set of open connections, appending the event to each
- for (int streamCount = 0; streamCount < oosList.size(); streamCount++) {
-
- ObjectOutputStream oos = null;
- try {
- oos = (ObjectOutputStream)oosList.elementAt(streamCount);
- }
- catch (ArrayIndexOutOfBoundsException e) {
- // catch this, but just don't assign a value
- // this should not really occur as this method is
- // the only one that can remove oos's (besides cleanUp).
- }
-
- // list size changed unexpectedly? Just exit the append.
- if (oos == null) {
- break;
- }
-
- try {
- oos.writeObject(event);
- oos.flush();
- // Failing to reset the object output stream every now and
- // then creates a serious memory leak.
- // right now we always reset. TODO - set up frequency counter per oos?
- oos.reset();
- }
- catch(IOException e) {
- if (e instanceof InterruptedIOException) {
- Thread.currentThread().interrupt();
- }
- // there was an io exception so just drop the connection
- oosList.removeElementAt(streamCount);
- LogLog.debug("dropped connection");
-
- // decrement to keep the counter in place (for loop always increments)
- streamCount--;
- }
- }
- }
-
- /**
- The SocketHubAppender does not use a layout. Hence, this method returns
- <code>false</code>. */
- public
- boolean requiresLayout() {
- return false;
- }
-
- /**
- The <b>Port</b> option takes a positive integer representing
- the port where the server is waiting for connections. */
- public
- void setPort(int _port) {
- port = _port;
- }
-
- /**
- * 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
- * property), don't set here.
- */
- public
- void setApplication(String lapp) {
- this.application = lapp;
- }
-
- /**
- * Returns value of the <b>Application</b> option.
- */
- public
- String getApplication() {
- return application;
- }
-
- /**
- Returns value of the <b>Port</b> option. */
- public
- int getPort() {
- return port;
- }
-
- /**
- * The <b>BufferSize</b> option takes a positive integer representing the number of events this appender will buffer and send to newly connected
- * clients.
- */
- public
- void setBufferSize(int _bufferSize) {
- buffer = new CyclicBuffer(_bufferSize);
- }
-
- /**
- * Returns value of the <b>bufferSize</b> option.
- */
- public
- int getBufferSize() {
- if (buffer == null) {
- return 0;
- } else {
- return buffer.getMaxSize();
- }
- }
-
- /**
- The <b>LocationInfo</b> option takes a boolean value. If true,
- the information sent to the remote host will include location
- information. By default no location information is sent to the server. */
- public
- void setLocationInfo(boolean _locationInfo) {
- locationInfo = _locationInfo;
- }
-
- /**
- Returns value of the <b>LocationInfo</b> option. */
- public
- boolean getLocationInfo() {
- return locationInfo;
- }
-
- public void setAdvertiseViaMulticastDNS(boolean advertiseViaMulticastDNS) {
- this.advertiseViaMulticastDNS = advertiseViaMulticastDNS;
- }
-
- public boolean isAdvertiseViaMulticastDNS() {
- return advertiseViaMulticastDNS;
- }
-
- /**
- Start the ServerMonitor thread. */
- private
- void startServer() {
- serverMonitor = new ServerMonitor(port, oosList);
- }
-
- /**
- * Creates a server socket to accept connections.
- * @param socketPort port on which the socket should listen, may be zero.
- * @return new socket.
- * @throws IOException IO error when opening the socket.
- */
- protected ServerSocket createServerSocket(final int socketPort) throws IOException {
- return new ServerSocket(socketPort);
- }
-
- /**
- This class is used internally to monitor a ServerSocket
- and register new connections in a vector passed in the
- constructor. */
- private class ServerMonitor implements Runnable {
- private int port;
- private Vector oosList;
- private boolean keepRunning;
- private Thread monitorThread;
-
- /**
- Create a thread and start the monitor. */
- public
- ServerMonitor(int _port, Vector _oosList) {
- port = _port;
- oosList = _oosList;
- keepRunning = true;
- monitorThread = new Thread(this);
- monitorThread.setDaemon(true);
- monitorThread.setName("SocketHubAppender-Monitor-" + port);
- monitorThread.start();
- }
-
- /**
- Stops the monitor. This method will not return until
- the thread has finished executing. */
- public synchronized void stopMonitor() {
- if (keepRunning) {
- LogLog.debug("server monitor thread shutting down");
- keepRunning = false;
- try {
- if (serverSocket != null) {
- serverSocket.close();
- serverSocket = null;
- }
- } catch (IOException ioe) {}
-
- try {
- monitorThread.join();
- }
- catch (InterruptedException e) {
- Thread.currentThread().interrupt();
- // do nothing?
- }
-
- // release the thread
- monitorThread = null;
- LogLog.debug("server monitor thread shut down");
- }
- }
-
- private
- void sendCachedEvents(ObjectOutputStream stream) throws IOException {
- if (buffer != null) {
- for (int i = 0; i < buffer.length(); i++) {
- stream.writeObject(buffer.get(i));
- }
- stream.flush();
- stream.reset();
- }
- }
-
- /**
- Method that runs, monitoring the ServerSocket and adding connections as
- they connect to the socket. */
- public
- void run() {
- serverSocket = null;
- try {
- serverSocket = createServerSocket(port);
- serverSocket.setSoTimeout(1000);
- }
- catch (Exception e) {
- if (e instanceof InterruptedIOException || e instanceof InterruptedException) {
- Thread.currentThread().interrupt();
- }
- LogLog.error("exception setting timeout, shutting down server socket.", e);
- keepRunning = false;
- return;
- }
-
- try {
- try {
- serverSocket.setSoTimeout(1000);
- }
- catch (SocketException e) {
- LogLog.error("exception setting timeout, shutting down server socket.", e);
- return;
- }
-
- while (keepRunning) {
- Socket socket = null;
- try {
- socket = serverSocket.accept();
- }
- catch (InterruptedIOException e) {
- // timeout occurred, so just loop
- }
- catch (SocketException e) {
- LogLog.error("exception accepting socket, shutting down server socket.", e);
- keepRunning = false;
- }
- catch (IOException e) {
- LogLog.error("exception accepting socket.", e);
- }
-
- // if there was a socket accepted
- if (socket != null) {
- try {
- InetAddress remoteAddress = socket.getInetAddress();
- LogLog.debug("accepting connection from " + remoteAddress.getHostName()
- + " (" + remoteAddress.getHostAddress() + ")");
-
- // create an ObjectOutputStream
- ObjectOutputStream oos = new ObjectOutputStream(socket.getOutputStream());
- if (buffer != null && buffer.length() > 0) {
- sendCachedEvents(oos);
- }
-
- // add it to the oosList. OK since Vector is synchronized.
- oosList.addElement(oos);
- } catch (IOException e) {
- if (e instanceof InterruptedIOException) {
- Thread.currentThread().interrupt();
- }
- LogLog.error("exception creating output stream on socket.", e);
- }
- }
- }
- }
- finally {
- // close the socket
- try {
- serverSocket.close();
- } catch(InterruptedIOException e) {
- Thread.currentThread().interrupt();
- } catch (IOException e) {
- // do nothing with it?
- }
- }
- }
- }
-}
-