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,58 @@ 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")) {
factory.getConfiguration(context, "test", (List<URI>) null);
Mockito.verify(factory).getConfiguration(Mockito.same(context), Mockito.eq("test"), (URI) Mockito.isNull());
}
}

@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 = Collections.singletonList(null);
factory.getConfiguration(context, "test", listWithNull);
Mockito.verify(factory).getConfiguration(Mockito.same(context), Mockito.eq("test"), (URI) Mockito.isNull());
}
}

@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,71 @@ public Configuration getConfiguration(
return getConfiguration(loggerContext, name, configLocation);
}

/**
* {@return a {@link Configuration} created using provided configuration location {@link URI}s}
* If the provided list of {@code URI}s is null or empty, {@code getConfiguration(loggerContext, name, (URI) null)} will be returned.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we explain more explicitly the behavior of an empty list?

Suggested change
* If the provided list of {@code URI}s is null or empty, {@code getConfiguration(loggerContext, name, (URI) null)} will be returned.
* <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.</p>

*
* @param loggerContext a logger context, may be null
* @param name a configuration name, may be null
* @param configLocations configuration location {@code URI}s, may be null or empty
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: I’d prefer not to allow null for a new API and instead require callers to pass an empty list.

* @throws ConfigurationException if configuration could not be created
*
* @since 2.26.0
*/
public Configuration getConfiguration(
final LoggerContext loggerContext, final String name, final List<URI> configLocations) {

// Sanitize URIs
final List<URI> distinctConfigLocations = configLocations == null
? Collections.emptyList()
: configLocations.stream().filter(Objects::nonNull).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>