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 @@ -25,8 +25,11 @@
import static org.junit.jupiter.api.Assertions.assertTrue;

import java.io.IOException;
import java.net.URI;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Arrays;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
Expand All @@ -36,12 +39,14 @@
import org.apache.logging.log4j.core.Logger;
import org.apache.logging.log4j.core.LoggerContext;
import org.apache.logging.log4j.core.appender.ConsoleAppender;
import org.apache.logging.log4j.core.config.composite.CompositeConfiguration;
import org.apache.logging.log4j.core.filter.ThreadContextMapFilter;
import org.apache.logging.log4j.core.test.junit.LoggerContextSource;
import org.apache.logging.log4j.test.junit.TempLoggingDir;
import org.apache.logging.log4j.util.Strings;
import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.Test;
import org.mockito.Mockito;

class ConfigurationFactoryTest {

Expand Down Expand Up @@ -130,4 +135,56 @@ void properties(final LoggerContext context) throws IOException {
final Path logFile = loggingPath.resolve("test-properties.log");
checkFileLogger(context, logFile);
}

@Test
void testGetConfigurationWithNullUris() {
final ConfigurationFactory factory = Mockito.spy(ConfigurationFactory.getInstance());
try (final LoggerContext context = new LoggerContext("test")) {
assertThrows(NullPointerException.class, () -> factory.getConfiguration(context, "test", (List<URI>) null));
}
}

@Test
void testGetConfigurationWithEmptyUris() {
final ConfigurationFactory factory = Mockito.spy(ConfigurationFactory.getInstance());
try (final LoggerContext context = new LoggerContext("test")) {
factory.getConfiguration(context, "test", Collections.emptyList());
Mockito.verify(factory).getConfiguration(Mockito.same(context), Mockito.eq("test"), (URI) Mockito.isNull());
}
}

@Test
void testGetConfigurationWithNullInList() {
final ConfigurationFactory factory = Mockito.spy(ConfigurationFactory.getInstance());
try (final LoggerContext context = new LoggerContext("test")) {
final List<URI> listWithNull = Arrays.asList(URI.create("path:://to/nowhere"), null);
assertThrows(NullPointerException.class, () -> factory.getConfiguration(context, "test", listWithNull));
}
}

@Test
void testGetConfigurationWithSingleUri() throws Exception {
final ConfigurationFactory factory = Mockito.spy(ConfigurationFactory.getInstance());
try (final LoggerContext context = new LoggerContext("test")) {
final URI configLocation =
getClass().getResource("/log4j-test1.xml").toURI();
factory.getConfiguration(context, "test", Collections.singletonList(configLocation));
Mockito.verify(factory)
.getConfiguration(Mockito.same(context), Mockito.eq("test"), Mockito.eq(configLocation));
}
}

@Test
void testGetConfigurationWithMultipleUris() throws Exception {
final ConfigurationFactory factory = ConfigurationFactory.getInstance();
try (final LoggerContext context = new LoggerContext("test")) {
final URI configLocation1 =
getClass().getResource("/log4j-test1.xml").toURI();
final URI configLocation2 =
getClass().getResource("/log4j-xinclude.xml").toURI();
final List<URI> configLocations = Arrays.asList(configLocation1, configLocation2);
final Configuration config = factory.getConfiguration(context, "test", configLocations);
assertInstanceOf(CompositeConfiguration.class, config);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ static Configuration addTestFixtures(final String name, final ConfigurationBuild

@Override
public Configuration getConfiguration(final LoggerContext loggerContext, final ConfigurationSource source) {
return getConfiguration(loggerContext, source.toString(), null);
return getConfiguration(loggerContext, source.toString(), (URI) null);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,10 @@
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.stream.Collectors;
import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.core.LoggerContext;
Expand Down Expand Up @@ -333,6 +335,85 @@ public Configuration getConfiguration(
return getConfiguration(loggerContext, name, configLocation);
}

/**
* {@return a {@link Configuration} created using provided configuration location {@link URI}s}
* <p>
* Configurations will be loaded and merged in the given order using the effective {@linkplain org.apache.logging.log4j.core.config.composite.MergeStrategy merge strategy}.
* The default can be changed using the {@value org.apache.logging.log4j.core.config.composite.CompositeConfiguration#MERGE_STRATEGY_PROPERTY} system property.
* <p>
* If the provided list of {@code URI}s is empty, the configuration factory attempts to load an implementation-dependent set of default locations.
* If no configuration can be found, a {@link ConfigurationException} is thrown.
*
* @param loggerContext a logger context, may be null
* @param name a configuration name, may be null
* @param configLocations configuration location {@code URI}s, may not contain or be null
* @throws ConfigurationException if configuration could not be created
* @throws NullPointerException if {@code configLocations} contains or is null
*
* @since 2.26.0
*/
public Configuration getConfiguration(
final LoggerContext loggerContext, final String name, final List<URI> configLocations) {

// Sanitize URIs
final int[] configLocationIndex = {0};
final List<URI> distinctConfigLocations = Objects.requireNonNull(configLocations, "configLocations").stream()
.peek(uri -> {
if (uri == null) {
final String message = String.format("configLocations[%d]", configLocationIndex[0]);
throw new NullPointerException(message);
}
configLocationIndex[0]++;
})
.distinct()
.collect(Collectors.toList());

// Short-circuit if provided URIs are null or empty
if (distinctConfigLocations.isEmpty()) {
final Configuration config = getConfiguration(loggerContext, name, (URI) null);
if (config == null) {
throw new ConfigurationException("Configuration could not be created");
}
return config;
}

// Short-circuit if there is only a single URI
if (distinctConfigLocations.size() == 1) {
final URI configLocation = distinctConfigLocations.get(0);
final Configuration config = getConfiguration(loggerContext, name, configLocation);
if (config == null) {
final String message =
String.format("Configuration could not be created from location: `%s`", configLocation);
throw new ConfigurationException(message);
}
return config;
}

// Create individual configurations
final List<AbstractConfiguration> configs = distinctConfigLocations.stream()
.map(configLocation -> {
final Configuration config = getConfiguration(loggerContext, name, configLocation);
if (config == null) {
final String message =
String.format("Configuration could not be created from location: `%s`", configLocation);
throw new ConfigurationException(message);
}
if (!(config instanceof AbstractConfiguration)) {
final String message = String.format(
"Configuration created from location `%s` was expected to be of type `%s`, found: `%s`",
configLocation,
AbstractConfiguration.class.getCanonicalName(),
config.getClass().getCanonicalName());
throw new ConfigurationException(message);
}
return (AbstractConfiguration) config;
})
.collect(Collectors.toList());

// Combine created configurations
return new CompositeConfiguration(configs);
}

static boolean isClassLoaderUri(final URI uri) {
if (uri == null) {
return false;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
* Classes and interfaces supporting configuration of Log4j 2 with JSON.
*/
@Export
@Version("2.20.1")
@Version("2.26.0")
package org.apache.logging.log4j.core.config.json;

import org.osgi.annotation.bundle.Export;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
* Configuration using Properties files.
*/
@Export
@Version("2.20.1")
@Version("2.26.0")
package org.apache.logging.log4j.core.config.properties;

import org.osgi.annotation.bundle.Export;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
* Classes and interfaces supporting configuration of Log4j 2 with XML.
*/
@Export
@Version("2.20.2")
@Version("2.26.0")
package org.apache.logging.log4j.core.config.xml;

import org.osgi.annotation.bundle.Export;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
* Classes and interfaces supporting configuration of Log4j 2 with YAML.
*/
@Export
@Version("2.20.1")
@Version("2.26.0")
package org.apache.logging.log4j.core.config.yaml;

import org.osgi.annotation.bundle.Export;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?>
<entry xmlns="https://logging.apache.org/xml/ns"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
https://logging.apache.org/xml/ns
https://logging.apache.org/xml/ns/log4j-changelog-0.xsd"
type="added">
<issue id="3775" link="https://github.com/apache/logging-log4j2/issues/3775"/>
<issue id="3921" link="https://github.com/apache/logging-log4j2/pull/3921"/>
<description format="asciidoc">
Add a new `ConfigurationFactory::getConfiguration` method accepting multiple `URI`s
</description>
</entry>