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}