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