001/* 002 * Logback: the reliable, generic, fast and flexible logging framework. 003 * Copyright (C) 1999-2026, 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 v2.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 081 // start the server in a separate thread 082 sss.start(); 083 } 084 085 public SimpleSocketServer(LoggerContext lc, int port) { 086 this.lc = lc; 087 this.port = port; 088 } 089 090 public void run() { 091 092 final String oldThreadName = Thread.currentThread().getName(); 093 094 try { 095 096 final String newThreadName = getServerThreadName(); 097 Thread.currentThread().setName(newThreadName); 098 099 logger.info("Listening on port " + port); 100 serverSocket = getServerSocketFactory().createServerSocket(port); 101 while (!closed) { 102 logger.info("Waiting to accept a new client."); 103 signalAlmostReadiness(); 104 Socket socket = serverSocket.accept(); 105 logger.info("Connected to client at " + socket.getInetAddress()); 106 logger.info("Starting new socket node."); 107 SocketNode newSocketNode = new SocketNode(this, socket, lc); 108 synchronized (socketNodeList) { 109 socketNodeList.add(newSocketNode); 110 } 111 final String clientThreadName = getClientThreadName(socket); 112 new Thread(newSocketNode, clientThreadName).start(); 113 } 114 } catch (Exception e) { 115 if (closed) { 116 logger.info("Exception in run method for a closed server. This is normal."); 117 } else { 118 logger.error("Unexpected failure in run method", e); 119 } 120 } 121 122 finally { 123 Thread.currentThread().setName(oldThreadName); 124 } 125 } 126 127 /** 128 * Returns the name given to the server thread. 129 */ 130 protected String getServerThreadName() { 131 return String.format("Logback %s (port %d)", getClass().getSimpleName(), port); 132 } 133 134 /** 135 * Returns a name to identify each client thread. 136 */ 137 protected String getClientThreadName(Socket socket) { 138 return String.format("Logback SocketNode (client: %s)", socket.getRemoteSocketAddress()); 139 } 140 141 /** 142 * Gets the platform default {@link ServerSocketFactory}. 143 * <p> 144 * Subclasses may override to provide a custom server socket factory. 145 */ 146 protected ServerSocketFactory getServerSocketFactory() { 147 return ServerSocketFactory.getDefault(); 148 } 149 150 /** 151 * Signal another thread that we have established a connection This is useful 152 * for testing purposes. 153 */ 154 void signalAlmostReadiness() { 155 if (latch != null && latch.getCount() != 0) { 156 // System.out.println("signalAlmostReadiness() with latch "+latch); 157 latch.countDown(); 158 } 159 } 160 161 /** 162 * Used for testing purposes 163 * 164 * @param latch 165 */ 166 void setLatch(CountDownLatch latch) { 167 this.latch = latch; 168 } 169 170 /** 171 * Used for testing purposes 172 */ 173 public CountDownLatch getLatch() { 174 return latch; 175 } 176 177 public boolean isClosed() { 178 return closed; 179 } 180 181 public void close() { 182 closed = true; 183 if (serverSocket != null) { 184 try { 185 serverSocket.close(); 186 } catch (IOException e) { 187 logger.error("Failed to close serverSocket", e); 188 } finally { 189 serverSocket = null; 190 } 191 } 192 193 logger.info("closing this server"); 194 synchronized (socketNodeList) { 195 for (SocketNode sn : socketNodeList) { 196 sn.close(); 197 } 198 } 199 if (socketNodeList.size() != 0) { 200 logger.warn("Was expecting a 0-sized socketNodeList after server shutdown"); 201 } 202 203 } 204 205 public void socketNodeClosing(SocketNode sn) { 206 logger.debug("Removing {}", sn); 207 208 // don't allow simultaneous access to the socketNodeList 209 // (e.g. removal whole iterating on the list causes 210 // java.util.ConcurrentModificationException) 211 synchronized (socketNodeList) { 212 socketNodeList.remove(sn); 213 } 214 } 215 216 static void usage(String msg) { 217 System.err.println(msg); 218 System.err.println("Usage: java " + SimpleSocketServer.class.getName() + " port configFile"); 219 System.exit(1); 220 } 221 222 static int parsePortNumber(String portStr) { 223 try { 224 return Integer.parseInt(portStr); 225 } catch (java.lang.NumberFormatException e) { 226 e.printStackTrace(); 227 usage("Could not interpret port number [" + portStr + "]."); 228 // we won't get here 229 return -1; 230 } 231 } 232 233 static public void configureLC(LoggerContext lc, String configFile) throws JoranException { 234 JoranConfigurator configurator = new JoranConfigurator(); 235 lc.reset(); 236 configurator.setContext(lc); 237 configurator.doConfigure(configFile); 238 } 239}