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/06/18 08:14:50 UTC

[logging-log4j2] branch master updated: Cherry-pick the `ServiceLoaderUtil` changes from `release-2.x`

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

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


The following commit(s) were added to refs/heads/master by this push:
     new c02b699bba Cherry-pick the `ServiceLoaderUtil` changes from `release-2.x`
c02b699bba is described below

commit c02b699bbaed4c889ff38cf9a21f4fd43a87391f
Author: Piotr P. Karwasz <pi...@karwasz.org>
AuthorDate: Sat Jun 18 10:14:41 2022 +0200

    Cherry-pick the `ServiceLoaderUtil` changes from `release-2.x`
---
 .../logging/log4j/ServiceLoaderUtilTest.java       |  69 +++++++++++++
 .../apache/logging/log4j/test/BetterService.java   |  21 ++++
 .../org/apache/logging/log4j/test/Service.java     |  21 ++++
 .../org/apache/logging/log4j/test/Service1.java    |  21 ++++
 .../org/apache/logging/log4j/test/Service2.java    |  21 ++++
 .../apache/logging/log4j/util/LoaderUtilTest.java  |   4 +-
 ...rg.apache.logging.log4j.util.test.BetterService |  16 +++
 .../org.apache.logging.log4j.util.test.Service     |  26 +++++
 .../logging/log4j/message/ThreadDumpMessage.java   |  14 +--
 .../org/apache/logging/log4j/util/LoaderUtil.java  |   8 +-
 .../apache/logging/log4j/util/PropertiesUtil.java  |  13 ++-
 .../log4j/util/PropertyFilePropertySource.java     |  10 +-
 .../apache/logging/log4j/util/ProviderUtil.java    |  15 ++-
 .../logging/log4j/util/ServiceLoaderUtil.java      | 113 ++++++++++++++-------
 .../apache/logging/log4j/util/ServiceRegistry.java |  32 +++---
 .../log4j/core/config/AbstractConfiguration.java   |  43 ++++----
 .../log4j/core/impl/ThreadContextDataInjector.java |  20 ++--
 .../logging/log4j/core/util/WatchManager.java      |  22 ++--
 .../logging/log4j/plugins/di/DefaultInjector.java  |   3 +-
 19 files changed, 371 insertions(+), 121 deletions(-)

