[Git][java-team/jboss-logmanager][upstream] New upstream version 2.1.4

Markus Koschany gitlab at salsa.debian.org
Sat Jul 7 21:20:07 BST 2018


Markus Koschany pushed to branch upstream at Debian Java Maintainers / jboss-logmanager


Commits:
a32bc41a by Markus Koschany at 2018-07-07T22:13:12+02:00
New upstream version 2.1.4
- - - - -


13 changed files:

- pom.xml
- src/main/java/org/jboss/logmanager/LogContext.java
- src/main/java/org/jboss/logmanager/LoggerNode.java
- src/main/java/org/jboss/logmanager/config/LogContextConfigurationImpl.java
- src/main/java/org/jboss/logmanager/formatters/Formatters.java
- + src/main/java/org/jboss/logmanager/handlers/ClientSocketFactory.java
- src/main/java/org/jboss/logmanager/handlers/SocketHandler.java
- src/main/java/org/jboss/logmanager/handlers/SyslogHandler.java
- src/main/java/org/jboss/logmanager/handlers/TcpOutputStream.java
- src/main/java/org/jboss/logmanager/handlers/UdpOutputStream.java
- + src/test/java/org/jboss/logmanager/LogContextCloseTests.java
- src/test/java/org/jboss/logmanager/formatters/PatternFormatterTests.java
- src/test/java/org/jboss/logmanager/handlers/PeriodicRotatingFileHandlerTests.java


Changes:

=====================================
pom.xml
=====================================
--- a/pom.xml
+++ b/pom.xml
@@ -28,7 +28,7 @@
     <groupId>org.jboss.logmanager</groupId>
     <artifactId>jboss-logmanager</artifactId>
     <packaging>jar</packaging>
-    <version>2.1.2.Final</version>
+    <version>2.1.4.Final</version>
 
     <parent>
         <groupId>org.jboss</groupId>
@@ -47,9 +47,9 @@
     <properties>
         <!-- Dependency versions -->
         <version.javax.json>1.0</version.javax.json>
-        <version.org.byteman>3.0.10</version.org.byteman>
+        <version.org.byteman>4.0.3</version.org.byteman>
         <version.org.glassfish.javax.json>1.0.4</version.org.glassfish.javax.json>
-        <version.org.jboss.modules.jboss-modules>1.7.0.Beta3</version.org.jboss.modules.jboss-modules>
+        <version.org.jboss.modules.jboss-modules>1.7.0.Final</version.org.jboss.modules.jboss-modules>
         <version.org.wildfly.common.wildfly-common>1.2.0.Final</version.org.wildfly.common.wildfly-common>
         <version.junit.junit>4.12</version.junit.junit>
 


=====================================
src/main/java/org/jboss/logmanager/LogContext.java
=====================================
--- a/src/main/java/org/jboss/logmanager/LogContext.java
+++ b/src/main/java/org/jboss/logmanager/LogContext.java
@@ -21,18 +21,20 @@ package org.jboss.logmanager;
 
 import java.lang.ref.WeakReference;
 import java.security.Permission;
+import java.util.Collection;
 import java.util.Enumeration;
 import java.util.HashMap;
 import java.util.Iterator;
+import java.util.LinkedHashSet;
 import java.util.Map;
 import java.util.Map.Entry;
 import java.util.NoSuchElementException;
+import java.util.Set;
 import java.util.concurrent.ConcurrentMap;
 import java.util.concurrent.ConcurrentSkipListMap;
 import java.util.concurrent.atomic.AtomicInteger;
 import java.util.concurrent.atomic.AtomicReference;
 import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
-
 import java.util.logging.Level;
 import java.util.logging.LoggingMXBean;
 import java.util.logging.LoggingPermission;
@@ -40,7 +42,8 @@ import java.util.logging.LoggingPermission;
 /**
  * A logging context, for producing isolated logging environments.
  */
