19

I'm trying to set Logback appender path programmatically. (RollingFileAppender with FixedWindowRollingPolicy to be exact)

I'm doing this because I want to enable my users to set the log path in a preference dialog (Eclipse RCP)

I've tried something like this, but I doesn't change the log path from what's defined in the configuration file:

Logger logback_logger = (ch.qos.logback.classic.Logger)LoggerFactory
   .getLogger(org.slf4j.Logger.ROOT_LOGGER_NAME);
RollingFileAppender<ILoggingEvent> rfappender = 
   (RollingFileAppender<ILoggingEvent>)logback_logger.getAppender("FILE");
rfappender.setFile(newFile);
FixedWindowRollingPolicy rollingPolicy = 
   (FixedWindowRollingPolicy)rfappender.getRollingPolicy();
rollingPolicy.setFileNamePattern(newPattern);
yshalbar
  • 1,455
  • 1
  • 9
  • 23

3 Answers3

28

Once you programmatically configure your appender, you need invoke its start() method. If the appender has sub-components, invoke start() on the sub-components first. You then add the appender to the logger of your choice.

Here is an example:

import ch.qos.logback.classic.Logger;
import ch.qos.logback.classic.encoder.PatternLayoutEncoder;
import ch.qos.logback.core.rolling.FixedWindowRollingPolicy;
import ch.qos.logback.core.rolling.RollingFileAppender;
import ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy;
import ch.qos.logback.core.util.StatusPrinter;
import org.slf4j.LoggerFactory;
import ch.qos.logback.classic.LoggerContext;

public class Main {
  public static void main(String[] args) {
    LoggerContext loggerContext = (LoggerContext) LoggerFactory.getILoggerFactory();

    RollingFileAppender rfAppender = new RollingFileAppender();
    rfAppender.setContext(loggerContext);
    rfAppender.setFile("testFile.log");
    FixedWindowRollingPolicy rollingPolicy = new FixedWindowRollingPolicy();
    rollingPolicy.setContext(loggerContext);
    // rolling policies need to know their parent
    // it's one of the rare cases, where a sub-component knows about its parent
    rollingPolicy.setParent(rfAppender);
    rollingPolicy.setFileNamePattern("testFile.%i.log.zip");
    rollingPolicy.start();

    SizeBasedTriggeringPolicy triggeringPolicy = new ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy();
    triggeringPolicy.setMaxFileSize("5MB");
    triggeringPolicy.start();

    PatternLayoutEncoder encoder = new PatternLayoutEncoder();
    encoder.setContext(loggerContext);
    encoder.setPattern("%-4relative [%thread] %-5level %logger{35} - %msg%n");
    encoder.start();

    rfAppender.setEncoder(encoder);
    rfAppender.setRollingPolicy(rollingPolicy);
    rfAppender.setTriggeringPolicy(triggeringPolicy);

    rfAppender.start();

    // attach the rolling file appender to the logger of your choice
    Logger logbackLogger = loggerContext.getLogger("Main");
    logbackLogger.addAppender(rfAppender);

    // OPTIONAL: print logback internal status messages
    StatusPrinter.print(loggerContext);

    // log something
    logbackLogger.debug("hello");
  }
}

The above code is the programmatic expression of the steps taken by the logback's XML configurator, i.e. Joran, when it parses the RollingFixedWindow.xml file.

Dancrumb
  • 26,597
  • 10
  • 74
  • 130
Ceki
  • 26,753
  • 7
  • 62
  • 71
  • 5
    (It’s a bit awkward disagreeing with you on logging stuff, but) this is not what I’m trying to do – I want to configure my logger with XML, and only change the location by code. That way, advanced users can control fine-grained logging properties, and novice users use the UI. Restarting the appender by code works; using system properties and ContextInitializer works better and less hard-coded, why is this incorrect? – yshalbar Jan 25 '12 at 10:28
  • Is this approach still valid with recent releases of Logback? – Paolo Fulgoni Apr 02 '14 at 08:45
  • The link to `RollingFixedWindow.xml` is broken – Paolo Fulgoni Apr 02 '14 at 08:47
  • Link fixed. Thanks. And yes, the general approach should still be valid with latest versions of logback. – Ceki Apr 02 '14 at 13:54
  • Link is broken again. – Florent Oct 31 '14 at 23:36
  • What if I want all settings to come from the XML, except the appender file path? I think that's why @yshalbar 's answers has a lot of upvotes. – Didier A. Nov 28 '15 at 01:36
  • I get `org.apache.logging.slf4j.Log4jLoggerFactory cannot be cast to ch.qos.logback.classic.LoggerContext`. This is why I prefer not to use slf4j. – Sridhar Sarnobat Aug 06 '18 at 18:32
  • Never mind, fixed it (using `` - note you have to not only do `mvn clean` but `rm ~/.m2/repository/org/apache/logging/log4j/log4j-slf4j-impl/2.2/log4j-slf4j-impl-2.2.jar` – Sridhar Sarnobat Aug 06 '18 at 18:44
15

Using system properties and reloading the configuration file seems cleaner:

change the logback.xml file:

<file>${log_path:-}myfile.log</file>
....
<FileNamePattern>${log_path:-}myfile.%i.log</FileNamePattern>

This will set the default location to the working directory. Then, use:

System.setProperty("log_path", my_log_path);

//Reload:
LoggerContext lc = (LoggerContext) LoggerFactory.getILoggerFactory();
ContextInitializer ci = new ContextInitializer(lc);
lc.reset();
try {
  //I prefer autoConfig() over JoranConfigurator.doConfigure() so I wouldn't need to find the file myself.
  ci.autoConfig(); 
} catch (JoranException e) {
  // StatusPrinter will try to log this
  e.printStackTrace();
}
StatusPrinter.printInCaseOfErrorsOrWarnings(lc);
yshalbar
  • 1,455
  • 1
  • 9
  • 23
  • 2
    Using the ContextInitializer is quite incorrect. See my answer for the correct approach. – Ceki Oct 19 '11 at 17:07
  • 1
    cool stuff! One thing I've changed: I use lc.putProperty("log_path", my_log_path) instead of System.setProperty. It looks better since 0 globals used. – Sasha Aug 04 '15 at 17:11
5

Looking at the Logback code, I have found a workaround:

rollingPolicy.stop();
rfappender.stop();
rollingPolicy.start();
rfappender.start();

This causes Logback to use the new definitions. It still feels like a workaround, though.

yshalbar
  • 1,455
  • 1
  • 9
  • 23