You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@logging.apache.org by pk...@apache.org on 2022/04/16 12:06:34 UTC

[logging-log4j2] branch release-2.x updated (bfc8004c42 -> a2f0faefa1)

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

pkarwasz pushed a change to branch release-2.x
in repository https://gitbox.apache.org/repos/asf/logging-log4j2.git


    from bfc8004c42 Fix `log4j-jul` ApiLoggerTest
     new 262828b193 [LOG4J2-3427] Replace ServiceLoader calls with ServiceLoaderUtil
     new a2f0faefa1 Add OSGI support to `ServiceLoaderUtil`

The 2 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 log4j-api-java9/pom.xml                            |   4 +
 log4j-api-java9/src/assembly/java9.xml             |   4 +
 log4j-api-java9/src/main/java/module-info.java     |   6 +
 .../log4j/status/{Dummy.java => StatusLogger.java} |  20 ++-
 .../EnvironmentPropertySource.java}                |   2 +-
 .../PropertySource.java => util/LoaderUtil.java}   |   6 +-
 .../log4j/{log4j => }/util/PropertySource.java     |  12 +-
 .../logging/log4j/util/ServiceLoaderUtil.java      | 111 +++++++++++++
 .../SystemPropertiesPropertySource.java}           |   2 +-
 .../log4j/util/java9/ServiceLoaderUtilTest.java    |  53 +++++++
 .../log4j/util/java9}/test/BetterService.java      |   2 +-
 .../logging/log4j/util/java9}/test/Service.java    |   2 +-
 .../logging/log4j/util/java9}/test/Service1.java   |   2 +-
 .../logging/log4j/util/java9}/test/Service2.java   |   2 +-
 log4j-api-java9/src/test/java9/module-info.java    |  13 ++
 log4j-api/pom.xml                                  |  60 +++++--
 .../logging/log4j/message/ThreadDumpMessage.java   |  26 +--
 .../org/apache/logging/log4j/util/LoaderUtil.java  |  37 +----
 .../logging/log4j/util/OsgiServiceLocator.java     |  67 ++++++++
 .../apache/logging/log4j/util/PropertiesUtil.java  |  25 ++-
 .../log4j/util/PropertyFilePropertySource.java     |  10 +-
 .../apache/logging/log4j/util/ProviderUtil.java    |  35 ++--
 .../logging/log4j/util/ServiceLoaderUtil.java      | 176 ++++++++++++---------
 .../log4j/util/OsgiServiceLocatorTest.java}        |  32 ++--
 .../logging/log4j/util/ServiceLoaderUtilTest.java  |  53 +------
 .../log4j/core/impl/ThreadContextDataInjector.java |  17 +-
 .../logging/log4j/core/util/WatchManager.java      |  25 +--
 log4j-osgi/pom.xml                                 |   4 -
 .../log4j/osgi/tests/AbstractLoadBundleTest.java   |  40 ++---
 pom.xml                                            |   6 -
 30 files changed, 541 insertions(+), 313 deletions(-)
 copy log4j-api-java9/src/main/java/org/apache/logging/log4j/status/{Dummy.java => StatusLogger.java} (67%)
 copy log4j-api-java9/src/main/java/org/apache/logging/log4j/{log4j/util/PropertySource.java => util/EnvironmentPropertySource.java} (93%)
 copy log4j-api-java9/src/main/java/org/apache/logging/log4j/{log4j/util/PropertySource.java => util/LoaderUtil.java} (86%)
 copy log4j-api-java9/src/main/java/org/apache/logging/log4j/{log4j => }/util/PropertySource.java (72%)
 create mode 100644 log4j-api-java9/src/main/java/org/apache/logging/log4j/util/ServiceLoaderUtil.java
 rename log4j-api-java9/src/main/java/org/apache/logging/log4j/{log4j/util/PropertySource.java => util/SystemPropertiesPropertySource.java} (93%)
 create mode 100644 log4j-api-java9/src/test/java/org/apache/logging/log4j/util/java9/ServiceLoaderUtilTest.java
 copy {log4j-api/src/test/java/org/apache/logging/log4j/util => log4j-api-java9/src/test/java/org/apache/logging/log4j/util/java9}/test/BetterService.java (94%)
 copy {log4j-api/src/test/java/org/apache/logging/log4j/util => log4j-api-java9/src/test/java/org/apache/logging/log4j/util/java9}/test/Service.java (94%)
 copy {log4j-api/src/test/java/org/apache/logging/log4j/util => log4j-api-java9/src/test/java/org/apache/logging/log4j/util/java9}/test/Service1.java (94%)
 copy {log4j-api/src/test/java/org/apache/logging/log4j/util => log4j-api-java9/src/test/java/org/apache/logging/log4j/util/java9}/test/Service2.java (94%)
 create mode 100644 log4j-api/src/main/java/org/apache/logging/log4j/util/OsgiServiceLocator.java
 copy log4j-api/src/{main/java/org/apache/logging/log4j/util/TriConsumer.java => test/java/org/apache/logging/log4j/util/OsgiServiceLocatorTest.java} (59%)


[logging-log4j2] 01/02: [LOG4J2-3427] Replace ServiceLoader calls with ServiceLoaderUtil

Posted by pk...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

pkarwasz pushed a commit to branch release-2.x
in repository https://gitbox.apache.org/repos/asf/logging-log4j2.git

commit 262828b193da7c63dd0b45509faaa0fb6e0bf16e
Author: Piotr P. Karwasz <pi...@karwasz.org>
AuthorDate: Wed Apr 13 20:09:51 2022 +0200

    [LOG4J2-3427] Replace ServiceLoader calls with ServiceLoaderUtil
    
    In an attempt to uniformize the calls to `ServiceLoader` and work around
    its limitations, this replaces the `ServiceLoader#load` call sites with
    `ServiceLoaderUtil#loadServices`.
    
    `ServiceLoaderUtil` is also modified to return `Stream` instead of a
    `List`, hence allowing for a more comfortable filtering and sorting of
    the results.
---
 log4j-api-java9/src/assembly/java9.xml             |   4 +
 log4j-api-java9/src/main/java/module-info.java     |   6 +
 .../StatusLogger.java}                             |  22 ++-
 .../EnvironmentPropertySource.java}                |   2 +-
 .../PropertySource.java => util/LoaderUtil.java}   |   6 +-
 .../log4j/{log4j => }/util/PropertySource.java     |  12 +-
 .../logging/log4j/util/ServiceLoaderUtil.java      | 111 +++++++++++++
 .../SystemPropertiesPropertySource.java}           |   2 +-
 .../src/{main => test}/java/module-info.java       |  26 ++--
 .../log4j/util/java9/ServiceLoaderUtilTest.java    |  77 +++++++++
 .../log4j/util/java9/test/BetterService.java}      |  11 +-
 .../logging/log4j/util/java9/test/Service.java}    |  11 +-
 .../logging/log4j/util/java9/test/Service1.java}   |  11 +-
 .../logging/log4j/util/java9/test/Service2.java}   |  11 +-
 .../logging/log4j/message/ThreadDumpMessage.java   |  26 +---
 .../org/apache/logging/log4j/util/LoaderUtil.java  |  37 +----
 .../apache/logging/log4j/util/PropertiesUtil.java  |  25 ++-
 .../log4j/util/PropertyFilePropertySource.java     |  10 +-
 .../apache/logging/log4j/util/ProviderUtil.java    |  35 ++---
 .../logging/log4j/util/ServiceLoaderUtil.java      | 173 ++++++++++++---------
 .../logging/log4j/util/ServiceLoaderUtilTest.java  |  53 +------
 .../log4j/core/impl/ThreadContextDataInjector.java |  17 +-
 .../logging/log4j/core/util/WatchManager.java      |  25 +--
 .../log4j/osgi/tests/AbstractLoadBundleTest.java   |  15 +-
 24 files changed, 436 insertions(+), 292 deletions(-)

diff --git a/log4j-api-java9/src/assembly/java9.xml b/log4j-api-java9/src/assembly/java9.xml
index 58c63876ee..ca7c4ba142 100644
--- a/log4j-api-java9/src/assembly/java9.xml
+++ b/log4j-api-java9/src/assembly/java9.xml
@@ -34,8 +34,12 @@
       <excludes>
         <exclude>**/Dummy.class</exclude>
         <exclude>**/spi/Provider.class</exclude>
+        <exclude>**/status/StatusLogger.class</exclude>
+        <exclude>**/util/EnvironmentPropertySource.class</exclude>
+        <exclude>**/util/LoaderUtil.class</exclude>
         <exclude>**/util/PropertySource.class</exclude>
         <exclude>**/util/PrivateSecurityManagerStackTraceUtil.class</exclude>
+        <exclude>**/util/SystemPropertiesPropertySource.class</exclude>
         <exclude>**/message/ThreadDumpMessage.class</exclude>
         <exclude>**/message/ThreadDumpMessage$ThreadInfoFactory.class</exclude>
       </excludes>
diff --git a/log4j-api-java9/src/main/java/module-info.java b/log4j-api-java9/src/main/java/module-info.java
index 34ff69899e..0008f537d0 100644
--- a/log4j-api-java9/src/main/java/module-info.java
+++ b/log4j-api-java9/src/main/java/module-info.java
@@ -14,6 +14,10 @@
  * See the license for the specific language governing permissions and
  * limitations under the license.
  */
