Read environment variables from logback configuration file

I have this logback.xml file:

<configuration debug="true" scan="true" scanPeriod="60 seconds">

  <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
    <layout class="ch.qos.logback.classic.PatternLayout">
      <Pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</Pattern>
    </layout>
  </appender>

  <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
    <File>${MY_HOME}/logs/mylog.log</File>

    <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
      <FileNamePattern>logs/my.%d{yyyy-MM-dd}.log</FileNamePattern>
      <MaxHistory>30</MaxHistory>
    </rollingPolicy>

    <layout class="ch.qos.logback.classic.PatternLayout">
      <Pattern>%d{HH:mm:ss.SSS} [%thread] %-5level - %msg%n</Pattern>
    </layout>

  </appender> 

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

</configuration>

And ${MY_HOME} is a defined system variable (echo $MY_HOME on linux shows the correct path).

The thing is that logback doesnt seem to read it properly, it stores the logs under MY_HOME_IS_UNDEFINED/logs/my.log

What am I doing wrong? Thanks a lot!

EDIT: I made a mistake and put OSC_HOME where I really meant MY_HOME. Sorry about that


Contrary to what the others have said, the logback documentation explicitly states that "During substitution, properties are looked up in the local scope first, in the context scope second, in the system properties scope third, and in the OS environment fourth and last". So if the property is defined in the environment, logback will find it.

I was having the same issue when running my project in Eclipse. If that's the issue you're having, it can be fixed by going to Run Configurations -> Environment and adding MY_HOME to the environment variables.

Not really sure why it isn't loading the native environment by default. There's even an option called "Append environment to native environment" which doesn't seem to have any effect for me.


There is an alternative way to read environment variables from config file. you can put your custom variables to logback context with context listener.

logback.xml

<?xml version="1.0" encoding="UTF-8"?>
<configuration debug="true" scan="true" scanPeriod="30 seconds">

    <!-- THIS IS OUR CUSTOM CONTEXT LISTENER -->
    <contextListener class="com.myapp.logging.listener.LoggerStartupListener"/>

    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>[%-5level] %d{HH:mm:ss.SSS} [%.6thread] %logger - %msg%n</pattern>
            <charset>UTF-8</charset>
        </encoder>
    </appender>

    <appender name="FILEOUT" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>${MY_HOME}/${LOG_FILE}.log</file>
        <append>true</append>
        <!-- Support multiple-JVM writing to the same log file -->
        <prudent>true</prudent>
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <!-- Daily rollover -->
            <fileNamePattern>${MY_HOME}/${LOG_FILE}.%d{yyyy-MM-dd}.log</fileNamePattern>
            <!-- Keep 7 days' worth of history -->
            <maxHistory>7</maxHistory>
        </rollingPolicy>
        <encoder>
            <pattern>[%-5level] %d{HH:mm:ss.SSS} [%.6thread] %logger - %msg%n</pattern>
            <charset>UTF-8</charset>
        </encoder>
    </appender>

    <root level="INFO">
        <appender-ref ref="STDOUT"/>
        <appender-ref ref="FILEOUT"/>
    </root>
</configuration>

LoggerStartupListener.java

package com.myapp.logging.listener;

import ch.qos.logback.classic.Level;
import ch.qos.logback.classic.Logger;
import ch.qos.logback.classic.LoggerContext;
import ch.qos.logback.classic.spi.LoggerContextListener;
import ch.qos.logback.core.Context;
import ch.qos.logback.core.spi.ContextAwareBase;
import ch.qos.logback.core.spi.LifeCycle;

public class LoggerStartupListener extends ContextAwareBase implements LoggerContextListener, LifeCycle {

    private static final String DEFAULT_LOG_FILE = "MYAPP";

    private boolean started = false;

    @Override
    public void start() {
        if (started) return;

        String userHome = System.getProperty("user.home"); 

        String logFile = System.getProperty("log.file"); // log.file is our custom jvm parameter to change log file name dynamicly if needed

        logFile = (logFile != null && logFile.length() > 0) ? logFile : DEFAULT_LOG_FILE;

        Context context = getContext();

        context.putProperty("MY_HOME", userHome);
        context.putProperty("LOG_FILE", logFile);

        started = true;
    }

    @Override
    public void stop() {
    }

    @Override
    public boolean isStarted() {
        return started;
    }

    @Override
    public boolean isResetResistant() {
        return true;
    }

    @Override
    public void onStart(LoggerContext context) {
    }

    @Override
    public void onReset(LoggerContext context) {
    }

    @Override
    public void onStop(LoggerContext context) {
    }

    @Override
    public void onLevelChange(Logger logger, Level level) {
    }
}

You perhaps mean MY_HOME. In your config file there is reference for OSC_HOME. See Variable substitution rules of Logback for details.

You can pass environment variable as a Java System property and then Logback will perform the variable substitution. You can pass this as JVM option in your command line. For example:

java -DMY_HOME=${MY_HOME} -cp ... MainClass

Or You can define MY_HOME in your config file itself.

<configuration debug="true" scan="true" scanPeriod="60 seconds">
  <property name="MY_HOME" value="/home/my" />
  <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
    <File>${MY_HOME}/logs/mylog.log</File>
  </appender> 
</configuration>

If you're using Eclipse you have to restart it to pick up environment variables, but you can't use: File -> Restart

Instead you actually have to fully shut it down and then start it back up again.