Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support optional: prefix with logging.log4j2.config.override #44399

Open
rgoers opened this issue Feb 21, 2025 · 9 comments
Open

Support optional: prefix with logging.log4j2.config.override #44399

rgoers opened this issue Feb 21, 2025 · 9 comments
Labels
type: enhancement A general enhancement
Milestone

Comments

@rgoers
Copy link
Contributor

rgoers commented Feb 21, 2025

Log4j2LoggingSystem contains the following method:

	protected void loadConfiguration(String location, LogFile logFile, List<String> overrides) {
		Assert.notNull(location, "'location' must not be null");
		try {
			List<Configuration> configurations = new ArrayList<>();
			LoggerContext context = getLoggerContext();
			configurations.add(load(location, context));
			for (String override : overrides) {
				configurations.add(load(override, context));
			}
			Configuration configuration = (configurations.size() > 1) ? createComposite(configurations)
					: configurations.iterator().next();
			context.start(configuration);
		}
		catch (Exception ex) {
			throw new IllegalStateException("Could not initialize Log4J2 logging from " + location, ex);
		}
	}

If an error occurs loading an override file the exception will percolate to the catch block and fail loading the logging configuration. The log4j-spring-boot module provided by Log4j had:

                        try {
                            final Configuration config =
                                    ConfigurationFactory.getInstance().getConfiguration(ctx, source);
                            if (config instanceof AbstractConfiguration) {
                                configs.add((AbstractConfiguration) config);
                            } else {
                                LOGGER.warn(
                                        "Configuration at {} cannot be combined in a CompositeConfiguration",
                                        sourceLocation);
                                return;
                            }
                        } catch (Exception ex) {
                            if (!first) {
                                LOGGER.warn(
                                        "Error accessing {}: {}. Ignoring override", sourceLocation, ex.getMessage());
                            } else {
                                throw ex;
                            }
                        }

So with log4j-spring-boot a warning message would be logged that the override file couldn't be loaded but configuration would still take place with the primary config file.

This behavior in Log4j2 was intentional. All the services I work with specify a shared primary logging configuration and a per application override location such as

logging:
  label: ${spring.cloud.config.label:master}
  config: "https://spring-configuration-server.acme.org/some-service/default/${logging.label}/log4j2-json-dev.xml"
  log4j2:
    config:
      override: "https://spring-configuration-server.acme.org/some-service/default/${logging.label}/log4j2-some-service-dev.xml"

If the application doesn't need to override anything then it just uses the primary configuration. If we want to add an override we can just add it to Spring Cloud Config without having to change the application.

In short, the configurations.add() call needs to be placed in its own try/catch block that logs a similar message to what log4j-spring-boot was doing and ignore the failure.

@spring-projects-issues spring-projects-issues added the status: waiting-for-triage An issue we've not yet triaged label Feb 21, 2025
@nosan
Copy link
Contributor

nosan commented Feb 26, 2025

In short, the configurations.add() call needs to be placed in its own try/catch block
that logs a similar message to what log4j-spring-boot was doing and ignore the failure.

I am not sure that is a good way to go, there could be a lot of people who rely on
current behaviour, and adding try { } catch (...) { } will introduce breaking change.

I believe a good compromise would be to add support for the optional: prefix. This approach won't impact those who currently using override files and expecting an Exception to be thrown, while also offering the flexibility to include optional override files. If the file location is prefixed with optional: and does not exist, it will not be loaded.

logging:
  label: ${spring.cloud.config.label:master}
  config: "https://spring-configuration-server.acme.org/some-service/default/${logging.label}/log4j2-json-dev.xml"
  log4j2:
    config:
      override: "optional:https://spring-configuration-server.acme.org/some-service/default/${logging.label}/log4j2-some-service-dev.xml"

I've added optional: support in main...nosan:spring-boot:gh-44399

nosan added a commit to nosan/spring-boot that referenced this issue Feb 27, 2025
Introduced support for the 'optional:' prefix in Log4j2
override file locations, ensuring missing files are ignored without
throwing exceptions.

See spring-projectsgh-44399

Signed-off-by: Dmytro Nosan <[email protected]>
@rgoers
Copy link
Contributor Author

rgoers commented Feb 27, 2025

I would expect that many users using this feature were using log4j-spring-boot, so removing that jar exposes this behavior. IOW, the breaking change was done in Spring Boot. However, I am open to making a change similar to what you propose but I don't think changing the URI string is the appropriate way to do it. I would expect it to be something like:

logging:
  label: ${spring.cloud.config.label:master}
  config: "https://spring-configuration-server.acme.org/some-service/default/${logging.label}/log4j2-json-dev.xml"
  log4j2:
    config:
      optional-override: "https://spring-configuration-server.acme.org/some-service/default/${logging.label}/log4j2-some-service-dev.xml"

or

logging:
  label: ${spring.cloud.config.label:master}
  config: "https://spring-configuration-server.acme.org/some-service/default/${logging.label}/log4j2-json-dev.xml"
  log4j2:
    config:
      optional:
        override: "https://spring-configuration-server.acme.org/some-service/default/${logging.label}/log4j2-some-service-dev.xml"

