Skip to content

Add new ConfigurationFactory::getConfiguration accepting multiple URIs#3921

Merged
vy merged 4 commits into
apache:2.xfrom
yybmion:add_getConfiguration_for_multiple_URIs
Oct 29, 2025
Merged

Add new ConfigurationFactory::getConfiguration accepting multiple URIs#3921
vy merged 4 commits into
apache:2.xfrom
yybmion:add_getConfiguration_for_multiple_URIs

Conversation

@yybmion

@yybmion yybmion commented Sep 8, 2025

Copy link
Copy Markdown
Contributor

Adds a new ConfigurationFactory::getConfiguration overload accepting multiple URIs.

This work is a spin-off from #3839 and fixes #3775.

@yybmion yybmion force-pushed the add_getConfiguration_for_multiple_URIs branch from 9377356 to 22ba22c Compare September 26, 2025 09:28
@yybmion

yybmion commented Sep 26, 2025

Copy link
Copy Markdown
Contributor Author

Thanks for the review, @vy. I've updated the PR.

If this looks good, I'll proceed with adding tests and changelog.

@vy

vy commented Sep 26, 2025

Copy link
Copy Markdown
Member

If this looks good, I'll proceed with adding tests and changelog.

@yybmion, yes, please proceed with tests and a changelog.

@yybmion

yybmion commented Sep 26, 2025

Copy link
Copy Markdown
Contributor Author

@yybmion, yes, please proceed with tests and a changelog.

@vy , Added tests and changelog as requested.

Comment thread src/changelog/.2.x.x/add_getConfiguration_method_for_mulitiple_URIs.xml Outdated
@yybmion yybmion force-pushed the add_getConfiguration_for_multiple_URIs branch from aeda617 to 40b26ea Compare October 3, 2025 14:12
@yybmion

yybmion commented Oct 3, 2025

Copy link
Copy Markdown
Contributor Author

Thanks for the review @vy, Changes applied as requested.

Added a null element check in the URI list to throw a ConfigurationException when any element is null. This ensures that testGetConfigurationWithNullInList passes.

