001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 * 
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 * 
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017
018package org.apache.log4j.net;
019
020import org.apache.log4j.AppenderSkeleton;
021import org.apache.log4j.helpers.LogLog;
022import org.apache.log4j.spi.LoggingEvent;
023
024import java.io.IOException;
025import java.io.PrintWriter;
026import java.io.InterruptedIOException;
027import java.net.ServerSocket;
028import java.net.Socket;
029import java.util.Enumeration;
030import java.util.Iterator;
031import java.util.Vector;
032
033/**
034  <p>The TelnetAppender is a log4j appender that specializes in
035  writing to a read-only socket.  The output is provided in a
036  telnet-friendly way so that a log can be monitored over TCP/IP.
037  Clients using telnet connect to the socket and receive log data.
038  This is handy for remote monitoring, especially when monitoring a
039  servlet.
040
041  <p>Here is a list of the available configuration options:
042
043  <table border=1>
044   <tr>
045   <th>Name</th>
046   <th>Requirement</th>
047   <th>Description</th>
048   <th>Sample Value</th>
049   </tr>
050
051   <tr>
052   <td>Port</td>
053   <td>optional</td>
054   <td>This parameter determines the port to use for announcing log events.  The default port is 23 (telnet).</td>
055   <td>5875</td>
056   </table>
057
058   @author <a HREF="mailto:jay@v-wave.com">Jay Funnell</a>
059*/
060
061public class TelnetAppender extends AppenderSkeleton {
062
063  private SocketHandler sh;
064  private int port = 23;
065
066  /** 
067      This appender requires a layout to format the text to the
068      attached client(s). */
069  public boolean requiresLayout() {
070    return true;
071  }
072
073  /** all of the options have been set, create the socket handler and
074      wait for connections. */
075  public void activateOptions() {
076    try {
077      sh = new SocketHandler(port);
078      sh.start();
079    }
080    catch(InterruptedIOException e) {
081      Thread.currentThread().interrupt();
082      e.printStackTrace();
083    } catch(IOException e) {
084      e.printStackTrace();
085    } catch(RuntimeException e) {
086      e.printStackTrace();
087    }
088    super.activateOptions();
089  }
090
091  public
092  int getPort() {
093    return port;
094  }
095
096  public
097  void setPort(int port) {
098    this.port = port;
099  }
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}