Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,7 @@

package org.springframework.boot.logging.log4j2;

import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.net.URLConnection;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashMap;
Expand All @@ -37,15 +33,11 @@
import org.apache.logging.log4j.core.LoggerContext;
import org.apache.logging.log4j.core.config.AbstractConfiguration;
import org.apache.logging.log4j.core.config.Configuration;
import org.apache.logging.log4j.core.config.ConfigurationException;
import org.apache.logging.log4j.core.config.ConfigurationFactory;
import org.apache.logging.log4j.core.config.ConfigurationSource;
import org.apache.logging.log4j.core.config.LoggerConfig;
import org.apache.logging.log4j.core.config.composite.CompositeConfiguration;
import org.apache.logging.log4j.core.filter.DenyAllFilter;
import org.apache.logging.log4j.core.net.UrlConnectionFactory;
import org.apache.logging.log4j.core.net.ssl.SslConfiguration;
import org.apache.logging.log4j.core.net.ssl.SslConfigurationFactory;
import org.apache.logging.log4j.core.util.AuthorizationProvider;
import org.apache.logging.log4j.core.util.NameUtil;
import org.apache.logging.log4j.jul.Log4jBridgeHandler;
import org.apache.logging.log4j.status.StatusConsoleListener;
Expand All @@ -72,7 +64,6 @@
import org.springframework.core.io.ResourceLoader;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

