Skip to content

Preserve Log4j 1 lookups when converting a properties configuration - #4353

Open
kalayciburak wants to merge 3 commits into
apache:2.xfrom
kalayciburak:fix/log4j1-converter-preserve-lookups
Open

kalayciburak wants to merge 3 commits into
apache:2.xfrom
kalayciburak:fix/log4j1-converter-preserve-lookups

Conversation

@kalayciburak

@kalayciburak kalayciburak commented Sep 25, 2026 •

Copy link
Copy Markdown
Contributor

Log4j1ConfigurationConverter shares Log4j1ConfigurationParser with the runtime factory. getProperty calls OptionConverter.substVars, which resolves each ${...} against the current JVM before the value is written. A converted file therefore captures host system properties (user.home, paths, anything set with -D) and loses the indirection.

The converter now rewrites every ${name} to ${sys:name} instead of resolving it. Log4j 1 reads system properties before the file, and ${sys:name} falls back to a configuration <Property> when -Dname is not set.

  • file-defined names are still emitted as <Property>
  • references, including nested names inside a property value, become ${sys:name}

Log4j1ConfigurationFactory still uses the default parser and resolves variables.

Fixes #4348

Checklist

  • Base your changes on 2.x branch if you are targeting Log4j 2; use main otherwise
  • Non-trivial changes contain an entry file in the src/changelog/.2.x.x directory
  • Tests are provided

./mvnw verify was not run.

Tests

Executed with JAVA_HOME set to Java 17:

  • ./mvnw -pl log4j-1.2-api -am -Dtest=Log4j1ConfigurationConverterLookupTest -Dsurefire.failIfNoSpecifiedTests=false test - 4 tests, 0 failures (includes the threshold lookup)
  • ./mvnw -pl log4j-1.2-api spotless:check - clean

The converter shared the runtime parser, so ${...} was resolved against
the current JVM and the output captured host system properties. Rewrite
file-defined names to configuration properties and other names to
${sys:name}. The runtime factory still resolves variables.

Fixes apache#4348

Signed-off-by: Burak KALAYCI <kalayciburak1996@gmail.com>
Signed-off-by: Burak KALAYCI <kalayciburak1996@gmail.com>
@ramanathan1504 ramanathan1504 added port-to-2.25.x port-to-2.26.x bug Incorrect, unexpected, or unintended behavior of existing code labels Sep 28, 2026

@ramanathan1504 ramanathan1504 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The converter now writes ${sys:user.home} instead of the home directory of the machine that ran it, and the new test fails on 2.x without the change.
Can translateLookups in Log4j1ConfigurationParser turn every ${name} into ${sys:name}? Log4j 1 reads system properties before the file, and ${sys:app.dir} falls back to the <Property> when -Dapp.dir is not set, so isDefinedProperty can go. The log4j.threshold branch for a value with ${ also needs a test in Log4j1ConfigurationConverterLookupTest.

Comment on lines +516 to +520
if (isDefinedProperty(key)) {
translated.append("${").append(key).append('}');
} else {
translated.append("${sys:").append(key).append('}');
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Log4j 1 checks system properties before the file. ${sys:app.dir} does the same and falls back to the <Property>, while ${app.dir} ignores -Dapp.dir.

Suggested change
if (isDefinedProperty(key)) {
translated.append("${").append(key).append('}');
} else {
translated.append("${sys:").append(key).append('}');
}
translated.append("${sys:").append(key).append('}');

Comment on lines 524 to 530

private boolean isDefinedProperty(final String key) {
return !key.startsWith("log4j.")
&& !key.equals(ROOTCATEGORY)
&& !key.equals(ROOTLOGGER)
&& properties.getProperty(key) != null;
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

With every name mapped to ${sys:...}, this method has no caller.

Suggested change
private boolean isDefinedProperty(final String key) {
return !key.startsWith("log4j.")
&& !key.equals(ROOTCATEGORY)
&& !key.equals(ROOTLOGGER)
&& properties.getProperty(key) != null;
}

+ "log4j.appender.FILE.File=${app.dir}/app.log\n");

assertFalse(xml.contains(home), xml);
assertTrue(xml.contains("${app.dir}/app.log"), xml);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This assert fails after the two changes above. The file property now reads through ${sys:app.dir}.

Suggested change
assertTrue(xml.contains("${app.dir}/app.log"), xml);
assertTrue(xml.contains("${sys:app.dir}/app.log"), xml);


assertTrue(xml.contains(home + "/logs/app.log"), xml);
assertFalse(xml.contains("${sys:user.home}"), xml);
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Nothing covers the threshold branch for a value with ${. This test fails if that branch is removed.

Suggested change
}
}
@Test
public void thresholdLookupIsKept() throws Exception {
final String xml = convert("log4j.rootLogger=INFO, FILE\n"
+ "log4j.threshold=${lvl}\n"
+ "log4j.appender.FILE=org.apache.log4j.FileAppender\n"
+ "log4j.appender.FILE.File=app.log\n");
assertTrue(xml.contains("level=\"${sys:lvl}\""), xml);
}

Log4j 1 reads system properties before the file, and ${sys:name} falls
back to a configuration property when the system property is unset.
Drop isDefinedProperty and cover the threshold lookup.

Signed-off-by: Burak KALAYCI <kalayciburak1996@gmail.com>
@kalayciburak

Copy link
Copy Markdown
Contributor Author

yes, every name is ${sys:name} now. dropped isDefinedProperty and added the threshold test.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Incorrect, unexpected, or unintended behavior of existing code port-to-2.26.x

Projects

Status: Changes requested

Development

Successfully merging this pull request may close these issues.

Log4j1ConfigurationConverter writes resolved system properties instead of lookups

2 participants