View Javadoc

    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  package org.apache.log4j.net;
    19
    20  import java.util.Vector;
    21  import java.net.Socket;
    22  import java.net.ServerSocket;
    23  import java.net.SocketException;
    24  import java.io.ObjectOutputStream;
    25  import java.io.IOException;
    26  import java.io.InterruptedIOException;
    27  import java.net.InetAddress;
    28
    29  import org.apache.log4j.helpers.LogLog;
    30  import org.apache.log4j.spi.LoggingEvent;
    31  import org.apache.log4j.AppenderSkeleton;
    32
    33  /***
    34    Sends {@link LoggingEvent} objects to a set of remote log servers,
    35    usually a {@link SocketNode SocketNodes}.
    36
    37    <p>Acts just like {@link SocketAppender} except that instead of
    38    connecting to a given remote log server,
    39    <code>SocketHubAppender</code> accepts connections from the remote
    40    log servers as clients.  It can accept more than one connection.
    41    When a log event is received, the event is sent to the set of
    42    currently connected remote log servers. Implemented this way it does
    43    not require any update to the configuration file to send data to
    44    another remote log server. The remote log server simply connects to
    45    the host and port the <code>SocketHubAppender</code> is running on.
    46
    47    <p>The <code>SocketHubAppender</code> does not store events such
    48    that the remote side will events that arrived after the
    49    establishment of its connection. Once connected, events arrive in
    50    order as guaranteed by the TCP protocol.
    51
    52    <p>This implementation borrows heavily from the {@link
    53    SocketAppender}.
    54
    55    <p>The SocketHubAppender has the following characteristics:
    56
    57    <ul>
    58
    59    <p><li>If sent to a {@link SocketNode}, logging is non-intrusive as
    60    far as the log event is concerned. In other words, the event will be
    61    logged with the same time stamp, {@link org.apache.log4j.NDC},
    62    location info as if it were logged locally.
    63
    64    <p><li><code>SocketHubAppender</code> does not use a layout. It
    65    ships a serialized {@link LoggingEvent} object to the remote side.
    66
    67    <p><li><code>SocketHubAppender</code> relies on the TCP
    68    protocol. Consequently, if the remote side is reachable, then log
    69    events will eventually arrive at remote client.
    70
    71    <p><li>If no remote clients are attached, the logging requests are
    72    simply dropped.
    73
    74    <p><li>Logging events are automatically <em>buffered</em> by the
    75    native TCP implementation. This means that if the link to remote
    76    client is slow but still faster than the rate of (log) event
    77    production, the application will not be affected by the slow network
    78    connection. However, if the network connection is slower then the
    79    rate of event production, then the local application can only
    80    progress at the network rate. In particular, if the network link to
    81    the the remote client is down, the application will be blocked.
    82
    83    <p>On the other hand, if the network link is up, but the remote
    84    client is down, the client will not be blocked when making log
    85    requests but the log events will be lost due to client
    86    unavailability.
    87
    88    <p>The single remote client case extends to multiple clients
    89    connections. The rate of logging will be determined by the slowest
    90    link.
    91
    92    <p><li>If the JVM hosting the <code>SocketHubAppender</code> exits
    93    before the <code>SocketHubAppender</code> is closed either
    94    explicitly or subsequent to garbage collection, then there might
    95    be untransmitted data in the pipe which might be lost. This is a
    96    common problem on Windows based systems.
    97
    98    <p>To avoid lost data, it is usually sufficient to {@link #close}
    99    the <code>SocketHubAppender</code> either explicitly or by calling
    100   the {@link org.apache.log4j.LogManager#shutdown} method before
    101   exiting the application.
    102
    103   </ul>
    104
    105   @author Mark Womack */
    106
    107 public class SocketHubAppender extends AppenderSkeleton {
    108
    109   /***
    110      The default port number of the ServerSocket will be created on. */
    111   static final int DEFAULT_PORT = 4560;
    112
    113   private int port = DEFAULT_PORT;
    114   private Vector oosList = new Vector();
    115   private ServerMonitor serverMonitor = null;
    116   private boolean locationInfo = false;
    117
    118   public SocketHubAppender() { }
    119
    120   /***
    121      Connects to remote server at <code>address</code> and <code>port</code>. */
    122   public
    123   SocketHubAppender(int _port) {
    124     port = _port;
    125     startServer();
    126   }
    127
    128   /***
    129      Set up the socket server on the specified port.  */
    130   public
    131   void activateOptions() {
    132     startServer();
    133   }
    134
    135   /***
    136      Close this appender.
    137      <p>This will mark the appender as closed and
    138      call then {@link #cleanUp} method. */
    139   synchronized
    140   public
    141   void close() {
    142     if(closed)
    143       return;
    144
    145 	LogLog.debug("closing SocketHubAppender " + getName());
    146     this.closed = true;
    147     cleanUp();
    148 	LogLog.debug("SocketHubAppender " + getName() + " closed");
    149   }
    150
    151   /***
    152      Release the underlying ServerMonitor thread, and drop the connections
    153      to all connected remote servers. */
    154   public
    155   void cleanUp() {
    156     // stop the monitor thread
    157 	LogLog.debug("stopping ServerSocket");
    158     serverMonitor.stopMonitor();
    159     serverMonitor = null;
    160
    161     // close all of the connections
    162 	LogLog.debug("closing client connections");
    163     while (oosList.size() != 0) {
    164       ObjectOutputStream oos = (ObjectOutputStream)oosList.elementAt(0);
    165       if(oos != null) {
    166         try {
    167         	oos.close();
    168         }
    169         catch(IOException e) {
    170         	LogLog.error("could not close oos.", e);
    171         }
    172
    173         oosList.removeElementAt(0);
    174       }
    175     }
    176   }
    177
    178   /***
    179     Append an event to all of current connections. */
    180   public
    181   void append(LoggingEvent event) {
    182 	// if no event or no open connections, exit now
    183     if(event == null || oosList.size() == 0)
    184       return;
    185
    186     // set up location info if requested
    187     if (locationInfo) {
    188     	event.getLocationInformation();
    189     }
    190
    191 	// loop through the current set of open connections, appending the event to each
    192     for (int streamCount = 0; streamCount < oosList.size(); streamCount++) {
    193
    194       ObjectOutputStream oos = null;
    195       try {
    196         oos = (ObjectOutputStream)oosList.elementAt(streamCount);
    197       }
    198       catch (ArrayIndexOutOfBoundsException e) {
    199         // catch this, but just don't assign a value
    200         // this should not really occur as this method is
    201         // the only one that can remove oos's (besides cleanUp).
    202       }
    203
    204       // list size changed unexpectedly? Just exit the append.
    205       if (oos == null)
    206         break;
    207
    208       try {
    209       	oos.writeObject(event);
    210       	oos.flush();
    211     	// Failing to reset the object output stream every now and
    212     	// then creates a serious memory leak.
    213     	// right now we always reset. TODO - set up frequency counter per oos?
    214     	oos.reset();
    215       }
    216       catch(IOException e) {
    217       	// there was an io exception so just drop the connection
    218       	oosList.removeElementAt(streamCount);
    219       	LogLog.debug("dropped connection");
    220
    221       	// decrement to keep the counter in place (for loop always increments)
    222       	streamCount--;
    223       }
    224     }
    225   }
    226
    227   /***
    228      The SocketHubAppender does not use a layout. Hence, this method returns
    229      <code>false</code>. */
    230   public
    231   boolean requiresLayout() {
    232     return false;
    233   }
    234
    235   /***
    236      The <b>Port</b> option takes a positive integer representing
    237      the port where the server is waiting for connections. */
    238   public
    239   void setPort(int _port) {
    240     port = _port;
    241   }
    242
    243   /***
    244      Returns value of the <b>Port</b> option. */
    245   public
    246   int getPort() {
    247     return port;
    248   }
    249
    250   /***
    251      The <b>LocationInfo</b> option takes a boolean value. If true,
    252      the information sent to the remote host will include location
    253      information. By default no location information is sent to the server. */
    254   public
    255   void setLocationInfo(boolean _locationInfo) {
    256     locationInfo = _locationInfo;
    257   }
    258
    259   /***
    260      Returns value of the <b>LocationInfo</b> option. */
    261   public
    262   boolean getLocationInfo() {
    263     return locationInfo;
    264   }
    265
    266   /***
    267     Start the ServerMonitor thread. */
    268   private
    269   void startServer() {
    270     serverMonitor = new ServerMonitor(port, oosList);
    271   }
    272
    273   /***
    274     This class is used internally to monitor a ServerSocket
    275     and register new connections in a vector passed in the
    276     constructor. */
    277   private
    278   class ServerMonitor implements Runnable {
    279     private int port;
    280     private Vector oosList;
    281     private boolean keepRunning;
    282     private Thread monitorThread;
    283
    284     /***
    285       Create a thread and start the monitor. */
    286     public
    287     ServerMonitor(int _port, Vector _oosList) {
    288       port = _port;
    289       oosList = _oosList;
    290       keepRunning = true;
    291       monitorThread = new Thread(this);
    292       monitorThread.setDaemon(true);
    293       monitorThread.start();
    294     }
    295
    296     /***
    297       Stops the monitor. This method will not return until
    298       the thread has finished executing. */
    299     public
    300     synchronized
    301     void stopMonitor() {
    302       if (keepRunning) {
    303     	LogLog.debug("server monitor thread shutting down");
    304         keepRunning = false;
    305         try {
    306           monitorThread.join();
    307         }
    308         catch (InterruptedException e) {
    309           // do nothing?
    310         }
    311
    312         // release the thread
    313         monitorThread = null;
    314     	LogLog.debug("server monitor thread shut down");
    315       }
    316     }
    317
    318     /***
    319       Method that runs, monitoring the ServerSocket and adding connections as
    320       they connect to the socket. */
    321     public
    322     void run() {
    323       ServerSocket serverSocket = null;
    324       try {
    325         serverSocket = new ServerSocket(port);
    326         serverSocket.setSoTimeout(1000);
    327       }
    328       catch (Exception e) {
    329         LogLog.error("exception setting timeout, shutting down server socket.", e);
    330         keepRunning = false;
    331         return;
    332       }
    333
    334       try {
    335     	try {
    336         	serverSocket.setSoTimeout(1000);
    337     	}
    338     	catch (SocketException e) {
    339           LogLog.error("exception setting timeout, shutting down server socket.", e);
    340           return;
    341     	}
    342
    343     	while (keepRunning) {
    344           Socket socket = null;
    345           try {
    346             socket = serverSocket.accept();
    347           }
    348           catch (InterruptedIOException e) {
    349             // timeout occurred, so just loop
    350           }
    351           catch (SocketException e) {
    352             LogLog.error("exception accepting socket, shutting down server socket.", e);
    353             keepRunning = false;
    354           }
    355           catch (IOException e) {
    356             LogLog.error("exception accepting socket.", e);
    357           }
    358
    359           // if there was a socket accepted
    360           if (socket != null) {
    361             try {
    362               InetAddress remoteAddress = socket.getInetAddress();
    363               LogLog.debug("accepting connection from " + remoteAddress.getHostName()
    364 			   + " (" + remoteAddress.getHostAddress() + ")");
    365
    366               // create an ObjectOutputStream
    367               ObjectOutputStream oos = new ObjectOutputStream(socket.getOutputStream());
    368
    369               // add it to the oosList.  OK since Vector is synchronized.
    370               oosList.addElement(oos);
    371             }
    372             catch (IOException e) {
    373               LogLog.error("exception creating output stream on socket.", e);
    374             }
    375           }
    376         }
    377       }
    378       finally {
    379     	// close the socket
    380     	try {
    381     		serverSocket.close();
    382     	}
    383     	catch (IOException e) {
    384     		// do nothing with it?
    385     	}
    386       }
    387     }
    388   }
    389 }
    390