This is an automated email from the ASF dual-hosted git repository.

rgoers pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/logging-log4j2.git


The following commit(s) were added to refs/heads/master by this push:
     new 9e1a2ce  LOG4J2-2586 - TCP Appender should support a host name 
resolving to multiple IP addresses.
9e1a2ce is described below

commit 9e1a2ce9fa24fceb09c8bf8ca46e3eb510c486dc
Author: Ralph Goers <[email protected]>
AuthorDate: Wed Apr 10 09:18:15 2019 -0700

    LOG4J2-2586 - TCP Appender should support a host name resolving to multiple 
IP addresses.
---
 log4j-core/revapi.json                             |  48 ++++
 .../logging/log4j/core/net/SslSocketManager.java   |  27 ++-
 .../logging/log4j/core/net/TcpSocketManager.java   |  93 ++++++-
 .../log4j/core/net/SocketReconnectTest.java        | 268 ++++++++++++++++-----
 log4j-core/src/test/resources/log4j-socket.xml     |   6 +-
 src/changes/changes.xml                            |   3 +
 src/site/asciidoc/manual/appenders.adoc            |  12 +-
 7 files changed, 377 insertions(+), 80 deletions(-)

diff --git a/log4j-core/revapi.json b/log4j-core/revapi.json
index 0881e05..2d9ad68 100644
--- a/log4j-core/revapi.json
+++ b/log4j-core/revapi.json
@@ -2059,6 +2059,54 @@
         "code": "java.method.removed",
         "old": "method void 
org.apache.logging.log4j.core.filter.AbstractFilterable::<init>(org.apache.logging.log4j.core.Filter)",
         "justification": "LOG4J2-2491 - Allow all appenders to optionally 
