First thumb up and then watch, form a good habit


The default logging framework for Spring Boot has always been Logback, which is well supported. For Logback, Spring Boot also provides an extension –

. This tag can be used in Logback’s XML configuration file. It is used with Spring’s profile to distinguish the environment, which is very convenient.

For example, you can configure a single logback-spring.xml configuration file as follows, and then use

to differentiate the environment, with the development environment only exporting to the console, while the other environment only exporting to the file

<Root level="INFO"> <! -- Use the Console Appender for your development environment. > <springProfile name="dev"> <AppenderRef ref="Console"/> </springProfile> <springProfile name="! dev"> <AppenderRef ref="File"/> </SpringProfile> </Root>

The advantage of this is that I only need one logback. XML configuration file to solve the problem for multiple environments, instead of one logback-spring.xml for each environment, which is really nice (the syntax for this Profile can also have some more flexible syntaxes (see more details)The official documentation for Spring Boot))

However, sometimes we choose log4j2 as the logging framework for Spring Boot for performance or other reasons. Spirng Boot also supports log4j2.

Switching to log4j2 is simple, but Spring Boot does not extend log4j2! Log4j2 does not support <SpringProfile> tag, does not happily configure multiple environments! Some people on StackOverflow have the same problem, and no one is currently offering this feature

So I had a bold idea: I would develop an extension to Spring Boot-Log4j2 XML that also supports the

tag, and then contribute to Spring Boot. If it was adopted, I would be very happy.

And this isn’t PR changing comments, changing punctuation, changing variable names; This is a new feature. If it is adopted, I will have a role to play in the documentation of Spring Boot.

The function development

Let’s start by analyzing the source code for Log4J2 XML parsing to see if we can get started

Log4J2 XML parsing source code analysis

After analysis, found the Log4j2 XML document parsing code in org. Apache. Logging. Log4j. Core. The config. XML. XmlConfiguration, carefully read + DEBUG after this class, The XML parsing class is either static or private. It is not designed to provide extended and customized tags. For example, this recursive method of parsing tags is directly private:

private void constructHierarchy(final Node node, final Element element) { processAttributes(node, element); final StringBuilder buffer = new StringBuilder(); final NodeList list = element.getChildNodes(); final List<Node> children = node.getChildren(); for (int i = 0; i < list.getLength(); i++) { final org.w3c.dom.Node w3cNode = list.item(i); if (w3cNode instanceof Element) { final Element child = (Element) w3cNode; final String name = getType(child); final PluginType<? > type = pluginManager.getPluginType(name); final Node childNode = new Node(node, name, type); constructHierarchy(childNode, child); if (type == null) { final String value = childNode.getValue(); if (! childNode.hasChildren() && value ! = null) { node.getAttributes().put(name, value); } else { status.add(new Status(name, element, ErrorType.CLASS_NOT_FOUND)); } } else { children.add(childNode); } } else if (w3cNode instanceof Text) { final Text data = (Text) w3cNode; buffer.append(data.getData()); } } final String text = buffer.toString().trim(); if (text.length() > 0 || (! node.hasChildren() && ! node.isRoot())) { node.setValue(text); }}

Even the parsed data is private

private Element rootElement;

It is impossible to override only part of a method by inheritance, unless you override the entire class to extend a custom tag…

Risk & Compatibility Considerations

This is embarrassing. It is possible to rewrite the entire class, but compatibility is not guaranteed. Because whenever Log4J2’s XML configuration is updated, my extension will become obsolete, no matter whether it’s a major update or a minor update, but whenever the class changes my extension will have to be rewritten, which is not safe.

But I’m checkingXmlConfigurationThe submission history of this class shows that it was last updated in June 2019

The entire Log4J2 framework, on the other hand, was released nine times between June 2019 and March 2021

The entire project has been updated for almost two years, and in nearly ten releases, XMLConfiguration has only been updated once, which indicates that the update frequency is low. And compared to the changelog, it turns out that this class has had very few updates in recent times.

With that in mind, what if I rewrite XMLConfiguration with such low update frequency and few updates? The risk of rewrite is low. And I am not all rewrite, just copy the original code, add a little custom tag support, change momentum is not big. Even if you need to follow Log4j2 updates, it’s not hard to compare the code and re-adjust it.

That’s how I convinced myself to start pulling the code…

Fork /clone code, local environment setup

Spring – the boot warehouse address:

  1. Fork a copy of Spring Boot code
  2. Clone the Fork’s warehouse
  3. Based on master, create a branch called Log4J2_Enhancement for development

Idea Clone can also be used directly here, but only if you have a “reliable and stable” network.

Since Spring/Spring Boot has migrated the build tools from Maven to Gradle, it is best that the IDEA version is not too old. The old version may not support Gradle well enough.

If your network is “reliable and stable”, just open the source code of Spring Boot in IDEA and you can build your own development environment and run your tests directly. Otherwise you may have problems with Gradle and related packages failing to download, Maven repository packages failing to download, etc…

Spring Boot support extension for Logback

Now that Spring Boot has enhanced Logback (XML), let’s take a look at how it is enhanced. It will save me a lot of work if I support Log4j2 later.

