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 }