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 */ 017package org.apache.logging.log4j.core.net; 018 019import java.io.BufferedReader; 020import java.io.EOFException; 021import java.io.File; 022import java.io.FileInputStream; 023import java.io.FileNotFoundException; 024import java.io.IOException; 025import java.io.InputStreamReader; 026import java.io.ObjectInputStream; 027import java.io.OptionalDataException; 028import java.net.MalformedURLException; 029import java.net.ServerSocket; 030import java.net.Socket; 031import java.net.URI; 032import java.net.URL; 033import java.nio.charset.Charset; 034import java.util.Map; 035import java.util.concurrent.ConcurrentHashMap; 036import java.util.concurrent.ConcurrentMap; 037 038import org.apache.logging.log4j.LogManager; 039import org.apache.logging.log4j.Logger; 040import org.apache.logging.log4j.core.AbstractServer; 041import org.apache.logging.log4j.core.LogEvent; 042import org.apache.logging.log4j.core.config.Configuration; 043import org.apache.logging.log4j.core.config.ConfigurationFactory; 044import org.apache.logging.log4j.core.config.XMLConfiguration; 045import org.apache.logging.log4j.core.config.XMLConfigurationFactory; 046 047/** 048 * Listens for events over a socket connection. 049 */ 050public class SocketServer extends AbstractServer implements Runnable { 051 052 private final Logger logger; 053 054 private static final int MAX_PORT = 65534; 055 056 private volatile boolean isActive = true; 057 058 private final ServerSocket server; 059 060 private final ConcurrentMap<Long, SocketHandler> handlers = new ConcurrentHashMap<Long, SocketHandler>(); 061 062 /** 063 * Constructor. 064 * @param port to listen on. 065 * @throws IOException If an error occurs. 066 */ 067 public SocketServer(final int port) throws IOException { 068 this.server = new ServerSocket(port); 069 this.logger = LogManager.getLogger(this.getClass().getName() + '.' + port); 070 } 071 /** 072 * Main startup for the server. 073 * @param args The command line arguments. 074 * @throws Exception if an error occurs. 075 */ 076 public static void main(final String[] args) throws Exception { 077 if (args.length < 1 || args.length > 2) { 078 System.err.println("Incorrect number of arguments"); 079 printUsage(); 080 return; 081 } 082 final int port = Integer.parseInt(args[0]); 083 if (port <= 0 || port >= MAX_PORT) { 084 System.err.println("Invalid port number"); 085 printUsage(); 086 return; 087 } 088 if (args.length == 2 && args[1].length() > 0) { 089 ConfigurationFactory.setConfigurationFactory(new ServerConfigurationFactory(args[1])); 090 } 091 final SocketServer sserver = new SocketServer(port); 092 final Thread server = new Thread(sserver); 093 server.start(); 094 final Charset enc = Charset.defaultCharset(); 095 final BufferedReader reader = new BufferedReader(new InputStreamReader(System.in, enc)); 096 while (true) { 097 final String line = reader.readLine(); 098 if (line == null || line.equalsIgnoreCase("Quit") || line.equalsIgnoreCase("Stop") || line.equalsIgnoreCase("Exit")) { 099 sserver.shutdown(); 100 server.join(); 101 break; 102 } 103 } 104 } 105 106 private static void printUsage() { 107 System.out.println("Usage: ServerSocket port configFilePath"); 108 } 109 110 /** 111 * Shutdown the server. 112 */ 113 public void shutdown() { 114 this.isActive = false; 115 Thread.currentThread().interrupt(); 116 } 117 118 /** 119 * Accept incoming events and processes them. 120 */ 121 @Override 122 public void run() { 123 while (isActive) { 124 try { 125 // Accept incoming connections. 126 final Socket clientSocket = server.accept(); 127 clientSocket.setSoLinger(true, 0); 128 129 // accept() will block until a client connects to the server. 130 // If execution reaches this point, then it means that a client 131 // socket has been accepted. 132 133 final SocketHandler handler = new SocketHandler(clientSocket); 134 handlers.put(Long.valueOf(handler.getId()), handler); 135 handler.start(); 136 } catch (final IOException ioe) { 137 System.out.println("Exception encountered on accept. Ignoring. Stack Trace :"); 138 ioe.printStackTrace(); 139 } 140 } 141 for (final Map.Entry<Long, SocketHandler> entry : handlers.entrySet()) { 142 final SocketHandler handler = entry.getValue(); 143 handler.shutdown(); 144 try { 145 handler.join(); 146 } catch (final InterruptedException ie) { 147 // Ignore the exception 148 } 149 } 150 } 151 152 /** 153 * Thread that processes the events. 154 */ 155 private class SocketHandler extends Thread { 156 private final ObjectInputStream ois; 157 158 private boolean shutdown = false; 159 160 public SocketHandler(final Socket socket) throws IOException { 161 162 ois = new ObjectInputStream(socket.getInputStream()); 163 } 164 165 public void shutdown() { 166 this.shutdown = true; 167 interrupt(); 168 } 169 170 @Override 171 public void run() { 172 boolean closed = false; 173 try { 174 try { 175 while (!shutdown) { 176 final LogEvent event = (LogEvent) ois.readObject(); 177 if (event != null) { 178 log(event); 179 } 180 } 181 } catch (final EOFException eof) { 182 closed = true; 183 } catch (final OptionalDataException opt) { 184 logger.error("OptionalDataException eof=" + opt.eof + " length=" + opt.length, opt); 185 } catch (final ClassNotFoundException cnfe) { 186 logger.error("Unable to locate LogEvent class", cnfe); 187 } catch (final IOException ioe) { 188 logger.error("IOException encountered while reading from socket", ioe); 189 } 190 if (!closed) { 191 try { 192 ois.close(); 193 } catch (final Exception ex) { 194 // Ignore the exception; 195 } 196 } 197 } finally { 198 handlers.remove(Long.valueOf(getId())); 199 } 200 } 201 } 202 203 /** 204 * Factory that creates a Configuration for the server. 205 */ 206 private static class ServerConfigurationFactory extends XMLConfigurationFactory { 207 208 private final String path; 209 210 public ServerConfigurationFactory(final String path) { 211 this.path = path; 212 } 213 214 @Override 215 public Configuration getConfiguration(final String name, final URI configLocation) { 216 if (path != null && path.length() > 0) { 217 File file = null; 218 ConfigurationSource source = null; 219 try { 220 file = new File(path); 221 final FileInputStream is = new FileInputStream(file); 222 source = new ConfigurationSource(is, file); 223 } catch (final FileNotFoundException ex) { 224 // Ignore this error 225 } 226 if (source == null) { 227 try { 228 final URL url = new URL(path); 229 source = new ConfigurationSource(url.openStream(), path); 230 } catch (final MalformedURLException mue) { 231 // Ignore this error 232 } catch (final IOException ioe) { 233 // Ignore this error 234 } 235 } 236 237 try { 238 if (source != null) { 239 return new XMLConfiguration(source); 240 } 241 } catch (final Exception ex) { 242 // Ignore this error. 243 } 244 System.err.println("Unable to process configuration at " + path + ", using default."); 245 } 246 return super.getConfiguration(name, configLocation); 247 } 248 } 249}