40

I am using logback, and I am trying to set the log file name programmatically within my Java program (similar to Setting Logback Appender path programmatically), and I tried to adapt that solution as follows:

In logback-test.xml:

<appender name="FILE" class="ch.qos.logback.core.FileAppender">
  <file>log/${log_file_name}.log</file>
  ...

And then again in my Java program:

String logFileName = "" + System.currentTimeMillis(); // just for example
System.setProperty("log_file_name", logFileName);

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);

However the result is two logs, one full and named as I wanted, e.g., "1319041145343.log", and the other is empty and named "log_file_name_IS_UNDEFINED.log". How do I stop this other empty log file from being created?

Cœur
  • 37,241
  • 25
  • 195
  • 267
conorsomahony
  • 505
  • 1
  • 4
  • 7
  • The only problem of your code seems to be that you are setting `System.setProperty("log_file_name", logFileName);` too late. Execute it before the Logback autoconfig has been executed and you have what you want. – Robert Jan 30 '13 at 15:05
  • It can actually be done much easier than in your code snippet: http://stackoverflow.com/a/21886071/709537 – Evgeniy Berezovsky Nov 25 '16 at 01:31

5 Answers5

41

I believe the following to be closer to what you want.

import ch.qos.logback.classic.Logger;
import ch.qos.logback.classic.encoder.PatternLayoutEncoder;
import ch.qos.logback.core.FileAppender;
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();

    FileAppender fileAppender = new FileAppender();
    fileAppender.setContext(loggerContext);
    fileAppender.setName("timestamp");
    // set the file name
    fileAppender.setFile("log/" + System.currentTimeMillis()+".log");

    PatternLayoutEncoder encoder = new PatternLayoutEncoder();
    encoder.setContext(loggerContext);
    encoder.setPattern("%r %thread %level - %msg%n");
    encoder.start();

    fileAppender.setEncoder(encoder);
    fileAppender.start();

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

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

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

If all you need is to add a timestamp of the log file name, logback already supports the timestamp element. Thus, you actually don't need any custom code at all.

Ceki
  • 26,753
  • 7
  • 62
  • 71
  • thanks for your help, I will give this a go. (I was just using a timestamp as an example, the actual filename would be different) – conorsomahony Oct 20 '11 at 09:10
  • Thanks, this works - in logback-test.xml I am no longer specifying a file appender, and in the java code I am creating one and attaching it to the 'root' logger. – conorsomahony Oct 20 '11 at 10:26
  • 4
    Isn't the point of slf4j to avoid importing ch.qos.logback.classic.Logger – Zombies Nov 02 '12 at 19:01
  • 2
    @Zombies: Yes, it is. However, slf4j is supposed to be _simple_, as the name implies, hence the slf4j API does not offer any control over log output. If you want that control, you must directly talk to (and depend on) the logging framework you use. – sleske Jul 07 '15 at 08:17
  • I've noticed this only changed the log file for the current class, if I want to change the log file for the whole application, is there a similar way? Because adding this excerpt of code to all files was a long journey, and the log file didn't seem to follow the normal chronological order of execution – Matt Feb 22 '22 at 12:52
  • 1
    To make this change apply to all loggers, set it on the ROOT logger. So instead of `loggerContext.getLogger("Main");`, do `loggerContext.getLogger("ROOT");`. – CryptoFool Jul 30 '22 at 00:21
17

To separate/sift log messages to different files depending on a runtime attribute, you might want to use ch.qos.logback.classic.sift.SiftingAppender.

In a nutshell, this allows you to set up a FileAppender (or any other appender) with <file>${userid}.log</file> where ${userId} is substituted based on the MDC (Mapped Diagnostic Context) (e.g., MDC.put("userid", "Alice");). See the first link for the complete example.

simon04
  • 3,054
  • 29
  • 25
13

Here is what you can do to ignore those extra file creation.Below is the config file

<configuration>
<appender name="FILE" class="ch.qos.logback.core.FileAppender">
<!-- "application-name" is a variable -->
<File>c:/logs/${application-name}.log</File>
<layout class="ch.qos.logback.classic.PatternLayout">
<Pattern>%d %p %t %c - %m%n</Pattern>
</layout>
</appender>
<root level="debug">
<appender-ref ref="FILE"/>
</root>
</configuration>  

Here is the java part,

LoggerContext context = (LoggerContext) LoggerFactory.getILoggerFactory();
JoranConfigurator jc = new JoranConfigurator();
jc.setContext(context);
context.reset(); // override default configuration
// inject the name of the current application as "application-name"
// property of the LoggerContext
context.putProperty("application-name", NAME_OF_CURRENT_APPLICATION);
jc.doConfigure("/path/to/the/above/configuration/file.xml");

I got this from here http://logback.qos.ch/faq.html#sharedConfiguration

Sneha Mohan
  • 133
  • 1
  • 6
6

Looks like the logger is initialized twice. First time, probably when the app loads and it couldn't resolve the ${log_file_name}. If you start the app with -Dlog_file_name=*something* you can verify this behavior if it creates another log file with the name *something*

srkavin
  • 1,152
  • 7
  • 17
  • What you could do to programmatically set the file name is to instantiate a file appender with required configuration (name, category, package, etc) and add it to the appender hierarchy. – srkavin Oct 19 '11 at 17:31
  • Thanks, I added exactly `-Dlog_file_name=verify` to the VM arguments, and I did indeed get an empty log called "verify.log" - Is there a way to stop the logger starting twice? – conorsomahony Oct 20 '11 at 09:07
1

Already some useful answers here for sure, and it seems that the OP was satisfied, which is great. But I came looking for an answer to the more precise question that was posed originally, which I believe was "how do I set the file name of AN EXISTING file appender programmatically?". For reasons I won't go into, this is what I had to figure out how to do.

I did figure it out without too much fuss. In case someone comes along looking for the same answer I was seeking, here is how to set the log file name of an existing FileAppender programmatically

String logFileName = "" + System.currentTimeMillis(); // just for example

LoggerContext loggerContext = (LoggerContext) LoggerFactory.getILoggerFactory();
ch.qos.logback.classic.Logger logbackLogger = (ch.qos.logback.classic.Logger) loggerContext.getLogger("ROOT");
FileAppender<ILoggingEvent> fileAppender = (FileAppender<ILoggingEvent>)logbackLogger.getAppender("FILE");
fileAppender.setFile(logFileName);

Note that this code looks for the appender in the "ROOT" logger, the logger that is the parent of all loggers. This is where you'll usually find existing appenders, and by making changes to this logger, you are changing the behavior of all of the logging in your system, which is usually what you want .

CryptoFool
  • 21,719
  • 5
  • 26
  • 44