Setting Logback Appender path programmatically
Asked Answered
D

3

20

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);
Dyane answered 27/9, 2010 at 11:4 Comment(0)
D
16

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);
Dyane answered 28/9, 2010 at 8:29 Comment(2)
Using the ContextInitializer is quite incorrect. See my answer for the correct approach.Maximinamaximize
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.Huck
M
30

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.

Maximinamaximize answered 19/10, 2011 at 17:39 Comment(8)
(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?Dyane
Is this approach still valid with recent releases of Logback?Jigaboo
The link to RollingFixedWindow.xml is brokenJigaboo
Link fixed. Thanks. And yes, the general approach should still be valid with latest versions of logback.Maximinamaximize
Link is broken again.Unbelief
What if I want all settings to come from the XML, except the appender file path? I think that's why @Dyane 's answers has a lot of upvotes.Distant
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.Wendish
Never mind, fixed it (using <exclusion> - 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.jarWendish
D
16

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);
Dyane answered 28/9, 2010 at 8:29 Comment(2)
Using the ContextInitializer is quite incorrect. See my answer for the correct approach.Maximinamaximize
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.Huck
D
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.

Dyane answered 28/9, 2010 at 6:51 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.