diff --git a/log4j-api-test/src/test/java/org/apache/logging/log4j/ServiceLoaderUtilTest.java b/log4j-api-test/src/test/java/org/apache/logging/log4j/ServiceLoaderUtilTest.java
new file mode 100644
index 0000000000..a40121a6dd
--- /dev/null
+++ b/log4j-api-test/src/test/java/org/apache/logging/log4j/ServiceLoaderUtilTest.java
@@ -0,0 +1,69 @@
+/*
+ * 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;
+
+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.test.BetterService;
+import org.apache.logging.log4j.test.Service;
+import org.apache.logging.log4j.util.PropertySource;
+import org.apache.logging.log4j.util.ServiceLoaderUtil;
+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");
+        }
+    }
+}
diff --git a/log4j-api-test/src/test/java/org/apache/logging/log4j/test/BetterService.java b/log4j-api-test/src/test/java/org/apache/logging/log4j/test/BetterService.java
new file mode 100644
index 0000000000..ca8b893e0e
--- /dev/null
+++ b/log4j-api-test/src/test/java/org/apache/logging/log4j/test/BetterService.java
@@ -0,0 +1,21 @@
+/*
+ * 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.test;
+
+public interface BetterService extends Service {
+}
\ No newline at end of file
diff --git a/log4j-api-test/src/test/java/org/apache/logging/log4j/test/Service.java b/log4j-api-test/src/test/java/org/apache/logging/log4j/test/Service.java
new file mode 100644
index 0000000000..25b1b33681
--- /dev/null
+++ b/log4j-api-test/src/test/java/org/apache/logging/log4j/test/Service.java
@@ -0,0 +1,21 @@
+/*
+ * 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.test;
+
+public interface Service {
+}
\ No newline at end of file
diff --git a/log4j-api-test/src/test/java/org/apache/logging/log4j/test/Service1.java b/log4j-api-test/src/test/java/org/apache/logging/log4j/test/Service1.java
new file mode 100644
index 0000000000..82c233b852
--- /dev/null
+++ b/log4j-api-test/src/test/java/org/apache/logging/log4j/test/Service1.java
@@ -0,0 +1,21 @@
+/*
+ * 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.test;
+
+public class Service1 implements Service {
+}
\ No newline at end of file
diff --git a/log4j-api-test/src/test/java/org/apache/logging/log4j/test/Service2.java b/log4j-api-test/src/test/java/org/apache/logging/log4j/test/Service2.java
new file mode 100644
index 0000000000..c4a8c39b97
--- /dev/null
+++ b/log4j-api-test/src/test/java/org/apache/logging/log4j/test/Service2.java
@@ -0,0 +1,21 @@
+/*
+ * 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.test;
+
+public class Service2 implements BetterService {
+}
\ No newline at end of file
diff --git a/log4j-api-test/src/test/java/org/apache/logging/log4j/util/LoaderUtilTest.java b/log4j-api-test/src/test/java/org/apache/logging/log4j/util/LoaderUtilTest.java
index fb4dd7c256..675c54f33e 100644
--- a/log4j-api-test/src/test/java/org/apache/logging/log4j/util/LoaderUtilTest.java
+++ b/log4j-api-test/src/test/java/org/apache/logging/log4j/util/LoaderUtilTest.java
@@ -49,10 +49,10 @@ public class LoaderUtilTest {
         };
         thread.setContextClassLoader(loader);
         try {
-            assertEquals(0, LoaderUtil.findUrlResources("Log4j-charsets.properties").size());
+            assertEquals(0, LoaderUtil.findUrlResources("Log4j-charsets.properties", false).size());
 
             LoaderUtil.forceTcclOnly = false;
-            assertEquals(1, LoaderUtil.findUrlResources("Log4j-charsets.properties").size());
+            assertEquals(1, LoaderUtil.findUrlResources("Log4j-charsets.properties", false).size());
         } finally {
             thread.setContextClassLoader(tccl);
         }
diff --git a/log4j-api-test/src/test/resources/META-INF/services/org.apache.logging.log4j.util.test.BetterService b/log4j-api-test/src/test/resources/META-INF/services/org.apache.logging.log4j.util.test.BetterService
new file mode 100644
index 0000000000..69b68983a6
--- /dev/null
+++ b/log4j-api-test/src/test/resources/META-INF/services/org.apache.logging.log4j.util.test.BetterService
@@ -0,0 +1,16 @@
+# 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.
+
+org.apache.logging.log4j.util.test.Service2
diff --git a/log4j-api-test/src/test/resources/META-INF/services/org.apache.logging.log4j.util.test.Service b/log4j-api-test/src/test/resources/META-INF/services/org.apache.logging.log4j.util.test.Service
new file mode 100644
index 0000000000..5fde1d5351
--- /dev/null
+++ b/log4j-api-test/src/test/resources/META-INF/services/org.apache.logging.log4j.util.test.Service
@@ -0,0 +1,26 @@
+# 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.
+
+# A correct entry
+org.apache.logging.log4j.util.test.Service1
+
+# Simulates a service retrieved from the server's classloader in a servlet environment.
+java.lang.String
+
+# Simulates a broken service.
+invalid.Service
+
+# Another correct service
+org.apache.logging.log4j.util.test.Service2
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 6b198f2688..07c35b2dbd 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
@@ -16,19 +16,19 @@
  */
 package org.apache.logging.log4j.message;
 
-import org.apache.logging.log4j.util.LazyValue;
-import org.apache.logging.log4j.util.ServiceRegistry;
-import org.apache.logging.log4j.util.StringBuilderFormattable;
-import org.apache.logging.log4j.util.Strings;
-
 import java.io.InvalidObjectException;
 import java.io.ObjectInputStream;
 import java.io.Serializable;
