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".