+import org.apache.logging.log4j.util.EnvironmentPropertySource;
+import org.apache.logging.log4j.util.PropertySource;
+import org.apache.logging.log4j.util.SystemPropertiesPropertySource;
+
 module org.apache.logging.log4j {
     requires java.base;
 
@@ -27,4 +31,6 @@ module org.apache.logging.log4j {
     uses org.apache.logging.log4j.spi.Provider;
     uses org.apache.logging.log4j.util.PropertySource;
     uses org.apache.logging.log4j.message.ThreadDumpMessage.ThreadInfoFactory;
+
+    provides PropertySource with EnvironmentPropertySource, SystemPropertiesPropertySource;
 }
diff --git a/log4j-api-java9/src/main/java/org/apache/logging/log4j/log4j/util/PropertySource.java b/log4j-api-java9/src/main/java/org/apache/logging/log4j/status/StatusLogger.java
similarity index 64%
copy from log4j-api-java9/src/main/java/org/apache/logging/log4j/log4j/util/PropertySource.java
copy to log4j-api-java9/src/main/java/org/apache/logging/log4j/status/StatusLogger.java
index f94d10a9b2..f139b159e1 100644
--- a/log4j-api-java9/src/main/java/org/apache/logging/log4j/log4j/util/PropertySource.java
+++ b/log4j-api-java9/src/main/java/org/apache/logging/log4j/status/StatusLogger.java
@@ -14,11 +14,25 @@
  * See the license for the specific language governing permissions and
  * limitations under the license.
  */
-package org.apache.logging.log4j.util;
+package org.apache.logging.log4j.status;
+
+import java.util.concurrent.atomic.AtomicInteger;
 
 /**
- * This is a dummy class and is only here to allow module-info.java to compile. It will not
- * be copied into the log4j-api module.
+ * This is a dummy class and is only here to allow module-info.java to compile.
+ * It will not be copied into the log4j-api module.
  */
-public class PropertySource {
+public final class StatusLogger {
+
+    private static final StatusLogger STATUS_LOGGER = new StatusLogger();
+
+    public void error(String message, Object p0, Object p1) {
+    }
+
+    public void warn(String message, Object p0, Object p1) {
+    }
+
+    public static StatusLogger getLogger() {
+        return STATUS_LOGGER;
+    }
 }
diff --git a/log4j-api-java9/src/main/java/org/apache/logging/log4j/log4j/util/PropertySource.java b/log4j-api-java9/src/main/java/org/apache/logging/log4j/util/EnvironmentPropertySource.java
similarity index 93%
copy from log4j-api-java9/src/main/java/org/apache/logging/log4j/log4j/util/PropertySource.java
copy to log4j-api-java9/src/main/java/org/apache/logging/log4j/util/EnvironmentPropertySource.java
index f94d10a9b2..d30430a362 100644
--- a/log4j-api-java9/src/main/java/org/apache/logging/log4j/log4j/util/PropertySource.java
+++ b/log4j-api-java9/src/main/java/org/apache/logging/log4j/util/EnvironmentPropertySource.java
@@ -20,5 +20,5 @@ package org.apache.logging.log4j.util;
  * This is a dummy class and is only here to allow module-info.java to compile. It will not
  * be copied into the log4j-api module.
  */
-public class PropertySource {
+public class EnvironmentPropertySource implements PropertySource {
 }
diff --git a/log4j-api-java9/src/main/java/org/apache/logging/log4j/log4j/util/PropertySource.java b/log4j-api-java9/src/main/java/org/apache/logging/log4j/util/LoaderUtil.java
similarity index 86%
copy from log4j-api-java9/src/main/java/org/apache/logging/log4j/log4j/util/PropertySource.java
copy to log4j-api-java9/src/main/java/org/apache/logging/log4j/util/LoaderUtil.java
index f94d10a9b2..790f0d0839 100644
--- a/log4j-api-java9/src/main/java/org/apache/logging/log4j/log4j/util/PropertySource.java
+++ b/log4j-api-java9/src/main/java/org/apache/logging/log4j/util/LoaderUtil.java
@@ -20,5 +20,9 @@ package org.apache.logging.log4j.util;
  * This is a dummy class and is only here to allow module-info.java to compile. It will not
  * be copied into the log4j-api module.
  */
-public class PropertySource {
+public final class LoaderUtil {
+
+    public static ClassLoader getThreadContextClassLoader() {
+        return LoaderUtil.class.getClassLoader();
+    }
 }
diff --git a/log4j-api-java9/src/main/java/org/apache/logging/log4j/log4j/util/PropertySource.java b/log4j-api-java9/src/main/java/org/apache/logging/log4j/util/PropertySource.java
similarity index 72%
copy from log4j-api-java9/src/main/java/org/apache/logging/log4j/log4j/util/PropertySource.java
copy to log4j-api-java9/src/main/java/org/apache/logging/log4j/util/PropertySource.java
index f94d10a9b2..b90e7d1f8f 100644
--- a/log4j-api-java9/src/main/java/org/apache/logging/log4j/log4j/util/PropertySource.java
+++ b/log4j-api-java9/src/main/java/org/apache/logging/log4j/util/PropertySource.java
@@ -16,9 +16,19 @@
  */
 package org.apache.logging.log4j.util;
 
+import java.lang.invoke.MethodHandles;
+import java.util.stream.Stream;
+
 /**
  * This is a dummy class and is only here to allow module-info.java to compile. It will not
  * be copied into the log4j-api module.
  */
-public class PropertySource {
+public interface PropertySource {
+
+    /**
+     * This method's only purpose is to test {@link ServiceLoaderUtil} from inside the module.
+     */
+    public static Stream<PropertySource> loadPropertySources() {
+        return ServiceLoaderUtil.loadServices(PropertySource.class, MethodHandles.lookup());
+    }
 }
diff --git a/log4j-api-java9/src/main/java/org/apache/logging/log4j/util/ServiceLoaderUtil.java b/log4j-api-java9/src/main/java/org/apache/logging/log4j/util/ServiceLoaderUtil.java
new file mode 100644
index 0000000000..9450e539a8
--- /dev/null
+++ b/log4j-api-java9/src/main/java/org/apache/logging/log4j/util/ServiceLoaderUtil.java
@@ -0,0 +1,111 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.apache.logging.log4j.util;
+
+import java.lang.invoke.MethodHandle;
+import java.lang.invoke.MethodHandles.Lookup;
+import java.lang.invoke.MethodType;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Objects;
+import java.util.ServiceConfigurationError;
+import java.util.ServiceLoader;
+import java.util.Set;
+import java.util.stream.Stream;
+
+import org.apache.logging.log4j.status.StatusLogger;
+
+public final class ServiceLoaderUtil {
+
+    private static final MethodType LOAD_CLASS_CLASSLOADER = MethodType.methodType(ServiceLoader.class, Class.class,
+            ClassLoader.class);
+
+    private ServiceLoaderUtil() {
+    }
+
+    /**
+     * Retrieves the available services from the caller's classloader.
+     * 
+     * Broken services will be ignored.
+     * 
+     * @param <T>         The service type.
+     * @param serviceType The class of the service.
+     * @param lookup      The calling class data.
+     * @return A stream of service instances.
+     */
+    public static <T> Stream<T> loadServices(final Class<T> serviceType, Lookup lookup) {
+        return loadServices(serviceType, lookup, false);
+    }
+
+    /**
+     * Retrieves the available services from the caller's classloader and possibly
+     * the thread context classloader.
+     * 
+     * Broken services will be ignored.
+     * 
+     * @param <T>         The service type.
+     * @param serviceType The class of the service.
+     * @param lookup      The calling class data.
+     * @param useTccl     If true the thread context classloader will also be used.
+     * @return A stream of service instances.
+     */
+    public static <T> Stream<T> loadServices(final Class<T> serviceType, Lookup lookup, boolean useTccl) {
+        return loadServices(serviceType, lookup, useTccl, true);
+    }
+
+    static <T> Stream<T> loadServices(final Class<T> serviceType, final Lookup lookup, final boolean useTccl,
+            final boolean verbose) {
+        final ClassLoader classLoader = lookup.lookupClass().getClassLoader();
+        Stream<T> services = loadClassloaderServices(serviceType, lookup, classLoader, verbose);
+        if (useTccl) {
+            final ClassLoader contextClassLoader = LoaderUtil.getThreadContextClassLoader();
+            if (contextClassLoader != classLoader) {
+                services = Stream.concat(services,
+                        loadClassloaderServices(serviceType, lookup, contextClassLoader, verbose));
+            }
+        }
+        final Set<Class<?>> classes = new HashSet<>();
+        // only the first occurrence of a class
+        return services.filter(service -> classes.add(service.getClass()));
+    }
+
+    static <T> Stream<T> loadClassloaderServices(final Class<T> serviceType, final Lookup lookup,
+            final ClassLoader classLoader, final boolean verbose) {
+        try {
+            final MethodHandle handle = lookup.findStatic(ServiceLoader.class, "load", LOAD_CLASS_CLASSLOADER);
+            final ServiceLoader<T> serviceLoader = (ServiceLoader<T>) handle.invokeExact(serviceType, classLoader);
+            return serviceLoader.stream().map(provider -> {
+                try {
+                    return provider.get();
+                } catch (ServiceConfigurationError e) {
+                    if (verbose) {
+                        StatusLogger.getLogger().warn("Unable to load service class for service {}",
+                                serviceType.getClass(), e);
+                    }
+                }
+                return null;
+            }).filter(Objects::nonNull);
+        } catch (Throwable e) {
+            if (verbose) {
+                StatusLogger.getLogger().error("Unable to load services for service {}", serviceType, e);
+            }
+        }
+        return Stream.empty();
+    }
+
+}
diff --git a/log4j-api-java9/src/main/java/org/apache/logging/log4j/log4j/util/PropertySource.java b/log4j-api-java9/src/main/java/org/apache/logging/log4j/util/SystemPropertiesPropertySource.java
similarity index 93%
copy from log4j-api-java9/src/main/java/org/apache/logging/log4j/log4j/util/PropertySource.java
copy to log4j-api-java9/src/main/java/org/apache/logging/log4j/util/SystemPropertiesPropertySource.java
index f94d10a9b2..23ceb72124 100644
--- a/log4j-api-java9/src/main/java/org/apache/logging/log4j/log4j/util/PropertySource.java
+++ b/log4j-api-java9/src/main/java/org/apache/logging/log4j/util/SystemPropertiesPropertySource.java
@@ -20,5 +20,5 @@ package org.apache.logging.log4j.util;
  * This is a dummy class and is only here to allow module-info.java to compile. It will not
  * be copied into the log4j-api module.
  */
-public class PropertySource {
+public class SystemPropertiesPropertySource implements PropertySource {
 }
diff --git a/log4j-api-java9/src/main/java/module-info.java b/log4j-api-java9/src/test/java/module-info.java
similarity index 57%
copy from log4j-api-java9/src/main/java/module-info.java
copy to log4j-api-java9/src/test/java/module-info.java
index 34ff69899e..12c3735381 100644
--- a/log4j-api-java9/src/main/java/module-info.java
+++ b/log4j-api-java9/src/test/java/module-info.java
@@ -14,17 +14,21 @@
  * See the license for the specific language governing permissions and
  * limitations under the license.
  */
-module org.apache.logging.log4j {
-    requires java.base;
+import org.apache.logging.log4j.util.java9.test.BetterService;
+import org.apache.logging.log4j.util.java9.test.Service;
+import org.apache.logging.log4j.util.java9.test.Service1;
+import org.apache.logging.log4j.util.java9.test.Service2;
 
-    exports org.apache.logging.log4j;
-    exports org.apache.logging.log4j.message;
-    exports org.apache.logging.log4j.simple;
-    exports org.apache.logging.log4j.spi;
-    exports org.apache.logging.log4j.status;
-    exports org.apache.logging.log4j.util;
+open module org.apache.logging.log4j.java9test {
+    exports org.apache.logging.log4j.util.java9;
 
-    uses org.apache.logging.log4j.spi.Provider;
-    uses org.apache.logging.log4j.util.PropertySource;
-    uses org.apache.logging.log4j.message.ThreadDumpMessage.ThreadInfoFactory;
+    requires org.apache.logging.log4j;
+    requires transitive org.junit.jupiter.engine;
+    requires transitive org.junit.jupiter.api;
+
+    uses Service;
+    uses BetterService;
+
+    provides Service with Service1, Service2;
+    provides BetterService with Service2;
 }
diff --git a/log4j-api-java9/src/test/java/org/apache/logging/log4j/util/java9/ServiceLoaderUtilTest.java b/log4j-api-java9/src/test/java/org/apache/logging/log4j/util/java9/ServiceLoaderUtilTest.java
new file mode 100644
index 0000000000..f95dd54c0a
--- /dev/null
+++ b/log4j-api-java9/src/test/java/org/apache/logging/log4j/util/java9/ServiceLoaderUtilTest.java
@@ -0,0 +1,77 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.apache.logging.log4j.util.java9;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.fail;
+
+import java.lang.invoke.MethodHandles;
+import java.util.Collections;
+import java.util.List;
+import java.util.ServiceConfigurationError;
+import java.util.stream.Collectors;
+
+import org.apache.logging.log4j.util.PropertySource;
+import org.apache.logging.log4j.util.ServiceLoaderUtil;
+import org.apache.logging.log4j.util.java9.test.BetterService;
+import org.apache.logging.log4j.util.java9.test.Service;
+import org.junit.jupiter.api.Test;
+
+public class ServiceLoaderUtilTest {
+
+    @Test
+    public void testServiceResolution() {
+        // Run only if we are a module
+        if (ServiceLoaderUtil.class.getModule().isNamed()) {
+            List<Object> services = Collections.emptyList();
+            // Service from test module
+            try {
+                services = ServiceLoaderUtil.loadServices(Service.class, MethodHandles.lookup())
+                        .collect(Collectors.toList());
+            } catch (ServiceConfigurationError e) {
+                fail(e);
+            }
+            assertEquals(2, services.size(), "Service services");
+            // BetterService from test module
+            services.clear();
+            try {
+                services = ServiceLoaderUtil.loadServices(BetterService.class, MethodHandles.lookup())
+                        .collect(Collectors.toList());
+            } catch (ServiceConfigurationError e) {
+                fail(e);
+            }
+            assertEquals(1, services.size(), "BetterService services");
+            // PropertySource from org.apache.logging.log4j module from this module
+            services.clear();
+            try {
+                services = ServiceLoaderUtil.loadServices(PropertySource.class, MethodHandles.lookup())
+                        .collect(Collectors.toList());
+            } catch (ServiceConfigurationError e) {
+                fail(e);
+            }
+            assertEquals(0, services.size(), "PropertySource services");
+            // PropertySource from within org.apache.logging.log4j module
+            services.clear();
+            try {
+                services = PropertySource.loadPropertySources().collect(Collectors.toList());
+            } catch (ServiceConfigurationError e) {
+                fail(e);
+            }
+            assertEquals(2, services.size(), "PropertySource services");
+        }
+    }
+}
diff --git a/log4j-api-java9/src/main/java/org/apache/logging/log4j/log4j/util/PropertySource.java b/log4j-api-java9/src/test/java/org/apache/logging/log4j/util/java9/test/BetterService.java
similarity index 79%
copy from log4j-api-java9/src/main/java/org/apache/logging/log4j/log4j/util/PropertySource.java
copy to log4j-api-java9/src/test/java/org/apache/logging/log4j/util/java9/test/BetterService.java
index f94d10a9b2..e2eaf384db 100644
--- a/log4j-api-java9/src/main/java/org/apache/logging/log4j/log4j/util/PropertySource.java
+++ b/log4j-api-java9/src/test/java/org/apache/logging/log4j/util/java9/test/BetterService.java
@@ -14,11 +14,8 @@
  * See the license for the specific language governing permissions and
  * limitations under the license.
  */
-package org.apache.logging.log4j.util;
 
-/**
- * This is a dummy class and is only here to allow module-info.java to compile. It will not
- * be copied into the log4j-api module.
- */
-public class PropertySource {
-}
+package org.apache.logging.log4j.util.java9.test;
+
+public interface BetterService extends Service {
+}
\ No newline at end of file
diff --git a/log4j-api-java9/src/main/java/org/apache/logging/log4j/log4j/util/PropertySource.java b/log4j-api-java9/src/test/java/org/apache/logging/log4j/util/java9/test/Service.java
similarity index 79%
copy from log4j-api-java9/src/main/java/org/apache/logging/log4j/log4j/util/PropertySource.java
copy to log4j-api-java9/src/test/java/org/apache/logging/log4j/util/java9/test/Service.java
index f94d10a9b2..c256fe907b 100644
--- a/log4j-api-java9/src/main/java/org/apache/logging/log4j/log4j/util/PropertySource.java
+++ b/log4j-api-java9/src/test/java/org/apache/logging/log4j/util/java9/test/Service.java
@@ -14,11 +14,8 @@
  * See the license for the specific language governing permissions and
  * limitations under the license.
  */
-package org.apache.logging.log4j.util;
 
-/**
- * This is a dummy class and is only here to allow module-info.java to compile. It will not
- * be copied into the log4j-api module.
- */
-public class PropertySource {
-}
+package org.apache.logging.log4j.util.java9.test;
+
+public interface Service {
+}
\ No newline at end of file
diff --git a/log4j-api-java9/src/main/java/org/apache/logging/log4j/log4j/util/PropertySource.java b/log4j-api-java9/src/test/java/org/apache/logging/log4j/util/java9/test/Service1.java
similarity index 79%
copy from log4j-api-java9/src/main/java/org/apache/logging/log4j/log4j/util/PropertySource.java
copy to log4j-api-java9/src/test/java/org/apache/logging/log4j/util/java9/test/Service1.java
index f94d10a9b2..6b1498c40e 100644
--- a/log4j-api-java9/src/main/java/org/apache/logging/log4j/log4j/util/PropertySource.java
+++ b/log4j-api-java9/src/test/java/org/apache/logging/log4j/util/java9/test/Service1.java
@@ -14,11 +14,8 @@
  * See the license for the specific language governing permissions and
  * limitations under the license.
  */
-package org.apache.logging.log4j.util;
 
-/**
- * This is a dummy class and is only here to allow module-info.java to compile. It will not
- * be copied into the log4j-api module.
- */
-public class PropertySource {
-}
+package org.apache.logging.log4j.util.java9.test;
+
+public class Service1 implements Service {
+}
\ No newline at end of file
diff --git a/log4j-api-java9/src/main/java/org/apache/logging/log4j/log4j/util/PropertySource.java b/log4j-api-java9/src/test/java/org/apache/logging/log4j/util/java9/test/Service2.java
similarity index 79%
rename from log4j-api-java9/src/main/java/org/apache/logging/log4j/log4j/util/PropertySource.java
rename to log4j-api-java9/src/test/java/org/apache/logging/log4j/util/java9/test/Service2.java
index f94d10a9b2..14b73cd034 100644
--- a/log4j-api-java9/src/main/java/org/apache/logging/log4j/log4j/util/PropertySource.java
+++ b/log4j-api-java9/src/test/java/org/apache/logging/log4j/util/java9/test/Service2.java
@@ -14,11 +14,8 @@
  * See the license for the specific language governing permissions and
  * limitations under the license.
  */
-package org.apache.logging.log4j.util;
 
-/**
- * This is a dummy class and is only here to allow module-info.java to compile. It will not
- * be copied into the log4j-api module.
- */
-public class PropertySource {
-}
+package org.apache.logging.log4j.util.java9.test;
+
+public class Service2 implements BetterService {
+}
\ No newline at end of file
diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/message/ThreadDumpMessage.java b/log4j-api/src/main/java/org/apache/logging/log4j/message/ThreadDumpMessage.java
index feb54b7b2a..6cdd2b5677 100644
--- a/log4j-api/src/main/java/org/apache/logging/log4j/message/ThreadDumpMessage.java
+++ b/log4j-api/src/main/java/org/apache/logging/log4j/message/ThreadDumpMessage.java
@@ -21,13 +21,11 @@ import static org.apache.logging.log4j.util.Chars.LF;
 import java.io.InvalidObjectException;
 import java.io.ObjectInputStream;
 import java.io.Serializable;
+import java.lang.invoke.MethodHandles;
 import java.util.HashMap;
-import java.util.Iterator;
 import java.util.Map;
-import java.util.ServiceConfigurationError;
-import java.util.ServiceLoader;
 
-import org.apache.logging.log4j.status.StatusLogger;
+import org.apache.logging.log4j.util.ServiceLoaderUtil;
 import org.apache.logging.log4j.util.StringBuilderFormattable;
 import org.apache.logging.log4j.util.Strings;
 
@@ -59,25 +57,15 @@ public class ThreadDumpMessage implements Message, StringBuilderFormattable {
 
     private static ThreadInfoFactory getFactory() {
         if (FACTORY == null) {
-            FACTORY = initFactory(ThreadDumpMessage.class.getClassLoader());
+            FACTORY = initFactory();
         }
         return FACTORY;
     }
 
-    private static ThreadInfoFactory initFactory(final ClassLoader classLoader) {
-        final ServiceLoader<ThreadInfoFactory> serviceLoader = ServiceLoader.load(ThreadInfoFactory.class, classLoader);
-        ThreadInfoFactory result = null;
-        try {
-            final Iterator<ThreadInfoFactory> iterator = serviceLoader.iterator();
-            while (result == null && iterator.hasNext()) {
-                result = iterator.next();
-            }
-        } catch (ServiceConfigurationError | LinkageError | Exception unavailable) { // if java management classes not available
-            StatusLogger.getLogger().info("ThreadDumpMessage uses BasicThreadInfoFactory: " +
-                            "could not load extended ThreadInfoFactory: {}", unavailable.toString());
-            result = null;
-        }
-        return result == null ? new BasicThreadInfoFactory() : result;
+    private static ThreadInfoFactory initFactory() {
+        return ServiceLoaderUtil.loadServices(ThreadInfoFactory.class, MethodHandles.lookup(), false)
+                .findFirst()
+                .orElseGet(BasicThreadInfoFactory::new);
     }
 
     @Override
diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/util/LoaderUtil.java b/log4j-api/src/main/java/org/apache/logging/log4j/util/LoaderUtil.java
index 37316c23f1..cdd6b5388a 100644
--- a/log4j-api/src/main/java/org/apache/logging/log4j/util/LoaderUtil.java
+++ b/log4j-api/src/main/java/org/apache/logging/log4j/util/LoaderUtil.java
@@ -17,6 +17,7 @@
 package org.apache.logging.log4j.util;
 
 import java.io.IOException;
+import java.lang.invoke.MethodHandles.Lookup;
 import java.lang.reflect.InvocationTargetException;
 import java.net.URL;
 import java.security.AccessController;
@@ -105,32 +106,6 @@ public final class LoaderUtil {
         }
     }
 
-    public static ClassLoader[] getClassLoaders() {
-        final Collection<ClassLoader> classLoaders = new LinkedHashSet<>();
-        final ClassLoader tcl = getThreadContextClassLoader();
-        if (tcl != null) {
-            classLoaders.add(tcl);
-        }
-        accumulateClassLoaders(LoaderUtil.class.getClassLoader(), classLoaders);
-        accumulateClassLoaders(tcl == null ? null : tcl.getParent(), classLoaders);
-        final ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();
-		if (systemClassLoader != null) {
-            classLoaders.add(systemClassLoader);
-        }
-        return classLoaders.toArray(EMPTY_CLASS_LOADER_ARRAY);
-    }
-
-    /**
-     * Adds the provided loader to the loaders collection, and traverses up the tree until either a null
-     * value or a classloader which has already been added is encountered.
-     */
-    private static void accumulateClassLoaders(ClassLoader loader, Collection<ClassLoader> loaders) {
-        // Some implementations may use null to represent the bootstrap class loader.
-        if (loader != null && loaders.add(loader)) {
-            accumulateClassLoaders(loader.getParent(), loaders);
-        }
-    }
-
     /**
      * Determines if a named Class can be loaded or not.
      *
@@ -272,7 +247,11 @@ public final class LoaderUtil {
      * @since 2.1
      */
     public static Collection<URL> findResources(final String resource) {
-        final Collection<UrlResource> urlResources = findUrlResources(resource);
+        return findResources(resource, true);
+    }
+
+    static Collection<URL> findResources(final String resource, final boolean useTccl) {
+        final Collection<UrlResource> urlResources = findUrlResources(resource, useTccl);
         final Collection<URL> resources = new LinkedHashSet<>(urlResources.size());
         for (final UrlResource urlResource : urlResources) {
             resources.add(urlResource.getUrl());
@@ -280,10 +259,10 @@ public final class LoaderUtil {
         return resources;
     }
 
-    static Collection<UrlResource> findUrlResources(final String resource) {
+    static Collection<UrlResource> findUrlResources(final String resource, final boolean useTccl) {
         // @formatter:off
         final ClassLoader[] candidates = {
-                getThreadContextClassLoader(), 
+                useTccl ? getThreadContextClassLoader() : null, 
                 LoaderUtil.class.getClassLoader(),
                 GET_CLASS_LOADER_DISABLED ? null : ClassLoader.getSystemClassLoader()};
         // @formatter:on
diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/util/PropertiesUtil.java b/log4j-api/src/main/java/org/apache/logging/log4j/util/PropertiesUtil.java
index 2a6954863c..f8c65dcd01 100644
--- a/log4j-api/src/main/java/org/apache/logging/log4j/util/PropertiesUtil.java
+++ b/log4j-api/src/main/java/org/apache/logging/log4j/util/PropertiesUtil.java
@@ -18,6 +18,7 @@ package org.apache.logging.log4j.util;
 
 import java.io.IOException;
 import java.io.InputStream;
+import java.lang.invoke.MethodHandles;
 import java.nio.charset.Charset;
 import java.time.Duration;
 import java.time.temporal.ChronoUnit;
@@ -50,7 +51,7 @@ public final class PropertiesUtil {
 
     private static final String LOG4J_PROPERTIES_FILE_NAME = "log4j2.component.properties";
     private static final String LOG4J_SYSTEM_PROPERTIES_FILE_NAME = "log4j2.system.properties";
-    private static final PropertiesUtil LOG4J_PROPERTIES = new PropertiesUtil(LOG4J_PROPERTIES_FILE_NAME);
+    private static final PropertiesUtil LOG4J_PROPERTIES = new PropertiesUtil(LOG4J_PROPERTIES_FILE_NAME, false);
 
     private final Environment environment;
 
@@ -70,7 +71,11 @@ public final class PropertiesUtil {
      * @param propertiesFileName the location of properties file to load
      */
     public PropertiesUtil(final String propertiesFileName) {
-        this.environment = new Environment(new PropertyFilePropertySource(propertiesFileName));
+        this(propertiesFileName, true);
+    }
+
+    private PropertiesUtil(final String propertiesFileName, final boolean useTccl) {
+        this.environment = new Environment(new PropertyFilePropertySource(propertiesFileName, useTccl));
     }
 
     /**
@@ -424,7 +429,7 @@ public final class PropertiesUtil {
         private final Map<List<CharSequence>, String> tokenized = new ConcurrentHashMap<>();
 
         private Environment(final PropertySource propertySource) {
-            PropertyFilePropertySource sysProps = new PropertyFilePropertySource(LOG4J_SYSTEM_PROPERTIES_FILE_NAME);
+            PropertyFilePropertySource sysProps = new PropertyFilePropertySource(LOG4J_SYSTEM_PROPERTIES_FILE_NAME, false);
             try {
                 sysProps.forEach((key, value) -> {
                     if (System.getProperty(key) == null) {
@@ -435,17 +440,9 @@ public final class PropertiesUtil {
                 // Access to System Properties is restricted so just skip it.
             }
             sources.add(propertySource);
-			for (final ClassLoader classLoader : LoaderUtil.getClassLoaders()) {
-				try {
-					for (final PropertySource source : ServiceLoader.load(PropertySource.class, classLoader)) {
-						sources.add(source);
-					}
-				} catch (final Throwable ex) {
-					/* Don't log anything to the console. It may not be a problem that a PropertySource
-					 * isn't accessible.
-					 */
-				}
-			}
+            // We don't log anything on the status logger.
+            ServiceLoaderUtil.loadServices(PropertySource.class, MethodHandles.lookup(), false, false)
+                    .forEach(sources::add);
 
             reload();
         }
diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/util/PropertyFilePropertySource.java b/log4j-api/src/main/java/org/apache/logging/log4j/util/PropertyFilePropertySource.java
index c4cf3596a0..cd5d1fd2fc 100644
--- a/log4j-api/src/main/java/org/apache/logging/log4j/util/PropertyFilePropertySource.java
+++ b/log4j-api/src/main/java/org/apache/logging/log4j/util/PropertyFilePropertySource.java
@@ -29,12 +29,16 @@ import java.util.Properties;
 public class PropertyFilePropertySource extends PropertiesPropertySource {
 
     public PropertyFilePropertySource(final String fileName) {
-        super(loadPropertiesFile(fileName));
+        this(fileName, true);
     }
 
-    private static Properties loadPropertiesFile(final String fileName) {
+    public PropertyFilePropertySource(final String fileName, final boolean useTccl) {
+        super(loadPropertiesFile(fileName, useTccl));
+    }
+
+    private static Properties loadPropertiesFile(final String fileName, final boolean useTccl) {
         final Properties props = new Properties();
-        for (final URL url : LoaderUtil.findResources(fileName)) {
+        for (final URL url : LoaderUtil.findResources(fileName, useTccl)) {
             try (final InputStream in = url.openStream()) {
                 props.load(in);
             } catch (final IOException e) {
diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/util/ProviderUtil.java b/log4j-api/src/main/java/org/apache/logging/log4j/util/ProviderUtil.java
index 8ae4462f62..fa766448b7 100644
--- a/log4j-api/src/main/java/org/apache/logging/log4j/util/ProviderUtil.java
+++ b/log4j-api/src/main/java/org/apache/logging/log4j/util/ProviderUtil.java
@@ -17,6 +17,7 @@
 package org.apache.logging.log4j.util;
 
 import java.io.IOException;
+import java.lang.invoke.MethodHandles;
 import java.net.URL;
 import java.util.Collection;
 import java.util.Enumeration;
@@ -63,14 +64,11 @@ public final class ProviderUtil {
     private static volatile ProviderUtil instance;
 
     private ProviderUtil() {
-        for (final ClassLoader classLoader : LoaderUtil.getClassLoaders()) {
-            try {
-                loadProviders(classLoader);
-            } catch (final Throwable ex) {
-                LOGGER.debug("Unable to retrieve provider from ClassLoader {}", classLoader, ex);
-            }
-        }
-        for (final LoaderUtil.UrlResource resource : LoaderUtil.findUrlResources(PROVIDER_RESOURCE)) {
+        ServiceLoaderUtil.loadServices(Provider.class, MethodHandles.lookup(), false)
+                .filter(provider -> validVersion(provider.getVersions()))
+                .forEach(PROVIDERS::add);
+
+        for (final LoaderUtil.UrlResource resource : LoaderUtil.findUrlResources(PROVIDER_RESOURCE, false)) {
             loadProvider(resource.getUrl(), resource.getClassLoader());
         }
     }
@@ -100,18 +98,15 @@ public final class ProviderUtil {
         }
     }
 
-	/**
-	 * 
-	 * @param classLoader null can be used to mark the bootstrap class loader.
-	 */
-	protected static void loadProviders(final ClassLoader classLoader) {
-		final ServiceLoader<Provider> serviceLoader = ServiceLoader.load(Provider.class, classLoader);
-		for (final Provider provider : serviceLoader) {
-			if (validVersion(provider.getVersions()) && !PROVIDERS.contains(provider)) {
-				PROVIDERS.add(provider);
-			}
-		}
-	}
+    /**
+     * 
+     * @param classLoader null can be used to mark the bootstrap class loader.
+     */
+    protected static void loadProviders(final ClassLoader classLoader) {
+        ServiceLoaderUtil.loadClassloaderServices(Provider.class, MethodHandles.lookup(), classLoader, true)
+                .filter(provider -> validVersion(provider.getVersions()))
+                .forEach(PROVIDERS::add);
+    }
 
     /**
      * @deprecated Use {@link #loadProvider(java.net.URL, ClassLoader)} instead. Will be removed in 3.0.
diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/util/ServiceLoaderUtil.java b/log4j-api/src/main/java/org/apache/logging/log4j/util/ServiceLoaderUtil.java
index 2a84f66600..5564f2eb89 100644
--- a/log4j-api/src/main/java/org/apache/logging/log4j/util/ServiceLoaderUtil.java
+++ b/log4j-api/src/main/java/org/apache/logging/log4j/util/ServiceLoaderUtil.java
@@ -17,121 +17,142 @@
 
 package org.apache.logging.log4j.util;
 
-import java.lang.reflect.InvocationTargetException;
-import java.util.ArrayList;
-import java.util.Collection;
+import java.lang.invoke.MethodHandle;
+import java.lang.invoke.MethodHandles.Lookup;
+import java.lang.invoke.MethodType;
+import java.util.Collections;
+import java.util.HashSet;
 import java.util.Iterator;
-import java.util.List;
 import java.util.ServiceConfigurationError;
 import java.util.ServiceLoader;
+import java.util.Set;
+import java.util.Spliterator;
 import java.util.function.Consumer;
+import java.util.stream.Stream;
+import java.util.stream.StreamSupport;
 
 import org.apache.logging.log4j.Logger;
 import org.apache.logging.log4j.status.StatusLogger;
 
-public class ServiceLoaderUtil {
-
-    private static boolean USE_HK2_SERVICE_LOCATOR = LoaderUtil.isClassAvailable("org.glassfish.hk2.osgiresourcelocator.ServiceLoader");
+/**
+ * This class should be considered internal.
+ */
+public final class ServiceLoaderUtil {
 
-    private static Logger getLogger() {
-        return StatusLogger.getLogger();
-    }
+    private static final MethodType LOAD_CLASS_CLASSLOADER = MethodType.methodType(ServiceLoader.class, Class.class,
+            ClassLoader.class);
 
     private ServiceLoaderUtil() {
     }
 
     /**
-     * Retrieves the available services.
+     * Retrieves the available services from the caller's classloader.
      * 
      * Broken services will be ignored.
      * 
      * @param <T>         The service type.
      * @param serviceType The class of the service.
-     * @return List of available services.
+     * @param lookup      The calling class data.
+     * @return A stream of service instances.
      */
-    public static <T> List<T> loadServices(final Class<T> serviceType) {
-        return loadServices(serviceType, null);
+    public static <T> Stream<T> loadServices(final Class<T> serviceType, Lookup lookup) {
+        return loadServices(serviceType, lookup, false);
     }
 
     /**
-     * Instantiates a service of the required type.
+     * Retrieves the available services from the caller's classloader and possibly
+     * the thread context classloader.
+     * 
+     * Broken services will be ignored.
      * 
      * @param <T>         The service type.
      * @param serviceType The class of the service.
-     * @return A service of the given type or {@code null} if none is available.
+     * @param lookup      The calling class data.
+     * @param useTccl     If true the thread context classloader will also be used.
+     * @return A stream of service instances.
      */
-    public static <T> T getService(final Class<T> serviceType) {
-        // 1. Check for a system property with the FQCN of serviceType
-        String serviceClassName = PropertiesUtil.getProperties().getStringProperty(serviceType.getName());
-        if (Strings.isNotEmpty(serviceClassName)) {
-            try {
-                @SuppressWarnings("unchecked")
-                final Class<T> serviceClass = (Class<T>) Class.forName(serviceClassName);
-                if (serviceType.isAssignableFrom(serviceClass)) {
-                    return LoaderUtil.newInstanceOf(serviceClass);
-                }
-                getLogger().error("Unable to load service {}: it is not of the required type {}", serviceClass,
-                        serviceType);
-            } catch (ClassNotFoundException | InstantiationException | IllegalAccessException
-                    | InvocationTargetException e) {
-                getLogger().error("Unable to load service class {}", serviceClassName, e);
+    public static <T> Stream<T> loadServices(final Class<T> serviceType, Lookup lookup, boolean useTccl) {
+        return loadServices(serviceType, lookup, useTccl, true);
+    }
+
+    static <T> Stream<T> loadServices(final Class<T> serviceType, final Lookup lookup, final boolean useTccl,
+            final boolean verbose) {
+        final ClassLoader classLoader = lookup.lookupClass().getClassLoader();
+        Stream<T> services = loadClassloaderServices(serviceType, lookup, classLoader, verbose);
+        if (useTccl) {
+            final ClassLoader contextClassLoader = LoaderUtil.getThreadContextClassLoader();
+            if (contextClassLoader != classLoader) {
+                services = Stream.concat(services,
+                        loadClassloaderServices(serviceType, lookup, contextClassLoader, verbose));
             }
         }
-        // 2. Use the ServiceLoader:
-        final List<T> services = loadServices(serviceType,
-                t -> getLogger().info("Unable to load service class for service {}", serviceType.getName(), t));
-        if (services.size() > 0) {
-            if (services.size() > 1) {
-                getLogger().warn("Multiple versions of service {} are available on the classpath: {}", serviceType,
-                        services);
+        final Set<Class<?>> classes = new HashSet<>();
+        // only the first occurrence of a class
+        return services.filter(service -> classes.add(service.getClass()));
+    }
+
+    static <T> Stream<T> loadClassloaderServices(final Class<T> serviceType, final Lookup lookup,
+            final ClassLoader classLoader, final boolean verbose) {
+        return StreamSupport.stream(new ServiceLoaderSpliterator<T>(serviceType, lookup, classLoader, verbose), false);
+    }
+
+    static <T> Iterable<T> callServiceLoader(Lookup lookup, Class<T> serviceType, ClassLoader classLoader,
+            boolean verbose) {
+        try {
+            final MethodHandle handle = lookup.findStatic(ServiceLoader.class, "load", LOAD_CLASS_CLASSLOADER);
+            final ServiceLoader<T> serviceLoader = (ServiceLoader<T>) handle.invokeExact(serviceType, classLoader);
+            return serviceLoader;
+        } catch (Throwable e) {
+            if (verbose) {
+                StatusLogger.getLogger().error("Unable to load services for service {}", serviceType, e);
             }
-            return services.get(0);
         }
-        return null;
+        return Collections.emptyList();
+
     }
 
-    static <T> List<T> loadServices(final Class<T> serviceType, final Consumer<Throwable> logger) {
-        final List<T> services = new ArrayList<T>();
-        for (final ClassLoader loader : LoaderUtil.getClassLoaders()) {
-            addServices(serviceType, loader, logger, services);
-        }
-        if (USE_HK2_SERVICE_LOCATOR) {
-            addOsgiServices(serviceType, services);
+    private static class ServiceLoaderSpliterator<S> implements Spliterator<S> {
+
+        private final Iterator<S> serviceIterator;
+        private final Logger logger;
+        private final String serviceName;
+
+        public ServiceLoaderSpliterator(final Class<S> serviceType, final Lookup lookup, final ClassLoader classLoader,
+                final boolean verbose) {
+            this.serviceIterator = callServiceLoader(lookup, serviceType, classLoader, verbose).iterator();
+            this.logger = verbose ? StatusLogger.getLogger() : null;
+            this.serviceName = serviceType.toString();
         }
-        return services;
-    }
 
-    /**
-     * Retrieves the available services of a given type from a single classloader.
-     * 
-     * @param <T>         The service type.
-     * @param serviceType The class of the service.
-     * @param classLoader The classloader to use.
-     * @param logger      An action to perform for each broken service.
-     * @param services    A list to add the services.
-     */
-    private static <T> void addServices(final Class<T> serviceType, final ClassLoader classLoader,
-            final Consumer<Throwable> logger, final Collection<T> services) {
-        final Iterator<T> iterator = ServiceLoader.load(serviceType, classLoader).iterator();
-        while (iterator.hasNext()) {
-            try {
-                final T service = iterator.next();
-                if (classLoader.equals(service.getClass().getClassLoader())) {
-                    services.add(service);
-                }
-            } catch (ServiceConfigurationError e) {
-                if (logger != null) {
-                    logger.accept(e);
+        @Override
+        public boolean tryAdvance(Consumer<? super S> action) {
+            while (serviceIterator.hasNext()) {
+                try {
+                    action.accept(serviceIterator.next());
+                    return true;
+                } catch (ServiceConfigurationError e) {
+                    if (logger != null) {
+                        logger.warn("Unable to load service class for service {}", serviceName, e);
+                    }
                 }
             }
+            return false;
+        }
+
+        @Override
+        public Spliterator<S> trySplit() {
+            return null;
         }
-    }
 
-    private static <T> void addOsgiServices(final Class<T> serviceType, final Collection<T> services) {
-        final Iterable<? extends T> iterable = org.glassfish.hk2.osgiresourcelocator.ServiceLoader
-                .lookupProviderInstances(serviceType);
-        if (iterable != null) {
-            iterable.forEach(services::add);
+        @Override
+        public long estimateSize() {
+            return Long.MAX_VALUE;
         }
+
+        @Override
+        public int characteristics() {
+            return NONNULL | IMMUTABLE;
+        }
+
     }
 }
diff --git a/log4j-api/src/test/java/org/apache/logging/log4j/util/ServiceLoaderUtilTest.java b/log4j-api/src/test/java/org/apache/logging/log4j/util/ServiceLoaderUtilTest.java
index a8eb283754..3c1cd76f42 100644
--- a/log4j-api/src/test/java/org/apache/logging/log4j/util/ServiceLoaderUtilTest.java
+++ b/log4j-api/src/test/java/org/apache/logging/log4j/util/ServiceLoaderUtilTest.java
@@ -18,30 +18,28 @@
 package org.apache.logging.log4j.util;
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.assertNull;
-import static org.junit.jupiter.api.Assertions.assertTrue;
 import static org.junit.jupiter.api.Assertions.fail;
 
 import java.io.IOException;
+import java.lang.invoke.MethodHandles;
 import java.util.Collections;
 import java.util.List;
 import java.util.ServiceConfigurationError;
 import java.util.concurrent.atomic.AtomicInteger;
+import java.util.stream.Collectors;
 
 import org.apache.logging.log4j.Level;
 import org.apache.logging.log4j.status.StatusData;
 import org.apache.logging.log4j.status.StatusListener;
 import org.apache.logging.log4j.status.StatusLogger;
-import org.apache.logging.log4j.util.test.BetterService;
 import org.apache.logging.log4j.util.test.Service;
-import org.apache.logging.log4j.util.test.Service1;
-import org.apache.logging.log4j.util.test.Service2;
 import org.junit.jupiter.api.AfterAll;
 import org.junit.jupiter.api.BeforeAll;
 import org.junit.jupiter.api.Test;
-import org.junitpioneer.jupiter.SetSystemProperty;
-import org.junitpioneer.jupiter.SetSystemProperty.SetSystemProperties;
+import org.junit.jupiter.api.parallel.Execution;
+import org.junit.jupiter.api.parallel.ExecutionMode;
 
+@Execution(ExecutionMode.SAME_THREAD)
 public class ServiceLoaderUtilTest {
 
     private static final AtomicInteger counter = new AtomicInteger();
@@ -77,7 +75,8 @@ public class ServiceLoaderUtilTest {
         List<Service> services = Collections.emptyList();
         final int warnings = counter.get();
         try {
-            services = ServiceLoaderUtil.loadServices(Service.class, StatusLogger.getLogger()::warn);
+            services = ServiceLoaderUtil.loadServices(Service.class, MethodHandles.lookup(), false)
+                    .collect(Collectors.toList());
         } catch (ServiceConfigurationError e) {
             fail(e);
         }
@@ -86,42 +85,4 @@ public class ServiceLoaderUtilTest {
         assertEquals(warnings + 2, counter.get());
     }
 
-    @Test
-    public void testMultipleServicesPresentWarning() {
-        final int warnings = counter.get();
-        final Service service = ServiceLoaderUtil.getService(Service.class);
-        assertTrue(service instanceof Service1);
-        assertEquals(warnings + 1, counter.get());
-        // No warning
-        final BetterService betterService = ServiceLoaderUtil.getService(BetterService.class);
-        assertTrue(betterService instanceof Service2);
-        assertEquals(warnings + 1, counter.get());
-    }
-
-    @Test
-    @SetSystemProperties({
-            @SetSystemProperty(key = "org.apache.logging.log4j.util.test.Service", value = "org.apache.logging.log4j.util.test.Service2"),
-            @SetSystemProperty(key = "org.apache.logging.log4j.util.test.BetterService", value = "java.lang.String") })
-    public void testOverrideService() {
-        final int warnings = counter.get();
-        // Valid override
-        final Service service = ServiceLoaderUtil.getService(Service.class);
-        assertTrue(service instanceof Service2);
-        assertEquals(warnings, counter.get());
-        // Invalid override
-        final BetterService betterService = ServiceLoaderUtil.getService(BetterService.class);
-        assertEquals(warnings + 1, counter.get());
-        assertTrue(betterService instanceof Service2);
-    }
-
-    @Test
-    @SetSystemProperty(key = "java.lang.String", value = "invalid.String")
-    public void testNonExistingService() {
-        final int warnings = counter.get();
-        assertNull(ServiceLoaderUtil.getService(Double.class));
-        assertEquals(warnings, counter.get());
-        // With manual override
-        assertNull(ServiceLoaderUtil.getService(String.class));
-        assertEquals(warnings + 1, counter.get());
-    }
 }
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/ThreadContextDataInjector.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/ThreadContextDataInjector.java
index 471a68ca0f..3d86f43964 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/ThreadContextDataInjector.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/ThreadContextDataInjector.java
@@ -16,13 +16,13 @@
  */
 package org.apache.logging.log4j.core.impl;
 
+import java.lang.invoke.MethodHandles;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
-import java.util.ServiceLoader;
 import java.util.concurrent.ConcurrentLinkedDeque;
 
 import org.apache.logging.log4j.Logger;
@@ -32,8 +32,8 @@ import org.apache.logging.log4j.core.config.Property;
 import org.apache.logging.log4j.core.util.ContextDataProvider;
 import org.apache.logging.log4j.spi.ReadOnlyThreadContextMap;
 import org.apache.logging.log4j.status.StatusLogger;
-import org.apache.logging.log4j.util.LoaderUtil;
 import org.apache.logging.log4j.util.ReadOnlyStringMap;
+import org.apache.logging.log4j.util.ServiceLoaderUtil;
 import org.apache.logging.log4j.util.StringMap;
 
 /**
@@ -74,17 +74,8 @@ public class ThreadContextDataInjector {
 
     private static List<ContextDataProvider> getServiceProviders() {
         List<ContextDataProvider> providers = new ArrayList<>();
-        for (final ClassLoader classLoader : LoaderUtil.getClassLoaders()) {
-            try {
-                for (final ContextDataProvider provider : ServiceLoader.load(ContextDataProvider.class, classLoader)) {
-                    if (providers.stream().noneMatch(p -> p.getClass().isAssignableFrom(provider.getClass()))) {
-                        providers.add(provider);
-                    }
-                }
-            } catch (final Throwable ex) {
-                LOGGER.debug("Unable to access Context Data Providers {}", ex.getMessage());
-            }
-        }
+        ServiceLoaderUtil.loadServices(ContextDataProvider.class, MethodHandles.lookup(), false)
+                .forEach(providers::add);
         return Collections.unmodifiableList(providers);
     }
 
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/util/WatchManager.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/util/WatchManager.java
index e4ab843d0f..3dc5f987eb 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/util/WatchManager.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/util/WatchManager.java
@@ -17,26 +17,26 @@
 package org.apache.logging.log4j.core.util;
 
 import java.io.File;
-import java.util.ArrayList;
+import java.lang.invoke.MethodHandles;
 import java.util.Date;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
-import java.util.ServiceLoader;
 import java.util.UUID;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.ConcurrentMap;
 import java.util.concurrent.ScheduledFuture;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicInteger;
+import java.util.stream.Collectors;
 
 import org.apache.logging.log4j.Logger;
 import org.apache.logging.log4j.core.AbstractLifeCycle;
 import org.apache.logging.log4j.core.config.ConfigurationFileWatcher;
 import org.apache.logging.log4j.core.config.ConfigurationScheduler;
 import org.apache.logging.log4j.status.StatusLogger;
-import org.apache.logging.log4j.util.LoaderUtil;
+import org.apache.logging.log4j.util.ServiceLoaderUtil;
 
 /**
  * Manages {@link FileWatcher}s.
@@ -134,7 +134,8 @@ public class WatchManager extends AbstractLifeCycle {
 
     public WatchManager(final ConfigurationScheduler scheduler) {
         this.scheduler = Objects.requireNonNull(scheduler, "scheduler");
-        eventServiceList = getEventServices();
+        eventServiceList = ServiceLoaderUtil.loadServices(WatchEventService.class, MethodHandles.lookup(), true)
+                .collect(Collectors.toList());
     }
 
     public void checkFiles() {
@@ -155,22 +156,6 @@ public class WatchManager extends AbstractLifeCycle {
         return map;
     }
 
-    private List<WatchEventService> getEventServices() {
-        List<WatchEventService> list = new ArrayList<>();
-        for (final ClassLoader classLoader : LoaderUtil.getClassLoaders()) {
-            try {
-                final ServiceLoader<WatchEventService> serviceLoader = ServiceLoader
-                    .load(WatchEventService.class, classLoader);
-                for (final WatchEventService service : serviceLoader) {
-                    list.add(service);
-                }
-            } catch (final Throwable ex) {
-                LOGGER.debug("Unable to retrieve WatchEventService from ClassLoader {}", classLoader, ex);
-            }
-        }
-        return list;
-    }
-
     public UUID getId() {
         return this.id;
     }
diff --git a/log4j-osgi/src/test/java/org/apache/logging/log4j/osgi/tests/AbstractLoadBundleTest.java b/log4j-osgi/src/test/java/org/apache/logging/log4j/osgi/tests/AbstractLoadBundleTest.java
index 678ca3de15..fe5e9e6cc0 100644
--- a/log4j-osgi/src/test/java/org/apache/logging/log4j/osgi/tests/AbstractLoadBundleTest.java
+++ b/log4j-osgi/src/test/java/org/apache/logging/log4j/osgi/tests/AbstractLoadBundleTest.java
@@ -21,12 +21,16 @@ import static org.junit.Assert.assertTrue;
 
 import java.io.ByteArrayOutputStream;
 import java.io.PrintStream;
+import java.lang.invoke.MethodHandles;
+import java.lang.invoke.MethodHandles.Lookup;
 import java.lang.reflect.Field;
 import java.lang.reflect.Method;
 import java.net.URL;
 import java.nio.file.Path;
 import java.nio.file.Paths;
 import java.util.List;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
 
 import org.apache.logging.log4j.osgi.tests.junit.BundleTestInfo;
 import org.apache.logging.log4j.osgi.tests.junit.OsgiRule;
@@ -35,6 +39,7 @@ import org.junit.Assert;
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
+import org.junit.jupiter.api.Disabled;
 import org.osgi.framework.Bundle;
 import org.osgi.framework.BundleContext;
 import org.osgi.framework.BundleException;
@@ -332,7 +337,7 @@ public abstract class AbstractLoadBundleTest {
      * @throws BundleException
      * @throws ReflectiveOperationException
      */
-    @Test
+    @Disabled("Until OSGI service detection is implemented correctly.")
     public void testServiceLoader() throws BundleException, ReflectiveOperationException {
         final Bundle api = getApiBundle();
         final Bundle core = getCoreBundle();
@@ -345,13 +350,13 @@ public abstract class AbstractLoadBundleTest {
 
         final Class<?> serviceLoaderUtil = api.loadClass("org.apache.logging.log4j.util.ServiceLoaderUtil");
         final Class<?> provider = api.loadClass("org.apache.logging.log4j.spi.Provider");
-        final Method getService = serviceLoaderUtil.getDeclaredMethod("loadServices", Class.class);
+        final Method getService = serviceLoaderUtil.getDeclaredMethod("loadServices", Class.class, Lookup.class);
 
-        final Object obj = getService.invoke(null, provider);
+        final Object obj = getService.invoke(null, provider, MethodHandles.lookup());
         assertEquals("serviceLoader is not in ACTIVE state", Bundle.ACTIVE, serviceLoader.getState());
-        assertTrue(obj instanceof List);
+        assertTrue(obj instanceof Stream);
         @SuppressWarnings("unchecked")
-        final List<Object> services = ((List<Object>) obj);
+        final List<Object> services = ((Stream<Object>) obj).collect(Collectors.toList());
         assertEquals(1, services.size());
         assertEquals("org.apache.logging.log4j.core.impl.Log4jProvider", services.get(0).getClass().getName());
 


[logging-log4j2] 02/02: Add OSGI support to `ServiceLoaderUtil`

Posted by pk...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

pkarwasz pushed a commit to branch release-2.x
in repository https://gitbox.apache.org/repos/asf/logging-log4j2.git

commit a2f0faefa1d68848d14c95fba5284d54a3ae721a
Author: Piotr P. Karwasz <pi...@karwasz.org>
AuthorDate: Tue Apr 5 22:10:53 2022 +0200

    Add OSGI support to `ServiceLoaderUtil`
---
 log4j-api-java9/pom.xml                            |  4 ++
 .../log4j/util/java9/ServiceLoaderUtilTest.java    | 60 ++++++-------------
 log4j-api-java9/src/test/java9/module-info.java    | 13 +++++
 log4j-api/pom.xml                                  | 60 +++++++++++++------
 .../logging/log4j/util/OsgiServiceLocator.java     | 67 ++++++++++++++++++++++
 .../logging/log4j/util/ServiceLoaderUtil.java      |  3 +
 .../logging/log4j/util/OsgiServiceLocatorTest.java | 31 +++++-----
 log4j-osgi/pom.xml                                 |  4 --
 .../log4j/osgi/tests/AbstractLoadBundleTest.java   | 41 ++++++-------
 pom.xml                                            |  6 --
 10 files changed, 184 insertions(+), 105 deletions(-)

diff --git a/log4j-api-java9/pom.xml b/log4j-api-java9/pom.xml
index d2f93dd862..d717482da2 100644
--- a/log4j-api-java9/pom.xml
+++ b/log4j-api-java9/pom.xml
@@ -36,6 +36,10 @@
       <groupId>org.junit.jupiter</groupId>
       <artifactId>junit-jupiter-engine</artifactId>
     </dependency>
+    <dependency>
+      <groupId>org.assertj</groupId>
+      <artifactId>assertj-core</artifactId>
+    </dependency>
   </dependencies>
   <build>
     <plugins>
diff --git a/log4j-api-java9/src/test/java/org/apache/logging/log4j/util/java9/ServiceLoaderUtilTest.java b/log4j-api-java9/src/test/java/org/apache/logging/log4j/util/java9/ServiceLoaderUtilTest.java
index f95dd54c0a..9117629940 100644
--- a/log4j-api-java9/src/test/java/org/apache/logging/log4j/util/java9/ServiceLoaderUtilTest.java
+++ b/log4j-api-java9/src/test/java/org/apache/logging/log4j/util/java9/ServiceLoaderUtilTest.java
@@ -16,13 +16,11 @@
  */
 package org.apache.logging.log4j.util.java9;
 
-import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.fail;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
 
 import java.lang.invoke.MethodHandles;
-import java.util.Collections;
 import java.util.List;
-import java.util.ServiceConfigurationError;
 import java.util.stream.Collectors;
 
 import org.apache.logging.log4j.util.PropertySource;
@@ -35,43 +33,21 @@ public class ServiceLoaderUtilTest {
 
     @Test
     public void testServiceResolution() {
-        // Run only if we are a module
-        if (ServiceLoaderUtil.class.getModule().isNamed()) {
-            List<Object> services = Collections.emptyList();
-            // Service from test module
-            try {
-                services = ServiceLoaderUtil.loadServices(Service.class, MethodHandles.lookup())
-                        .collect(Collectors.toList());
-            } catch (ServiceConfigurationError e) {
-                fail(e);
-            }
-            assertEquals(2, services.size(), "Service services");
-            // BetterService from test module
-            services.clear();
-            try {
-                services = ServiceLoaderUtil.loadServices(BetterService.class, MethodHandles.lookup())
-                        .collect(Collectors.toList());
-            } catch (ServiceConfigurationError e) {
-                fail(e);
-            }
-            assertEquals(1, services.size(), "BetterService services");
-            // PropertySource from org.apache.logging.log4j module from this module
-            services.clear();
-            try {
-                services = ServiceLoaderUtil.loadServices(PropertySource.class, MethodHandles.lookup())
-                        .collect(Collectors.toList());
-            } catch (ServiceConfigurationError e) {
-                fail(e);
-            }
-            assertEquals(0, services.size(), "PropertySource services");
-            // PropertySource from within org.apache.logging.log4j module
-            services.clear();
-            try {
-                services = PropertySource.loadPropertySources().collect(Collectors.toList());
-            } catch (ServiceConfigurationError e) {
-                fail(e);
-            }
-            assertEquals(2, services.size(), "PropertySource services");
-        }
+        List<Object> services;
+        // Service from test module
+        services = assertDoesNotThrow(() -> ServiceLoaderUtil.loadServices(Service.class, MethodHandles.lookup())
+                .collect(Collectors.toList()));
+        assertThat(services).hasSize(2);
+        // BetterService from test module
+        services = assertDoesNotThrow(() -> ServiceLoaderUtil.loadServices(BetterService.class, MethodHandles.lookup())
+                .collect(Collectors.toList()));
+        assertThat(services).hasSize(1);
+        // PropertySource from org.apache.logging.log4j module from this module
+        services = assertDoesNotThrow(() -> ServiceLoaderUtil.loadServices(PropertySource.class, MethodHandles.lookup())
+                .collect(Collectors.toList()));
+        assertThat(services).hasSize(0);
+        // PropertySource from within org.apache.logging.log4j module
+        services = assertDoesNotThrow(() -> PropertySource.loadPropertySources().collect(Collectors.toList()));
+        assertThat(services).hasSize(2);
     }
 }
diff --git a/log4j-api-java9/src/test/java9/module-info.java b/log4j-api-java9/src/test/java9/module-info.java
index 761c4be9ee..cc0c24cca3 100644
--- a/log4j-api-java9/src/test/java9/module-info.java
+++ b/log4j-api-java9/src/test/java9/module-info.java
@@ -14,9 +14,22 @@
  * See the license for the specific language governing permissions and
  * limitations under the license.
  */
+import org.apache.logging.log4j.util.java9.test.BetterService;
+import org.apache.logging.log4j.util.java9.test.Service;
+import org.apache.logging.log4j.util.java9.test.Service1;
+import org.apache.logging.log4j.util.java9.test.Service2;
+
 open module org.apache.logging.log4j.java9test {
     exports org.apache.logging.log4j.util.java9;
+
     requires org.apache.logging.log4j;
     requires transitive org.junit.jupiter.engine;
     requires transitive org.junit.jupiter.api;
+    requires transitive org.assertj.core;
+
+    uses Service;
+    uses BetterService;
+
+    provides Service with Service1, Service2;
+    provides BetterService with Service2;
 }
diff --git a/log4j-api/pom.xml b/log4j-api/pom.xml
index 20fa405930..63ae69c719 100644
--- a/log4j-api/pom.xml
+++ b/log4j-api/pom.xml
@@ -95,10 +95,6 @@
       <groupId>org.junit-pioneer</groupId>
       <artifactId>junit-pioneer</artifactId>
     </dependency>
-    <dependency>
-      <groupId>org.glassfish.hk2</groupId>
-      <artifactId>osgi-resource-locator</artifactId>
-    </dependency>
   </dependencies>
   <build>
     <plugins>
@@ -215,7 +211,7 @@
             </goals>
             <configuration>
               <archive>
-                <manifestFile>${manifestfile}</manifestFile>
+                <manifestFile>${project.build.testOutputDirectory}/META-INF/MANIFEST.MF</manifestFile>
                 <manifestEntries>
                   <Specification-Title>${project.name}</Specification-Title>
                   <Specification-Version>${project.version}</Specification-Version>
@@ -250,18 +246,48 @@
       <plugin>
         <groupId>org.apache.felix</groupId>
         <artifactId>maven-bundle-plugin</artifactId>
-        <configuration>
-          <instructions>
-            <Export-Package>org.apache.logging.log4j.*</Export-Package>
-            <Import-Package>
-              sun.reflect;resolution:=optional,
-              org.glassfish.hk2.osgiresourcelocator;version="[2.5,3.0)";resolution:=optional,
-              *
-            </Import-Package>
-            <Bundle-Activator>org.apache.logging.log4j.util.Activator</Bundle-Activator>
-            <_fixupmessages>"Classes found in the wrong directory";is:=warning</_fixupmessages>
-          </instructions>
-        </configuration>
+        <executions>
+          <execution>
+            <id>default-manifest</id>
+            <phase>process-classes</phase>
+            <goals>
+              <goal>manifest</goal>
+            </goals>
+            <configuration>
+              <instructions>
+                <Export-Package>org.apache.logging.log4j.*</Export-Package>
+                <Import-Package>
+                  sun.reflect;resolution:=optional,
+                  *
+                </Import-Package>
+                <Bundle-Activator>org.apache.logging.log4j.util.Activator</Bundle-Activator>
+                <_fixupmessages>"Classes found in the wrong directory";is:=warning</_fixupmessages>
+              </instructions>
+            </configuration>
+          </execution>
+          <execution>
+            <id>default-test-manifest</id>
+            <phase>process-test-classes</phase>
+            <goals>
+              <goal>manifest</goal>
+            </goals>
+            <configuration>
+              <classifier>tests</classifier>
+              <outputDirectory>${project.build.testOutputDirectory}</outputDirectory>
+              <manifestLocation>${project.build.testOutputDirectory}/META-INF</manifestLocation>
+              <instructions>
+                <Bundle-SymbolicName>${maven-symbolicname}.tests</Bundle-SymbolicName>
+                <Fragment-Host>org.apache.logging.log4j.api</Fragment-Host>
+                <Export-Package>org.apache.logging.log4j.*</Export-Package>
+                <Import-Package></Import-Package>
+              </instructions>
+            </configuration>
+          </execution>
+        </executions>
+      </plugin>
+      <plugin>
+        <groupId>org.apache.felix</groupId>
+        <artifactId>maven-scr-plugin</artifactId>
       </plugin>
       <plugin>
         <groupId>org.apache.maven.plugins</groupId>
diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/util/OsgiServiceLocator.java b/log4j-api/src/main/java/org/apache/logging/log4j/util/OsgiServiceLocator.java
new file mode 100644
index 0000000000..ef356e073f
--- /dev/null
+++ b/log4j-api/src/main/java/org/apache/logging/log4j/util/OsgiServiceLocator.java
@@ -0,0 +1,67 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.apache.logging.log4j.util;
+
+import java.lang.invoke.MethodHandles.Lookup;
+import java.util.stream.Stream;
+
+import org.apache.logging.log4j.status.StatusLogger;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.FrameworkUtil;
+
+public class OsgiServiceLocator {
+
+    private static final boolean OSGI_AVAILABLE = checkOsgiAvailable();
+
+    private static boolean checkOsgiAvailable() {
+        try {
+            Class.forName("org.osgi.framework.Bundle");
+            return true;
+        } catch (final ClassNotFoundException | LinkageError e) {
+            return false;
+        } catch (final Throwable e) {
+            LowLevelLogUtil.logException("Unknown error checking for existence of class: org.osgi.framework.Bundle", e);
+            return false;
+        }
+    }
+
+    public static boolean isAvailable() {
+        return OSGI_AVAILABLE;
+    }
+
+    public static <T> Stream<T> loadServices(final Class<T> serviceType, final Lookup lookup) {
+        return loadServices(serviceType, lookup, true);
+    }
+
+    public static <T> Stream<T> loadServices(final Class<T> serviceType, final Lookup lookup, final boolean verbose) {
+        final Bundle bundle = FrameworkUtil.getBundle(lookup.lookupClass());
+        if (bundle != null) {
+            final BundleContext ctx = bundle.getBundleContext();
+            try {
+                return ctx.getServiceReferences(serviceType, null)
+                        .stream()
+                        .map(ctx::getService);
+            } catch (Throwable e) {
+                if (verbose) {
+                    StatusLogger.getLogger().error("Unable to load OSGI services for service {}", serviceType, e);
+                }
+            }
+        }
+        return Stream.empty();
+    }
+}
diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/util/ServiceLoaderUtil.java b/log4j-api/src/main/java/org/apache/logging/log4j/util/ServiceLoaderUtil.java
index 5564f2eb89..ab2ddd3934 100644
--- a/log4j-api/src/main/java/org/apache/logging/log4j/util/ServiceLoaderUtil.java
+++ b/log4j-api/src/main/java/org/apache/logging/log4j/util/ServiceLoaderUtil.java
@@ -86,6 +86,9 @@ public final class ServiceLoaderUtil {
                         loadClassloaderServices(serviceType, lookup, contextClassLoader, verbose));
             }
         }
+        if (OsgiServiceLocator.isAvailable()) {
+            services = Stream.concat(services, OsgiServiceLocator.loadServices(serviceType, lookup, verbose));
+        }
         final Set<Class<?>> classes = new HashSet<>();
         // only the first occurrence of a class
         return services.filter(service -> classes.add(service.getClass()));
diff --git a/log4j-api-java9/src/test/java/module-info.java b/log4j-api/src/test/java/org/apache/logging/log4j/util/OsgiServiceLocatorTest.java
similarity index 55%
rename from log4j-api-java9/src/test/java/module-info.java
rename to log4j-api/src/test/java/org/apache/logging/log4j/util/OsgiServiceLocatorTest.java
index 12c3735381..ba83fd03c4 100644
--- a/log4j-api-java9/src/test/java/module-info.java
+++ b/log4j-api/src/test/java/org/apache/logging/log4j/util/OsgiServiceLocatorTest.java
@@ -14,21 +14,26 @@
  * See the license for the specific language governing permissions and
  * limitations under the license.
  */
-import org.apache.logging.log4j.util.java9.test.BetterService;
-import org.apache.logging.log4j.util.java9.test.Service;
-import org.apache.logging.log4j.util.java9.test.Service1;
-import org.apache.logging.log4j.util.java9.test.Service2;
+package org.apache.logging.log4j.util;
 
-open module org.apache.logging.log4j.java9test {
-    exports org.apache.logging.log4j.util.java9;
+import java.lang.invoke.MethodHandles;
+import java.util.stream.Stream;
 
-    requires org.apache.logging.log4j;
-    requires transitive org.junit.jupiter.engine;
-    requires transitive org.junit.jupiter.api;
+import org.apache.logging.log4j.spi.Provider;
 
-    uses Service;
-    uses BetterService;
+public class OsgiServiceLocatorTest {
 
-    provides Service with Service1, Service2;
-    provides BetterService with Service2;
+    /**
+     * Used by OSGI {@link AbstractLoadBundleTest} to preserve caller
+     * sensitivity.
+     * 
+     * @return
+     */
+    public static Stream<Provider> loadProviders() {
+        return OsgiServiceLocator.loadServices(Provider.class, MethodHandles.lookup());
+    }
+
+    public static Stream<PropertySource> loadPropertySources() {
+        return OsgiServiceLocator.loadServices(PropertySource.class, MethodHandles.lookup());
+    }
 }
diff --git a/log4j-osgi/pom.xml b/log4j-osgi/pom.xml
index b13792f6d9..bc8eafe5ca 100644
--- a/log4j-osgi/pom.xml
+++ b/log4j-osgi/pom.xml
@@ -68,10 +68,6 @@
       <artifactId>commons-lang3</artifactId>
       <scope>test</scope>
     </dependency>
-    <dependency>
-      <groupId>org.glassfish.hk2</groupId>
-      <artifactId>osgi-resource-locator</artifactId>
-    </dependency>
   </dependencies>
   <build>
     <plugins>
diff --git a/log4j-osgi/src/test/java/org/apache/logging/log4j/osgi/tests/AbstractLoadBundleTest.java b/log4j-osgi/src/test/java/org/apache/logging/log4j/osgi/tests/AbstractLoadBundleTest.java
index fe5e9e6cc0..41dc963f6e 100644
--- a/log4j-osgi/src/test/java/org/apache/logging/log4j/osgi/tests/AbstractLoadBundleTest.java
+++ b/log4j-osgi/src/test/java/org/apache/logging/log4j/osgi/tests/AbstractLoadBundleTest.java
@@ -21,11 +21,8 @@ import static org.junit.Assert.assertTrue;
 
 import java.io.ByteArrayOutputStream;
 import java.io.PrintStream;
-import java.lang.invoke.MethodHandles;
-import java.lang.invoke.MethodHandles.Lookup;
 import java.lang.reflect.Field;
 import java.lang.reflect.Method;
-import java.net.URL;
 import java.nio.file.Path;
 import java.nio.file.Paths;
 import java.util.List;
@@ -34,12 +31,10 @@ import java.util.stream.Stream;
 
 import org.apache.logging.log4j.osgi.tests.junit.BundleTestInfo;
 import org.apache.logging.log4j.osgi.tests.junit.OsgiRule;
-import org.glassfish.hk2.osgiresourcelocator.ServiceLoader;
 import org.junit.Assert;
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
-import org.junit.jupiter.api.Disabled;
 import org.osgi.framework.Bundle;
 import org.osgi.framework.BundleContext;
 import org.osgi.framework.BundleException;
@@ -96,9 +91,11 @@ public abstract class AbstractLoadBundleTest {
         return bundleContext.installBundle(apiPath.toUri().toString());
     }
 
-    private Bundle getServiceLoaderBundle() throws BundleException {
-        final URL serviceLocatorUrl = ServiceLoader.class.getProtectionDomain().getCodeSource().getLocation();
-        return bundleContext.installBundle(serviceLocatorUrl.toString());
+    private Bundle getApiTestsBundle() throws BundleException {
+      final Path apiTestsPath = here.resolveSibling("log4j-api")
+          .resolve("target")
+          .resolve("log4j-api-" + bundleTestInfo.getVersion() + "-tests.jar");
+        return bundleContext.installBundle(apiTestsPath.toUri().toString());
     }
 
     protected abstract FrameworkFactory getFactory();
@@ -337,32 +334,30 @@ public abstract class AbstractLoadBundleTest {
      * @throws BundleException
      * @throws ReflectiveOperationException
      */
-    @Disabled("Until OSGI service detection is implemented correctly.")
+    @Test
     public void testServiceLoader() throws BundleException, ReflectiveOperationException {
         final Bundle api = getApiBundle();
         final Bundle core = getCoreBundle();
-        final Bundle serviceLoader = getServiceLoaderBundle();
-        assertEquals("serviceLoader is not in RESOLVED state", Bundle.INSTALLED, serviceLoader.getState());
+        final Bundle apiTests = getApiTestsBundle();
 
         api.start();
         core.start();
-        serviceLoader.start();
+        assertEquals("api-tests is not in RESOLVED state", Bundle.RESOLVED, apiTests.getState());
 
-        final Class<?> serviceLoaderUtil = api.loadClass("org.apache.logging.log4j.util.ServiceLoaderUtil");
-        final Class<?> provider = api.loadClass("org.apache.logging.log4j.spi.Provider");
-        final Method getService = serviceLoaderUtil.getDeclaredMethod("loadServices", Class.class, Lookup.class);
+        final Class<?> osgiServiceLocatorTest = api.loadClass("org.apache.logging.log4j.util.OsgiServiceLocatorTest");
 
-        final Object obj = getService.invoke(null, provider, MethodHandles.lookup());
-        assertEquals("serviceLoader is not in ACTIVE state", Bundle.ACTIVE, serviceLoader.getState());
+        final Method loadProviders = osgiServiceLocatorTest.getDeclaredMethod("loadProviders");
+        Object obj = loadProviders.invoke(null);
         assertTrue(obj instanceof Stream);
         @SuppressWarnings("unchecked")
-        final List<Object> services = ((Stream<Object>) obj).collect(Collectors.toList());
+        List<Object> services = ((Stream<Object>) obj).collect(Collectors.toList());
         assertEquals(1, services.size());
         assertEquals("org.apache.logging.log4j.core.impl.Log4jProvider", services.get(0).getClass().getName());
 
-        stop(core, api, serviceLoader);
-        assertEquals("serviceLoader is not in ACTIVE state", Bundle.RESOLVED, serviceLoader.getState());
-        uninstall(serviceLoader, api, core);
-        assertEquals("serviceLoader is not in ACTIVE state", Bundle.UNINSTALLED, serviceLoader.getState());
+        core.stop();
+        api.stop();
+        assertEquals("api-tests is not in ACTIVE state", Bundle.RESOLVED, apiTests.getState());
+        uninstall(apiTests, api, core);
+        assertEquals("api-tests is not in ACTIVE state", Bundle.UNINSTALLED, apiTests.getState());
     }
-}
+}
\ No newline at end of file
diff --git a/pom.xml b/pom.xml
index 11ccfd43da..12968c10ba 100644
--- a/pom.xml
+++ b/pom.xml
@@ -504,12 +504,6 @@
         <version>${osgi.api.version}</version>
         <scope>provided</scope>
       </dependency>
-      <dependency>
-        <groupId>org.glassfish.hk2</groupId>
-        <artifactId>osgi-resource-locator</artifactId>
-        <version>2.5.0-b42</version>
-        <scope>provided</scope>
-      </dependency>
       <dependency>
         <groupId>org.fusesource.jansi</groupId>
         <artifactId>jansi</artifactId>