carry a property array"
+      },
+      {
+        "code": "java.method.numberOfParametersChanged",
+        "old": "method java.net.Socket 
org.apache.logging.log4j.core.net.SslSocketManager::createSocket(java.lang.String,
 int) throws java.io.IOException",
+        "new": "method java.net.Socket 
org.apache.logging.log4j.core.net.TcpSocketManager::createSocket(java.net.InetSocketAddress,
 org.apache.logging.log4j.core.net.SocketOptions, int) throws 
java.io.IOException @ org.apache.logging.log4j.core.net.SslSocketManager",
+        "justification": "LOG4J2-2586 - Support the host name resolving to 
mulitple ip addresses"
+      },
+      {
+        "code": "java.method.nowStatic",
+        "old": "method java.net.Socket 
org.apache.logging.log4j.core.net.SslSocketManager::createSocket(java.lang.String,
 int) throws java.io.IOException",
+        "new": "method java.net.Socket 
org.apache.logging.log4j.core.net.TcpSocketManager::createSocket(java.net.InetSocketAddress,
 org.apache.logging.log4j.core.net.SocketOptions, int) throws 
java.io.IOException @ org.apache.logging.log4j.core.net.SslSocketManager",
+        "justification": "LOG4J2-2586 - Support the host name resolving to 
mulitple ip addresses"
+      },
+      {
+        "code": "java.method.numberOfParametersChanged",
+        "old": "method java.net.Socket 
org.apache.logging.log4j.core.net.TcpSocketManager::createSocket(java.lang.String,
 int, org.apache.logging.log4j.core.net.SocketOptions, int) throws 
java.io.IOException @ org.apache.logging.log4j.core.net.SslSocketManager",
+        "new": "method java.net.Socket 
org.apache.logging.log4j.core.net.SslSocketManager::createSocket(java.net.InetSocketAddress)
 throws java.io.IOException",
+        "justification": "LOG4J2-2586 - Support the host name resolving to 
mulitple ip addresses"
+      },
+      {
+        "code": "java.method.noLongerStatic",
+        "old": "method java.net.Socket 
org.apache.logging.log4j.core.net.TcpSocketManager::createSocket(java.lang.String,
 int, org.apache.logging.log4j.core.net.SocketOptions, int) throws 
java.io.IOException @ org.apache.logging.log4j.core.net.SslSocketManager",
+        "new": "method java.net.Socket 
org.apache.logging.log4j.core.net.SslSocketManager::createSocket(java.net.InetSocketAddress)
 throws java.io.IOException",
+        "justification": "LOG4J2-2586 - Support the host name resolving to 
mulitple ip addresses"
+      },
+      {
+        "code": "java.method.numberOfParametersChanged",
+        "old": "method java.net.Socket 
org.apache.logging.log4j.core.net.TcpSocketManager::createSocket(java.lang.String,
 int) throws java.io.IOException",
+        "new": "method java.net.Socket 
org.apache.logging.log4j.core.net.TcpSocketManager::createSocket(java.net.InetSocketAddress,
 org.apache.logging.log4j.core.net.SocketOptions, int) throws 
java.io.IOException",
+        "justification": "LOG4J2-2586 - Support the host name resolving to 
mulitple ip addresses"
+      },
+      {
+        "code": "java.method.nowStatic",
+        "old": "method java.net.Socket 
org.apache.logging.log4j.core.net.TcpSocketManager::createSocket(java.lang.String,
 int) throws java.io.IOException",
+        "new": "method java.net.Socket 
org.apache.logging.log4j.core.net.TcpSocketManager::createSocket(java.net.InetSocketAddress,
 org.apache.logging.log4j.core.net.SocketOptions, int) throws 
java.io.IOException",
+        "justification": "LOG4J2-2586 - Support the host name resolving to 
mulitple ip addresses"
+      },
+      {
+        "code": "java.method.numberOfParametersChanged",
+        "old": "method java.net.Socket 
org.apache.logging.log4j.core.net.TcpSocketManager::createSocket(java.lang.String,
 int, org.apache.logging.log4j.core.net.SocketOptions, int) throws 
java.io.IOException",
+        "new": "method java.net.Socket 
org.apache.logging.log4j.core.net.TcpSocketManager::createSocket(java.net.InetSocketAddress)
 throws java.io.IOException",
+        "justification": "LOG4J2-2586 - Support the host name resolving to 
mulitple ip addresses"
+      },
+      {
+        "code": "java.method.noLongerStatic",
+        "old": "method java.net.Socket 
org.apache.logging.log4j.core.net.TcpSocketManager::createSocket(java.lang.String,
 int, org.apache.logging.log4j.core.net.SocketOptions, int) throws 
java.io.IOException",
+        "new": "method java.net.Socket 
org.apache.logging.log4j.core.net.TcpSocketManager::createSocket(java.net.InetSocketAddress)
 throws java.io.IOException",
+        "justification": "LOG4J2-2586 - Support the host name resolving to 
mulitple ip addresses"
       }
     ]
   }
diff --git 
a/log4j-core/src/main/java/org/apache/logging/log4j/core/net/SslSocketManager.java
 
b/log4j-core/src/main/java/org/apache/logging/log4j/core/net/SslSocketManager.java
index 296b4ea..666aa7e 100644
--- 
a/log4j-core/src/main/java/org/apache/logging/log4j/core/net/SslSocketManager.java
+++ 
b/log4j-core/src/main/java/org/apache/logging/log4j/core/net/SslSocketManager.java
@@ -22,6 +22,7 @@ import java.io.Serializable;
 import java.net.InetAddress;
 import java.net.InetSocketAddress;
 import java.net.Socket;
+import java.util.List;
 
 import javax.net.ssl.SSLSocket;
 import javax.net.ssl.SSLSocketFactory;
@@ -99,11 +100,10 @@ public class SslSocketManager extends TcpSocketManager {
     }
 
     @Override
