Hot questions for Spring Logback

Top 10 Java Open Source / Spring / Spring Logback

Question:

I'm using Discovery first bootstrap feature and Consul as a Discovery Server, url to Config Server is located during start-up and I was able to get application.properties. I need also to get logback-spring.xml configuration from Config server and I don't know how.

What should I specify in logging.config={???}logback-spring.xml property to not hardcode url to Config Server?

Before Consul integration I was using url formed according to Serving Plain text documentation with hardcoded Config server url in properties and it was working fine, but now we want to avoid this.

From what I debugged there is no usage of Discovery client during reinitializing logging system in PropertySourceBootstrapConfiguration.


Answer:

I used Customizing Bootstrap Configuration to resolve my issue in a 'custom' way because I didn't find the solution in the documentation and source code.

Example: Add new file src/main/resources/META-INF/spring.factories and add there custom bootstrap configuration: org.springframework.cloud.bootstrap.BootstrapConfiguration=sample.custom.CustomPropertySourceLocator

In CustomPropertySourceLocator create property that will point to config server url (looked up via discovery)

@Configuration
public class CustomPropertySourceLocator implements PropertySourceLocator {

  private final String configServiceName;
  private final DiscoveryClient discoveryClient;

  public CustomPropertySourceLocator(
      @Value("${spring.cloud.config.discovery.service-id}") String configServiceName,
      DiscoveryClient discoveryClient){
    this.configServiceName = configServiceName;
    this.discoveryClient = discoveryClient;
  }

  @Override
  public PropertySource<?> locate(Environment environment) {
    List<ServiceInstance> instances = this.discoveryClient.getInstances(this.configServiceName);
    ServiceInstance serviceInstance = instances.get(0);

    return new MapPropertySource("customProperty",
      Collections.singletonMap("configserver.discovered.uri", serviceInstance.getUri()));
  }
}

In code above we created custom property source that will have one property configserver.discovered.uri. We can use this property in our code (using @Value) or in other property files (even if they are located in the config-server storage).

logging.config=${configserver.discovered.uri}/<path to the text file>/logback-spring.xml where <path to text file> should be formed according to the Serving Plain Text Documentation and the way how you configured your config-server.

Question:

I am converting a spring app to spring-boot, using boot-starter-parent version: 2.0.4.RELEASE. When I build using mvn install, its is going through fine, but when I am trying to run the app using command:mvn spring-boot:run -Dspring.profiles.active=dev, I am getting this exception: ClassNotFoundException: org.slf4j.impl.StaticLoggerBinder

Here are dependencies in my pom:

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.0.4.RELEASE</version>
</parent>

<dependencies>
  <dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-sleuth</artifactId>
    <version>2.0.1.RELEASE</version>
  </dependency>
  <dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-api</artifactId>
    <version>1.7.24</version>
  </dependency>
  <dependency>
    <groupId>ch.qos.logback</groupId>
    <artifactId>logback-classic</artifactId>
    <version>1.3.0-alpha4</version>
  </dependency>
  <dependency>
    <groupId>ch.qos.logback</groupId>
    <artifactId>logback-core</artifactId>
    <version>1.3.0-alpha4</version>
  </dependency>
</dependencies>

I have tried following the advice from this question, and using new and old versions of logback (both core and classic) dependencies, and adding the 'slf4j-log4j12' and 'slf4j-simple' but still getting the exception. Stack Trace is:

java.lang.NoClassDefFoundError: org/slf4j/impl/StaticLoggerBinder at org.springframework.boot.logging.logback.LogbackLoggingSystem.getLoggerContext (LogbackLoggingSystem.java:285) at org.springframework.boot.logging.logback.LogbackLoggingSystem.beforeInitialize (LogbackLoggingSystem.java:102) at org.springframework.boot.context.logging.LoggingApplicationListener.onApplicationStartingEvent (LoggingApplicationListener.java:191) at org.springframework.boot.context.logging.LoggingApplicationListener.onApplicationEvent (LoggingApplicationListener.java:170) at org.springframework.context.event.SimpleApplicationEventMulticaster.invokeListener (SimpleApplicationEventMulticaster.java:167) at org.springframework.context.event.SimpleApplicationEventMulticaster.multicastEvent (SimpleApplicationEventMulticaster.java:139) at org.springframework.context.event.SimpleApplicationEventMulticaster.multicastEvent (SimpleApplicationEventMulticaster.java:122) at org.springframework.boot.context.event.EventPublishingRunListener.starting (EventPublishingRunListener.java:68) at org.springframework.boot.SpringApplicationRunListeners.starting (SpringApplicationRunListeners.java:48) at org.springframework.boot.SpringApplication.run (SpringApplication.java:316) at org.springframework.boot.SpringApplication.run (SpringApplication.java:1258) at org.springframework.boot.SpringApplication.run (SpringApplication.java:1246) at com.hbo.esp.MyApplication.main (MyApplication.java:17) at sun.reflect.NativeMethodAccessorImpl.invoke0 (Native Method)


