Skip to content

Commit

Permalink
Upgrade to Logback 1.4 and SLF4J 2.0
Browse files Browse the repository at this point in the history
Closes gh-12649
  • Loading branch information
wilkinsona committed Sep 28, 2022
1 parent 05d2f3c commit 0bfa9cd
Show file tree
Hide file tree
Showing 19 changed files with 355 additions and 190 deletions.
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2012-2021 the original author or authors.
* Copyright 2012-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand All @@ -24,7 +24,7 @@
import io.micrometer.core.instrument.binder.MeterBinder;
import io.micrometer.core.instrument.composite.CompositeMeterRegistry;
import org.junit.jupiter.api.Test;
import org.slf4j.impl.StaticLoggerBinder;
import org.slf4j.LoggerFactory;

import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.boot.actuate.autoconfigure.metrics.export.atlas.AtlasMetricsExportAutoConfiguration;
Expand Down Expand Up @@ -76,8 +76,7 @@ void customizersAreAppliedBeforeBindersAreCreated() {
void counterIsIncrementedOncePerEventWithoutCompositeMeterRegistry() {
new ApplicationContextRunner().with(MetricsRun.limitedTo(JmxMetricsExportAutoConfiguration.class))
.withConfiguration(AutoConfigurations.of(LogbackMetricsAutoConfiguration.class)).run((context) -> {
Logger logger = ((LoggerContext) StaticLoggerBinder.getSingleton().getLoggerFactory())
.getLogger("test-logger");
Logger logger = ((LoggerContext) LoggerFactory.getILoggerFactory()).getLogger("test-logger");
logger.error("Error.");
Map<String, MeterRegistry> registriesByName = context.getBeansOfType(MeterRegistry.class);
assertThat(registriesByName).hasSize(1);
Expand All @@ -92,8 +91,7 @@ void counterIsIncrementedOncePerEventWithCompositeMeterRegistry() {
.with(MetricsRun.limitedTo(JmxMetricsExportAutoConfiguration.class,
PrometheusMetricsExportAutoConfiguration.class))
.withConfiguration(AutoConfigurations.of(LogbackMetricsAutoConfiguration.class)).run((context) -> {
Logger logger = ((LoggerContext) StaticLoggerBinder.getSingleton().getLoggerFactory())
.getLogger("test-logger");
Logger logger = ((LoggerContext) LoggerFactory.getILoggerFactory()).getLogger("test-logger");
logger.error("Error.");
Map<String, MeterRegistry> registriesByName = context.getBeansOfType(MeterRegistry.class);
assertThat(registriesByName).hasSize(3);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
import ch.qos.logback.classic.LoggerContext;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.slf4j.impl.StaticLoggerBinder;
import org.slf4j.LoggerFactory;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.condition.ConditionEvaluationReport;
Expand Down Expand Up @@ -147,8 +147,8 @@ void noErrorIfNotInitialized(CapturedOutput output) {
}

private void withDebugLogging(Runnable runnable) {
LoggerContext context = (LoggerContext) StaticLoggerBinder.getSingleton().getLoggerFactory();
Logger logger = context.getLogger(ConditionEvaluationReportLoggingListener.class);
Logger logger = ((LoggerContext) LoggerFactory.getILoggerFactory())
.getLogger(ConditionEvaluationReportLoggingListener.class);
Level currentLevel = logger.getLevel();
logger.setLevel(Level.DEBUG);
try {
Expand Down
6 changes: 4 additions & 2 deletions spring-boot-project/spring-boot-dependencies/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -863,7 +863,7 @@ bom {
]
}
}
library("Logback", "1.2.11") {
library("Logback", "1.4.1") {
group("ch.qos.logback") {
modules = [
"logback-access",
Expand Down Expand Up @@ -1366,7 +1366,7 @@ bom {
]
}
}
library("SLF4J", "1.7.36") {
library("SLF4J", "2.0.2") {
group("org.slf4j") {
modules = [
"jcl-over-slf4j",
Expand All @@ -1375,9 +1375,11 @@ bom {
"slf4j-api",
"slf4j-ext",
"slf4j-jcl",
"slf4j-jdk-platform-logging",
"slf4j-jdk14",
"slf4j-log4j12",
"slf4j-nop",
"slf4j-reload4j",
"slf4j-simple"
]
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ plugins {
description = "Starter for using Log4j2 for logging. An alternative to spring-boot-starter-logging"

dependencies {
api("org.apache.logging.log4j:log4j-slf4j-impl")
api("org.apache.logging.log4j:log4j-slf4j2-impl")
api("org.apache.logging.log4j:log4j-core")
api("org.apache.logging.log4j:log4j-jul")
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
import ch.qos.logback.classic.Level;
import org.slf4j.ILoggerFactory;
import org.slf4j.Logger;
import org.slf4j.impl.StaticLoggerBinder;
import org.slf4j.LoggerFactory;

import org.springframework.util.ClassUtils;

Expand All @@ -32,7 +32,7 @@
public abstract class LogbackInitializer {

public static void initialize() {
if (ClassUtils.isPresent("org.slf4j.impl.StaticLoggerBinder", null)
if (ClassUtils.isPresent("org.slf4j.LoggerFactory", null)
&& ClassUtils.isPresent("ch.qos.logback.classic.Logger", null)) {
new Initializer().setRootLogLevel();
}
Expand All @@ -41,7 +41,7 @@ public static void initialize() {
private static class Initializer {

void setRootLogLevel() {
ILoggerFactory factory = StaticLoggerBinder.getSingleton().getLoggerFactory();
ILoggerFactory factory = LoggerFactory.getILoggerFactory();
Logger logger = factory.getLogger(Logger.ROOT_LOGGER_NAME);
((ch.qos.logback.classic.Logger) logger).setLevel(Level.INFO);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
import ch.qos.logback.core.ConsoleAppender;
import ch.qos.logback.core.rolling.RollingFileAppender;
import ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy;
import ch.qos.logback.core.spi.ScanException;
import ch.qos.logback.core.util.FileSize;
import ch.qos.logback.core.util.OptionHelper;

Expand Down Expand Up @@ -146,7 +147,12 @@ private Charset resolveCharset(LogbackConfigurator config, String val) {
}

private String resolve(LogbackConfigurator config, String val) {
return OptionHelper.substVars(val, config.getContext());
try {
return OptionHelper.substVars(val, config.getContext());
}
catch (ScanException ex) {
throw new RuntimeException(ex);
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,9 @@
import ch.qos.logback.core.util.StatusListenerConfigHelper;
import org.slf4j.ILoggerFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.Marker;
import org.slf4j.bridge.SLF4JBridgeHandler;
import org.slf4j.impl.StaticLoggerBinder;

import org.springframework.boot.logging.AbstractLoggingSystem;
import org.springframework.boot.logging.LogFile;
Expand Down Expand Up @@ -348,7 +348,7 @@ private ch.qos.logback.classic.Logger getLogger(String name) {
}

private LoggerContext getLoggerContext() {
ILoggerFactory factory = StaticLoggerBinder.getSingleton().getLoggerFactory();
ILoggerFactory factory = LoggerFactory.getILoggerFactory();
Assert.isInstanceOf(LoggerContext.class, factory,
() -> String.format(
"LoggerFactory is not a Logback LoggerContext but Logback is on "
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2012-2019 the original author or authors.
* Copyright 2012-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand All @@ -17,18 +17,18 @@
package org.springframework.boot.logging.logback;

import ch.qos.logback.classic.joran.JoranConfigurator;
import ch.qos.logback.core.joran.action.NOPAction;
import ch.qos.logback.core.joran.spi.ElementSelector;
import ch.qos.logback.core.joran.spi.RuleStore;
import ch.qos.logback.core.model.processor.DefaultProcessor;

import org.springframework.boot.logging.LoggingInitializationContext;
import org.springframework.core.env.Environment;

/**
* Extended version of the Logback {@link JoranConfigurator} that adds additional Spring
* Boot rules.
*
* @author Phillip Webb
* @author Andy Wilkinson
*/
class SpringBootJoranConfigurator extends JoranConfigurator {

Expand All @@ -39,12 +39,22 @@ class SpringBootJoranConfigurator extends JoranConfigurator {
}

@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());
protected void addModelHandlerAssociations(DefaultProcessor defaultProcessor) {
super.addModelHandlerAssociations(defaultProcessor);
defaultProcessor.addHandler(SpringPropertyModel.class,
(handlerContext, handlerMic) -> new SpringPropertyModelHandler(this.context,
this.initializationContext.getEnvironment()));
defaultProcessor.addHandler(SpringProfileModel.class,
(handlerContext, handlerMic) -> new SpringProfileModelHandler(this.context,
this.initializationContext.getEnvironment()));
}

@Override
public void addElementSelectorAndActionAssociations(RuleStore ruleStore) {
super.addElementSelectorAndActionAssociations(ruleStore);
ruleStore.addRule(new ElementSelector("configuration/springProperty"), SpringPropertyAction::new);
ruleStore.addRule(new ElementSelector("*/springProfile"), SpringProfileAction::new);
ruleStore.addTransparentPathPart("springProfile");
}

}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2012-2019 the original author or authors.
* Copyright 2012-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand All @@ -16,102 +16,29 @@

package org.springframework.boot.logging.logback;

import java.util.ArrayList;
import java.util.List;

import ch.qos.logback.core.joran.action.Action;
import ch.qos.logback.core.joran.event.InPlayListener;
import ch.qos.logback.core.joran.event.SaxEvent;
import ch.qos.logback.core.joran.spi.ActionException;
import ch.qos.logback.core.joran.spi.InterpretationContext;
import ch.qos.logback.core.joran.spi.Interpreter;
import ch.qos.logback.core.util.OptionHelper;
import ch.qos.logback.core.joran.action.BaseModelAction;
import ch.qos.logback.core.joran.spi.SaxEventInterpretationContext;
import ch.qos.logback.core.model.Model;
import org.xml.sax.Attributes;

import org.springframework.core.env.Environment;
import org.springframework.core.env.Profiles;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;

/**
* Logback {@link Action} to support {@code <springProfile>} tags. Allows section of a
* logback configuration to only be enabled when a specific profile is active.
* Logback {@link BaseModelAction} for {@code <springProperty>} tags. Allows a section of
* a Logback configuration to only be enabled when a specific profile is active.
*
* @author Phillip Webb
* @author Eddú Meléndez
* @author Andy Wilkinson
* @see SpringProfileModel
* @see SpringProfileModelHandler
*/
class SpringProfileAction extends Action implements InPlayListener {

private final Environment environment;

private int depth = 0;

private boolean acceptsProfile;

private List<SaxEvent> events;

SpringProfileAction(Environment environment) {
this.environment = environment;
}

@Override
public void begin(InterpretationContext ic, String name, Attributes attributes) throws ActionException {
this.depth++;
if (this.depth != 1) {
return;
}
ic.pushObject(this);
this.acceptsProfile = acceptsProfiles(ic, attributes);
this.events = new ArrayList<>();
ic.addInPlayListener(this);
}

private boolean acceptsProfiles(InterpretationContext ic, Attributes attributes) {
if (this.environment == null) {
return false;
}
String[] profileNames = StringUtils
.trimArrayElements(StringUtils.commaDelimitedListToStringArray(attributes.getValue(NAME_ATTRIBUTE)));
if (profileNames.length == 0) {
return false;
}
for (int i = 0; i < profileNames.length; i++) {
profileNames[i] = OptionHelper.substVars(profileNames[i], ic, this.context);
}
return this.environment.acceptsProfiles(Profiles.of(profileNames));
}

@Override
public void end(InterpretationContext ic, String name) throws ActionException {
this.depth--;
if (this.depth != 0) {
return;
}
ic.removeInPlayListener(this);
verifyAndPop(ic);
if (this.acceptsProfile) {
addEventsToPlayer(ic);
}
}

private void verifyAndPop(InterpretationContext ic) {
Object o = ic.peekObject();
Assert.state(o != null, "Unexpected null object on stack");
Assert.isInstanceOf(SpringProfileAction.class, o, "logback stack error");
Assert.state(o == this, "ProfileAction different than current one on stack");
ic.popObject();
}

private void addEventsToPlayer(InterpretationContext ic) {
Interpreter interpreter = ic.getJoranInterpreter();
this.events.remove(0);
this.events.remove(this.events.size() - 1);
interpreter.getEventPlayer().addEventsDynamically(this.events, 1);
}
class SpringProfileAction extends BaseModelAction {

@Override
public void inPlay(SaxEvent event) {
this.events.add(event);
protected Model buildCurrentModel(SaxEventInterpretationContext interpretationContext, String name,
Attributes attributes) {
SpringProfileModel model = new SpringProfileModel();
model.setName(attributes.getValue(NAME_ATTRIBUTE));
return model;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/*
* Copyright 2012-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.springframework.boot.logging.logback;

import ch.qos.logback.core.model.NamedModel;

/**
* Logback {@link NamedModel model} to support {@code <springProfile>} tags.
*
* @author Andy Wilkinson
* @see SpringProfileAction
* @see SpringProfileModelHandler
*/
class SpringProfileModel extends NamedModel {

}
Loading

2 comments on commit 0bfa9cd

@eugeniace
Copy link

Choose a reason for hiding this comment

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

Hello! After this upgrade, on application having slf4j and logback, we receive the following error when building in native mode:

Fatal error: com.oracle.graal.pointsto.util.AnalysisError$ParsingError: Error encountered while parsing org.springframework.web.util.UrlPathHelper.()
Parsing context:
at org.graalvm.nativeimage.pointsto/com.oracle.graal.pointsto.util.AnalysisError.parsingError(AnalysisError.java:152)
...
Caused by: org.graalvm.compiler.java.BytecodeParser$BytecodeParserError: java.util.ServiceConfigurationError: org.slf4j.spi.SLF4JServiceProvider: ch.qos.logback.classic.spi.LogbackServiceProvider not a subtype
at parsing org.springframework.web.util.UrlPathHelper.(UrlPathHelper.java:65)
at jdk.internal.vm.compiler/org.graalvm.compiler.java.BytecodeParser.throwParserError(BytecodeParser.java:2506)
...
Caused by: java.util.ServiceConfigurationError: org.slf4j.spi.SLF4JServiceProvider: ch.qos.logback.classic.spi.LogbackServiceProvider not a subtype
...

@snicoll
Copy link
Member

Choose a reason for hiding this comment

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

Thanks @eugeniace we've noticed this too and are working on a fix.

Please sign in to comment.