-    protected Socket createSocket(final String host, final int port) throws 
IOException {
+    protected Socket createSocket(final InetSocketAddress socketAddress) 
throws IOException {
         final SSLSocketFactory socketFactory = 
createSslSocketFactory(sslConfig);
-        final InetSocketAddress address = new InetSocketAddress(host, port);
         final Socket newSocket = socketFactory.createSocket();
-        newSocket.connect(address, getConnectTimeoutMillis());
+        newSocket.connect(socketAddress, getConnectTimeoutMillis());
         return newSocket;
     }
 
@@ -129,23 +129,32 @@ public class SslSocketManager extends TcpSocketManager {
                     data.connectTimeoutMillis, data.reconnectDelayMillis, 
data.immediateFail, data.layout, data.bufferSize,
                     data.socketOptions);
         }
-        
+
         @Override
         Socket createSocket(final SslFactoryData data) throws IOException {
-            return SslSocketManager.createSocket(data.host, data.port, 
data.connectTimeoutMillis, data.sslConfiguration,
-                    data.socketOptions);
+            List<InetSocketAddress> socketAddresses = 
resolver.resolveHost(data.host, data.port);
+            IOException ioe = null;
+            for (InetSocketAddress socketAddress : socketAddresses) {
+                try {
+                    return SslSocketManager.createSocket(socketAddress, 
data.connectTimeoutMillis,
+                            data.sslConfiguration, data.socketOptions);
+                } catch (IOException ex) {
+                    ioe = ex;
+                }
+            }
+            throw new IOException(errorMessage(data, socketAddresses) , ioe);
         }
     }
 
