Skip to content

Commit 56313a1

Browse files
committed
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
1 parent c696ce2 commit 56313a1

File tree

4 files changed

+150
-58
lines changed

4 files changed

+150
-58
lines changed

log4j-api/src/main/java/org/apache/logging/log4j/simple/SimpleLoggerContext.java

Lines changed: 10 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,10 @@
1717
package org.apache.logging.log4j.simple;
1818

1919
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
20-
import java.io.FileNotFoundException;
21-
import java.io.FileOutputStream;
2220
import java.io.PrintStream;
2321
import org.apache.logging.log4j.Level;
2422
import org.apache.logging.log4j.message.MessageFactory;
23+
import org.apache.logging.log4j.simple.internal.SimpleProvider;
2524
import org.apache.logging.log4j.spi.AbstractLogger;
2625
import org.apache.logging.log4j.spi.ExtendedLogger;
2726
import org.apache.logging.log4j.spi.LoggerContext;
@@ -36,10 +35,6 @@ public class SimpleLoggerContext implements LoggerContext {
3635
/** Singleton instance. */
3736
static final SimpleLoggerContext INSTANCE = new SimpleLoggerContext();
3837

39-
private static final String SYSTEM_OUT = "system.out";
40-
41-
private static final String SYSTEM_ERR = "system.err";
42-
4338
/** The default format to use when formatting dates */
4439
protected static final String DEFAULT_DATE_TIME_FORMAT = "yyyy/MM/dd HH:mm:ss:SSS zzz";
4540

@@ -79,34 +74,15 @@ public class SimpleLoggerContext implements LoggerContext {
7974
value = "PATH_TRAVERSAL_OUT",
8075
justification = "Opens a file retrieved from configuration (Log4j properties)")
8176
public SimpleLoggerContext() {
82-
props = new PropertiesUtil("log4j2.simplelog.properties");
83-
84-
showContextMap = props.getBooleanProperty(SYSTEM_PREFIX + "showContextMap", false);
85-
showLogName = props.getBooleanProperty(SYSTEM_PREFIX + "showlogname", false);
86-
showShortName = props.getBooleanProperty(SYSTEM_PREFIX + "showShortLogname", true);
87-
showDateTime = props.getBooleanProperty(SYSTEM_PREFIX + "showdatetime", false);
88-
final String lvl = props.getStringProperty(SYSTEM_PREFIX + "level");
89-
defaultLevel = Level.toLevel(lvl, Level.ERROR);
90-
91-
dateTimeFormat = showDateTime
92-
? props.getStringProperty(
93-
SimpleLoggerContext.SYSTEM_PREFIX + "dateTimeFormat", DEFAULT_DATE_TIME_FORMAT)
94-
: null;
95-
96-
final String fileName = props.getStringProperty(SYSTEM_PREFIX + "logFile", SYSTEM_ERR);
97-
PrintStream ps;
98-
if (SYSTEM_ERR.equalsIgnoreCase(fileName)) {
99-
ps = System.err;
100-
} else if (SYSTEM_OUT.equalsIgnoreCase(fileName)) {
101-
ps = System.out;
102-
} else {
103-
try {
104-
ps = new PrintStream(new FileOutputStream(fileName));
105-
} catch (final FileNotFoundException fnfe) {
106-
ps = System.err;
107-
}
108-
}
109-
this.stream = ps;
77+
final SimpleProvider.Config config = SimpleProvider.Config.INSTANCE;
78+
props = config.props;
79+
showContextMap = config.showContextMap;
80+
showLogName = config.showLogName;
81+
showShortName = config.showShortName;
82+
showDateTime = config.showDateTime;
83+
defaultLevel = config.defaultLevel;
84+
dateTimeFormat = config.dateTimeFormat;
85+
stream = config.stream;
11086
}
11187

11288
@Override
Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one or more
3+
* contributor license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright ownership.
5+
* The ASF licenses this file to you under the Apache License, Version 2.0
6+
* (the "License"); you may not use this file except in compliance with
7+
* the License. You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
package org.apache.logging.log4j.simple.internal;
18+
19+
import java.io.FileNotFoundException;
20+
import java.io.FileOutputStream;
21+
import java.io.PrintStream;
22+
import org.apache.logging.log4j.Level;
23+
import org.apache.logging.log4j.simple.SimpleLoggerContext;
24+
import org.apache.logging.log4j.simple.SimpleLoggerContextFactory;
25+
import org.apache.logging.log4j.spi.LoggerContextFactory;
26+
import org.apache.logging.log4j.spi.NoOpThreadContextMap;
27+
import org.apache.logging.log4j.spi.Provider;
28+
import org.apache.logging.log4j.spi.ThreadContextMap;
29+
import org.apache.logging.log4j.util.PropertiesUtil;
30+
import org.jspecify.annotations.NullMarked;
31+
import org.jspecify.annotations.Nullable;
32+
33+
/**
34+
* A {@link Provider} implementation to use {@link SimpleLoggerContext}.
35+
*
36+
* @since 2.24.0
37+
*/
38+
@NullMarked
39+
public final class SimpleProvider extends Provider {
40+
41+
private final ThreadContextMap threadContextMap;
42+
43+
public SimpleProvider() {
44+
super(null, CURRENT_VERSION);
45+
this.threadContextMap =
46+
Config.INSTANCE.showContextMap ? super.getThreadContextMapInstance() : NoOpThreadContextMap.INSTANCE;
47+
}
48+
49+
@Override
50+
public LoggerContextFactory getLoggerContextFactory() {
51+
return SimpleLoggerContextFactory.INSTANCE;
52+
}
53+
54+
@Override
55+
public ThreadContextMap getThreadContextMapInstance() {
56+
return threadContextMap;
57+
}
58+
59+
public static final class Config {
60+
61+
/** The default format to use when formatting dates */
62+
private static final String DEFAULT_DATE_TIME_FORMAT = "yyyy/MM/dd HH:mm:ss:SSS zzz";
63+
64+
/** All system properties used by <code>SimpleLog</code> start with this */
65+
private static final String SYSTEM_PREFIX = "org.apache.logging.log4j.simplelog.";
66+
67+
private static final String SYSTEM_OUT = "system.out";
68+
69+
private static final String SYSTEM_ERR = "system.err";
70+
71+
public static final Config INSTANCE = new Config();
72+
73+
public final PropertiesUtil props;
74+
75+
public final boolean showContextMap;
76+
77+
public final boolean showLogName;
78+
79+
public final boolean showShortName;
80+
81+
public final boolean showDateTime;
82+
83+
public final Level defaultLevel;
84+
85+
public final @Nullable String dateTimeFormat;
86+
87+
public final PrintStream stream;
88+
89+
private Config() {
90+
props = new PropertiesUtil("log4j2.simplelog.properties");
91+
92+
showContextMap = props.getBooleanProperty(SYSTEM_PREFIX + "showContextMap", false);
93+
showLogName = props.getBooleanProperty(SYSTEM_PREFIX + "showlogname", false);
94+
showShortName = props.getBooleanProperty(SYSTEM_PREFIX + "showShortLogname", true);
95+
showDateTime = props.getBooleanProperty(SYSTEM_PREFIX + "showdatetime", false);
96+
final String lvl = props.getStringProperty(SYSTEM_PREFIX + "level");
97+
defaultLevel = Level.toLevel(lvl, Level.ERROR);
98+
99+
dateTimeFormat = showDateTime
100+
? props.getStringProperty(SYSTEM_PREFIX + "dateTimeFormat", DEFAULT_DATE_TIME_FORMAT)
101+
: null;
102+
103+
final String fileName = props.getStringProperty(SYSTEM_PREFIX + "logFile", SYSTEM_ERR);
104+
PrintStream ps;
105+
if (SYSTEM_ERR.equalsIgnoreCase(fileName)) {
106+
ps = System.err;
107+
} else if (SYSTEM_OUT.equalsIgnoreCase(fileName)) {
108+
ps = System.out;
109+
} else {
110+
try {
111+
ps = new PrintStream(new FileOutputStream(fileName));
112+
} catch (final FileNotFoundException fnfe) {
113+
ps = System.err;
114+
}
115+
}
116+
this.stream = ps;
117+
}
118+
}
119+
}

log4j-api/src/main/java/org/apache/logging/log4j/simple/package-info.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
* Providers are able to be loaded at runtime.
2121
*/
2222
@Export
23-
@Version("2.20.2")
23+
@Version("2.24.0")
2424
package org.apache.logging.log4j.simple;
2525

2626
import org.osgi.annotation.bundle.Export;

log4j-api/src/main/java/org/apache/logging/log4j/util/ProviderUtil.java

Lines changed: 20 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@
1616
*/
1717
package org.apache.logging.log4j.util;
1818

19-
import static org.apache.logging.log4j.LogManager.FACTORY_PROPERTY_NAME;
2019
import static org.apache.logging.log4j.spi.Provider.PROVIDER_PROPERTY_NAME;
2120

2221
import aQute.bnd.annotation.Cardinality;
@@ -34,10 +33,10 @@
3433
import java.util.concurrent.locks.Lock;
3534
import java.util.concurrent.locks.ReentrantLock;
3635
import java.util.stream.Collectors;
36+
import org.apache.logging.log4j.LogManager;
3737
import org.apache.logging.log4j.Logger;
38-
import org.apache.logging.log4j.simple.SimpleLoggerContextFactory;
38+
import org.apache.logging.log4j.simple.internal.SimpleProvider;
3939
import org.apache.logging.log4j.spi.LoggerContextFactory;
40-
import org.apache.logging.log4j.spi.NoOpThreadContextMap;
4140
import org.apache.logging.log4j.spi.Provider;
4241
import org.apache.logging.log4j.status.StatusLogger;
4342

@@ -72,19 +71,17 @@ public final class ProviderUtil {
7271
*/
7372
static final Lock STARTUP_LOCK = new ReentrantLock();
7473

75-
private static final String API_VERSION = "Log4jAPIVersion";
7674
private static final String[] COMPATIBLE_API_VERSIONS = {"2.6.0"};
7775
private static final Logger LOGGER = StatusLogger.getLogger();
7876

7977
private static volatile Provider PROVIDER;
80-
private static final Provider FALLBACK_PROVIDER = new SimpleProvider();
8178

8279
private ProviderUtil() {}
8380

8481
static void addProvider(final Provider provider) {
8582
if (validVersion(provider.getVersions())) {
8683
PROVIDERS.add(provider);
87-
LOGGER.debug("Loaded Provider {}", provider);
84+
LOGGER.debug("Loaded provider:\n{}", provider);
8885
} else {
8986
LOGGER.warn("Ignoring provider for incompatible version {}:\n{}", provider.getVersions(), provider);
9087
}
@@ -100,6 +97,7 @@ static void addProvider(final Provider provider) {
10097
@SuppressFBWarnings(
10198
value = "URLCONNECTION_SSRF_FD",
10299
justification = "Uses a fixed URL that ends in 'META-INF/log4j-provider.properties'.")
100+
@SuppressWarnings("deprecation")
103101
static void loadProvider(final URL url, final ClassLoader cl) {
104102
try {
105103
final Properties props = PropertiesUtil.loadClose(url.openStream(), url);
@@ -178,32 +176,37 @@ static void lazyInit() {
178176
/**
179177
* Used to test the public {@link #getProvider()} method.
180178
*/
179+
@SuppressWarnings("deprecation")
181180
static Provider selectProvider(
182181
final PropertiesUtil properties, final Collection<Provider> providers, final Logger statusLogger) {
183182
Provider selected = null;
184183
// 1. Select provider using "log4j.provider" property
185184
final String providerClass = properties.getStringProperty(PROVIDER_PROPERTY_NAME);
186185
if (providerClass != null) {
187-
try {
188-
selected = LoaderUtil.newInstanceOf(providerClass);
189-
} catch (final Exception e) {
190-
statusLogger.error(
191-
"Unable to create provider {}.\nFalling back to default selection process.", PROVIDER, e);
186+
if (SimpleProvider.class.getName().equals(providerClass)) {
187+
selected = new SimpleProvider();
188+
} else {
189+
try {
190+
selected = LoaderUtil.newInstanceOf(providerClass);
191+
} catch (final Exception e) {
192+
statusLogger.error(
193+
"Unable to create provider {}.\nFalling back to default selection process.", PROVIDER, e);
194+
}
192195
}
193196
}
194197
// 2. Use deprecated "log4j2.loggerContextFactory" property to choose the provider
195-
final String factoryClassName = properties.getStringProperty(FACTORY_PROPERTY_NAME);
198+
final String factoryClassName = properties.getStringProperty(LogManager.FACTORY_PROPERTY_NAME);
196199
if (factoryClassName != null) {
197200
if (selected != null) {
198201
statusLogger.warn(
199202
"Ignoring {} system property, since {} was set.",
200-
FACTORY_PROPERTY_NAME,
203+
LogManager.FACTORY_PROPERTY_NAME,
201204
PROVIDER_PROPERTY_NAME);
202205
// 2a. Scan the known providers for one matching the logger context factory class name.
203206
} else {
204207
statusLogger.warn(
205208
"Usage of the {} property is deprecated. Use the {} property instead.",
206-
FACTORY_PROPERTY_NAME,
209+
LogManager.FACTORY_PROPERTY_NAME,
207210
PROVIDER_PROPERTY_NAME);
208211
for (final Provider provider : providers) {
209212
if (factoryClassName.equals(provider.getClassName())) {
@@ -225,14 +228,14 @@ static Provider selectProvider(
225228
statusLogger.error(
226229
"Class {} specified in the {} system property does not extend {}",
227230
factoryClassName,
228-
FACTORY_PROPERTY_NAME,
231+
LogManager.FACTORY_PROPERTY_NAME,
229232
LoggerContextFactory.class.getName());
230233
}
231234
} catch (final Exception e) {
232235
statusLogger.error(
233236
"Unable to create class {} specified in the {} system property",
234237
factoryClassName,
235-
FACTORY_PROPERTY_NAME,
238+
LogManager.FACTORY_PROPERTY_NAME,
236239
e);
237240
}
238241
}
@@ -253,7 +256,7 @@ static Provider selectProvider(
253256
.collect(Collectors.joining("\n", "Log4j API found multiple logging providers:\n", "")));
254257
break;
255258
}
256-
selected = providers.stream().max(comparator).orElse(FALLBACK_PROVIDER);
259+
selected = providers.stream().max(comparator).orElseGet(SimpleProvider::new);
257260
}
258261
statusLogger.info("Using provider:\n{}", selected);
259262
return selected;
@@ -271,10 +274,4 @@ private static boolean validVersion(final String version) {
271274
}
272275
return false;
273276
}
274-
275-
private static final class SimpleProvider extends Provider {
276-
private SimpleProvider() {
277-
super(null, CURRENT_VERSION, SimpleLoggerContextFactory.class, NoOpThreadContextMap.class);
278-
}
279-
}
280277
}

0 commit comments

Comments
 (0)