Answer:

Try changing version of each dependency as follow.

<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-api</artifactId>
    <version>1.7.25</version>
</dependency>
<dependency>
    <groupId>ch.qos.logback</groupId>
    <artifactId>logback-classic</artifactId>
    <version>1.2.3</version>
</dependency>
<dependency>
    <groupId>ch.qos.logback</groupId>
    <artifactId>logback-core</artifactId>
    <version>1.2.3</version>
</dependency>

Hope this helps.

Question:

Do I need to worry about logging to console, using Slf4j on top of Logback, being a blocking operation?

I wasn't sure, so I've been using reactor.logback.AsyncAppender from io.projectreactor.addons:reactor-logback:3.2.3-RELEASE

The issue I have now is that I can't compile my code using Maven as there's a transitive dependency on com.sun.java:tools:11-internal that can't be satisfied. I'm not sure how to rectify this issue, so now questioning do I even need this library.

Using Spring-boot 2.1.4-RELEASE and OpenJDK 11.0.3 with Maven 3.6.0

Maven error:

[ERROR] Failed to execute goal on project yooblr-web: Could not resolve dependencies for project com.yooblr:yooblr-web:jar:1.0.0-SNAPSHOT: Could not find artifact com.sun.java:tools:jar:11.0.3 at specified path /Library/Java/JavaVirtualMachines/adoptopenjdk-11.jdk/Contents/Home/../lib/tools.jar -> [Help 1]

Answer:

ch.qos.Logback.classic.AsyncAppender can be suitable in your case. It asynchronously delegates logging to regular appenders without blocking the main thread of execution. Its pros/cons are pretty well described in the following SO thread: When not to use AsyncAppender in logback by default

Question:

Here is my sample logback-spring.xml

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <property name="LOG_FILE" value="some file path here"/>
    <property name="LOG_FILE_MAX_SIZE" value="50MB" />
    <property name="LOG_FILE_MAX_HISTORY" value="30" />
    <include resource="org/springframework/boot/logging/logback/defaults.xml" />
    <include resource="org/springframework/boot/logging/logback/file-appender.xml" />
    <include resource="org/springframework/boot/logging/logback/console-appender.xml" />

<springProfile name="!test &amp; !prod">
    <logger name="com.myapp" level="DEBUG" />
    <root level="INFO">
        <appender-ref ref="CONSOLE"/>
    </root> 
</springProfile>

<springProfile name="test">
    <logger name="com.myapp" level="DEBUG" />
    <root level="WARN">
        <appender-ref ref="FILE" />
    </root>
</springProfile>

<springProfile name="prod">
    <logger name="com.myapp" level="INFO" />
    <root level="WARN">
        <appender-ref ref="FILE"/>
    </root>
</springProfile>

My intent is to log contents only to FILE for test & prod profile, however for any other profile(i.e., localhost), I would want my logs to get only in CONSOLE.

With the above setting, if i start my spring boot app(version 2.1.1.RELEASE) with localhost profile, Its getting logged only in CONSOLE as expected, however if i use test or prod profile, it logs the content both in CONSOLE as well as FILE.

Do you see any problem in the logback xml ?


Answer:

As far as I know Spring does not allow logical expressions in the profile selection. One can have just say !test but not a combination of such. So in your case the condition will trigger if test is not active or prod is not active which means for test active or prod active.

What you can do is to have an enumeration of other profiles for which you actually want to log to console. E. g. localhost.