+import java.lang.invoke.MethodHandles;
 import java.util.HashMap;
 import java.util.Map;
-import java.util.ServiceLoader;
 import java.util.function.Supplier;
 
+import org.apache.logging.log4j.util.LazyValue;
+import org.apache.logging.log4j.util.ServiceRegistry;
+import org.apache.logging.log4j.util.StringBuilderFormattable;
+import org.apache.logging.log4j.util.Strings;
+
 /**
  * Captures information about all running Threads.
  */
@@ -37,7 +37,7 @@ public class ThreadDumpMessage implements Message, StringBuilderFormattable {
     private static final long serialVersionUID = -1103400781608841088L;
     private static final Supplier<ThreadInfoFactory> FACTORY = new LazyValue<>(() -> {
         final var services = ServiceRegistry.getInstance()
-                .getServices(ThreadInfoFactory.class, layer -> ServiceLoader.load(layer, ThreadInfoFactory.class), null);
+                .getServices(ThreadInfoFactory.class, MethodHandles.lookup(), null);
         return services.isEmpty() ? new BasicThreadInfoFactory() : services.get(0);
     });
 
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 9974a8e2f1..3c5e646c10 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
@@ -353,7 +353,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());
@@ -368,7 +372,7 @@ public final class LoaderUtil {
      * @param resource The resource to locate.
      * @return The located resources.
      */
-    static Collection<UrlResource> findUrlResources(final String resource) {
+    static Collection<UrlResource> findUrlResources(final String resource, boolean useTccl) {
         // @formatter:off
         final ClassLoader[] candidates = {
                 getThreadContextClassLoader(),
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 453f72354c..27f4486a92 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,12 +18,13 @@ package org.apache.logging.log4j.util;
 
 import java.io.IOException;
 import java.io.InputStream;
+import java.lang.invoke.MethodHandles;
+import java.lang.invoke.MethodHandles.Lookup;
 import java.nio.charset.Charset;
 import java.time.Duration;
 import java.time.temporal.ChronoUnit;
 import java.time.temporal.TemporalUnit;
 import java.util.ArrayList;
-import java.util.Collections;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
@@ -73,7 +74,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));
     }
 
     /**
@@ -455,9 +460,7 @@ public final class PropertiesUtil {
             }
             sources.add(propertySource);
             final ServiceRegistry registry = ServiceRegistry.getInstance();
-            sources.addAll(registry.getServices(PropertySource.class,
-                    layer -> ServiceLoader.load(layer, PropertySource.class),
-                    null));
+            sources.addAll(registry.getServices(PropertySource.class, MethodHandles.lookup(), null));
             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 bb8ccd7319..318011524a 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 08eec62db6..3c103c7c57 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
@@ -16,19 +16,19 @@
  */
 package org.apache.logging.log4j.util;
 
-import org.apache.logging.log4j.Logger;
-import org.apache.logging.log4j.spi.Provider;
-import org.apache.logging.log4j.status.StatusLogger;
-
 import java.io.IOException;
+import java.lang.invoke.MethodHandles;
 import java.net.URL;
 import java.util.Collection;
 import java.util.HashSet;
 import java.util.Properties;
-import java.util.ServiceLoader;
 import java.util.concurrent.locks.Lock;
 import java.util.concurrent.locks.ReentrantLock;
 
+import org.apache.logging.log4j.Logger;
+import org.apache.logging.log4j.spi.Provider;
+import org.apache.logging.log4j.status.StatusLogger;
+
 /**
  * <em>Consider this class private.</em> Utility class for Log4j {@link Provider}s. When integrating with an application
  * container framework, any Log4j Providers not accessible through standard classpath scanning should
@@ -63,9 +63,8 @@ public final class ProviderUtil {
 
     private ProviderUtil() {
         PROVIDERS.addAll(ServiceRegistry.getInstance()
-                .getServices(Provider.class, layer -> ServiceLoader.load(layer, Provider.class),
-                        provider -> validVersion(provider.getVersions())));
-        for (final LoaderUtil.UrlResource resource : LoaderUtil.findUrlResources(PROVIDER_RESOURCE)) {
+                .getServices(Provider.class, MethodHandles.lookup(), provider -> validVersion(provider.getVersions())));
+        for (final LoaderUtil.UrlResource resource : LoaderUtil.findUrlResources(PROVIDER_RESOURCE, false)) {
             loadProvider(resource.getUrl(), resource.getClassLoader());
         }
     }
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 33ac8762a1..32a2790b62 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
@@ -16,51 +16,96 @@
  */
 package org.apache.logging.log4j.util;
 
-import java.util.ArrayList;
-import java.util.List;
+import java.lang.invoke.MethodHandle;
+import java.lang.invoke.MethodHandles.Lookup;
+import java.lang.invoke.MethodType;
+import java.util.HashSet;
+import java.util.Objects;
+import java.util.ServiceConfigurationError;
 import java.util.ServiceLoader;
-import java.util.function.Function;
-import java.util.function.Predicate;
+import java.util.Set;
+import java.util.stream.Stream;
+
+import org.apache.logging.log4j.status.StatusLogger;
 
 /**
  * Loads all valid instances of a service.
  */
 public class ServiceLoaderUtil {
 
-    public static <S> List<S> loadServices(final Class<S> clazz, final Function<ModuleLayer, ServiceLoader<S>> loader,
-            final Predicate<S> validator) {
-        final List<S> services = new ArrayList<>();
-        final ModuleLayer moduleLayer = ServiceLoaderUtil.class.getModule().getLayer();
-        if (moduleLayer == null) {
-            final ClassLoader[] classLoaders = LoaderUtil.getClassLoaders();
-            Throwable throwable = null;
-            ClassLoader errorClassLoader = null;
-            for (ClassLoader classLoader : classLoaders) {
+    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 {
-                    final ServiceLoader<S> serviceLoader = ServiceLoader.load(clazz, classLoader);
-                    for (final S service : serviceLoader) {
-                        if (!services.contains(service) && (validator == null || validator.test(service))) {
-                            services.add(service);
-                        }
-                    }
-                } catch (final Throwable ex) {
-                    if (throwable == null) {
-                        throwable = ex;
-                        errorClassLoader = classLoader;
+                    return provider.get();
+                } catch (ServiceConfigurationError e) {
+                    if (verbose) {
+                        StatusLogger.getLogger().warn("Unable to load service class for service {}", serviceType, e);
                     }
                 }
-            }
-            if (services.size() == 0 && throwable != null) {
-                LowLevelLogUtil.logException("Unable to retrieve provider from ClassLoader " + errorClassLoader, throwable);
-            }
-        } else {
-            final ServiceLoader<S> serviceLoader = loader.apply(moduleLayer);
-            for (final S service : serviceLoader) {
-                if (!services.contains(service) && (validator == null || validator.test(service))) {
-                    services.add(service);
-                }
+                return null;
+            }).filter(Objects::nonNull);
+        } catch (Throwable e) {
+            if (verbose) {
+                StatusLogger.getLogger().error("Unable to load services for service {}", serviceType, e);
             }
         }
-        return services;
+        return Stream.empty();
     }
+
 }
diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/util/ServiceRegistry.java b/log4j-api/src/main/java/org/apache/logging/log4j/util/ServiceRegistry.java
index 83a40fdb73..23f55b6b16 100644
--- a/log4j-api/src/main/java/org/apache/logging/log4j/util/ServiceRegistry.java
+++ b/log4j-api/src/main/java/org/apache/logging/log4j/util/ServiceRegistry.java
@@ -17,12 +17,12 @@
 
 package org.apache.logging.log4j.util;
 
+import java.lang.invoke.MethodHandles.Lookup;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Map;
 import java.util.ServiceLoader;
 import java.util.concurrent.ConcurrentHashMap;
-import java.util.function.Function;
 import java.util.function.Predicate;
 import java.util.function.Supplier;
 import java.util.stream.Collectors;
@@ -56,27 +56,27 @@ public class ServiceRegistry {
     }
 
     /**
-     * Gets service instances loaded from the calling context and any previously registered bundle services.
-     * The {@code loaderCallerContext} parameter is provided to ensure the caller can specify which calling
-     * function context to load services.
+     * Gets service instances loaded from the calling context and any previously registered bundle services. The {@code loaderCallerContext} parameter is
+     * provided to ensure the caller can specify which calling function context to
+     * load services.
      *
      * @param serviceType         service class
-     * @param loaderCallerContext function located in same module as caller context to use for loading services
-     * @param validator           if non-null, used to validate service instances, removing invalid instances from the returned list
+     * @param loaderCallerContext function located in same module as caller context
+     *                            to use for loading services
+     * @param validator           if non-null, used to validate service instances,
+     *                            removing invalid instances from the returned list
      * @param <S>                 type of service
      * @return loaded service instances
      */
-    public <S> List<S> getServices(
-            final Class<S> serviceType, final Function<ModuleLayer, ServiceLoader<S>> loaderCallerContext,
-            final Predicate<S> validator) {
+    public <S> List<S> getServices(final Class<S> serviceType, final Lookup lookup, final Predicate<S> validator) {
         final List<S> services = cast(mainServices.computeIfAbsent(serviceType,
-                ignored -> ServiceLoaderUtil.loadServices(serviceType, loaderCallerContext, validator)));
-        return Stream.concat(services.stream(), bundleServices.values().stream()
-                        .flatMap(map -> {
-                            final Stream<S> stream = map.getOrDefault(serviceType, List.of()).stream().map(serviceType::cast);
-                            return validator != null ? stream.filter(validator) : stream;
-                        }))
-                .distinct().collect(Collectors.toCollection(ArrayList::new));
+                ignored -> ServiceLoaderUtil.loadServices(serviceType, lookup)
+                        .filter(validator != null ? validator : unused -> true)
+                        .collect(Collectors.toList())));
+        return Stream.concat(services.stream(), bundleServices.values().stream().flatMap(map -> {
+            final Stream<S> stream = map.getOrDefault(serviceType, List.of()).stream().map(serviceType::cast);
+            return validator != null ? stream.filter(validator) : stream;
+        })).distinct().collect(Collectors.toCollection(ArrayList::new));
     }
 
     /**
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/AbstractConfiguration.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/AbstractConfiguration.java
index 692fa0e288..8c3adced80 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/AbstractConfiguration.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/AbstractConfiguration.java
@@ -16,6 +16,26 @@
  */
 package org.apache.logging.log4j.core.config;
 
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.Serializable;
+import java.lang.invoke.MethodHandles;
+import java.lang.ref.WeakReference;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.concurrent.TimeUnit;
+import java.util.function.Function;
+import java.util.function.Supplier;
+
 import org.apache.logging.log4j.Level;
 import org.apache.logging.log4j.core.Appender;
 import org.apache.logging.log4j.core.Core;
