Skip to content

Commit

Permalink
Improve GraalVM support of SimpleLoggerContext
Browse files Browse the repository at this point in the history
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
  • Loading branch information
ppkarwasz committed Aug 31, 2024
1 parent c696ce2 commit 56313a1
Show file tree
Hide file tree
Showing 4 changed files with 150 additions and 58 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,10 @@
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;
import org.apache.logging.log4j.simple.internal.SimpleProvider;
import org.apache.logging.log4j.spi.AbstractLogger;
import org.apache.logging.log4j.spi.ExtendedLogger;
import org.apache.logging.log4j.spi.LoggerContext;
Expand All @@ -36,10 +35,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";

Expand Down Expand Up @@ -79,34 +74,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
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
/*
* 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.internal;

import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.PrintStream;
import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.simple.SimpleLoggerContext;
import org.apache.logging.log4j.simple.SimpleLoggerContextFactory;
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;
}

public static final class Config {

/** The default format to use when formatting dates */
private static final String DEFAULT_DATE_TIME_FORMAT = "yyyy/MM/dd HH:mm:ss:SSS zzz";

/** All system properties used by <code>SimpleLog</code> start with this */
private static final String SYSTEM_PREFIX = "org.apache.logging.log4j.simplelog.";

private static final String SYSTEM_OUT = "system.out";

private static final String SYSTEM_ERR = "system.err";

public static final Config INSTANCE = new Config();

public final PropertiesUtil props;

public final boolean showContextMap;

public final boolean showLogName;

public final boolean showShortName;

public final boolean showDateTime;

public final Level defaultLevel;

public final @Nullable String dateTimeFormat;

public 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;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -34,10 +33,10 @@
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.internal.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;

Expand Down Expand Up @@ -72,19 +71,17 @@ 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() {}

static void addProvider(final Provider provider) {
if (validVersion(provider.getVersions())) {
PROVIDERS.add(provider);
LOGGER.debug("Loaded Provider {}", provider);
LOGGER.debug("Loaded provider:\n{}", provider);
} else {
LOGGER.warn("Ignoring provider for incompatible version {}:\n{}", provider.getVersions(), provider);
}
Expand All @@ -100,6 +97,7 @@ static void addProvider(final Provider provider) {
@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);
Expand Down Expand Up @@ -178,32 +176,37 @@ static void lazyInit() {
/**
* 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())) {
Expand All @@ -225,14 +228,14 @@ static Provider selectProvider(
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);
}
}
Expand All @@ -253,7 +256,7 @@ static Provider selectProvider(
.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;
Expand All @@ -271,10 +274,4 @@ private static boolean validVersion(final String version) {
}
return false;
}

private static final class SimpleProvider extends Provider {
private SimpleProvider() {
super(null, CURRENT_VERSION, SimpleLoggerContextFactory.class, NoOpThreadContextMap.class);
}
}
}

0 comments on commit 56313a1

Please sign in to comment.