001/** 002 * Logback: the reliable, generic, fast and flexible logging framework. 003 * Copyright (C) 1999-2015, QOS.ch. All rights reserved. 004 * 005 * This program and the accompanying materials are dual-licensed under 006 * either the terms of the Eclipse Public License v1.0 as published by 007 * the Eclipse Foundation 008 * 009 * or (per the licensee's choosing) 010 * 011 * under the terms of the GNU Lesser General Public License version 2.1 012 * as published by the Free Software Foundation. 013 */ 014package ch.qos.logback.classic.net; 015 016import java.io.IOException; 017import java.net.ServerSocket; 018import java.net.Socket; 019import java.util.ArrayList; 020import java.util.List; 021import java.util.concurrent.CountDownLatch; 022 023import javax.net.ServerSocketFactory; 024 025import org.slf4j.Logger; 026import org.slf4j.LoggerFactory; 027 028import ch.qos.logback.classic.LoggerContext; 029import ch.qos.logback.classic.joran.JoranConfigurator; 030import ch.qos.logback.core.joran.spi.JoranException; 031 032/** 033 * A simple {@link SocketNode} based server. 034 * 035 * <pre> 036 * <b>Usage:</b> java ch.qos.logback.classic.net.SimpleSocketServer port configFile 037 * </pre> 038 * 039 * where <em>port</em> is a port number where the server listens and 040 * <em>configFile</em> is an XML configuration file fed to 041 * {@link JoranConfigurator}. 042 * 043 * </pre> 044 * 045 * @author Ceki Gülcü 046 * @author Sébastien Pennec 047 * 048 * @since 0.8.4 049 */ 050public class SimpleSocketServer extends Thread { 051 052 Logger logger = LoggerFactory.getLogger(SimpleSocketServer.class); 053 054 private final int port; 055 private final LoggerContext lc; 056 private boolean closed = false; 057 private ServerSocket serverSocket; 058 private List<SocketNode> socketNodeList = new ArrayList<SocketNode>(); 059 060 // used for testing purposes 061 private CountDownLatch latch; 062 063 public static void main(String argv[]) throws Exception { 064 doMain(SimpleSocketServer.class, argv); 065 } 066 067 protected static void doMain(Class<? extends SimpleSocketServer> serverClass, String argv[]) throws Exception { 068 int port = -1; 069 if (argv.length == 2) { 070 port = parsePortNumber(argv[0]); 071 } else { 072 usage("Wrong number of arguments."); 073 } 074 075 String configFile = argv[1]; 076 LoggerContext lc = (LoggerContext) LoggerFactory.getILoggerFactory(); 077 configureLC(lc, configFile); 078 079 SimpleSocketServer sss = new SimpleSocketServer(lc, port); 080 sss.start(); 081 } 082 083 public SimpleSocketServer(LoggerContext lc, int port) { 084 this.lc = lc; 085 this.port = port; 086 } 087 088 public void run() { 089 090 final String oldThreadName = Thread.currentThread().getName(); 091 092 try { 093 094 final String newThreadName = getServerThreadName(); 095 Thread.currentThread().setName(newThreadName); 096 097 logger.info("Listening on port " + port); 098 serverSocket = getServerSocketFactory().createServerSocket(port); 099 while (!closed) { 100 logger.info("Waiting to accept a new client."); 101 signalAlmostReadiness(); 102 Socket socket = serverSocket.accept(); 103 logger.info("Connected to client at " + socket.getInetAddress()); 104 logger.info("Starting new socket node."); 105 SocketNode newSocketNode = new SocketNode(this, socket, lc); 106 synchronized (socketNodeList) { 107 socketNodeList.add(newSocketNode); 108 } 109 final String clientThreadName = getClientThreadName(socket); 110 new Thread(newSocketNode, clientThreadName).start(); 111 } 112 } catch (Exception e) { 113 if (closed) { 114 logger.info("Exception in run method for a closed server. This is normal."); 115 } else { 116 logger.error("Unexpected failure in run method", e); 117 } 118 } 119 120 finally { 121 Thread.currentThread().setName(oldThreadName); 122 } 123 } 124 125 /** 126 * Returns the name given to the server thread. 127 */ 128 protected String getServerThreadName() { 129 return String.format("Logback %s (port %d)", getClass().getSimpleName(), port); 130 } 131 132 /** 133 * Returns a name to identify each client thread. 134 */ 135 protected String getClientThreadName(Socket socket) { 136 return String.format("Logback SocketNode (client: %s)", socket.getRemoteSocketAddress()); 137 } 138 139 /** 140 * Gets the platform default {@link ServerSocketFactory}. 141 * <p> 142 * Subclasses may override to provide a custom server socket factory. 143 */ 144 protected ServerSocketFactory getServerSocketFactory() { 145 return ServerSocketFactory.getDefault(); 146 } 147 148 /** 149 * Signal another thread that we have established a connection This is useful 150 * for testing purposes. 151 */ 152 void signalAlmostReadiness() { 153 if (latch != null && latch.getCount() != 0) { 154 // System.out.println("signalAlmostReadiness() with latch "+latch); 155 latch.countDown(); 156 } 157 } 158 159 /** 160 * Used for testing purposes 161 * 162 * @param latch 163 */ 164 void setLatch(CountDownLatch latch) { 165 this.latch = latch; 166 } 167 168 /** 169 * Used for testing purposes 170 */ 171 public CountDownLatch getLatch() { 172 return latch; 173 } 174 175 public boolean isClosed() { 176 return closed; 177 } 178 179 public void close() { 180 closed = true; 181 if (serverSocket != null) { 182 try { 183 serverSocket.close(); 184 } catch (IOException e) { 185 logger.error("Failed to close serverSocket", e); 186 } finally { 187 serverSocket = null; 188 } 189 } 190 191 logger.info("closing this server"); 192 synchronized (socketNodeList) { 193 for (SocketNode sn : socketNodeList) { 194 sn.close(); 195 } 196 } 197 if (socketNodeList.size() != 0) { 198 logger.warn("Was expecting a 0-sized socketNodeList after server shutdown"); 199 } 200 201 } 202 203 public void socketNodeClosing(SocketNode sn) { 204 logger.debug("Removing {}", sn); 205 206 // don't allow simultaneous access to the socketNodeList 207 // (e.g. removal whole iterating on the list causes 208 // java.util.ConcurrentModificationException) 209 synchronized (socketNodeList) { 210 socketNodeList.remove(sn); 211 } 212 } 213 214 static void usage(String msg) { 215 System.err.println(msg); 216 System.err.println("Usage: java " + SimpleSocketServer.class.getName() + " port configFile"); 217 System.exit(1); 218 } 219 220 static int parsePortNumber(String portStr) { 221 try { 222 return Integer.parseInt(portStr); 223 } catch (java.lang.NumberFormatException e) { 224 e.printStackTrace(); 225 usage("Could not interpret port number [" + portStr + "]."); 226 // we won't get here 227 return -1; 228 } 229 } 230 231 static public void configureLC(LoggerContext lc, String configFile) throws JoranException { 232 JoranConfigurator configurator = new JoranConfigurator(); 233 lc.reset(); 234 configurator.setContext(lc); 235 configurator.doConfigure(configFile); 236 } 237}