-public final class LogContext implements Protectable {
+ at SuppressWarnings("unused")
+public final class LogContext implements Protectable, AutoCloseable {
     private static final LogContext SYSTEM_CONTEXT = new LogContext(false);
 
     static final Permission CREATE_CONTEXT_PERMISSION = new RuntimePermission("createLogContext", null);
@@ -98,6 +101,8 @@ public final class LogContext implements Protectable {
     }
 
     private final AtomicReference<Map<String, LevelRef>> levelMapReference;
+    // Guarded by treeLock
+    private final Set<AutoCloseable> closeHandlers;
 
     /**
      * This lock is taken any time a change is made which affects multiple nodes in the hierarchy.
@@ -109,6 +114,7 @@ public final class LogContext implements Protectable {
         levelMapReference = new AtomicReference<Map<String, LevelRef>>(LazyHolder.INITIAL_LEVEL_MAP);
         rootLogger = new LoggerNode(this);
         loggerNames = new ConcurrentSkipListMap<String, AtomicInteger>();
+        closeHandlers = new LinkedHashSet<>();
     }
 
     /**
@@ -340,6 +346,20 @@ public final class LogContext implements Protectable {
         granted.remove();
     }
 
+    @Override
+    public void close() throws Exception {
+        synchronized (treeLock) {
+            // First we want to close all loggers
+            recursivelyClose(rootLogger);
+            // Next process the close handlers associated with this log context
+            for (AutoCloseable handler : closeHandlers) {
+                handler.close();
+            }
+            // Finally clear any logger names
+            loggerNames.clear();
+        }
+    }
+
     /**
      * Returns an enumeration of the logger names that have been created. This does not return names of loggers that
      * may have been garbage collected. Logger names added after the enumeration has been retrieved may also be added to
@@ -383,6 +403,49 @@ public final class LogContext implements Protectable {
         };
     }
 
+    /**
+     * Adds a handler invoked during the {@linkplain #close() close} of this log context. The close handlers will be
+     * invoked in the order they are added.
+     * <p>
+     * The loggers associated with this context will always be closed.
+     * </p>
+     *
+     * @param closeHandler the close handler to use
+     */
+    public void addCloseHandler(final AutoCloseable closeHandler) {
+        synchronized (treeLock) {
+            closeHandlers.add(closeHandler);
+        }
+    }
+
+    /**
+     * Gets the current close handlers associated with this log context.
+     *
+     * @return the current close handlers
+     */
+    public Set<AutoCloseable> getCloseHandlers() {
+        synchronized (treeLock) {
+            return new LinkedHashSet<>(closeHandlers);
+        }
+    }
+
+    /**
+     * Clears any current close handlers associated with log context, then adds the handlers to be invoked during
+     * the {@linkplain #close() close} of this log context. The close handlers will be invoked in the order they are
+     * added.
+     * <p>
+     * The loggers associated with this context will always be closed.
+     * </p>
+     *
+     * @param closeHandlers the close handlers to use
+     */
+    public void setCloseHandlers(final Collection<AutoCloseable> closeHandlers) {
+        synchronized (treeLock) {
+            this.closeHandlers.clear();
+            this.closeHandlers.addAll(closeHandlers);
+        }
+    }
+
     protected void incrementRef(final String name) {
         AtomicInteger counter = loggerNames.get(name);
         if (counter == null) {
@@ -426,6 +489,15 @@ public final class LogContext implements Protectable {
         return strong ? new CopyOnWriteMap<String, LoggerNode>() : new CopyOnWriteWeakMap<String, LoggerNode>();
     }
 
+    private void recursivelyClose(final LoggerNode loggerNode) {
+        synchronized (treeLock) {
+            for (LoggerNode child : loggerNode.getChildren()) {
+                recursivelyClose(child);
+            }
+            loggerNode.close();
+        }
+    }
+
     private interface LevelRef {
         Level get();
     }


=====================================
src/main/java/org/jboss/logmanager/LoggerNode.java
=====================================
--- a/src/main/java/org/jboss/logmanager/LoggerNode.java
+++ b/src/main/java/org/jboss/logmanager/LoggerNode.java
@@ -24,7 +24,6 @@ import java.security.PrivilegedAction;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.HashMap;
-import java.util.Iterator;
 import java.util.Map;
 import java.util.concurrent.ConcurrentMap;
 import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
@@ -35,7 +34,7 @@ import java.util.logging.Level;
 /**
  * A node in the tree of logger names.  Maintains weak references to children and a strong reference to its parent.
  */
-final class LoggerNode {
+final class LoggerNode implements AutoCloseable {
 
     /**
      * The log context.
@@ -144,6 +143,26 @@ final class LoggerNode {
         children = context.createChildMap();
     }
 
+    @Override
+    public void close() {
+        synchronized (context.treeLock) {
+            // Reset everything to defaults
+            filter = null;
+            if ("".equals(fullName)) {
+                level = Level.INFO;
+                effectiveLevel = Level.INFO.intValue();
+            } else {
+                level = null;
+                effectiveLevel = Level.INFO.intValue();
+            }
+            handlersUpdater.clear(this);
+            useParentFilter = false;
+            useParentHandlers = true;
+            attachmentsUpdater.get(this).clear();
+            children.clear();
+        }
+    }
+
     /**
      * Get or create a relative logger node.  The name is relatively qualified to this node.
      *
@@ -380,13 +399,8 @@ final class LoggerNode {
         V old;
         do {
             oldAttachments = attachments;
-            if (oldAttachments.isEmpty() || oldAttachments.size() == 1 && oldAttachments.containsKey(key)) {
-                old = (V) oldAttachments.get(key);
-                newAttachments = Collections.<Logger.AttachmentKey, Object>singletonMap(key, value);
-            } else {
-                newAttachments = new HashMap<Logger.AttachmentKey, Object>(oldAttachments);
-                old = (V) newAttachments.put(key, value);
-            }
+            newAttachments = new HashMap<>(oldAttachments);
+            old = (V) newAttachments.put(key, value);
         } while (! attachmentsUpdater.compareAndSet(this, oldAttachments, newAttachments));
         return old;
     }
@@ -403,15 +417,11 @@ final class LoggerNode {
         Map<Logger.AttachmentKey, Object> newAttachments;
         do {
             oldAttachments = attachments;
-            if (oldAttachments.isEmpty()) {
-                newAttachments = Collections.<Logger.AttachmentKey, Object>singletonMap(key, value);
-            } else {
-                if (oldAttachments.containsKey(key)) {
-                    return (V) oldAttachments.get(key);
-                }
-                newAttachments = new HashMap<Logger.AttachmentKey, Object>(oldAttachments);
-                newAttachments.put(key, value);
+            if (oldAttachments.containsKey(key)) {
+                return (V) oldAttachments.get(key);
             }
+            newAttachments = new HashMap<>(oldAttachments);
+            newAttachments.put(key, value);
         } while (! attachmentsUpdater.compareAndSet(this, oldAttachments, newAttachments));
         return null;
     }
@@ -434,16 +444,6 @@ final class LoggerNode {
             if (size == 1) {
                 // special case - the new map is empty
                 newAttachments = Collections.emptyMap();
-            } else if (size == 2) {
-                // special case - the new map is a singleton
-                final Iterator<Map.Entry<Logger.AttachmentKey,Object>> it = oldAttachments.entrySet().iterator();
-                // find the entry that we are not removing
-                Map.Entry<Logger.AttachmentKey, Object> entry = it.next();
-                if (entry.getKey() == key) {
-                    // must be the next one
-                    entry = it.next();
-                }
-                newAttachments = Collections.singletonMap(entry.getKey(), entry.getValue());
             } else {
                 newAttachments = new HashMap<Logger.AttachmentKey, Object>(oldAttachments);
             }


=====================================
src/main/java/org/jboss/logmanager/config/LogContextConfigurationImpl.java
=====================================
--- a/src/main/java/org/jboss/logmanager/config/LogContextConfigurationImpl.java
+++ b/src/main/java/org/jboss/logmanager/config/LogContextConfigurationImpl.java
@@ -85,6 +85,7 @@ final class LogContextConfigurationImpl implements LogContextConfiguration {
 
     LogContextConfigurationImpl(final LogContext logContext) {
         this.logContext = logContext;
+        logContext.addCloseHandler(new LogContextConfigurationCloseHandler());
     }
 
     public LogContext getLogContext() {
@@ -738,4 +739,43 @@ final class LogContextConfigurationImpl implements LogContextConfiguration {
     ObjectProducer resolveFilter(String expression) {
         return resolveFilter(expression, false);
     }
+
+    private class LogContextConfigurationCloseHandler implements AutoCloseable {
+
+        @Override
+        public void close() {
+            final LogContextConfigurationImpl configuration = LogContextConfigurationImpl.this;
+            // Remove all the loggers first
+            for (String name : configuration.getLoggerNames()) {
+                configuration.removeLoggerConfiguration(name);
+            }
+
+            // Remove all handlers next
+            for (String name : configuration.getHandlerNames()) {
+                configuration.removeHandlerConfiguration(name);
+            }
+
+            // Remove all filters
+            for (String name : configuration.getFilterNames()) {
+                configuration.removeFilterConfiguration(name);
+            }
+
+            // Remove all formatters
+            for (String name : configuration.getFormatterNames()) {
+                configuration.removeFormatterConfiguration(name);
+            }
+
+            // Remove all error managers
+            for (String name : configuration.getErrorManagerNames()) {
+                configuration.removeErrorManagerConfiguration(name);
+            }
+
+            // Finally remove all POJO's
+            for (String name : configuration.getPojoNames()) {
+                configuration.removePojoConfiguration(name);
+            }
+
+            configuration.commit();
+        }
+    }
 }


=====================================
src/main/java/org/jboss/logmanager/formatters/Formatters.java
=====================================
--- a/src/main/java/org/jboss/logmanager/formatters/Formatters.java
+++ b/src/main/java/org/jboss/logmanager/formatters/Formatters.java
@@ -34,6 +34,7 @@ import java.util.HashMap;
 import java.util.Locale;
 import java.util.Map;
 import java.util.TimeZone;
+import java.util.TreeMap;
 import java.util.logging.Formatter;
 import java.util.logging.Level;
 import java.util.logging.LogRecord;
@@ -1073,9 +1074,13 @@ public final class Formatters {
     public static FormatStep mdcFormatStep(final String key, final boolean leftJustify, final int minimumWidth, final boolean truncateBeginning, final int maximumWidth) {
         return new JustifyingFormatStep(leftJustify, minimumWidth, truncateBeginning, maximumWidth) {
             public void renderRaw(final StringBuilder builder, final ExtLogRecord record) {
-                final String value = record.getMdc(key);
-                if (value != null) {
-                    builder.append(value);
+                if (key == null) {
+                    builder.append(new TreeMap<>(record.getMdcCopy()));
+                } else {
+                    final String value = record.getMdc(key);
+                    if (value != null) {
+                        builder.append(value);
+                    }
                 }
             }
         };


=====================================
src/main/java/org/jboss/logmanager/handlers/ClientSocketFactory.java
=====================================
--- /dev/null
+++ b/src/main/java/org/jboss/logmanager/handlers/ClientSocketFactory.java
@@ -0,0 +1,134 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ *
+ * Copyright 2018 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jboss.logmanager.handlers;
+
+import java.io.IOException;
+import java.net.DatagramSocket;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.Socket;
+import java.net.SocketAddress;
+import java.net.SocketException;
+import javax.net.SocketFactory;
+
+/**
+ * A factory used to create writable sockets.
+ *
+ * @author <a href="mailto:jperkins at redhat.com">James R. Perkins</a>
+ */
+public interface ClientSocketFactory {
+
+    /**
+     * Creates a datagram socket for UDP communication.
+     *
+     * @return the newly created socket
+     *
+     * @throws SocketException if binding the socket fails
+     */
+    DatagramSocket createDatagramSocket() throws SocketException;
+
+    /**
+     * Creates a TCP socket.
+     *
+     * @return the newly created socket
+     *
+     * @throws IOException if an error occurs creating the socket
+     */
+    Socket createSocket() throws IOException;
+
+    /**
+     * Returns the address being used to create sockets.
+     *
+     * @return the address being used
+     */
+    InetAddress getAddress();
+
+    /**
+     * Returns the port being used to create sockets.
+     *
+     * @return the port being used
+     */
+    int getPort();
+
+    /**
+     * A convenience method to return the socket address.
+     * <p>
+     * The default implementation simply returns {@code new InetSocketAddress(getAddress(), getPort())}.
+     * </p>
+     *
+     * @return a socket address
+     */
+    default SocketAddress getSocketAddress() {
+        return new InetSocketAddress(getAddress(), getPort());
+    }
+
+    /**
+     * Creates a new default implementation of the factory which uses {@link SocketFactory#getDefault()} for TCP
+     * sockets and {@code new DatagramSocket()} for UDP sockets.
+     *
+     * @param address the address to bind to
+     * @param port    the port to bind to
+     *
+     * @return the client socket factory
+     */
+    static ClientSocketFactory of(final InetAddress address, final int port) {
+        return of(SocketFactory.getDefault(), address, port);
+    }
+
+    /**
+     * Creates a new default implementation of the factory which uses the provided
+     * {@linkplain SocketFactory#createSocket(InetAddress, int) socket factory} to create TCP connections and
+     * {@code new DatagramSocket()} for UDP sockets.
+     *
+     * @param socketFactory the socket factory used for TCP connections, if {@code null} the
+     *                      {@linkplain SocketFactory#getDefault() default} socket factory will be used
+     * @param address       the address to bind to
+     * @param port          the port to bind to
+     *
+     * @return the client socket factory
+     */
+    static ClientSocketFactory of(final SocketFactory socketFactory, final InetAddress address, final int port) {
+        if (address == null || port < 0) {
+            throw new IllegalArgumentException(String.format("The address cannot be null (%s) and the port must be a positive integer (%d)", address, port));
+        }
+        final SocketFactory factory = (socketFactory == null ? SocketFactory.getDefault() : socketFactory);
+        return new ClientSocketFactory() {
+            @Override
+            public DatagramSocket createDatagramSocket() throws SocketException {
+                return new DatagramSocket();
+            }
+
+            @Override
+            public Socket createSocket() throws IOException {
+                return factory.createSocket(address, port);
+            }
+
+            @Override
+            public InetAddress getAddress() {
+                return address;
+            }
+
+            @Override
+            public int getPort() {
+                return port;
+            }
+        };
+    }
+}


=====================================
src/main/java/org/jboss/logmanager/handlers/SocketHandler.java
=====================================
--- a/src/main/java/org/jboss/logmanager/handlers/SocketHandler.java
+++ b/src/main/java/org/jboss/logmanager/handlers/SocketHandler.java
@@ -68,6 +68,7 @@ public class SocketHandler extends ExtHandler {
     private final Object outputLock = new Object();
 
     // All the following fields are guarded by outputLock
+    private ClientSocketFactory clientSocketFactory;
     private SocketFactory socketFactory;
     private InetAddress address;
     private int port;
@@ -143,6 +144,7 @@ public class SocketHandler extends ExtHandler {
      * @param port          the port to connect to
      *
      * @throws UnknownHostException if an error occurs resolving the hostname
+     * @see #SocketHandler(ClientSocketFactory, Protocol)
      */
     public SocketHandler(final SocketFactory socketFactory, final Protocol protocol, final String hostname, final int port) throws UnknownHostException {
         this(socketFactory, protocol, InetAddress.getByName(hostname), port);
@@ -157,14 +159,35 @@ public class SocketHandler extends ExtHandler {
      * @param protocol      the protocol to connect with
      * @param address       the address to connect to
      * @param port          the port to connect to
+     *
+     * @see #SocketHandler(ClientSocketFactory, Protocol)
      */
     public SocketHandler(final SocketFactory socketFactory, final Protocol protocol, final InetAddress address, final int port) {
+        this.socketFactory = socketFactory;
+        this.clientSocketFactory = null;
         this.address = address;
         this.port = port;
-        this.protocol = protocol;
+        this.protocol = (protocol == null ? Protocol.TCP : protocol);
+        initialize = true;
+        writer = null;
+        blockOnReconnect = false;
+    }
+
+    /**
+     * Creates a socket handler.
+     *
+     * @param clientSocketFactory the client socket factory used to create sockets
+     * @param protocol            the protocol to connect with
+     */
+    public SocketHandler(final ClientSocketFactory clientSocketFactory, final Protocol protocol) {
+        this.clientSocketFactory = clientSocketFactory;
+        if (clientSocketFactory != null) {
+            address = clientSocketFactory.getAddress();
+            port = clientSocketFactory.getPort();
+        }
+        this.protocol = (protocol == null ? Protocol.TCP : protocol);
         initialize = true;
         writer = null;
-        this.socketFactory = socketFactory;
         blockOnReconnect = false;
     }
 
@@ -229,19 +252,28 @@ public class SocketHandler extends ExtHandler {
 
     /**
      * Sets the address to connect to.
+     * <p>
+     * Note that is resets the {@linkplain #setClientSocketFactory(ClientSocketFactory) client socket factory}.
+     * </p>
      *
      * @param address the address
      */
     public void setAddress(final InetAddress address) {
         checkAccess(this);
         synchronized (outputLock) {
+            if (!this.address.equals(address)) {
+                initialize = true;
+                clientSocketFactory = null;
+            }
             this.address = address;
-            initialize = true;
         }
     }
 
     /**
      * Sets the address to connect to by doing a lookup on the hostname.
+     * <p>
+     * Note that is resets the {@linkplain #setClientSocketFactory(ClientSocketFactory) client socket factory}.
+     * </p>
      *
      * @param hostname the host name used to resolve the address
      *
@@ -277,6 +309,7 @@ public class SocketHandler extends ExtHandler {
         checkAccess(this);
         synchronized (outputLock) {
             this.blockOnReconnect = blockOnReconnect;
+            initialize = true;
         }
     }
 
@@ -293,7 +326,7 @@ public class SocketHandler extends ExtHandler {
      * Sets the protocol to use. If the value is {@code null} the protocol will be set to
      * {@linkplain Protocol#TCP TCP}.
      * <p>
-     * Note that is resets the {@linkplain #setSocketFactory(SocketFactory) socket factory}.
+     * Note that is resets the {@linkplain #setSocketFactory(SocketFactory) socket factory} if it was previously set.
      * </p>
      *
      * @param protocol the protocol to use
@@ -304,10 +337,11 @@ public class SocketHandler extends ExtHandler {
             if (protocol == null) {
                 this.protocol = Protocol.TCP;
             }
-            // Reset the socket factory
-            socketFactory = null;
+            if (this.protocol != protocol) {
+                socketFactory = null;
+                initialize = true;
+            }
             this.protocol = protocol;
-            initialize = true;
         }
     }
 
@@ -322,14 +356,20 @@ public class SocketHandler extends ExtHandler {
 
     /**
      * Sets the port to connect to.
+     * <p>
+     * Note that is resets the {@linkplain #setClientSocketFactory(ClientSocketFactory) client socket factory}.
+     * </p>
      *
      * @param port the port
      */
     public void setPort(final int port) {
         checkAccess(this);
         synchronized (outputLock) {
+            if (this.port != port) {
+                initialize = true;
+                clientSocketFactory = null;
+            }
             this.port = port;
-            initialize = true;
         }
     }
 
@@ -338,15 +378,33 @@ public class SocketHandler extends ExtHandler {
      * connections.
      * <p>
      * Note that if the {@linkplain #setProtocol(Protocol) protocol} is set the socket factory will be set to
-     * {@code null} and reset.
+     * {@code null} and reset. Setting a value here also resets the
+     * {@linkplain #setClientSocketFactory(ClientSocketFactory) client socket factory}.
      * </p>
      *
      * @param socketFactory the socket factory
+     *
+     * @see #setClientSocketFactory(ClientSocketFactory)
      */
     public void setSocketFactory(final SocketFactory socketFactory) {
         checkAccess(this);
         synchronized (outputLock) {
             this.socketFactory = socketFactory;
+            this.clientSocketFactory = null;
+            initialize = true;
+        }
+    }
+
+    /**
+     * Sets the client socket factory used to create sockets. If {@code null} the
+     * {@linkplain #setAddress(InetAddress) address} and {@linkplain #setPort(int) port} are required to be set.
+     *
+     * @param clientSocketFactory the client socket factory to use
+     */
+    public void setClientSocketFactory(final ClientSocketFactory clientSocketFactory) {
+        checkAccess(this);
+        synchronized (outputLock) {
+            this.clientSocketFactory = clientSocketFactory;
             initialize = true;
         }
     }
@@ -388,19 +446,11 @@ public class SocketHandler extends ExtHandler {
     private OutputStream createOutputStream() {
         if (address != null || port >= 0) {
             try {
+                final ClientSocketFactory socketFactory = getClientSocketFactory();
                 if (protocol == Protocol.UDP) {
-                    return new UdpOutputStream(address, port);
-                }
-                SocketFactory socketFactory = this.socketFactory;
-                if (socketFactory == null) {
-                    if (protocol == Protocol.SSL_TCP) {
-                        this.socketFactory = socketFactory = SSLSocketFactory.getDefault();
-                    } else {
-                        // Assume we want a TCP connection
-                        this.socketFactory = socketFactory = SocketFactory.getDefault();
-                    }
+                    return new UdpOutputStream(socketFactory);
                 }
-                return new TcpOutputStream(socketFactory, address, port, blockOnReconnect);
+                return new TcpOutputStream(socketFactory, blockOnReconnect);
             } catch (IOException e) {
                 reportError("Failed to create socket output stream", e, ErrorManager.OPEN_FAILURE);
             }
@@ -408,6 +458,28 @@ public class SocketHandler extends ExtHandler {
         return null;
     }
 
+    private ClientSocketFactory getClientSocketFactory() {
+        synchronized (outputLock) {
+            if (clientSocketFactory != null) {
+                return clientSocketFactory;
+            }
+            if (address == null || port <= 0) {
+                throw new IllegalStateException("An address and port greater than 0 is required.");
+            }
+            final ClientSocketFactory clientSocketFactory;
+            if (socketFactory == null) {
+                if (protocol == Protocol.SSL_TCP) {
+                    clientSocketFactory = ClientSocketFactory.of(SSLSocketFactory.getDefault(), address, port);
+                } else {
+                    clientSocketFactory = ClientSocketFactory.of(address, port);
+                }
+            } else {
+                clientSocketFactory = ClientSocketFactory.of(socketFactory, address, port);
+            }
+            return clientSocketFactory;
+        }
+    }
+
     private void writeHead(final Writer writer) {
         try {
             final Formatter formatter = getFormatter();


=====================================
src/main/java/org/jboss/logmanager/handlers/SyslogHandler.java
=====================================
--- a/src/main/java/org/jboss/logmanager/handlers/SyslogHandler.java
+++ b/src/main/java/org/jboss/logmanager/handlers/SyslogHandler.java
@@ -35,6 +35,8 @@ import java.util.logging.ErrorManager;
 import java.util.logging.Formatter;
 import java.util.logging.Level;
 import java.util.regex.Pattern;
+import javax.net.SocketFactory;
+import javax.net.ssl.SSLSocketFactory;
 
 import org.jboss.logmanager.ExtHandler;
 import org.jboss.logmanager.ExtLogRecord;
@@ -324,6 +326,7 @@ public class SyslogHandler extends ExtHandler {
     private boolean truncate;
     private int maxLen;
     private boolean blockOnReconnect;
+    private ClientSocketFactory clientSocketFactory;
 
     /**
      * The default class constructor.
@@ -668,6 +671,19 @@ public class SyslogHandler extends ExtHandler {
     }
 
     /**
+     * Sets the client socket factory used to create sockets.
+     *
+     * @param clientSocketFactory the client socket factory to use
+     */
+    public void setClientSocketFactory(final ClientSocketFactory clientSocketFactory) {
+        checkAccess(this);
+        synchronized (outputLock) {
+            this.clientSocketFactory = clientSocketFactory;
+            initializeConnection = true;
+        }
+    }
+
+    /**
      * Checks whether or not characters below decimal 32, traditional US-ASCII control values expect {@code DEL}, are
      * being escaped or not.
      *
@@ -1084,14 +1100,11 @@ public class SyslogHandler extends ExtHandler {
             final OutputStream out;
             // Check the sockets
             try {
-                if (protocol == Protocol.TCP) {
-                    out = new TcpOutputStream(serverAddress, port, blockOnReconnect);
-                } else if (protocol == Protocol.UDP) {
-                    out = new UdpOutputStream(serverAddress, port);
-                } else if (protocol == Protocol.SSL_TCP) {
-                    out = new SslTcpOutputStream(serverAddress, port, blockOnReconnect);
+                final ClientSocketFactory clientSocketFactory = getClientSocketFactory();
+                if (protocol == Protocol.UDP) {
+                    out = new UdpOutputStream(clientSocketFactory);
                 } else {
-                    throw new IllegalStateException("Invalid protocol: " + protocol);
+                    out = new TcpOutputStream(clientSocketFactory, blockOnReconnect);
                 }
                 setOutputStream(out, false);
             } catch (IOException e) {
@@ -1296,6 +1309,16 @@ public class SyslogHandler extends ExtHandler {
         return buffer.toArray();
     }
 
+    private ClientSocketFactory getClientSocketFactory() {
+        synchronized (outputLock) {
+            if (clientSocketFactory != null) {
+                return clientSocketFactory;
+            }
+            final SocketFactory socketFactory = (protocol == Protocol.SSL_TCP ? SSLSocketFactory.getDefault() : SocketFactory.getDefault());
+            return ClientSocketFactory.of(socketFactory, serverAddress, port);
+        }
+    }
+
     private static String checkPrintableAscii(final String name, final String value) {
         if (value != null && PRINTABLE_ASCII_PATTERN.matcher(value).find()) {
             final String upper = Character.toUpperCase(name.charAt(0)) + name.substring(1);


=====================================
src/main/java/org/jboss/logmanager/handlers/TcpOutputStream.java
=====================================
--- a/src/main/java/org/jboss/logmanager/handlers/TcpOutputStream.java
+++ b/src/main/java/org/jboss/logmanager/handlers/TcpOutputStream.java
@@ -54,9 +54,7 @@ public class TcpOutputStream extends OutputStream implements FlushableCloseable 
 
     protected final Object outputLock = new Object();
 
-    private final SocketFactory socketFactory;
-    private final InetAddress address;
-    private final int port;
+    private final ClientSocketFactory socketFactory;
     private final Deque<Exception> errors = new ArrayDeque<Exception>(maxErrors);
 
     // Guarded by outputLock
@@ -108,13 +106,11 @@ public class TcpOutputStream extends OutputStream implements FlushableCloseable 
      *
      * @param socket the socket used to write the output to
      *
-     * @deprecated Use {@link #TcpOutputStream(javax.net.SocketFactory, java.net.InetAddress, int)}
+     * @deprecated Use {@link #TcpOutputStream(ClientSocketFactory, boolean)}
      */
     @Deprecated
     protected TcpOutputStream(final Socket socket) {
         this.socketFactory = null;
-        this.address = null;
-        this.port = -1;
         this.socket = socket;
         reconnectThread = null;
         connected = true;
@@ -152,12 +148,21 @@ public class TcpOutputStream extends OutputStream implements FlushableCloseable 
      *                     a reconnect will be attempted on the next write.
      */
     protected TcpOutputStream(final SocketFactory socketFactory, final InetAddress address, final int port, final boolean blockOnReconnect) throws IOException {
+        this(ClientSocketFactory.of(socketFactory, address, port), blockOnReconnect);
+    }
+
+    /**
+     * Creates a new TCP stream which uses the {@link ClientSocketFactory#createSocket()} to create the socket.
+     *
+     * @param socketFactory    the socket factory used to create TCP sockets
+     * @param blockOnReconnect {@code true} to block when attempting to reconnect the socket or {@code false} to
+     *                         reconnect asynchronously
+     */
+    public TcpOutputStream(final ClientSocketFactory socketFactory, final boolean blockOnReconnect) {
         this.socketFactory = socketFactory;
-        this.address = address;
-        this.port = port;
         this.blockOnReconnect = blockOnReconnect;
         try {
-            socket = socketFactory.createSocket(address, port);
+            socket = this.socketFactory.createSocket();
             connected = true;
         } catch (IOException e) {
             connected = false;
@@ -166,7 +171,7 @@ public class TcpOutputStream extends OutputStream implements FlushableCloseable 
 
     @Override
     public void write(final int b) throws IOException {
-        write(new byte[]{(byte) b}, 0, 1);
+        write(new byte[] {(byte) b}, 0, 1);
     }
 
     @Override
@@ -208,7 +213,9 @@ public class TcpOutputStream extends OutputStream implements FlushableCloseable 
     public void flush() throws IOException {
         synchronized (outputLock) {
             try {
-                socket.getOutputStream().flush();
+                if (socket != null) {
+                    socket.getOutputStream().flush();
+                }
             } catch (SocketException e) {
                 // This should likely never be hit, but should attempt to reconnect if it does happen
                 if (isReconnectAllowed()) {
@@ -230,7 +237,9 @@ public class TcpOutputStream extends OutputStream implements FlushableCloseable 
             if (reconnectThread != null) {
                 reconnectThread.interrupt();
             }
-            socket.close();
+            if (socket != null) {
+                socket.close();
+            }
         }
     }
 
@@ -345,7 +354,7 @@ public class TcpOutputStream extends OutputStream implements FlushableCloseable 
             while (socketFactory != null && !connected) {
                 Socket socket = null;
                 try {
-                    socket = socketFactory.createSocket(address, port);
+                    socket = socketFactory.createSocket();
                     synchronized (outputLock) {
                         // Unlikely but if we've been interrupted due to a close, we should shutdown
                         if (Thread.currentThread().isInterrupted()) {


=====================================
src/main/java/org/jboss/logmanager/handlers/UdpOutputStream.java
=====================================
--- a/src/main/java/org/jboss/logmanager/handlers/UdpOutputStream.java
+++ b/src/main/java/org/jboss/logmanager/handlers/UdpOutputStream.java
@@ -24,31 +24,39 @@ import java.io.OutputStream;
 import java.net.DatagramPacket;
 import java.net.DatagramSocket;
 import java.net.InetAddress;
+import java.net.SocketAddress;
+import java.net.SocketException;
 
 /**
  * An output stream that writes data to a {@link java.net.DatagramSocket DatagramSocket}.
  *
  * @author <a href="mailto:jperkins at redhat.com">James R. Perkins</a>
  */
+ at SuppressWarnings("WeakerAccess")
 public class UdpOutputStream extends OutputStream implements FlushableCloseable {
     private final DatagramSocket socket;
+    private final SocketAddress socketAddress;
 
     public UdpOutputStream(final InetAddress address, final int port) throws IOException {
-        socket = new DatagramSocket();
-        socket.connect(address, port);
+        this(ClientSocketFactory.of(address, port));
+    }
+
+    public UdpOutputStream(final ClientSocketFactory socketManager) throws SocketException {
+        socket = socketManager.createDatagramSocket();
+        socketAddress = socketManager.getSocketAddress();
     }
 
     @Override
     public void write(final int b) throws IOException {
         final byte[] msg = new byte[] {(byte) b};
-        final DatagramPacket packet = new DatagramPacket(msg, 1);
+        final DatagramPacket packet = new DatagramPacket(msg, 1, socketAddress);
         socket.send(packet);
     }
 
     @Override
     public void write(final byte[] b) throws IOException {
         if (b != null) {
-            final DatagramPacket packet = new DatagramPacket(b, b.length);
+            final DatagramPacket packet = new DatagramPacket(b, b.length, socketAddress);
             socket.send(packet);
         }
     }
@@ -56,7 +64,7 @@ public class UdpOutputStream extends OutputStream implements FlushableCloseable 
     @Override
     public void write(final byte[] b, final int off, final int len) throws IOException {
         if (b != null) {
-            final DatagramPacket packet = new DatagramPacket(b, off, len);
+            final DatagramPacket packet = new DatagramPacket(b, off, len, socketAddress);
             socket.send(packet);
         }
     }


=====================================
src/test/java/org/jboss/logmanager/LogContextCloseTests.java
=====================================
--- /dev/null
+++ b/src/test/java/org/jboss/logmanager/LogContextCloseTests.java
@@ -0,0 +1,396 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ *
+ * Copyright 2017 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jboss.logmanager;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.UUID;
+import java.util.logging.ErrorManager;
+import java.util.logging.Filter;
+import java.util.logging.Formatter;
+import java.util.logging.Handler;
+import java.util.logging.Level;
+import java.util.logging.LogRecord;
+
+import org.jboss.logmanager.config.ErrorManagerConfiguration;
+import org.jboss.logmanager.config.FilterConfiguration;
+import org.jboss.logmanager.config.FormatterConfiguration;
+import org.jboss.logmanager.config.HandlerConfiguration;
+import org.jboss.logmanager.config.LogContextConfiguration;
+import org.jboss.logmanager.config.LoggerConfiguration;
+import org.jboss.logmanager.config.PojoConfiguration;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ * @author <a href="mailto:jperkins at redhat.com">James R. Perkins</a>
+ */
+public class LogContextCloseTests {
+
+    @Before
+    public void resetTestObjects() {
+        TestErrorManager.POJO_OBJECT = null;
+        TestFilter.POJO_OBJECT = null;
+        TestFormatter.POJO_OBJECT = null;
+        TestHandler.ERROR_MANAGER = null;
+        TestHandler.FILTER = null;
+        TestHandler.FORMATTER = null;
+        TestHandler.HANDLERS = null;
+        TestHandler.IS_CLOSED = false;
+        TestHandler.POJO_OBJECT = null;
+    }
+
+
+    @Test
+    public void testCloseLogContext() throws Exception {
+        LogContext logContext = LogContext.create();
+
+        // Create a test handler to use
+        final TestHandler handler = new TestHandler();
+        handler.setErrorManager(new TestErrorManager());
+        handler.setFilter(new TestFilter());
+        handler.setFormatter(new TestFormatter());
+        handler.setLevel(org.jboss.logmanager.Level.TRACE);
+
+        final Logger rootLogger = logContext.getLogger("");
+        rootLogger.setLevel(org.jboss.logmanager.Level.WARN);
+        final Logger testLogger = logContext.getLogger(LogContextCloseTests.class.getName());
+        testLogger.setLevel(Level.FINE);
+        final Logger randomLogger = logContext.getLogger(UUID.randomUUID().toString());
+        randomLogger.setUseParentFilters(true);
+
+        rootLogger.addHandler(handler);
+
+        logContext.close();
+
+        // Loggers should have no handlers and have been reset
+        Assert.assertEquals(Level.INFO, rootLogger.getLevel());
+        final Handler[] handlers = randomLogger.getHandlers();
+        Assert.assertTrue(handlers == null || handlers.length == 0);
+
+        assertEmptyContext(logContext, rootLogger, testLogger, randomLogger);
+    }
+
+    @Test
+    public void testCloseLogContextConfiguration() throws Exception {
+        final LogContext logContext = LogContext.create();
+        final LogContextConfiguration logContextConfiguration = LogContextConfiguration.Factory.create(logContext);
+
+        // Add a POJO to ensure it gets removed
+        final PojoConfiguration pojoConfiguration = logContextConfiguration.addPojoConfiguration(null,
+                PojoObject.class.getName(), "pojo");
+
+        // Create an error manager
+        final ErrorManagerConfiguration errorManagerConfiguration = logContextConfiguration.addErrorManagerConfiguration(null,
+                TestErrorManager.class.getName(), "error-manager");
+        errorManagerConfiguration.setPropertyValueString("pojoObject", pojoConfiguration.getName());
+
+        // Create a filter
+        final FilterConfiguration filterConfiguration = logContextConfiguration.addFilterConfiguration(null,
+                TestFilter.class.getName(), "filter");
+        filterConfiguration.setPropertyValueString("pojoObject", pojoConfiguration.getName());
+
+        // Create a formatter
+        final FormatterConfiguration formatterConfiguration = logContextConfiguration.addFormatterConfiguration(null,
+                TestFormatter.class.getName(), "formatter");
+        formatterConfiguration.setPropertyValueString("pojoObject", pojoConfiguration.getName());
+
+        // Create a handler
+        final HandlerConfiguration handlerConfiguration = logContextConfiguration.addHandlerConfiguration(null,
+                TestHandler.class.getName(), "handler");
+        handlerConfiguration.setPropertyValueString("pojoObject", pojoConfiguration.getName());
+        handlerConfiguration.setFilter(filterConfiguration.getName());
+        handlerConfiguration.setErrorManagerName(errorManagerConfiguration.getName());
+        handlerConfiguration.setFormatterName(formatterConfiguration.getName());
+
+        // Create the root-logger configuration
+        final LoggerConfiguration rootLoggerConfig = logContextConfiguration.addLoggerConfiguration("");
+        rootLoggerConfig.setFilter(filterConfiguration.getName());
+        rootLoggerConfig.addHandlerName(handlerConfiguration.getName());
+        rootLoggerConfig.setLevel("WARN");
+
+        final LoggerConfiguration testLoggerConfig = logContextConfiguration.addLoggerConfiguration(LogContextCloseTests.class.getName());
+        testLoggerConfig.setLevel("DEBUG");
+        testLoggerConfig.addHandlerName(handlerConfiguration.getName());
+
+        final LoggerConfiguration randomLoggerConfig = logContextConfiguration.addLoggerConfiguration(UUID.randomUUID().toString());
+        randomLoggerConfig.setLevel("ERROR");
+        randomLoggerConfig.setUseParentHandlers(false);
+
+
+        logContextConfiguration.commit();
+
+        // Create the loggers on the log context to test they've been reset, note this is required to be done before
+        // the context is closed, but after a commit
+        final Logger rootLogger = logContext.getLogger(rootLoggerConfig.getName());
+        final Logger testLogger = logContext.getLogger(testLoggerConfig.getName());
+        final Logger randomLogger = logContext.getLogger(randomLoggerConfig.getName());
+
+        logContext.close();
+
+        assertEmptyNames("error manager", logContextConfiguration.getErrorManagerNames());
+        assertEmptyNames("filter", logContextConfiguration.getFilterNames());
+        assertEmptyNames("formatter", logContextConfiguration.getFormatterNames());
+        assertEmptyNames("handler", logContextConfiguration.getHandlerNames());
+        assertEmptyNames("logger", logContextConfiguration.getLoggerNames());
+        assertEmptyNames("POJO", logContextConfiguration.getPojoNames());
+
+        assertEmptyContext(logContext, rootLogger, testLogger, randomLogger);
+        // The handler is really the only object available for context since it has a close on it
+        Assert.assertNull("Expected the handler to be reset.", TestHandler.FORMATTER);
+
+        // Assert the handler itself has been closed
+        Assert.assertTrue("The handler was expected to be closed", TestHandler.IS_CLOSED);
+    }
+
+    @Test
+    public void testCloseWithAttachment() throws Exception {
+        LogContext logContext = LogContext.create();
+        final Logger.AttachmentKey<String> key = new Logger.AttachmentKey<>();
+        final String value = "test value";
+        Logger rootLogger = logContext.getLogger("");
+        Assert.assertNull(rootLogger.attach(key, value));
+
+        // Close and ensure the context is clean
+        logContext.close();
+        Assert.assertNull(rootLogger.getAttachment(key));
+        assertEmptyContext(logContext, rootLogger);
+
+        // Test attachIfAbsent()
+        logContext = LogContext.create();
+        rootLogger = logContext.getLogger("");
+        Assert.assertNull(rootLogger.attachIfAbsent(key, value));
+
+        // Close and ensure the context is clean
+        logContext.close();
+        Assert.assertNull(rootLogger.getAttachment(key));
+        assertEmptyContext(logContext, rootLogger);
+
+        // Test detach()
+        logContext = LogContext.create();
+        rootLogger = logContext.getLogger("");
+        Assert.assertNull(rootLogger.attach(key, value));
+        Assert.assertEquals(value, rootLogger.detach(key));
+        logContext.close();
+        Assert.assertNull(rootLogger.getAttachment(key));
+        assertEmptyContext(logContext, rootLogger);
+    }
+
+    private void assertEmptyContext(final LogContext logContext, final Logger... loggers) {
+        // Inspect the log context and ensure it's "empty"
+        final LoggerNode rootLogger = logContext.getRootLoggerNode();
+        final Handler[] handlers = rootLogger.getHandlers();
+        Assert.assertTrue("Expected the handlers to be removed.", handlers == null || handlers.length == 0);
+        Assert.assertNull("Expected the filter to be null", rootLogger.getFilter());
+        Assert.assertEquals("Expected the level to be INFO for logger the root logger", Level.INFO, rootLogger.getLevel());
+        Assert.assertFalse("Expected the useParentFilters to be false for the root logger", rootLogger.getUseParentFilters());
+        Assert.assertTrue("Expected the useParentHandlers to be true for the root logger", rootLogger.getUseParentHandlers());
+        final Collection<LoggerNode> children = rootLogger.getChildren();
+        if (!children.isEmpty()) {
+            final StringBuilder msg = new StringBuilder("Expected no children to be remaining on the root logger. Remaining loggers: ");
+            final Iterator<LoggerNode> iter = children.iterator();
+            while (iter.hasNext()) {
+                msg.append('\'').append(iter.next().getFullName()).append('\'');
+                if (iter.hasNext()) {
+                    msg.append(", ");
+                }
+            }
+            Assert.fail(msg.toString());
+        }
+
+        for (Logger logger : loggers) {
+            assertLoggerReset(logger);
+        }
+    }
+
+    private void assertLoggerReset(final Logger logger) {
+        String loggerName = logger.getName();
+        final Level expectedLevel;
+        if ("".equals(loggerName)) {
+            loggerName = "root";
+            expectedLevel = Level.INFO;
+        } else {
+            expectedLevel = null;
+        }
+        final Handler[] handlers = logger.getHandlers();
+        Assert.assertNull("Expected the filter to be null for logger " + loggerName, logger.getFilter());
+        Assert.assertTrue("Empty handlers expected for logger " + loggerName, handlers == null || handlers.length == 0);
+        Assert.assertEquals("Expected the level to be " + expectedLevel + " for logger " + loggerName, expectedLevel, logger.getLevel());
+        Assert.assertFalse("Expected the useParentFilters to be false for logger " + loggerName, logger.getUseParentFilters());
+        Assert.assertTrue("Expected the useParentHandlers to be true for logger " + loggerName, logger.getUseParentHandlers());
+    }
+
+    private void assertEmptyNames(final String description, final Collection<String> names) {
+        Assert.assertTrue(String.format("The configuration should not have any %s names, but found: %s", description, names),
+                names.isEmpty());
+    }
+
+
+    @SuppressWarnings("unused")
+    public static class TestFilter implements Filter {
+        private static PojoObject POJO_OBJECT;
+
+        @Override
+        public boolean isLoggable(final LogRecord record) {
+            return true;
+        }
+
+        public void setPojoObject(final PojoObject pojoObject) {
+            POJO_OBJECT = pojoObject;
+        }
+    }
+
+    @SuppressWarnings("unused")
+    public static class TestFormatter extends Formatter {
+        private static PojoObject POJO_OBJECT;
+
+        public void setPojoObject(final PojoObject pojoObject) {
+            POJO_OBJECT = pojoObject;
+        }
+
+        @Override
+        public String format(final LogRecord record) {
+            return ExtLogRecord.wrap(record).getFormattedMessage();
+        }
+    }
+
+    @SuppressWarnings("unused")
+    public static class TestErrorManager extends ErrorManager {
+        private static PojoObject POJO_OBJECT;
+
+        public void setPojoObject(final PojoObject pojoObject) {
+            POJO_OBJECT = pojoObject;
+        }
+    }
+
+    @SuppressWarnings({"unused", "WeakerAccess"})
+    public static class TestHandler extends ExtHandler {
+        private static PojoObject POJO_OBJECT;
+        private static Handler[] HANDLERS;
+        private static Formatter FORMATTER;
+        private static Filter FILTER;
+        private static ErrorManager ERROR_MANAGER;
+        private static boolean IS_CLOSED;
+
+        public TestHandler() {
+            IS_CLOSED = false;
+        }
+
+        @Override
+        public void close() throws SecurityException {
+            // Null out static values
+            POJO_OBJECT = null;
+            FORMATTER = null;
+            HANDLERS = null;
+            FORMATTER = null;
+            FILTER = null;
+            ERROR_MANAGER = null;
+            IS_CLOSED = true;
+            super.close();
+        }
+
+        @Override
+        public Handler[] setHandlers(final Handler[] newHandlers) throws SecurityException {
+            HANDLERS = Arrays.copyOf(newHandlers, newHandlers.length);
+            return super.setHandlers(newHandlers);
+        }
+
+        @Override
+        public void addHandler(final Handler handler) throws SecurityException {
+            if (handler == null) {
+                throw new RuntimeException("Cannot add a null handler");
+            }
+            if (HANDLERS == null) {
+                HANDLERS = new Handler[] {handler};
+            } else {
+                final int len = HANDLERS.length + 1;
+                HANDLERS = Arrays.copyOf(HANDLERS, len);
+                HANDLERS[len - 1] = handler;
+            }
+            super.addHandler(handler);
+        }
+
+        @Override
+        public void removeHandler(final Handler handler) throws SecurityException {
+            if (handler == null) {
+                throw new RuntimeException("Cannot remove a null handler");
+            }
+            if (HANDLERS == null) {
+                throw new RuntimeException("Attempting to remove a handler that does not exist: " + handler);
+            } else {
+                if (HANDLERS.length == 1) {
+                    HANDLERS = null;
+                } else {
+                    boolean success = false;
+                    final Handler[] newHandlers = new Handler[HANDLERS.length - 1];
+                    int newIndex = 0;
+                    for (int i = 0; i < HANDLERS.length; i++) {
+                        final Handler current = HANDLERS[i];
+                        if (!success && i > newHandlers.length) {
+                            break;
+                        }
+                        if (handler != current) {
+                            newHandlers[newIndex++] = current;
+                        } else {
+                            success = true;
+                        }
+                    }
+                    if (!success) {
+                        throw new RuntimeException("Failed to remove handler " + handler + " as it did no appear to exist.");
+                    }
+                }
+            }
+            super.removeHandler(handler);
+        }
+
+        @Override
+        public void setFormatter(final Formatter newFormatter) throws SecurityException {
+            FORMATTER = newFormatter;
+            super.setFormatter(newFormatter);
+        }
+
+        @Override
+        public void setFilter(final Filter newFilter) throws SecurityException {
+            FILTER = newFilter;
+            super.setFilter(newFilter);
+        }
+
+        @Override
+        public void setErrorManager(final ErrorManager em) {
+            ERROR_MANAGER = em;
+            super.setErrorManager(em);
+        }
+
+        @Override
+        public void setLevel(final Level newLevel) throws SecurityException {
+            super.setLevel(newLevel);
+        }
+
+        public void setPojoObject(final PojoObject pojoObject) {
+            POJO_OBJECT = pojoObject;
+        }
+    }
+
+    @SuppressWarnings("WeakerAccess")
+    public static class PojoObject {
+    }
+}


=====================================
src/test/java/org/jboss/logmanager/formatters/PatternFormatterTests.java
=====================================
--- a/src/test/java/org/jboss/logmanager/formatters/PatternFormatterTests.java
+++ b/src/test/java/org/jboss/logmanager/formatters/PatternFormatterTests.java
@@ -20,6 +20,7 @@
 package org.jboss.logmanager.formatters;
 
 import org.jboss.logmanager.ExtLogRecord;
+import org.jboss.logmanager.MDC;
 import org.jboss.logmanager.NDC;
 import org.junit.Assert;
 import org.junit.Test;
@@ -114,6 +115,28 @@ public class PatternFormatterTests {
     }
 
     @Test
+    public void mdc() throws Exception {
+        try {
+            MDC.put("primaryKey", "primaryValue");
+            MDC.put("key1", "value1");
+            MDC.put("key2", "value2");
+            final ExtLogRecord record = createLogRecord("test");
+
+            PatternFormatter formatter = new PatternFormatter("%X{key1}");
+            Assert.assertEquals("value1", formatter.format(record));
+
+            formatter = new PatternFormatter("%X{not.found}");
+            Assert.assertEquals("", formatter.format(record));
+
+            formatter = new PatternFormatter("%X");
+            String formatted = formatter.format(record);
+            Assert.assertEquals("{key1=value1, key2=value2, primaryKey=primaryValue}", formatted);
+        } finally {
+            MDC.clear();
+        }
+    }
+
+    @Test
     public void threads() throws Exception {
         final ExtLogRecord record = createLogRecord("test");
         record.setThreadName("testThreadName");


=====================================
src/test/java/org/jboss/logmanager/handlers/PeriodicRotatingFileHandlerTests.java
=====================================
--- a/src/test/java/org/jboss/logmanager/handlers/PeriodicRotatingFileHandlerTests.java
+++ b/src/test/java/org/jboss/logmanager/handlers/PeriodicRotatingFileHandlerTests.java
@@ -100,7 +100,7 @@ public class PeriodicRotatingFileHandlerTests extends AbstractHandlerTest {
             targetClass = "java.nio.file.Files",
             targetMethod = "move",
             targetLocation = "AT ENTRY",
-            condition = "$2.getFileName().toString().matches(\"periodic-rotating-file-handler\\.log\\.\\d+\")",
+            condition = "$2.getFileName().toString().matches(\"periodic-rotating-file-handler\\\\.log\\\\.\\\\d+\")",
             action = "throw new IOException(\"Fail on purpose\")")
     public void testFailedRotate() throws Exception {
         final Calendar cal = Calendar.getInstance();



View it on GitLab: https://salsa.debian.org/java-team/jboss-logmanager/commit/a32bc41a48e5b92dbac7f3655f48facd84e9e3f4

-- 
View it on GitLab: https://salsa.debian.org/java-team/jboss-logmanager/commit/a32bc41a48e5b92dbac7f3655f48facd84e9e3f4
You're receiving this email because of your account on salsa.debian.org.
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://alioth-lists.debian.net/pipermail/pkg-java-commits/attachments/20180707/689e4ca5/attachment.html>


More information about the pkg-java-commits mailing list