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 org.apache.log4j.AppenderSkeleton;
    21  import org.apache.log4j.helpers.LogLog;
    22  import org.apache.log4j.spi.LoggingEvent;
    23
    24  import java.io.IOException;
    25  import java.io.PrintWriter;
    26  import java.io.InterruptedIOException;
    27  import java.net.ServerSocket;
    28  import java.net.Socket;
    29  import java.util.Enumeration;
    30  import java.util.Iterator;
    31  import java.util.Vector;
    32
    33  /**
    34    <p>The TelnetAppender is a log4j appender that specializes in
    35    writing to a read-only socket.  The output is provided in a
    36    telnet-friendly way so that a log can be monitored over TCP/IP.
    37    Clients using telnet connect to the socket and receive log data.
    38    This is handy for remote monitoring, especially when monitoring a
    39    servlet.
    40
    41    <p>Here is a list of the available configuration options:
    42
    43    <table border=1>
    44     <tr>
    45     <th>Name</th>
    46     <th>Requirement</th>
    47     <th>Description</th>
    48     <th>Sample Value</th>
    49     </tr>
    50
    51     <tr>
    52     <td>Port</td>
    53     <td>optional</td>
    54     <td>This parameter determines the port to use for announcing log events.  The default port is 23 (telnet).</td>
    55     <td>5875</td>
    56     </table>
    57
    58     @author <a HREF="mailto:jay@v-wave.com">Jay Funnell</a>
    59  */
    60
    61  public class TelnetAppender extends AppenderSkeleton {
    62
    63    private SocketHandler sh;
    64    private int port = 23;
    65
    66    /**
    67        This appender requires a layout to format the text to the
    68        attached client(s). */
    69    public boolean requiresLayout() {
    70      return true;
    71    }
    72
    73    /** all of the options have been set, create the socket handler and
    74        wait for connections. */
    75    public void activateOptions() {
    76      try {
    77        sh = new SocketHandler(port);
    78        sh.start();
    79      }
    80      catch(InterruptedIOException e) {
    81        Thread.currentThread().interrupt();
    82        e.printStackTrace();
    83      } catch(IOException e) {
    84        e.printStackTrace();
    85      } catch(RuntimeException e) {
    86        e.printStackTrace();
    87      }
    88      super.activateOptions();
    89    }
    90
    91    public
    92    int getPort() {
    93      return port;
    94    }
    95
    96    public
    97    void setPort(int port) {
    98      this.port = port;
    99    }
    100
    101
    102   /** shuts down the appender. */
    103   public void close() {
    104     if (sh != null) {
    105         sh.close();
    106         try {
    107             sh.join();
    108         } catch(InterruptedException ex) {
    109             Thread.currentThread().interrupt();
    110         }
    111     }
    112   }
    113
    114   /** Handles a log event.  For this appender, that means writing the
    115     message to each connected client.  */
    116   protected void append(LoggingEvent event) {
    117       if(sh != null) {
    118         sh.send(layout.format(event));
    119         if(layout.ignoresThrowable()) {
    120             String[] s = event.getThrowableStrRep();
    121             if (s != null) {
    122                 StringBuffer buf = new StringBuffer();
    123                 for(int i = 0; i < s.length; i++) {
    124                     buf.append(s[i]);
    125                     buf.append("\r\n");
    126                 }
    127                 sh.send(buf.toString());
    128             }
    129         }
    130       }
    131   }
    132
    133   //---------------------------------------------------------- SocketHandler:
    134
    135   /** The SocketHandler class is used to accept connections from
    136       clients.  It is threaded so that clients can connect/disconnect
    137       asynchronously. */
    138   protected class SocketHandler extends Thread {
    139
    140     private Vector writers = new Vector();
    141     private Vector connections = new Vector();
    142     private ServerSocket serverSocket;
    143     private int MAX_CONNECTIONS = 20;
    144
    145     public void finalize() {
    146         close();
    147     }
    148
    149     /**
    150     * make sure we close all network connections when this handler is destroyed.
    151     * @since 1.2.15
    152     */
    153     public void close() {
    154       synchronized(this) {
    155         for(Enumeration e = connections.elements();e.hasMoreElements();) {
    156             try {
    157                 ((Socket)e.nextElement()).close();
    158             } catch(InterruptedIOException ex) {
    159                 Thread.currentThread().interrupt();
    160             } catch(IOException ex) {
    161             } catch(RuntimeException ex) {
    162             }
    163         }
    164       }
    165
    166       try {
    167         serverSocket.close();
    168       } catch(InterruptedIOException ex) {
    169           Thread.currentThread().interrupt();
    170       } catch(IOException ex) {
    171       } catch(RuntimeException ex) {
    172       }
    173     }
    174
    175     /** sends a message to each of the clients in telnet-friendly output. */
    176     public synchronized void send(final String message) {
    177       Iterator ce = connections.iterator();
    178       for(Iterator e = writers.iterator();e.hasNext();) {
    179         ce.next();
    180         PrintWriter writer = (PrintWriter)e.next();
    181         writer.print(message);
    182         if(writer.checkError()) {
    183           ce.remove();
    184           e.remove();
    185         }
    186       }
    187     }
    188
    189     /**
    190 	Continually accepts client connections.  Client connections
    191         are refused when MAX_CONNECTIONS is reached.
    192     */
    193     public void run() {
    194       while(!serverSocket.isClosed()) {
    195         try {
    196           Socket newClient = serverSocket.accept();
    197           PrintWriter pw = new PrintWriter(newClient.getOutputStream());
    198           if(connections.size() < MAX_CONNECTIONS) {
    199             synchronized(this) {
    200                 connections.addElement(newClient);
    201                 writers.addElement(pw);
    202                 pw.print("TelnetAppender v1.0 (" + connections.size()
    203 		            + " active connections)\r\n\r\n");
    204                 pw.flush();
    205             }
    206           } else {
    207             pw.print("Too many connections.\r\n");
    208             pw.flush();
    209             newClient.close();
    210           }
    211         } catch(Exception e) {
    212           if (e instanceof InterruptedIOException || e instanceof InterruptedException) {
    213               Thread.currentThread().interrupt();
    214           }
    215           if (!serverSocket.isClosed()) {
    216             LogLog.error("Encountered error while in SocketHandler loop.", e);
    217           }
    218           break;
    219         }
    220       }
    221
    222       try {
    223           serverSocket.close();
    224       } catch(InterruptedIOException ex) {
    225           Thread.currentThread().interrupt();
    226       } catch(IOException ex) {
    227       }
    228     }
    229
    230     public SocketHandler(int port) throws IOException {
    231       serverSocket = new ServerSocket(port);
    232       setName("TelnetAppender-" + getName() + "-" + port);
    233     }
    234
    235   }
    236 }