for (final URI uri : uris) {

     if (uri == null) {
          throw new ConfigurationException("URI list contains null element");
   }
...

@vy vy left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

@yybmion, thanks so much for your patience. I am happy with the last state of the PR.

@ppkarwasz, do you any remarks?

@github-actions

github-actions Bot commented Oct 3, 2025

Copy link
Copy Markdown
Contributor
Job Requested goals Build Tool Version Build Outcome Build Scan®
build-macos-latest clean install 3.9.8 Build Scan PUBLISHED
build-ubuntu-latest clean install 3.9.8 Build Scan PUBLISHED
build-windows-latest clean install 3.9.8 Build Scan PUBLISHED
Generated by gradle/develocity-actions

@yybmion yybmion force-pushed the add_getConfiguration_for_multiple_URIs branch from 6af187e to e91c25b Compare October 3, 2025 17:41
@yybmion

yybmion commented Oct 3, 2025

Copy link
Copy Markdown
Contributor Author

@vy, I'm sorry about the confusion. Previously, I bumped the versions of those packages in the last PR (#3839) after making some changes, but I forgot to revert them. I've now restored them to their original values.

@vy

vy commented Oct 10, 2025

Copy link
Copy Markdown
Member

@yybmion, CI builds are failing. Would you mind reading the Log4j Development Guide and resolving all build failures, please?

@yybmion yybmion force-pushed the add_getConfiguration_for_multiple_URIs branch from e91c25b to 93819f2 Compare October 10, 2025 08:41
@yybmion

yybmion commented Oct 10, 2025

Copy link
Copy Markdown
Contributor Author

@yybmion, just a sec, why do we need these version bumps? We did not touch to these packages.

@vy I think the version bumps are actually needed because the new public method in ConfigurationFactory is inherited by all its subclasses (JsonConfigurationFactory, XmlConfigurationFactory, etc.), which adds the method to those packages' public API.

This is causing the build failure. I've updated the versions back to 2.26.0.

@ppkarwasz ppkarwasz left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Hi @yybmion,

Thanks for the PR, and apologies for the delayed feedback: I haven’t had as much time to spend on Log4j recently as I’d like.

I like the direction of your change, but there are still a few edge cases we need to address.

In particular, I expected the PR to integrate the newly introduced method into the existing code.

For example, ConfigurationFactory.Factory#getConfiguration(LoggerContext, String, URI) could benefit from using it: we currently have a couple of legacy mechanisms for passing multiple URIs through a single URI parameter:

  • If uri == null, the log4j2.configurationFile property is treated as a comma-separated list of file paths or URIs.
  • If uri != null, there’s a legacy (and admittedly hacky) method of cramming two URIs into one string (see parseConfigLocations), which unfortunately still needs to be supported.

These mechanisms are handled in getConfiguration(LoggerContext, String, URI), but I think we should delegate them to the newly introduced method.

@ppkarwasz

Copy link
Copy Markdown
Member

I realize my comments go a bit beyond the immediate scope of this PR, but the section of code below has become a real maintenance headache. It would be great if we could take the opportunity to clean it up once and for all:

public Configuration getConfiguration(
final LoggerContext loggerContext, final String name, final URI configLocation) {
if (configLocation == null) {
final String configLocationStr = this.substitutor.replace(
PropertiesUtil.getProperties().getStringProperty(CONFIGURATION_FILE_PROPERTY));
if (configLocationStr != null) {
final String[] sources = parseConfigLocations(configLocationStr);
if (sources.length > 1) {
final List<AbstractConfiguration> configs = new ArrayList<>();
for (final String sourceLocation : sources) {
final Configuration config = getConfiguration(loggerContext, sourceLocation.trim());
if (config != null) {
if (config instanceof AbstractConfiguration) {
configs.add((AbstractConfiguration) config);
} else {
LOGGER.error("Failed to created configuration at {}", sourceLocation);
return null;
}
} else {
LOGGER.warn("Unable to create configuration for {}, ignoring", sourceLocation);
}
}
if (configs.size() > 1) {
return new CompositeConfiguration(configs);
} else if (configs.size() == 1) {
return configs.get(0);
}
}
return getConfiguration(loggerContext, configLocationStr);
}
final String log4j1ConfigStr = this.substitutor.replace(
PropertiesUtil.getProperties().getStringProperty(LOG4J1_CONFIGURATION_FILE_PROPERTY));
if (log4j1ConfigStr != null) {
System.setProperty(LOG4J1_EXPERIMENTAL, "true");
return getConfiguration(LOG4J1_VERSION, loggerContext, log4j1ConfigStr);
}
for (final ConfigurationFactory factory : getFactories()) {
final String[] types = factory.getSupportedTypes();
if (types != null) {
for (final String type : types) {
if (type.equals(ALL_TYPES)) {
final Configuration config =
factory.getConfiguration(loggerContext, name, configLocation);
if (config != null) {
return config;
}
}
}
}
}
} else {
final String[] sources = parseConfigLocations(configLocation);
if (sources.length > 1) {
final List<AbstractConfiguration> configs = new ArrayList<>();
for (final String sourceLocation : sources) {
final Configuration config = getConfiguration(loggerContext, sourceLocation.trim());
if (config instanceof AbstractConfiguration) {
configs.add((AbstractConfiguration) config);
} else {
LOGGER.error("Failed to created configuration at {}", sourceLocation);
return null;
}
}
return new CompositeConfiguration(configs);
}
// configLocation != null
final String configLocationStr = configLocation.toString();
for (final ConfigurationFactory factory : getFactories()) {
final String[] types = factory.getSupportedTypes();
if (types != null) {
for (final String type : types) {
if (type.equals(ALL_TYPES) || configLocationStr.endsWith(type)) {
final Configuration config =
factory.getConfiguration(loggerContext, name, configLocation);
if (config != null) {
return config;
}
}
}
}
}
}
Configuration config = getConfiguration(loggerContext, true, name);
if (config == null) {
config = getConfiguration(loggerContext, true, null);
if (config == null) {
config = getConfiguration(loggerContext, false, name);
if (config == null) {
config = getConfiguration(loggerContext, false, null);
}
}
}
if (config != null) {
return config;
}
LOGGER.warn(
"No Log4j 2 configuration file found. "
+ "Using default configuration (logging only errors to the console), "
+ "or user programmatically provided configurations. "
+ "Set system property 'log4j2.debug' "
+ "to show Log4j 2 internal initialization logging. "
+ "See https://logging.apache.org/log4j/2.x/manual/configuration.html for instructions on how to configure Log4j 2");
return new DefaultConfiguration();
}

@yybmion

yybmion commented Oct 13, 2025

Copy link
Copy Markdown
Contributor Author

Hi @vy and @ppkarwasz,

Thanks for the feedback! Before I finalize this PR, I'd like to clarify the scope of follow-up work.

Based on the discussion, I see two distinct improvement areas:

1. Factory refactoring (removing duplicate code)

I realize my comments go a bit beyond the immediate scope of this PR, but the section of code below has become a real maintenance headache. It would be great if we could take the opportunity to clean it up once and for all:

public Configuration getConfiguration(
final LoggerContext loggerContext, final String name, final URI configLocation) {
if (configLocation == null) {
final String configLocationStr = this.substitutor.replace(
PropertiesUtil.getProperties().getStringProperty(CONFIGURATION_FILE_PROPERTY));
if (configLocationStr != null) {
final String[] sources = parseConfigLocations(configLocationStr);
if (sources.length > 1) {
final List<AbstractConfiguration> configs = new ArrayList<>();
for (final String sourceLocation : sources) {
final Configuration config = getConfiguration(loggerContext, sourceLocation.trim());
if (config != null) {
if (config instanceof AbstractConfiguration) {
configs.add((AbstractConfiguration) config);
} else {
LOGGER.error("Failed to created configuration at {}", sourceLocation);
return null;
}
} else {
LOGGER.warn("Unable to create configuration for {}, ignoring", sourceLocation);
}
}
if (configs.size() > 1) {
return new CompositeConfiguration(configs);
} else if (configs.size() == 1) {
return configs.get(0);
}
}
return getConfiguration(loggerContext, configLocationStr);
}
final String log4j1ConfigStr = this.substitutor.replace(
PropertiesUtil.getProperties().getStringProperty(LOG4J1_CONFIGURATION_FILE_PROPERTY));
if (log4j1ConfigStr != null) {
System.setProperty(LOG4J1_EXPERIMENTAL, "true");
return getConfiguration(LOG4J1_VERSION, loggerContext, log4j1ConfigStr);
}
for (final ConfigurationFactory factory : getFactories()) {
final String[] types = factory.getSupportedTypes();
if (types != null) {
for (final String type : types) {
if (type.equals(ALL_TYPES)) {
final Configuration config =
factory.getConfiguration(loggerContext, name, configLocation);
if (config != null) {
return config;
}
}
}
}
}
} else {
final String[] sources = parseConfigLocations(configLocation);
if (sources.length > 1) {
final List<AbstractConfiguration> configs = new ArrayList<>();
for (final String sourceLocation : sources) {
final Configuration config = getConfiguration(loggerContext, sourceLocation.trim());
if (config instanceof AbstractConfiguration) {
configs.add((AbstractConfiguration) config);
} else {
LOGGER.error("Failed to created configuration at {}", sourceLocation);
return null;
}
}
return new CompositeConfiguration(configs);
}
// configLocation != null
final String configLocationStr = configLocation.toString();
for (final ConfigurationFactory factory : getFactories()) {
final String[] types = factory.getSupportedTypes();
if (types != null) {
for (final String type : types) {
if (type.equals(ALL_TYPES) || configLocationStr.endsWith(type)) {
final Configuration config =
factory.getConfiguration(loggerContext, name, configLocation);
if (config != null) {
return config;
}
}
}
}
}
}
Configuration config = getConfiguration(loggerContext, true, name);
if (config == null) {
config = getConfiguration(loggerContext, true, null);
if (config == null) {
config = getConfiguration(loggerContext, false, name);
if (config == null) {
config = getConfiguration(loggerContext, false, null);
}
}
}
if (config != null) {
return config;
}
LOGGER.warn(
"No Log4j 2 configuration file found. "
+ "Using default configuration (logging only errors to the console), "
+ "or user programmatically provided configurations. "
+ "Set system property 'log4j2.debug' "
+ "to show Log4j 2 internal initialization logging. "
+ "See https://logging.apache.org/log4j/2.x/manual/configuration.html for instructions on how to configure Log4j 2");
return new DefaultConfiguration();
}

Delegate composite configuration logic in Factory.getConfiguration(ctx, name, URI) to the new getConfiguration(ctx, name, List<URI>) method.

2. Exception-throwing consistency

@yybmion, would you be interested in implementing changes necessary to make other ConfigurationFactory::getConfiguration methods throw on failure too?

Align all getConfiguration methods to throw exceptions instead of returning null


I'm currently working on two changes. Should I handle them in separate PRs?

@vy

vy commented Oct 17, 2025

Copy link
Copy Markdown
Member

@ppkarwasz, I've addressed all your remarks. Unless you have more, would you mind approving the PR, please?

@yybmion, you've done a great job in this PR, nothing else is needed from your side. 💯 When this PR gets merged, please submit another one addressing the newly created #3958.

@vy vy self-assigned this Oct 17, 2025
@vy vy added api Affects the public API configuration Affects the configuration system in a general way labels Oct 17, 2025
@vy vy changed the title Add getConfiguration method for multiple URIs Add new ConfigurationFactory::getConfiguration accepting multiple URIs Oct 17, 2025
@vy vy moved this to Waiting for review in Log4j pull request tracker Oct 27, 2025

@ppkarwasz ppkarwasz left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

LGTM

@github-project-automation github-project-automation Bot moved this from Waiting for review to Approved in Log4j pull request tracker Oct 29, 2025
@vy vy merged commit a6cb7b7 into apache:2.x Oct 29, 2025
11 checks passed
@github-project-automation github-project-automation Bot moved this from Approved to Merged in Log4j pull request tracker Oct 29, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

api Affects the public API configuration Affects the configuration system in a general way

Projects

Archived in project

Development

Successfully merging this pull request may close these issues.

Add API to List Supported Configuration File Locations

3 participants