-    static Socket createSocket(final String host, int port, int 
connectTimeoutMillis,
-            final SslConfiguration sslConfiguration, SocketOptions 
socketOptions) throws IOException {
+    static Socket createSocket(final InetSocketAddress socketAddress, final 
int connectTimeoutMillis,
+            final SslConfiguration sslConfiguration, final SocketOptions 
socketOptions) throws IOException {
         final SSLSocketFactory socketFactory = 
createSslSocketFactory(sslConfiguration);
         final SSLSocket socket = (SSLSocket) socketFactory.createSocket();
         if (socketOptions != null) {
             // Not sure which options must be applied before or after the 
connect() call.
             socketOptions.apply(socket);
         }
-        socket.connect(new InetSocketAddress(host, port), 
connectTimeoutMillis);
+        socket.connect(socketAddress, connectTimeoutMillis);
         if (socketOptions != null) {
             // Not sure which options must be applied before or after the 
connect() call.
             socketOptions.apply(socket);
diff --git 
a/log4j-core/src/main/java/org/apache/logging/log4j/core/net/TcpSocketManager.java
 
b/log4j-core/src/main/java/org/apache/logging/log4j/core/net/TcpSocketManager.java
index d30d7de..6108c0c 100644
--- 
a/log4j-core/src/main/java/org/apache/logging/log4j/core/net/TcpSocketManager.java
+++ 
b/log4j-core/src/main/java/org/apache/logging/log4j/core/net/TcpSocketManager.java
@@ -24,7 +24,9 @@ import java.net.InetAddress;
 import java.net.InetSocketAddress;
 import java.net.Socket;
 import java.net.UnknownHostException;
+import java.util.ArrayList;
 import java.util.HashMap;
+import java.util.List;
 import java.util.Map;
 import java.util.concurrent.CountDownLatch;
 
@@ -279,9 +281,30 @@ public class TcpSocketManager extends 
AbstractSocketManager {
         }
 
         void reconnect() throws IOException {
-            final Socket sock = createSocket(inetAddress.getHostName(), port);
+            List<InetSocketAddress> socketAddresses = 
FACTORY.resolver.resolveHost(host, port);
+            if (socketAddresses.size() == 1) {
+                LOGGER.debug("Reconnecting " + socketAddresses.get(0));
+                connect(socketAddresses.get(0));
+            } else {
+                IOException ioe = null;
+                for (InetSocketAddress socketAddress : socketAddresses) {
+                    try {
+                        LOGGER.debug("Reconnecting " + socketAddress);
+                        connect(socketAddress);
+                        return;
+                    } catch (IOException ex) {
+                        ioe = ex;
+                    }
+                }
+                throw ioe;
+            }
+        }
+
+        private void connect(InetSocketAddress socketAddress) throws 
IOException {
+            final Socket sock = createSocket(socketAddress);
             @SuppressWarnings("resource") // newOS is managed by the enclosing 
Manager.
             final OutputStream newOS = sock.getOutputStream();
+            InetAddress prev = socket != null ? socket.getInetAddress() : null;
             synchronized (owner) {
                 Closer.closeSilently(getOutputStream());
                 setOutputStream(newOS);
@@ -289,7 +312,9 @@ public class TcpSocketManager extends AbstractSocketManager 
{
                 reconnector = null;
                 shutdown = true;
             }
-            LOGGER.debug("Connection to {}:{} reestablished: {}", host, port, 
socket);
+            String type = prev != null && 
prev.getHostAddress().equals(socketAddress.getAddress().getHostAddress()) ?
+                    "reestablished" : "established";
+            LOGGER.debug("Connection to {}:{} {}: {}", host, port, type, 
socket);
         }
 
         @Override
@@ -305,19 +330,19 @@ public class TcpSocketManager extends 
AbstractSocketManager {
         return recon;
     }
 
-    protected Socket createSocket(final String host, final int port) throws 
IOException {
-        return createSocket(host, port, socketOptions, connectTimeoutMillis);
+    protected Socket createSocket(final InetSocketAddress socketAddress) 
throws IOException {
+        return createSocket(socketAddress, socketOptions, 
connectTimeoutMillis);
     }
 
-    protected static Socket createSocket(final String host, final int port, 
final SocketOptions socketOptions,
+    protected static Socket createSocket(final InetSocketAddress 
socketAddress, final SocketOptions socketOptions,
             final int connectTimeoutMillis) throws IOException {
-        LOGGER.debug("Creating socket {}:{}", host, port);
+        LOGGER.debug("Creating socket {}", socketAddress.toString());
         final Socket newSocket = new Socket();
         if (socketOptions != null) {
             // Not sure which options must be applied before or after the 
connect() call.
             socketOptions.apply(newSocket);
         }
-        newSocket.connect(new InetSocketAddress(host, port), 
connectTimeoutMillis);
+        newSocket.connect(socketAddress, connectTimeoutMillis);
         if (socketOptions != null) {
             // Not sure which options must be applied before or after the 
connect() call.
             socketOptions.apply(newSocket);
@@ -325,6 +350,7 @@ public class TcpSocketManager extends AbstractSocketManager 
{
         return newSocket;
     }
 
+
     /**
      * Data for the factory.
      */
@@ -370,6 +396,8 @@ public class TcpSocketManager extends AbstractSocketManager 
{
     protected static class TcpSocketManagerFactory<M extends TcpSocketManager, 
T extends FactoryData>
             implements ManagerFactory<M, T> {
 
+        static HostResolver resolver = new HostResolver();
+
         @SuppressWarnings("resource")
         @Override
         public M createManager(final String name, final T data) {
@@ -406,9 +434,58 @@ public class TcpSocketManager extends 
AbstractSocketManager {
         }
 
         Socket createSocket(final T data) throws IOException {
-            return TcpSocketManager.createSocket(data.host, data.port, 
data.socketOptions, data.connectTimeoutMillis);
+            List<InetSocketAddress> socketAddresses = 
resolver.resolveHost(data.host, data.port);
+            IOException ioe = null;
+            for (InetSocketAddress socketAddress : socketAddresses) {
+                try {
+                    return TcpSocketManager.createSocket(socketAddress, 
data.socketOptions, data.connectTimeoutMillis);
+                } catch (IOException ex) {
+                    ioe = ex;
+                }
+            }
+            throw new IOException(errorMessage(data, socketAddresses) , ioe);
         }
 
+        protected String errorMessage(final T data, List<InetSocketAddress> 
socketAddresses) {
+            StringBuilder sb = new StringBuilder("Unable to create socket for 
");
+            sb.append(data.host).append(" at port ").append(data.port);
+            if (socketAddresses.size() == 1) {
+                if 
(!socketAddresses.get(0).getAddress().getHostAddress().equals(data.host)) {
+                    sb.append(" using ip address 
").append(socketAddresses.get(0).getAddress().getHostAddress());
+                    sb.append(" and port 
").append(socketAddresses.get(0).getPort());
+                }
+            } else {
+                sb.append(" using ip addresses and ports ");
+                for (int i = 0; i < socketAddresses.size(); ++i) {
+                    if (i > 0) {
+                        sb.append(", ");
+                        
sb.append(socketAddresses.get(i).getAddress().getHostAddress());
+                        
sb.append(":").append(socketAddresses.get(i).getPort());
+                    }
+                }
+            }
+            return sb.toString();
+        }
+    }
+
+    /**
+     * This method is only for unit testing. It is not Thread-safe.
+     * @param resolver the HostResolver.
+     */
+    public static void setHostResolver(HostResolver resolver) {
+        TcpSocketManagerFactory.resolver = resolver;
+    }
+
+    public static class HostResolver {
+
+        public List<InetSocketAddress> resolveHost(String host, int port) 
throws UnknownHostException {
+            InetAddress[] addresses = InetAddress.getAllByName(host);
+            List<InetSocketAddress> socketAddresses = new 
ArrayList<>(addresses.length);
+            for (InetAddress address: addresses) {
+                socketAddresses.add(new InetSocketAddress(address, port));
+            }
+            return socketAddresses;
+        }
     }
 
     /**
diff --git 
a/log4j-core/src/test/java/org/apache/logging/log4j/core/net/SocketReconnectTest.java
 
b/log4j-core/src/test/java/org/apache/logging/log4j/core/net/SocketReconnectTest.java
index 151d444..2b4ebfd 100644
--- 
a/log4j-core/src/test/java/org/apache/logging/log4j/core/net/SocketReconnectTest.java
+++ 
b/log4j-core/src/test/java/org/apache/logging/log4j/core/net/SocketReconnectTest.java
@@ -16,71 +16,123 @@
  */
 package org.apache.logging.log4j.core.net;
 
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.apache.logging.log4j.core.LoggerContext;
+import org.apache.logging.log4j.core.appender.AppenderLoggingException;
+import org.apache.logging.log4j.core.config.Configurator;
+import org.apache.logging.log4j.test.AvailablePortFinder;
+import org.apache.logging.log4j.util.Strings;
+import org.junit.After;
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
 import java.io.BufferedReader;
+import java.io.IOException;
 import java.io.InputStreamReader;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
 import java.net.ServerSocket;
 import java.net.Socket;
+import java.net.SocketException;
+import java.net.UnknownHostException;
 import java.util.ArrayList;
 import java.util.List;
 
-import org.apache.logging.log4j.Logger;
-import org.apache.logging.log4j.core.appender.AppenderLoggingException;
-import org.apache.logging.log4j.junit.LoggerContextRule;
-import org.apache.logging.log4j.test.AvailablePortFinder;
-import org.apache.logging.log4j.util.Strings;
-import org.junit.ClassRule;
-import org.junit.Ignore;
-import org.junit.Test;
-
 import static org.junit.Assert.*;
 
-@Ignore("Currently needs better port choosing support")
+//@Ignore("Currently needs better port choosing support")
 public class SocketReconnectTest {
-    private static final int SOCKET_PORT = 
AvailablePortFinder.getNextAvailable();
+    private static final int SOCKET_PORT1 = 
AvailablePortFinder.getNextAvailable();
+    private static final int SOCKET_PORT2 = 
AvailablePortFinder.getNextAvailable();
 
     private static final String CONFIG = "log4j-socket.xml";
 
     private static final String SHUTDOWN = "Shutdown" + Strings.LINE_SEPARATOR 
+
-        "................................................................" + 
Strings.LINE_SEPARATOR +
-        "................................................................" + 
Strings.LINE_SEPARATOR +
-        "................................................................" + 
Strings.LINE_SEPARATOR +
-        "................................................................" + 
Strings.LINE_SEPARATOR;
+            "................................................................" 
+ Strings.LINE_SEPARATOR +
+            "................................................................" 
+ Strings.LINE_SEPARATOR +
+            "................................................................" 
+ Strings.LINE_SEPARATOR +
+            "................................................................" 
+ Strings.LINE_SEPARATOR;
 
-    @ClassRule
-    public static LoggerContextRule context = new LoggerContextRule(CONFIG);
+    public static LocalHostResolver resolver = new LocalHostResolver();
 
-    @Test
-    public void testReconnect() throws Exception {
+    private static LoggerContext loggerContext;
 
-        final List<String> list = new ArrayList<>();
-        TestSocketServer server = new TestSocketServer(list);
-        server.start();
-        Thread.sleep(300);
+    private static final List<String> list = new ArrayList<>();
+    private static int[] ports;
+    private static TestSocketServer server1;
+    private static TestSocketServer server2;
+    private static Logger logger;
 
-        //System.err.println("Initializing logger");
-        final Logger logger = context.getLogger();
 
-        String message = "Log #1";
-        logger.error(message);
-        final String expectedHeader = "Header";
+    @BeforeClass
+    public static void beforeClass() throws IOException, InterruptedException {
+        server1 = new TestSocketServer(0);
+        server2 = new TestSocketServer(0);
+        server1.start();
+        server2.start();
+        Thread.sleep(100);
+        ports = new int[] { server1.getPort(), server2.getPort()};
+        resolver.ports = ports;
+        TcpSocketManager.setHostResolver(resolver);
+        loggerContext = Configurator.initialize("SocketReconnectTest", 
SocketReconnectTest.class.getClassLoader(),
+                CONFIG);
+        logger = LogManager.getLogger(SocketReconnectTest.class);
+        server1.shutdown();
+        server1.join();
+        server2.shutdown();
+        server2.join();
+        server1 = null;
+        server2 = null;
+        Thread.sleep(100);
+        list.clear();
+    }
+
+    @AfterClass
+    public static void afterClass() {
+        Configurator.shutdown(loggerContext);
+    }
+
+    @After
+    public void after() throws InterruptedException {
+        if (server1 != null) {
+            server1.shutdown();
+            server1.join();
+        }
+        if (server2 != null) {
+            server2.shutdown();
+            server2.join();
+        }
+        server1 = null;
+        server2 = null;
+        Thread.sleep(300);
+    }
 
+    @Test
+    public void testReconnect() throws Exception {
+        list.clear();
+        resolver.ports = new int[] {ports[0]};
+        server1 = new TestSocketServer(ports[0]);
+        server1.start();
+        Thread.sleep(200);
+        String message = "Log #1";
         String msg = null;
-        String header = null;
         for (int i = 0; i < 5; ++i) {
+            logger.error(message);
             Thread.sleep(100);
-            if (list.size() > 1) {
-                header = list.get(0);
-                msg = list.get(1);
-                break;
+            if (list.size() > 0) {
+                msg = list.get(0);
+                if (msg != null) {
+                    break;
+                }
             }
         }
-        assertNotNull("No header", header);
-        assertEquals(expectedHeader, header);
         assertNotNull("No message", msg);
         assertEquals(message, msg);
 
         logger.error(SHUTDOWN);
-        server.join();
+        server1.join();
 
         list.clear();
 
@@ -100,73 +152,173 @@ public class SocketReconnectTest {
         message = "Log #3";
 
 
-        server = new TestSocketServer(list);
-        server.start();
+        server1 = new TestSocketServer(ports[0]);
+        server1.start();
         Thread.sleep(300);
 
         msg = null;
-        header = null;
-        logger.error(message);
         for (int i = 0; i < 5; ++i) {
+            logger.error(message);
             Thread.sleep(100);
-            if (list.size() > 1) {
-                header = list.get(0);
-                msg = list.get(1);
-                break;
+            if (list.size() > 0) {
+                msg = list.get(0);
+                if (msg != null) {
+                    break;
+                }
             }
         }
-        assertNotNull("No header", header);
-        assertEquals(expectedHeader, header);
         assertNotNull("No message", msg);
         assertEquals(message, msg);
         logger.error(SHUTDOWN);
-        server.join();
+        server1.join();
+    }
+
+    @Test
+    public void testFailover() throws Exception {
+        list.clear();
+        server1 = new TestSocketServer(ports[0]);
+        server2 = new TestSocketServer(ports[1]);
+        resolver.ports = ports;
+        server1.start();
+        server2.start();
+        Thread.sleep(100);
+
+        String message = "Log #1";
+
+        String msg = null;
+        for (int i = 0; i < 5; ++i) {
+            logger.error(message);
+            Thread.sleep(100);
+            if (list.size() > 0) {
+                msg = list.get(0);
+                if (msg != null) {
+                    break;
+                }
+            }
+        }
+        assertNotNull("No message", msg);
+        assertEquals(message, msg);
+
+        server1.shutdown();
+        server1.join();
+        logger.error("Ignore");
+
+        list.clear();
+
+        message = "Log #2";
+        for (int i = 0; i < 5; ++i) {
+            logger.error(message);
+            Thread.sleep(100);
+            if (list.size() > 0) {
+                msg = list.get(0);
+                if (msg != null) {
+                    break;
+                }
+            }
+        }
+        assertNotNull("No message", msg);
+        assertEquals(message, msg);
+
+        server2.shutdown();
+        server2.join();
     }
 
 
     private static class TestSocketServer extends Thread {
         private volatile boolean shutdown = false;
-        private final List<String> list;
-        private Socket client;
+        private volatile boolean started = false;
+        private volatile Socket client;
+        private final int port;
+        private ServerSocket server;
+
+        public TestSocketServer(int port) throws IOException {
+            this.port = port;
+            server = new ServerSocket(port);
+        }
 
-        public TestSocketServer(final List<String> list) {
-            this.list = list;
+        public int getPort() {
+            return port == 0 ? server.getLocalPort() : port;
+        }
+
+        public void shutdown() {
+            if (!shutdown) {
+                shutdown = true;
+                if (server != null && server.isBound()) {
+                    try {
+                        if (client != null) {
+                            Socket serverClient = client;
+                            client = null;
+                            serverClient.shutdownInput();
+                            serverClient.shutdownOutput();
+                            serverClient.setSoLinger(true, 0);
+                            serverClient.close();
+                        }
+                        ServerSocket serverSocket = server;
+                        server = null;
+                        serverSocket.close();
+                    } catch (Exception ex) {
+                        System.out.println("Unable to send shutdown message");
+                        ex.printStackTrace();
+                    }
+                }
+                return;
+            }
         }
 
         @Override
         public void run() {
-            ServerSocket server = null;
             client = null;
             try {
-                server = new ServerSocket(SOCKET_PORT);
                 client = server.accept();
+                started = true;
                 while (!shutdown) {
                     final BufferedReader reader = new BufferedReader(new 
InputStreamReader(client.getInputStream()));
                     final String line = reader.readLine();
-                    if (line.equals("Shutdown")) {
+                    if (line == null || line.equals("Shutdown")) {
                         shutdown = true;
                     } else {
                         list.add(line);
                     }
                 }
+            } catch (final SocketException ex) {
+                if (!shutdown) {
+                    ex.printStackTrace();
+                }
             } catch (final Exception ex) {
                 ex.printStackTrace();
             } finally {
-                if (client != null) {
+                if (client != null && !client.isClosed()) {
                     try {
+                        client.setSoLinger(true, 0);
+                        client.shutdownOutput();
                         client.close();
                     } catch (final Exception ex) {
-                        System.out.println("Unable to close socket " + 
ex.getMessage());
+                        System.out.println("Unable to close socket: " + 
ex.getMessage());
                     }
                 }
-                if (server != null) {
+                if (server != null && !server.isClosed()) {
                     try {
                         server.close();
                     } catch (final Exception ex) {
-                        System.out.println("Unable to close server socket " + 
ex.getMessage());
+                        System.out.println("Unable to close server socket: " + 
ex.getMessage());
                     }
                 }
             }
         }
     }
+
+    private static class LocalHostResolver extends 
TcpSocketManager.HostResolver {
+        public volatile int[] ports;
+
+        @Override
+        public List<InetSocketAddress> resolveHost(String host, int port) 
throws UnknownHostException {
+            int[] socketPorts = ports;
+            List<InetSocketAddress> socketAddresses = new 
ArrayList<>(ports.length);
+            InetAddress addr = InetAddress.getLocalHost();
+            for (int i = 0; i < socketPorts.length; ++i){
+                socketAddresses.add(new InetSocketAddress(addr, 
socketPorts[i]));
+            }
+            return socketAddresses;
+        }
+    }
 }
diff --git a/log4j-core/src/test/resources/log4j-socket.xml 
b/log4j-core/src/test/resources/log4j-socket.xml
index a437071..b511fa6 100644
--- a/log4j-core/src/test/resources/log4j-socket.xml
+++ b/log4j-core/src/test/resources/log4j-socket.xml
@@ -15,11 +15,11 @@
   ~ See the License for the specific language governing permissions and
   ~ limitations under the License.
   -->
-<Configuration status="OFF" name="MyApp">
+<Configuration status="ERROR" name="MyApp">
        <Appenders>
                <Socket name="socket" host="localhost" port="5514" 
protocol="TCP" ignoreExceptions="false"
-                               reconnectionDelay="100">
-                       <BasicLayout />
+                                               reconnectionDelay="100" 
immediateFlush="true">
+                       <PatternLayout pattern="%m%n"/>
                </Socket>
        </Appenders>
        <Loggers>
diff --git a/src/changes/changes.xml b/src/changes/changes.xml
index dec86fe..a87fd73 100644
--- a/src/changes/changes.xml
+++ b/src/changes/changes.xml
@@ -378,6 +378,9 @@
       </action>
     </release>
     <release version="2.12.0" date="2019-MM-DD" description="GA Release 
2.12.0">
+      <action issue="LOG4J2-2586" dev="rgoers" type="add">
+        TCP Appender should support a host name resolving to multiple IP 
addresses.
+      </action>
       <action issue="LOG4J2-2559" dev="ggregory" type="fix" due-to="Li Lei, 
Gary Gregory">
         NullPointerException in JdbcAppender.createAppender().
       </action>
diff --git a/src/site/asciidoc/manual/appenders.adoc 
b/src/site/asciidoc/manual/appenders.adoc
index 533350c..05d129a 100644
--- a/src/site/asciidoc/manual/appenders.adoc
+++ b/src/site/asciidoc/manual/appenders.adoc
@@ -4063,7 +4063,13 @@ example, "SelectIt".
 The `SocketAppender` is an OutputStreamAppender that writes its output
 to a remote destination specified by a host and port. The data can be
 sent over either TCP or UDP and can be sent in any format. You can
-optionally secure communication with link:#SSL[SSL].
+optionally secure communication with link:#SSL[SSL]. Note that the TCP and SSL
+variants write to the socket as a stream and do not expect response from
+the target destination. Due to limitations in the TCP protocol that
+means that when the target server closes its connection some log events
+may continue to appear to succeed until a closed connection exception
+is raised, causing those events to be lost. If guaranteed delivery is
+required a protocol that requires acknowledgements must be used.
 
 .`SocketAppender` Parameters
 [cols=",,",options="header",]
@@ -4075,7 +4081,9 @@ optionally secure communication with link:#SSL[SSL].
 log events. This parameter is required.
 
 |port |integer |The port on the host that is listening for log events.
-This parameter must be specified.
+This parameter must be specified.If the host name resolves to multiple
+IP addresses the TCP and SSL variations will fail over to the next IP
+address when a connection is lost.
 
 |protocol |String |"TCP" (default), "SSL" or "UDP".
 

Reply via email to