71
votes

Should it be possible to set logging levels through environment variables alone in a Spring Boot application?

I don't want to use application.properties as I'm running on Cloud Foundry and want to pick up changes without a deploy (but after the app has restarted, or restaged to be more precise).

I've tried setting env vars like LOGGING_LEVEL_ORG_SPRINGFRAMEWORK=TRACE but that has no effect. Putting logging.level.org.springframework: TRACE in application.properties does work though.

10
The two methods presented in the question should be equivalent based on Relaxed Binding. Maybe there was a bug in Spring Boot at the time (version not specified).Brent Bradburn
Maybe the OP changed the example value to simplify the problem description and thereby falsified the problem unintentionally. Please be aware that setting log levels via env variables only works for packages but not for classes! See my answer below for details.Peter Wippermann

10 Answers

95
votes

This is just an idea, but did you try setting

_JAVA_OPTIONS=-Dlogging.level.org.springframework=TRACE?

Theoretically, this way -Dlogging.level.org.springframework=TRACE will be passed as default JVM argument and should affect every JVM instance in your environment.

37
votes

Setting log levels via environment variables can only be done for Packages but not for Classes

I had the same problem as the OP. And I was wondering why some of the users here reported that the proposed solutions worked well while others responded they didn't.
I'm on Spring Boot 2.1 and the problem obviously changed a bit over the last years, but the current situation is as follows:

TL;DR

Setting the log level for a package works:

LOGGING_LEVEL_COM_ACME_PACKAGE=DEBUG

While setting the log level for a specific class has no effect:

LOGGING_LEVEL_COM_ACME_PACKAGE_CLASS=DEBUG

How can that be?

Have a look at Spring Boot's LoggingApplicationListener.
If you'd debug it and set a breakpoint in the highlighted code block, you'd see that the log level definition for a class com.acme.mypackage.MyClass becomes com.acme.mypackage.myclass.
So a log level definition for a class looks exactly like a log level definition for a package.

This is related to Spring's Relaxed Binding, which proposes an upper case notation for environment variables. Thus the typical camel case notation of a class is not visible for the LoggingApplicationListener: The environment variable for MyClass has to be defined as MYCLASS and will be available as myclass in Spring's Environment (this example ignores the fully-qualified name of the class).

Once the camel case notation of the class is lost, during runtime there's no chance to recover the original class name. Thus log definitions in environment variables don't work for classes but only for packages.

25
votes

I also tried to set logging level via environment variable but as already mentioned it is not possible by using environment variable with upper case name, eg. LOGGING_LEVEL_ORG_SPRINGFRAMEWORK=DEBUG. I also didn't want to do it via application.properties or _JAVA_OPTIONS.

After digging into class org.springframework.boot.logging.LoggingApplicationListener I've checked that spring boot tries to set logging level DEBUG to ORG_SPRINGFRAMEWORK package which is not real package name. So conclusion is that you can use environment variable to set logging level but it needs to be in the form: LOGGING_LEVEL_org.springframework=DEBUG or logging.level.org.springframework=DEBUG

Tested on spring boot 1.5.3

9
votes

Yes, you can control logging level using environment variable. Here is how I have implemented for my Spring Boot application, deployed on Cloud Foundry platform.

In you log configuration file provide placeholder for logging level to read value from environment variable. Default is INFO.

    <logger name="com.mycompany.apps.cf" level="${APP_LOGGING_LEVEL:-INFO}">
      <appender-ref ref="CONSOLE"/>
    </logger>

And then, in CF deployment manifest file provide environment variable.

    applications:
    - name: my-app-name
      memory: 2048
      env:
        APP_LOGGING_LEVEL: DEBUG

I hope this will help.

5
votes

Starting with Spring Boot 2.0.x this works again. Tested with Spring Boot v2.0.9.RELEASE. E.g. enable connection pool debug log:

LOGGING_LEVEL_COM_ZAXXER=DEBUG java -jar myApp.jar

or Spring framework debug log:

LOGGING_LEVEL_ORG_SPRINGFRAMEWORK=DEBUG java -jar myApp.jar

or both:

LOGGING_LEVEL_ORG_SPRINGFRAMEWORK=DEBUG LOGGING_LEVEL_COM_ZAXXER=DEBUG java -jar myApp.jar

See "Application Poperties" in Spring Boot Reference Documentation for more application properties.

4
votes

