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}