/**
Expand All @@ -88,6 +79,8 @@
*/
public class Log4J2LoggingSystem extends AbstractLoggingSystem {

private static final org.apache.logging.log4j.Logger STATUS_LOGGER = StatusLogger.getLogger();

private static final String OPTIONAL_PREFIX = "optional:";

/**
Expand All @@ -100,41 +93,6 @@ public class Log4J2LoggingSystem extends AbstractLoggingSystem {
*/
static final String LOG4J_LOG_MANAGER = "org.apache.logging.log4j.jul.LogManager";

/**
* JSON tree parser used by Log4j 2 (optional dependency).
*/
static final String JSON_TREE_PARSER_V2 = "com.fasterxml.jackson.databind.ObjectMapper";

/**
* JSON tree parser embedded in Log4j 3.
*/
static final String JSON_TREE_PARSER_V3 = "org.apache.logging.log4j.kit.json.JsonReader";

/**
* Configuration factory for properties files (Log4j 2).
*/
static final String PROPS_CONFIGURATION_FACTORY_V2 = "org.apache.logging.log4j.core.config.properties.PropertiesConfigurationFactory";

/**
* Configuration factory for properties files (Log4j 3, optional dependency).
*/
static final String PROPS_CONFIGURATION_FACTORY_V3 = "org.apache.logging.log4j.config.properties.JavaPropsConfigurationFactory";

/**
* YAML tree parser used by Log4j 2 (optional dependency).
*/
static final String YAML_TREE_PARSER_V2 = "com.fasterxml.jackson.dataformat.yaml.YAMLMapper";

/**
* Configuration factory for YAML files (Log4j 2, embedded).
*/
static final String YAML_CONFIGURATION_FACTORY_V2 = "org.apache.logging.log4j.core.config.yaml.YamlConfigurationFactory";

/**
* Configuration factory for YAML files (Log4j 3, optional dependency).
*/
static final String YAML_CONFIGURATION_FACTORY_V3 = "org.apache.logging.log4j.config.yaml.YamlConfigurationFactory";

private static final SpringEnvironmentPropertySource propertySource = new SpringEnvironmentPropertySource();

static final String ENVIRONMENT_KEY = Conventions.getQualifiedAttributeName(Log4J2LoggingSystem.class,
Expand All @@ -157,73 +115,69 @@ public class Log4J2LoggingSystem extends AbstractLoggingSystem {

private static final Filter FILTER = DenyAllFilter.newBuilder().build();

public Log4J2LoggingSystem(ClassLoader classLoader) {
super(classLoader);
}
private final LoggerContext loggerContext;

@Override
protected String[] getStandardConfigLocations() {
List<String> locations = new ArrayList<>();
addLocationsFromProperties(locations);
addStandardLocations(locations);
return StringUtils.toStringArray(locations);
/**
* Create a new {@link Log4J2LoggingSystem} instance.
* @param classLoader the class loader to use.
* @param loggerContext the {@link LoggerContext} to use.
*/
Log4J2LoggingSystem(ClassLoader classLoader, LoggerContext loggerContext) {
super(classLoader);
this.loggerContext = loggerContext;
}

private void addLocationsFromProperties(List<String> locations) {
for (String property : List.of("log4j2.configurationFile", "log4j.configuration.location")) {
String propertyDefinedLocation = PropertiesUtil.getProperties().getStringProperty(property);
if (propertyDefinedLocation != null) {
locations.add(propertyDefinedLocation);
}
/**
* Create a new {@link Log4J2LoggingSystem} instance.
* @param classLoader the class loader to use
* @return a new {@link Log4J2LoggingSystem} instance
* @throws IllegalStateException if Log4j Core is not the active Log4j API provider.
*/
private static Log4J2LoggingSystem createLoggingSystem(ClassLoader classLoader) {
org.apache.logging.log4j.spi.LoggerContext loggerContext = LogManager.getContext(classLoader, false);
if (loggerContext instanceof LoggerContext) {
return new Log4J2LoggingSystem(classLoader, (LoggerContext) loggerContext);
}
throw new IllegalStateException("Log4j Core is not the active Log4j API provider");
}

private void addStandardLocations(List<String> locations) {
LoggerContext loggerContext = getLoggerContext();
String contextName = loggerContext.getName();
List<String> extensions = getStandardConfigExtensions();
addLocation(locations, "log4j2-test" + contextName, extensions);
addLocation(locations, "log4j2-test", extensions);
addLocation(locations, "log4j2" + contextName, extensions);
addLocation(locations, "log4j2", extensions);
}

private List<String> getStandardConfigExtensions() {
List<String> extensions = new ArrayList<>();
// These classes need to be visible by the classloader that loads Log4j Core.
ClassLoader classLoader = LoggerContext.class.getClassLoader();
// The order of the extensions corresponds to the order in which Log4j Core 2 and
// 3 will try to load them, in decreasing value of @Order.
if (isPresent(classLoader, PROPS_CONFIGURATION_FACTORY_V2)
|| isPresent(classLoader, PROPS_CONFIGURATION_FACTORY_V3)) {
extensions.add(".properties");
}
if (isPresent(classLoader, YAML_CONFIGURATION_FACTORY_V2, YAML_TREE_PARSER_V2)
|| isPresent(classLoader, YAML_CONFIGURATION_FACTORY_V3)) {
Collections.addAll(extensions, ".yaml", ".yml");
}
if (isPresent(classLoader, JSON_TREE_PARSER_V2) || isPresent(classLoader, JSON_TREE_PARSER_V3)) {
Collections.addAll(extensions, ".json", ".jsn");
}
extensions.add(".xml");
return extensions;
/**
* {@inheritDoc}
* @deprecated Since 4.0.0, in favor of the {@link ConfigurationFactory} SPI.
*/
@Override
@Deprecated(since = "4.0.0", forRemoval = true)
protected String[] getStandardConfigLocations() {
return new String[] { "log4j2.xml" };
}

private void addLocation(List<String> locations, String location, List<String> extensions) {
extensions.forEach((extension) -> locations.add(location + extension));
@Override
protected @Nullable String getSelfInitializationConfig() {
Configuration currentConfiguration = getLoggerContext().getConfiguration();
return getConfigLocation(currentConfiguration);
}

private boolean isPresent(ClassLoader classLoader, String... classNames) {
for (String className : classNames) {
if (!isClassAvailable(classLoader, className)) {
return false;
}
@Override
protected @Nullable String getSpringInitializationConfig() {
ConfigurationFactory configurationFactory = ConfigurationFactory.getInstance();
try {
Configuration springConfiguration = configurationFactory.getConfiguration(getLoggerContext(), "-spring",
null, getClassLoader());
String configLocation = getConfigLocation(springConfiguration);
return (configLocation != null && configLocation.contains("-spring")) ? configLocation : null;
}
catch (ConfigurationException ex) {
STATUS_LOGGER.warn("Could not load Spring-specific Log4j Core configuration", ex);
return null;
}
return true;
}

protected boolean isClassAvailable(ClassLoader classLoader, String className) {
return ClassUtils.isPresent(className, classLoader);
private @Nullable String getConfigLocation(Configuration configuration) {
// The location may be:
// - null: if DefaultConfiguration is used (no explicit config loaded)
// - a file path: if provided explicitly by the user
// - a URI: if loaded from the classpath default or a custom location
return configuration.getConfigurationSource().getLocation();
}

@Deprecated(since = "4.0.0", forRemoval = true)
Expand Down Expand Up @@ -335,7 +289,7 @@ private void load(LoggingInitializationContext initializationContext, String loc
Environment environment = initializationContext.getEnvironment();
Assert.state(environment != null, "'environment' must not be null");
applySystemProperties(environment, logFile);
loadConfiguration(location, logFile, overrides);
reconfigure(location, overrides);
}

private List<String> getOverrides(LoggingInitializationContext initializationContext) {
Expand All @@ -346,66 +300,56 @@ private List<String> getOverrides(LoggingInitializationContext initializationCon
return overrides.orElse(Collections.emptyList());
}

/**
* Load the configuration from the given {@code location}, creating a composite using
* the configuration from the given {@code overrides}.
* @param location the location
* @param logFile log file configuration
* @param overrides the overriding locations
* @since 2.6.0
*/
protected void loadConfiguration(String location, @Nullable LogFile logFile, List<String> overrides) {
private void reconfigure(String location, List<String> overrides) {
Assert.notNull(location, "'location' must not be null");
try {
List<Configuration> configurations = new ArrayList<>();
LoggerContext context = getLoggerContext();
ResourceLoader resourceLoader = ApplicationResourceLoader.get();
configurations.add(load(resourceLoader.getResource(location), context));
ResourceLoader resourceLoader = ApplicationResourceLoader.get(getClassLoader());
configurations.add(load(resourceLoader, location));
for (String override : overrides) {
Configuration overrideConfiguration = loadOverride(resourceLoader, override, context);
Configuration overrideConfiguration = loadOverride(resourceLoader, override);
if (overrideConfiguration != null) {
configurations.add(overrideConfiguration);
}
}
context.start(mergeConfigurations(configurations));
this.loggerContext.reconfigure(mergeConfigurations(configurations));
}
catch (Exception ex) {
throw new IllegalStateException("Could not initialize Log4J2 logging from " + location, ex);
String message = "Could not initialize Log4J2 logging from " + location;
if (!overrides.isEmpty()) {
message += " with overrides " + overrides;
}
throw new IllegalStateException(message, ex);
}
}

private Configuration load(Resource resource, LoggerContext context) throws IOException {
private Configuration load(ResourceLoader resourceLoader, String location) throws IOException {
ConfigurationFactory factory = ConfigurationFactory.getInstance();
if (resource.isFile()) {
try (InputStream inputStream = resource.getInputStream()) {
return factory.getConfiguration(context, new ConfigurationSource(inputStream, resource.getFile()));
}
}
URL url = resource.getURL();
AuthorizationProvider authorizationProvider = ConfigurationFactory
.authorizationProvider(PropertiesUtil.getProperties());
SslConfiguration sslConfiguration = url.getProtocol().equals("https")
? SslConfigurationFactory.getSslConfiguration() : null;
URLConnection connection = UrlConnectionFactory.createConnection(url, 0, sslConfiguration,
authorizationProvider);
try (InputStream inputStream = connection.getInputStream()) {
return factory.getConfiguration(context,
new ConfigurationSource(inputStream, url, connection.getLastModified()));
Resource resource = resourceLoader.getResource(location);
Configuration configuration = factory.getConfiguration(getLoggerContext(), null, resource.getURI(),
getClassLoader());
// The error handling in Log4j Core 2.25.x is not consistent:
// some loading and parsing errors result in a null configuration,
// others in an exception.
if (configuration == null) {
throw new ConfigurationException("Could not load Log4j Core configuration from " + location);
}
return configuration;
}

private @Nullable Configuration loadOverride(ResourceLoader resourceLoader, String location, LoggerContext context)
throws IOException {
private @Nullable Configuration loadOverride(ResourceLoader resourceLoader, String location) throws IOException {
if (location.startsWith(OPTIONAL_PREFIX)) {
Resource resource = resourceLoader.getResource(location.substring(OPTIONAL_PREFIX.length()));
String actualLocation = location.substring(OPTIONAL_PREFIX.length());
Resource resource = resourceLoader.getResource(actualLocation);
try {
return (resource.exists()) ? load(resource, context) : null;
return (resource.exists()) ? load(resourceLoader, actualLocation) : null;
}
catch (FileNotFoundException ex) {
catch (ConfigurationException | IOException ex) {
STATUS_LOGGER.debug("Could not load optional Log4j2 override from {}", actualLocation, ex);
return null;
}
}
return load(resourceLoader.getResource(location), context);
return load(resourceLoader, location);
}

private Configuration mergeConfigurations(List<Configuration> configurations) {
Expand All @@ -417,33 +361,11 @@ private Configuration mergeConfigurations(List<Configuration> configurations) {

@Override
protected void reinitialize(LoggingInitializationContext initializationContext) {
List<String> overrides = getOverrides(initializationContext);
if (!CollectionUtils.isEmpty(overrides)) {
reinitializeWithOverrides(overrides);
}
else {
LoggerContext context = getLoggerContext();
context.reconfigure();
}
}

private void reinitializeWithOverrides(List<String> overrides) {
LoggerContext context = getLoggerContext();
List<Configuration> configurations = new ArrayList<>();
configurations.add(context.getConfiguration());
ResourceLoader resourceLoader = ApplicationResourceLoader.get();
for (String override : overrides) {
try {
Configuration overrideConfiguration = loadOverride(resourceLoader, override, context);
if (overrideConfiguration != null) {
configurations.add(overrideConfiguration);
}
}
catch (IOException ex) {
throw new RuntimeException("Failed to load overriding configuration from '" + override + "'", ex);
}
}
context.reconfigure(mergeConfigurations(configurations));
String currentLocation = getSelfInitializationConfig();
// `reinitialize` is only triggered when `getSelfInitializationConfig` returns a
// non-null value
Assert.notNull(currentLocation, "'currentLocation' must not be null");
load(initializationContext, currentLocation, null);
}

@Override
Expand Down Expand Up @@ -584,8 +506,8 @@ public void cleanUp() {
return configuration.getLoggers().get(name);
}

private LoggerContext getLoggerContext() {
return (LoggerContext) LogManager.getContext(false);
LoggerContext getLoggerContext() {
return this.loggerContext;
}

private boolean isAlreadyInitialized(LoggerContext loggerContext) {
Expand Down Expand Up @@ -630,7 +552,12 @@ public static class Factory implements LoggingSystemFactory {
@Override
public @Nullable LoggingSystem getLoggingSystem(ClassLoader classLoader) {
if (PRESENT) {
return new Log4J2LoggingSystem(classLoader);
try {
return createLoggingSystem(classLoader);
}
catch (IllegalStateException ex) {
// Log4j Core is not the active Log4j API provider
}
}
return null;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,13 +44,6 @@ private void registerLog4j2Hints(RuntimeHints hints, @Nullable ClassLoader class
hints.resources().registerPattern("org/springframework/boot/logging/log4j2/log4j2-file.xml");
hints.resources().registerPattern("log4j2.springboot");
// Declares the types that Log4j2LoggingSystem checks for existence reflectively.
hints.reflection().registerTypeIfPresent(classLoader, Log4J2LoggingSystem.JSON_TREE_PARSER_V2);
hints.reflection().registerTypeIfPresent(classLoader, Log4J2LoggingSystem.JSON_TREE_PARSER_V3);
hints.reflection().registerTypeIfPresent(classLoader, Log4J2LoggingSystem.PROPS_CONFIGURATION_FACTORY_V2);
hints.reflection().registerTypeIfPresent(classLoader, Log4J2LoggingSystem.PROPS_CONFIGURATION_FACTORY_V3);
hints.reflection().registerTypeIfPresent(classLoader, Log4J2LoggingSystem.YAML_TREE_PARSER_V2);
hints.reflection().registerTypeIfPresent(classLoader, Log4J2LoggingSystem.YAML_CONFIGURATION_FACTORY_V2);
hints.reflection().registerTypeIfPresent(classLoader, Log4J2LoggingSystem.YAML_CONFIGURATION_FACTORY_V3);
hints.reflection().registerTypeIfPresent(classLoader, Log4J2LoggingSystem.LOG4J_BRIDGE_HANDLER);
hints.reflection().registerTypeIfPresent(classLoader, Log4J2LoggingSystem.LOG4J_LOG_MANAGER);
}
Expand Down
Loading