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 2024/08/30 11:33:11 UTC

(logging-log4j2) 01/01: Improve GraalVM support of `SimpleLoggerContext`

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

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

commit ba15860b99b147a00e814f740568905abb7226d6
Author: Piotr P. Karwasz <pi...@karwasz.org>
AuthorDate: Fri Aug 30 13:32:49 2024 +0200

    Improve GraalVM support of `SimpleLoggerContext`
    
    This PR allows to create GraalVM applications that only use
    this `SimpleLoggerContext`.
    
    For this purpose we create a `SimpleProvider` implementation of `Provider`,
    which is **not** instantiated by reflection.
    
    Part of #2830
---
 .../logging/log4j/simple/SimpleLoggerContext.java  |  43 ++------
 .../logging/log4j/simple/SimpleProvider.java       | 114 +++++++++++++++++++++
 .../apache/logging/log4j/simple/package-info.java  |   2 +-
 .../apache/logging/log4j/util/ProviderUtil.java    |  41 ++++----
 4 files changed, 143 insertions(+), 57 deletions(-)

diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/simple/SimpleLoggerContext.java b/log4j-api/src/main/java/org/apache/logging/log4j/simple/SimpleLoggerContext.java
index b70a4c5e08..57782f04aa 100644
--- a/log4j-api/src/main/java/org/apache/logging/log4j/simple/SimpleLoggerContext.java
+++ b/log4j-api/src/main/java/org/apache/logging/log4j/simple/SimpleLoggerContext.java
@@ -17,8 +17,6 @@
 package org.apache.logging.log4j.simple;
 
 import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
-import java.io.FileNotFoundException;
-import java.io.FileOutputStream;
 import java.io.PrintStream;
 import org.apache.logging.log4j.Level;
 import org.apache.logging.log4j.message.MessageFactory;