After some analysis, I found the extension point for this Logback:

class SpringBootJoranConfigurator extends JoranConfigurator { private LoggingInitializationContext initializationContext; SpringBootJoranConfigurator(LoggingInitializationContext initializationContext) { this.initializationContext = initializationContext; } @Override public void addInstanceRules(RuleStore rs) { super.addInstanceRules(rs); Environment environment = this.initializationContext.getEnvironment(); rs.addRule(new ElementSelector("configuration/springProperty"), new SpringPropertyAction(environment)); rs.addRule(new ElementSelector("*/springProfile"), new SpringProfileAction(environment)); rs.addRule(new ElementSelector("*/springProfile/*"), new NOPAction()); }}

Just…… It’s that simple? After going through the JoranConfigurator and the associated classes, we realized that it was all Logback’s work.

As mentioned in the Logback documentation, this Joran is actually a generic configuration system that can be used independently of the logging system. But I did a search and couldn’t find the source of the Joran beyond the Logback documentation.

It doesn’t matter though, I just refer to it as a generic configuration parser referenced by Logback.

The parser is flexible enough to customize the behavior of tag/tag parsing by overwriting the addInstanceRules method and adding custom tag names and behavior classes:

@Override public void addInstanceRules(RuleStore rs) { super.addInstanceRules(rs); Environment environment = this.initializationContext.getEnvironment(); rs.addRule(new ElementSelector("configuration/springProperty"), new SpringPropertyAction(environment)); // It is as simple as that... rs.addRule(new ElementSelector("*/springProfile"), new SpringProfileAction(environment)); rs.addRule(new ElementSelector("*/springProfile/*"), new NOPAction()); }

Then in the SpringProfileAction, through the Spring Environment object, get the currently active Profiles and match them

Do the same thing and add a custom extension to Log4J2

Although Log4J2’s XML parsing is not as flexible as Logback’s, it simply inserts extensions. However, based on my previous risk & compatibility analysis, it is possible to rewrite XMLConfiguration to implement custom tag resolution:

First create a SpringBootXmlConfiguration

The code for this class, it is completely copied the org. Apache. Logging. Log4j. Core. Config. XML. XmlConfiguration, then add the two Environment related parameters:

private final LoggingInitializationContext initializationContext;

private final Environment environment;

Then add the InitializationContext to the constructor and inject:

public SpringBootXmlConfiguration(final LoggingInitializationContext initializationContext, final LoggerContext loggerContext, final ConfigurationSource configSource) { super(loggerContext, configSource); this.initializationContext = initializationContext; this.environment = initializationContext.getEnvironment(); . }

Finally, just adjust the recursive parsing method mentioned above to add support for the SpringProfile tag:

private void constructHierarchy(final Node node, final Element element, Boolean Profilenode) {//SpringProfile Node does not need to handle property if (! profileNode) { processAttributes(node, element); } final StringBuilder buffer = new StringBuilder(); final NodeList list = element.getChildNodes(); final List<Node> children = node.getChildren(); for (int i = 0; i < list.getLength(); i++) { final org.w3c.dom.Node w3cNode = list.item(i); if (w3cNode instanceof Element) { final Element child = (Element) w3cNode; final String name = getType(child); // If the <SpringProfile> tag, Enhance log4j2.xml configuration if (spring_profile_tag_name.equalsignorecase (name)) { // If the defined Profile matches the currently active Profiles, recursively resolve the child nodes, If not, skip the current node (and children) if (acceptsProfiles(child.getAttribute("name"))) {constructhiy (node, child, true); } // Break <SpringProfile> node continue; } // Find the corresponding plug-in for the node, parse the node, add it to the node, and build the rootElement tree //...... }} // Copy the profile from Spring Boot-Logback to Spring Boot-Logback. private boolean acceptsProfiles(String profile) { if (this.environment == null) { return false; } String[] profileNames = StringUtils.trimArrayElements(StringUtils.commaDelimitedListToStringArray(profile)); if (profileNames.length == 0) { return false; } return this.environment.acceptsProfiles(Profiles.of(profileNames)); }

In the configuration SpringBootXmlConfiguration’s entrance

Well, you’re done, and that’s it. This little bit of code completes the enhancements to the Log4J2 XML. Now only need at the time of assembly Log4j2, to replace the default XmlConfiguration with my SpringBootXmlConfiguration can:

//org.springframework.boot.logging.log4j2.Log4J2LoggingSystem ...... LoggerContext ctx = getLoggerContext(); URL url = ResourceUtils.getURL(location); ConfigurationSource source = getConfigurationSource(url); Configuration configuration; if (url.toString().endsWith("xml") && initializationContext ! = null) {//XML file and the InitializationContext is not empty, Use the enhanced SpringBootXmlConfiguration parsing configuration = new SpringBootXmlConfiguration (initializationContext, CTX, source); } else { configuration = ConfigurationFactory.getInstance().getConfiguration(ctx, source); }...

Prepare unit tests

Now that the functionality is complete, it’s time to prepare the unit tests. Again, you can refer to the unit test class associated with Logback and just copy it and change it to the Log4J2 version.

Spring Boot the current version is used Junit5, now a new SpringBootXmlConfigurationTests class, and imitate Logback unit test class to write a pile test method and test the configuration file:

<! --profile-expression.xml--> <springProfile name="production | test"> <logger name="org.springframework.boot.logging.log4j2" level="TRACE" /> </springProfile> <! --production-file.xml--> <springProfile name="production"> <logger name="org.springframework.boot.logging.log4j2" level="TRACE" /> </springProfile> <! --multi-profile-names.xml--> <springProfile name="production, test"> <logger name="org.springframework.boot.logging.log4j2" level="TRACE" /> </springProfile> <! --nested.xml--> <springProfile name="outer"> <springProfile name="inner"> <logger name="org.springframework.boot.logging.log4j2" level="TRACE" /> </springProfile> </springProfile> ...
void profileActive(); void multipleNamesFirstProfileActive(); void multipleNamesSecondProfileActive(); void profileNotActive(); void profileExpressionMatchFirst(); void profileExpressionMatchSecond(); void profileExpressionNoMatch(); void profileNestedActiveActive(); void profileNestedActiveNotActive(); .

After a while, I finally wrote the unit tests and passed them all. Now it’s time to get ready to mention PR

Submit a PR

First, in the project after fork, make the Pull request

Then, select the branch to which you want PR and create PR

Then you need to fill out your PR description in detail

I described in detail the functionality I submitted, as well as the compatibility and risk issues I analyzed above:

Enhance the configuration of log4j2 (xml), support Profile-specific Configuration (<SpringProfile>), consistent with logback extension.

Spring Boot currently only enhances the Logback (XML) configuration to support the tag. This feature is very useful, but is not supported by Log4j2.

I copied the code in Log4j2 XML to parse the XML configuration and created a new SpringBootXmlConfiguration to support the tag, which is as simple and easy to use as Logback Extension.

Compatibility issues with rewriting the Log4j2 parsing code:

  1. I just copied the XmlConfiguration code directly from Log4j2, adding very little code and making no big changes like formatting. If there is an update to Log4j2, it is easy to rewrite the parsing class and update it accordingly.
  2. The XmlConfiguration class in Log4j2 was last updated in June 2019, with no updates between [2.12.0,2.14.1] and the default dependent version of Log4j2 in Springboot (master) is 2.14.1

To sum up, there is no risk in this kind of enhancement

Stuck with an unsympathetic CI check

After submitting the PR, I thought that was the end of the matter…

Spring Boot’s Github Action has a CI check. After a long wait, it tells me that the build failed…

Here details can be accessed to view the specific build log

CheckFormat/checkStyle failure…

This is an open source project that has strict code style requirements. My code is copied from Log4j2. The code style standards of the two projects are definitely different.

Adjust your code style

I went back to the Spring Boot contribution guide and found that they mentioned a Spring-JavaFormat plugin for checking/formatting code, Eclipse/Idea plugins, and Gradle/Maven plugins.

I naively thought that this IDEA plug-in could easily format my Code into Spring’s specification, but after installing it, the Reformat Code was found to be useless, and still could not pass the checkstyle… Anyone who knows how to use it can share it in the comments section

Then I started executing its CheckStyle task locally, tweaking the code style…

Execution of checkStyle/checkFormat is done from Gradle, so it can also be done from Gradle:

Spring Boot code style is very strict, such as comments must be full stop, the end of the file must be blank line, the order of the guide package, the length of each line of code requirements, etc. Very much

CheckStyle/CheckFormat will tell you which file or line has a problem, and you can modify it accordingly

After more than an hour of adjustment, I finally passed the code inspection… My glasses are all gone

Resubmit the code

After the code style/format adjustment was completed, I submitted the code again with the same branch. If you submit here, the CI check in that PR will be triggered automatically.

After about 20 minutes, the build was finally completed and passed

A reply from an official

Three or four days later, I received a response from the official and the PR I had submitted was closed…

Log4j2 is expected to provide an extension, which Spring Boot will then implement to enhance Log4j2.

And attach a issue, theme is Spring Boot for Log4j2 support problem, and added to this PR:


Although Spring Boot did not accept the code I contributed, it was not because my code was shitty 😂, but because it was intrusive, risky, and unfriendly. It would have been better to implement it in an extended way.

This also shows how important the extensibility of the program is. When designing a program or framework, we must consider extensibility and follow the Open Closed Principle.

It doesn’t matter that my contribution was rejected this time. At least Spring Boot has been officially aware of this requirement and has ready-made implementation code. If I have the opportunity in the future, I will continue to contribute other codes.

The appendix

The code submitted this time and the relevant PR address are all here. Those who are interested can refer to it.

  • “Pull Request” log4j2(XML) configuration enhancement, support < Springprofile > tag
  • “Source address”
  • “Log4J2 Issue” Investigate impact of switching default logging system to log4J2

Original is not easy, unauthorized reprint is prohibited. If my article is helpful to you, please feel free to support me at thumb up/bookmark/follow me