@wilkinsona
Copy link
Member

There's support elsewhere in Boot for using optional: as a prefix. If we go down the route of wanting to indicate that the configuration is optional, @nosan's suggestion would be the idiomatic of doing it in Boot.

@wilkinsona
Copy link
Member

Reading through the issue again, I think it's important to be able to distinguish between an override that's present yet broken and an override that's not there. I think the former should cause a failure while the latter should not. This would be consistent with, for example, our handling of application.yaml where an app will start without one but will fail to start if the file's present but its syntax is invalid.

@wilkinsona wilkinsona changed the title Application initialization fails if a log4j2 override file is not present Support optional: prefix with logging.log4j2.config.override Feb 27, 2025
@wilkinsona wilkinsona added type: enhancement A general enhancement and removed status: waiting-for-triage An issue we've not yet triaged labels Feb 27, 2025
@wilkinsona wilkinsona added this to the 3.x milestone Feb 27, 2025
@nosan
Copy link
Contributor

nosan commented Feb 27, 2025

Agreed, that's why I chose not to include a try {} catch(...) {} block and instead added checks only for Resource.exists().

@rgoers
Copy link
Contributor Author

rgoers commented Feb 27, 2025

If using optional: as a prefix is the approved way of handling this kind of thing in Spring then I am fine with the proposed solution.

@nosan
Copy link
Contributor

nosan commented Feb 27, 2025

I think the former should cause a failure while the latter should not. This would be consistent with, for example, our handling of application.yaml where an app will start without one but will fail to start if the file's present but its syntax is invalid.

Unfortunately, it is not possible to have the same behaviour with Log4j2. If the configuration file is invalid, Log4j2 does not throw an exception.

@Test
@WithNonDefaultXmlResource
@WithResource(name = "override.xml", content = """
		<?xml version="1.0" encoding="UTF-8"?>
		<Configuration status="WARN"
		""")
void loadOptionalOverrideConfigurationWhenConfigurationIsInvalid() {
	this.environment.setProperty("logging.log4j2.config.override", "optional:classpath:override.xml");

        //Exception is not thrown here. 
	assertThatException().isThrownBy(
			() -> this.loggingSystem.initialize(this.initializationContext, "classpath:nondefault.xml", null));
}

XmlConfiguration
https://github.com/apache/logging-log4j2/blob/d8cbe77a0a1f60e6708a2bd8f84ea2fb8666c95a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/xml/XmlConfiguration.java#L136-L138

JsonConfiguration/YamlConfiguration
https://github.com/apache/logging-log4j2/blob/d8cbe77a0a1f60e6708a2bd8f84ea2fb8666c95a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/json/JsonConfiguration.java#L97-L99

BuiltConfiguration/PropertiesConfiguration
https://github.com/apache/logging-log4j2/blob/d8cbe77a0a1f60e6708a2bd8f84ea2fb8666c95a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/builder/impl/BuiltConfiguration.java#L122-L129

nosan added a commit to nosan/spring-boot that referenced this issue Feb 28, 2025
Introduced support for the 'optional:' prefix in Log4j2
override file locations, ensuring missing files are ignored without
throwing exceptions.

See spring-projectsgh-44399

Signed-off-by: Dmytro Nosan <[email protected]>
nosan added a commit to nosan/spring-boot that referenced this issue Feb 28, 2025
Introduced support for the 'optional:' prefix in Log4j2
override file locations, ensuring missing files are ignored without
throwing exceptions.

See spring-projectsgh-44399

Signed-off-by: Dmytro Nosan <[email protected]>
nosan added a commit to nosan/spring-boot that referenced this issue Feb 28, 2025
Introduced support for the 'optional:' prefix in Log4j2
override file locations, ensuring missing files are ignored without
throwing exceptions.

See spring-projectsgh-44399

Signed-off-by: Dmytro Nosan <[email protected]>
@nosan
Copy link
Contributor

nosan commented Feb 28, 2025

I opened PR: #44488 but then started wondering if it is the best solution for this issue.

Perhaps in Spring Boot 4.x, the Log4j2 override configuration loading could be revisited to align
more closely with Log4j2's default behavior. Instead of failing fast, it might be better
to simply log an error message to StandardLogger.

In that case, optional: will not be needed at all.

The remaining question is: how should this issue be handled in 3.x. ...?

@nosan
Copy link
Contributor

nosan commented Feb 28, 2025

Perhaps in Spring Boot 4.x, the Log4j2 override configuration loading could be revisited to align
more closely with Log4j2's default behavior. Instead of failing fast, it might be better
to simply log an error message to StandardLogger.

It seems that optional: is the preferred choice as it handles both scenarios:

  • When the override resource does not exist, it fails fast. (current behavior)
  • When the override resource does not exist and is prefixed with optional:, it skips gracefully.

On the other hand, if exceptions are caught and logged to StandardLogger, the fail-fast is not available anymore. (breaking change)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
type: enhancement A general enhancement
Projects
None yet
Development

No branches or pull requests

4 participants