@@ -65,26 +85,6 @@ import org.apache.logging.log4j.util.NameUtil;
 import org.apache.logging.log4j.util.PropertiesUtil;
 import org.apache.logging.log4j.util.ServiceRegistry;
 
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.Serializable;
-import java.lang.ref.WeakReference;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Objects;
-import java.util.ServiceLoader;
-import java.util.Set;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.ConcurrentMap;
-import java.util.concurrent.CopyOnWriteArrayList;
-import java.util.concurrent.TimeUnit;
-import java.util.function.Function;
-import java.util.function.Supplier;
-
 /**
  * The base Configuration. Many configuration implementations will extend this class.
  */
@@ -271,8 +271,7 @@ public abstract class AbstractConfiguration extends AbstractFilterable implement
     private void initializeScriptManager() {
         try {
             ServiceRegistry.getInstance()
-                    .getServices(ScriptManagerFactory.class, layer -> ServiceLoader.load(layer, ScriptManagerFactory.class),
-                            null)
+                    .getServices(ScriptManagerFactory.class, MethodHandles.lookup(), null)
                     .stream()
                     .findFirst()
                     .ifPresent(factory -> setScriptManager(factory.createScriptManager(this, getWatchManager())));
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 43712fd281..9fc5dbcee8 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,6 +16,15 @@
  */
 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.concurrent.ConcurrentLinkedDeque;
+
 import org.apache.logging.log4j.ThreadContext;
 import org.apache.logging.log4j.core.ContextDataInjector;
 import org.apache.logging.log4j.core.config.Property;
@@ -25,15 +34,6 @@ import org.apache.logging.log4j.util.ReadOnlyStringMap;
 import org.apache.logging.log4j.util.ServiceRegistry;
 import org.apache.logging.log4j.util.StringMap;
 
-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;
-
 /**
  * {@code ThreadContextDataInjector} contains a number of strategies for copying key-value pairs from the various
  * {@code ThreadContext} map implementations into a {@code StringMap}. In the case of duplicate keys,
@@ -71,7 +71,7 @@ public class ThreadContextDataInjector {
     private static List<ContextDataProvider> getServiceProviders() {
         final List<ContextDataProvider> providers = new ArrayList<>();
         final List<ContextDataProvider> services = ServiceRegistry.getInstance()
-                .getServices(ContextDataProvider.class, layer -> ServiceLoader.load(layer, ContextDataProvider.class), null);
+                .getServices(ContextDataProvider.class, MethodHandles.lookup(), null);
         for (final ContextDataProvider provider : services) {
             if (providers.stream().noneMatch((p) -> p.getClass().isAssignableFrom(provider.getClass()))) {
                 providers.add(provider);
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 960f246a43..457da9181d 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
@@ -16,22 +16,13 @@
  */
 package org.apache.logging.log4j.core.util;
 
-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.plugins.Inject;
-import org.apache.logging.log4j.plugins.Singleton;
-import org.apache.logging.log4j.status.StatusLogger;
-import org.apache.logging.log4j.util.ServiceRegistry;
-
 import java.io.File;
+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;
@@ -39,6 +30,15 @@ import java.util.concurrent.ScheduledFuture;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicInteger;
 
+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.plugins.Inject;
+import org.apache.logging.log4j.plugins.Singleton;
+import org.apache.logging.log4j.status.StatusLogger;
+import org.apache.logging.log4j.util.ServiceRegistry;
+
 /**
  * Manages {@link FileWatcher}s.
  *
@@ -138,7 +138,7 @@ public class WatchManager extends AbstractLifeCycle {
     public WatchManager(final ConfigurationScheduler scheduler) {
         this.scheduler = Objects.requireNonNull(scheduler, "scheduler");
         eventServiceList = ServiceRegistry.getInstance()
-                .getServices(WatchEventService.class, layer -> ServiceLoader.load(layer, WatchEventService.class), null);
+                .getServices(WatchEventService.class, MethodHandles.lookup(), null);
     }
 
     public void checkFiles() {
diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/di/DefaultInjector.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/di/DefaultInjector.java
index 13a0c72e01..aee5cf183c 100644
--- a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/di/DefaultInjector.java
+++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/di/DefaultInjector.java
@@ -45,6 +45,7 @@ import org.apache.logging.log4j.util.ServiceRegistry;
 import org.apache.logging.log4j.util.StringBuilders;
 
 import java.lang.annotation.Annotation;
+import java.lang.invoke.MethodHandles;
 import java.lang.reflect.AccessibleObject;
 import java.lang.reflect.AnnotatedElement;
 import java.lang.reflect.Constructor;
@@ -100,7 +101,7 @@ class DefaultInjector implements Injector {
     @Override
     public void init() {
         final List<InjectorCallback> callbacks = ServiceRegistry.getInstance()
-                .getServices(InjectorCallback.class, layer -> ServiceLoader.load(layer, InjectorCallback.class), null);
+                .getServices(InjectorCallback.class, MethodHandles.lookup(), null);
         callbacks.sort(InjectorCallback.COMPARATOR);
         for (final InjectorCallback callback : callbacks) {
             try {