Also using Spring Boot (v1.2.3) in Cloud Foundry, I've found that it is possible to adjust the root logging level using an environment variable as follows:

$ cf set-env <app name> LOGGING_LEVEL_ROOT DEBUG

Unfortunately, it does not appear to be possible to dial-down the logging level for specific packages (at least with the version of Java Buildpack and Spring Boot I am using). For example adding the following in addition to the above does not reduce the log level for Spring framework:

$ cf set-env <app name> LOGGING_LEVEL_ORG_SPRINGFRAMEWORK INFO

If you are using something like Splunk to gather your logs, you may be able to filter out the noise, however.

Another alternative which looks promising could be based on customisation of the build pack's arguments option (see here):

$ cf set-env <app name> '{arguments: "-logging.level.root=DEBUG -logging.level.org.springframework=INFO"}'

Sadly, I couldn't get this to actually work. I certainly agree that being able to reconfigure logging levels at package level without changing the application code would be handy to get working.

3
votes

I would anyway suggest you to use Spring profiles:

  1. Create 2 properties files:

    application-local.properties and application-remote.properties

    (profile names can be different obviously)

  2. Set the logging level in each file accordingly (logging.level.org.springframework)

  3. Run your application with -Dspring.profiles.active=local locally and -Dspring.profiles.active=remote for CF.

1
votes

In spring-boot 2.0.0, adding --trace works. For instance java -jar myapp.jar --debug or java -jar myapp.jar --trace

https://docs.spring.io/spring-boot/docs/current/reference/html/boot-features-logging.html#boot-features-logging-console-output

0
votes

Here's an example using Logback with Janino to conditionally include different logging configs via properties or environmental variables... The base config, logback.xml is using conditionals for development console logging or production file logging... just drop the following files in /resources/


logback.xml


<?xml version="1.0" encoding="UTF-8"?>
<configuration scan="true">
    <if condition='property("spring.profiles.active").contains("dev")'>
        <then>
            <include resource="org/springframework/boot/logging/logback/base.xml"/>
            <include resource="dev.xml" optional="true"/>
        </then>
    </if>
    <if condition='property("spring.profiles.active").contains("pro")'>
        <then>
            <include resource="org/springframework/boot/logging/logback/base.xml"/>
            <include resource="pro.xml" optional="true"/>
        </then>
    </if>
</configuration>

dev.xml

<included>
    <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <charset>utf-8</charset>
            <Pattern>%-30([%p] [%c:%L]) » %m%n%rEx</Pattern>
        </encoder>
    </appender>

    <!-- CHATTY LOGGERS HERE.-->
    <logger name="org.springframework" level="DEBUG"/>

    <contextListener class="ch.qos.logback.classic.jul.LevelChangePropagator">
        <resetJUL>true</resetJUL>
    </contextListener>

    <root level="${logback.loglevel}">
        <appender-ref ref="CONSOLE"/>
    </root>

</included>

pro.xml

<included>
    <conversionRule conversionWord="wex"
                    converterClass="org.springframework.boot.logging.logback.WhitespaceThrowableProxyConverter"/>
    <property name="FILE_LOG_PATTERN"
              value="%d{yyyy-MM-dd HH:mm:ss.SSS} %5p ${PID:- } --- [%t] %-40.40logger{39} : %m%n%wex"/>
    <property name="FILE_NAME_PATTERN" value="./logs/%d{yyyy-MM-dd}-exec.log"/>

    <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <fileNamePattern>FILE_NAME_PATTERN</fileNamePattern>
            <maxHistory>7</maxHistory>
        </rollingPolicy>
        <encoder>
            <pattern>${FILE_LOG_PATTERN}</pattern>
        </encoder>
    </appender>

    <appender name="ASYNC" class="ch.qos.logback.classic.AsyncAppender">
        <queueSize>512</queueSize>
        <appender-ref ref="FILE"/>
    </appender>

    <!-- APP SPECIFIC LOGGERS HERE.-->
    <logger name="org.springframework.boot.SpringApplication" level="INFO"/>

    <root level="INFO">
        <appender-ref ref="FILE"/>
    </root>

</included>
0
votes

Folks can anyone explain why this is not working?

$ export LOGGING_LEVEL_COM_ACME=ERROR

For all other configuration using environment variables as an override seems to work with no issues, for example:

$ export EUREKA_CLIENT_ENABLED=false

Thanks.