@@ -36,10 +34,6 @@ public class SimpleLoggerContext implements LoggerContext {
     /** Singleton instance. */
     static final SimpleLoggerContext INSTANCE = new SimpleLoggerContext();
 
-    private static final String SYSTEM_OUT = "system.out";
-
-    private static final String SYSTEM_ERR = "system.err";
-
     /** The default format to use when formatting dates */
     protected static final String DEFAULT_DATE_TIME_FORMAT = "yyyy/MM/dd HH:mm:ss:SSS zzz";
 
@@ -79,34 +73,15 @@ public class SimpleLoggerContext implements LoggerContext {
             value = "PATH_TRAVERSAL_OUT",
             justification = "Opens a file retrieved from configuration (Log4j properties)")
     public SimpleLoggerContext() {
-        props = new PropertiesUtil("log4j2.simplelog.properties");
-
-        showContextMap = props.getBooleanProperty(SYSTEM_PREFIX + "showContextMap", false);
-        showLogName = props.getBooleanProperty(SYSTEM_PREFIX + "showlogname", false);
-        showShortName = props.getBooleanProperty(SYSTEM_PREFIX + "showShortLogname", true);
-        showDateTime = props.getBooleanProperty(SYSTEM_PREFIX + "showdatetime", false);
-        final String lvl = props.getStringProperty(SYSTEM_PREFIX + "level");
-        defaultLevel = Level.toLevel(lvl, Level.ERROR);
-
-        dateTimeFormat = showDateTime
-                ? props.getStringProperty(
-                        SimpleLoggerContext.SYSTEM_PREFIX + "dateTimeFormat", DEFAULT_DATE_TIME_FORMAT)
-                : null;
-
-        final String fileName = props.getStringProperty(SYSTEM_PREFIX + "logFile", SYSTEM_ERR);
-        PrintStream ps;
-        if (SYSTEM_ERR.equalsIgnoreCase(fileName)) {
-            ps = System.err;
-        } else if (SYSTEM_OUT.equalsIgnoreCase(fileName)) {
-            ps = System.out;
-        } else {
-            try {
-                ps = new PrintStream(new FileOutputStream(fileName));
-            } catch (final FileNotFoundException fnfe) {
-                ps = System.err;
-            }
-        }
-        this.stream = ps;
+        final SimpleProvider.Config config = SimpleProvider.Config.INSTANCE;
+        props = config.props;
+        showContextMap = config.showContextMap;
+        showLogName = config.showLogName;
+        showShortName = config.showShortName;
+        showDateTime = config.showDateTime;
+        defaultLevel = config.defaultLevel;
+        dateTimeFormat = config.dateTimeFormat;
+        stream = config.stream;
     }
 
     @Override
diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/simple/SimpleProvider.java b/log4j-api/src/main/java/org/apache/logging/log4j/simple/SimpleProvider.java
new file mode 100644
index 0000000000..aa617acc35
--- /dev/null
+++ b/log4j-api/src/main/java/org/apache/logging/log4j/simple/SimpleProvider.java
@@ -0,0 +1,114 @@
+/*
+ * 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.simple;
+
+import static org.apache.logging.log4j.simple.SimpleLoggerContext.DEFAULT_DATE_TIME_FORMAT;
+import static org.apache.logging.log4j.simple.SimpleLoggerContext.SYSTEM_PREFIX;
+
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.PrintStream;
+import org.apache.logging.log4j.Level;
+import org.apache.logging.log4j.spi.LoggerContextFactory;
+import org.apache.logging.log4j.spi.NoOpThreadContextMap;
+import org.apache.logging.log4j.spi.Provider;
+import org.apache.logging.log4j.spi.ThreadContextMap;
+import org.apache.logging.log4j.util.PropertiesUtil;
+import org.jspecify.annotations.NullMarked;
+import org.jspecify.annotations.Nullable;
+
+/**
+ * A {@link Provider} implementation to use {@link SimpleLoggerContext}.
+ *
+ * @since 2.24.0
+ */
+@NullMarked
+public final class SimpleProvider extends Provider {
+
+    private final ThreadContextMap threadContextMap;
+
+    public SimpleProvider() {
+        super(null, CURRENT_VERSION);
+        this.threadContextMap =
+                Config.INSTANCE.showContextMap ? super.getThreadContextMapInstance() : NoOpThreadContextMap.INSTANCE;
+    }
+
+    @Override
+    public LoggerContextFactory getLoggerContextFactory() {
+        return SimpleLoggerContextFactory.INSTANCE;
+    }
+
+    @Override
+    public ThreadContextMap getThreadContextMapInstance() {
+        return threadContextMap;
+    }
+
+    static final class Config {
+
+        private static final String SYSTEM_OUT = "system.out";
+
+        private static final String SYSTEM_ERR = "system.err";
+
+        static final Config INSTANCE = new Config();
+
+        final PropertiesUtil props;
+
+        final boolean showContextMap;
+
+        final boolean showLogName;
+
+        final boolean showShortName;
+
+        final boolean showDateTime;
+
+        final Level defaultLevel;
+
+        final @Nullable String dateTimeFormat;
+
+        final PrintStream stream;
+
+        private Config() {
+            props = new PropertiesUtil("log4j2.simplelog.properties");
+
+            showContextMap = props.getBooleanProperty(SYSTEM_PREFIX + "showContextMap", false);
+            showLogName = props.getBooleanProperty(SYSTEM_PREFIX + "showlogname", false);
+            showShortName = props.getBooleanProperty(SYSTEM_PREFIX + "showShortLogname", true);
+            showDateTime = props.getBooleanProperty(SYSTEM_PREFIX + "showdatetime", false);
+            final String lvl = props.getStringProperty(SYSTEM_PREFIX + "level");
+            defaultLevel = Level.toLevel(lvl, Level.ERROR);
+
+            dateTimeFormat = showDateTime
+                    ? props.getStringProperty(SYSTEM_PREFIX + "dateTimeFormat", DEFAULT_DATE_TIME_FORMAT)
+                    : null;
+
+            final String fileName = props.getStringProperty(SYSTEM_PREFIX + "logFile", SYSTEM_ERR);
+            PrintStream ps;
+            if (SYSTEM_ERR.equalsIgnoreCase(fileName)) {
+                ps = System.err;
+            } else if (SYSTEM_OUT.equalsIgnoreCase(fileName)) {
+                ps = System.out;
+            } else {
+                try {
+                    ps = new PrintStream(new FileOutputStream(fileName));
+                } catch (final FileNotFoundException fnfe) {
+                    ps = System.err;
+                }
+            }
+            this.stream = ps;
+        }
+    }
+}
diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/simple/package-info.java b/log4j-api/src/main/java/org/apache/logging/log4j/simple/package-info.java
index 15801ad9d7..1481df9462 100644
--- a/log4j-api/src/main/java/org/apache/logging/log4j/simple/package-info.java
+++ b/log4j-api/src/main/java/org/apache/logging/log4j/simple/package-info.java
@@ -20,7 +20,7 @@
  * Providers are able to be loaded at runtime.
  */
 @Export
-@Version("2.20.2")
+@Version("2.24.0")
 package org.apache.logging.log4j.simple;
 
 import org.osgi.annotation.bundle.Export;
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 c02526b4a0..14740b6818 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,7 +16,6 @@
  */
 package org.apache.logging.log4j.util;
 
-import static org.apache.logging.log4j.LogManager.FACTORY_PROPERTY_NAME;
 import static org.apache.logging.log4j.spi.Provider.PROVIDER_PROPERTY_NAME;
 
 import aQute.bnd.annotation.Cardinality;
@@ -34,10 +33,10 @@ import java.util.ServiceLoader;
 import java.util.concurrent.locks.Lock;
 import java.util.concurrent.locks.ReentrantLock;
 import java.util.stream.Collectors;
+import org.apache.logging.log4j.LogManager;
 import org.apache.logging.log4j.Logger;
-import org.apache.logging.log4j.simple.SimpleLoggerContextFactory;
+import org.apache.logging.log4j.simple.SimpleProvider;
 import org.apache.logging.log4j.spi.LoggerContextFactory;
-import org.apache.logging.log4j.spi.NoOpThreadContextMap;
 import org.apache.logging.log4j.spi.Provider;
 import org.apache.logging.log4j.status.StatusLogger;
 
@@ -72,12 +71,10 @@ public final class ProviderUtil {
      */
     static final Lock STARTUP_LOCK = new ReentrantLock();
 
-    private static final String API_VERSION = "Log4jAPIVersion";
     private static final String[] COMPATIBLE_API_VERSIONS = {"2.6.0"};
     private static final Logger LOGGER = StatusLogger.getLogger();
 
     private static volatile Provider PROVIDER;
-    private static final Provider FALLBACK_PROVIDER = new SimpleProvider();
 
     private ProviderUtil() {}
 
@@ -100,6 +97,7 @@ public final class ProviderUtil {
     @SuppressFBWarnings(
             value = "URLCONNECTION_SSRF_FD",
             justification = "Uses a fixed URL that ends in 'META-INF/log4j-provider.properties'.")
+    @SuppressWarnings("deprecation")
     static void loadProvider(final URL url, final ClassLoader cl) {
         try {
             final Properties props = PropertiesUtil.loadClose(url.openStream(), url);
@@ -178,32 +176,37 @@ public final class ProviderUtil {
     /**
      * Used to test the public {@link #getProvider()} method.
      */
+    @SuppressWarnings("deprecation")
     static Provider selectProvider(
             final PropertiesUtil properties, final Collection<Provider> providers, final Logger statusLogger) {
         Provider selected = null;
         // 1. Select provider using "log4j.provider" property
         final String providerClass = properties.getStringProperty(PROVIDER_PROPERTY_NAME);
         if (providerClass != null) {
-            try {
-                selected = LoaderUtil.newInstanceOf(providerClass);
-            } catch (final Exception e) {
-                statusLogger.error(
-                        "Unable to create provider {}.\nFalling back to default selection process.", PROVIDER, e);
+            if (SimpleProvider.class.getName().equals(providerClass)) {
+                selected = new SimpleProvider();
+            } else {
+                try {
+                    selected = LoaderUtil.newInstanceOf(providerClass);
+                } catch (final Exception e) {
+                    statusLogger.error(
+                            "Unable to create provider {}.\nFalling back to default selection process.", PROVIDER, e);
+                }
             }
         }
         // 2. Use deprecated "log4j2.loggerContextFactory" property to choose the provider
-        final String factoryClassName = properties.getStringProperty(FACTORY_PROPERTY_NAME);
+        final String factoryClassName = properties.getStringProperty(LogManager.FACTORY_PROPERTY_NAME);
         if (factoryClassName != null) {
             if (selected != null) {
                 statusLogger.warn(
                         "Ignoring {} system property, since {} was set.",
-                        FACTORY_PROPERTY_NAME,
+                        LogManager.FACTORY_PROPERTY_NAME,
                         PROVIDER_PROPERTY_NAME);
                 // 2a. Scan the known providers for one matching the logger context factory class name.
             } else {
                 statusLogger.warn(
                         "Usage of the {} property is deprecated. Use the {} property instead.",
-                        FACTORY_PROPERTY_NAME,
+                        LogManager.FACTORY_PROPERTY_NAME,
                         PROVIDER_PROPERTY_NAME);
                 for (final Provider provider : providers) {
                     if (factoryClassName.equals(provider.getClassName())) {
@@ -225,14 +228,14 @@ public final class ProviderUtil {
                         statusLogger.error(
                                 "Class {} specified in the {} system property does not extend {}",
                                 factoryClassName,
-                                FACTORY_PROPERTY_NAME,
+                                LogManager.FACTORY_PROPERTY_NAME,
                                 LoggerContextFactory.class.getName());
                     }
                 } catch (final Exception e) {
                     statusLogger.error(
                             "Unable to create class {} specified in the {} system property",
                             factoryClassName,
-                            FACTORY_PROPERTY_NAME,
+                            LogManager.FACTORY_PROPERTY_NAME,
                             e);
                 }
             }
@@ -253,7 +256,7 @@ public final class ProviderUtil {
                             .collect(Collectors.joining("\n", "Log4j API found multiple logging providers:\n", "")));
                     break;
             }
-            selected = providers.stream().max(comparator).orElse(FALLBACK_PROVIDER);
+            selected = providers.stream().max(comparator).orElseGet(SimpleProvider::new);
         }
         statusLogger.info("Using provider:\n{}", selected);
         return selected;
@@ -271,10 +274,4 @@ public final class ProviderUtil {
         }
         return false;
     }
-
-    private static final class SimpleProvider extends Provider {
-        private SimpleProvider() {
-            super(null, CURRENT_VERSION, SimpleLoggerContextFactory.class, NoOpThreadContextMap.class);
-        }
-    }
 }