87

I have a logback appender defined in the logback.xml, it's a DB appender, but I'm curious if there is any way to configure the appender in java using my own connection pool defined as a bean.

I find similar things, but never the actual answer.

7 Answers7

131

Here a simple example that works for me (note that I use the FileAppender in this example)

import org.slf4j.LoggerFactory;

import ch.qos.logback.classic.Level;
import ch.qos.logback.classic.Logger;
import ch.qos.logback.classic.LoggerContext;
import ch.qos.logback.classic.encoder.PatternLayoutEncoder;
import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.core.FileAppender;

public class Loggerutils {

    public static void main(String[] args) {
          Logger foo = createLoggerFor("foo", "foo.log");
          Logger bar = createLoggerFor("bar", "bar.log");
          foo.info("test");
          bar.info("bar");
    }

    private static Logger createLoggerFor(String string, String file) {
          LoggerContext lc = (LoggerContext) LoggerFactory.getILoggerFactory();
          PatternLayoutEncoder ple = new PatternLayoutEncoder();

          ple.setPattern("%date %level [%thread] %logger{10} [%file:%line] %msg%n");
          ple.setContext(lc);
          ple.start();
          FileAppender<ILoggingEvent> fileAppender = new FileAppender<ILoggingEvent>();
          fileAppender.setFile(file);
          fileAppender.setEncoder(ple);
          fileAppender.setContext(lc);
          fileAppender.start();

          Logger logger = (Logger) LoggerFactory.getLogger(string);
          logger.addAppender(fileAppender);
          logger.setLevel(Level.DEBUG);
          logger.setAdditive(false); /* set to true if root should log too */

          return logger;
    }

}
reto
  • 16,189
  • 7
  • 53
  • 67
  • @Andrew Swan: Could you please note in a comment what you would like to change with the current version. I'll try to update it accordingly. – reto Nov 25 '13 at 12:50
  • @reto My change mainly (1) adds the class declaration so that it compiles as-is and (2) adds the missing "g" to "Loger". – Andrew Swan Nov 25 '13 at 21:56
  • Don't you prefer to take the LoggerContent from the logger you have a few lines before the end? – Asaf Mesika Apr 13 '16 at 08:05
  • scratch my last comment: When I use your code, it still reads what's in my logback.xml file and uses that. Any way I can tell logback to ignore what's in logback.xml and use only what we set up programatically (instead of via xml) – user64141 Sep 20 '16 at 14:29
  • oops, I see my error -- I didn't set do a "logbackLogger.setAdditive(false)" – user64141 Sep 20 '16 at 16:23
  • 10
    `Logger` no longer exposes the `addAppender()` method. – Mike C Jun 29 '17 at 00:33
  • I never used to get this, but now I do: `java.lang.ClassCastException: org.apache.logging.slf4j.Log4jLoggerFactory cannot be cast to ch.qos.logback.classic.LoggerContext` – Sridhar Sarnobat Aug 04 '18 at 02:32
  • @MikeC did you ever discover another way to add an appender? – Trevor Jun 13 '19 at 20:47
  • 1
    @trevor i don't remember and that Java codebase is out of reach now. – Mike C Jun 17 '19 at 17:29
  • 8
    For those who realise the `Logger` doesn't have the `addAppender` method, is because you're pointing out to the abstraction `Logger` class from sl4j. If you point out to the implementation one, `Logger` from `logback` package, then you can use the `addAppender`. The downside of this is that your test code will be coupled to the `logback` implementation. – Tiago Nov 05 '19 at 11:43
  • 3
    better to use `lc.getLogger(string)`. – user8681709 Nov 20 '19 at 05:32
  • @retro does it work asynchronous? – Raza Zaidi Dec 31 '21 at 07:45
19

You can configure appenders programmatically. Almost all appenders are tested using programmatic configuration. It follows that there are many examples of programmatic appender configuration in the logback project source code. For a logback-core appender, look under logback-core/src/test/java, and for a logback-classic appender look under logback-classic/src/test/java.

Ceki
  • 26,753
  • 7
  • 62
  • 71
19

As a reference, when you try to modify code responsible for creating loggers, there is a bunch of rules that must be satisfied in order for a logger to work.

These rules were described in a great and helpful article Programmatic configuration of slf4j/logback:

Now I have experience with programmatic configuration of slf4j/logback.

Task

A program must open separate log file for each processed input file.

Solution for task

Instead of configuring logback via xml, the one needs to “manually” instantiate encoders, appenders and loggers, then configure and link them together.

Caveat 1

Logback goes crazy on attempt to share encoder (i.e. PatternLayoutEncoder) between appenders.

Solution for caveat 1

Create separate encoder for each appender.

Caveat 2

Logback refuses to log anything, if encoders and appenders are not associated with logging context.

Solution for caveat 2

Call setContext on each encoder and appender, passing LoggerFactory as a parameter.

Caveat 3

Logback refuses to log anything, if encoders and appenders are not started.

Solution for caveat 3

encoders and appenders need to be started in the correct order, i.e. first encoders, then appenders.

Caveat 4

RollingPolicy objects (i.e. TimeBasedRollingPolicy) produce strange error messages like “date format not recognized”, when they are not attached to the same context as appender.

Solution for caveat 4

call setContext on RollingPolicy same way as on encoders and appenders.

Here is working example of “manual” logback configuration:

package testpackage

import ch.qos.logback.classic.Level
import ch.qos.logback.classic.Logger
import ch.qos.logback.classic.LoggerContext
import ch.qos.logback.classic.encoder.PatternLayoutEncoder
import ch.qos.logback.core.ConsoleAppender
import ch.qos.logback.core.rolling.RollingFileAppender
import ch.qos.logback.core.rolling.TimeBasedRollingPolicy

import org.slf4j.LoggerFactory

class TestLogConfig {

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

    PatternLayoutEncoder logEncoder = new PatternLayoutEncoder();
    logEncoder.setContext(logCtx);
    logEncoder.setPattern("%-12date{YYYY-MM-dd HH:mm:ss.SSS} %-5level - %msg%n");
    logEncoder.start();

    ConsoleAppender logConsoleAppender = new ConsoleAppender();
    logConsoleAppender.setContext(logCtx);
    logConsoleAppender.setName("console");
    logConsoleAppender.setEncoder(logEncoder);
    logConsoleAppender.start();

    logEncoder = new PatternLayoutEncoder();
    logEncoder.setContext(logCtx);
    logEncoder.setPattern("%-12date{YYYY-MM-dd HH:mm:ss.SSS} %-5level - %msg%n");
    logEncoder.start();

    RollingFileAppender logFileAppender = new RollingFileAppender();
    logFileAppender.setContext(logCtx);
    logFileAppender.setName("logFile");
    logFileAppender.setEncoder(logEncoder);
    logFileAppender.setAppend(true);
    logFileAppender.setFile("logs/logfile.log");

    TimeBasedRollingPolicy logFilePolicy = new TimeBasedRollingPolicy();
    logFilePolicy.setContext(logCtx);
    logFilePolicy.setParent(logFileAppender);
    logFilePolicy.setFileNamePattern("logs/logfile-%d{yyyy-MM-dd_HH}.log");
    logFilePolicy.setMaxHistory(7);
    logFilePolicy.start();

    logFileAppender.setRollingPolicy(logFilePolicy);
    logFileAppender.start();

    Logger log = logCtx.getLogger("Main");
    log.setAdditive(false);
    log.setLevel(Level.INFO);
    log.addAppender(logConsoleAppender);
    log.addAppender(logFileAppender);
  }
}
Community
  • 1
  • 1
dominik
  • 2,404
  • 2
  • 29
  • 33
  • 2
    For some reason I get `java.lang.ClassCastException: org.apache.logging.slf4j.Log4jLoggerFactory cannot be cast to ch.qos.logback.classic.LoggerContext` – Sridhar Sarnobat Aug 04 '18 at 02:32
  • It seems you are using the logger factory of Log4j, not slf4j – gargaroff Aug 06 '18 at 00:09
  • 1
    `"Main"` name seems to be invalid. `Logger.ROOT_LOGGER_NAME` is `"ROOT"`. – Miha_x64 Dec 29 '18 at 20:15
  • @Miha_x64: any string is valid. Passing "ROOT" would mean configuring root instance of the logger. What if you want to configure more loggers that inherit from root logger? You create child loggers (e.g. "Main", "Main2", "Auxiliary", "whateverNameYouWant") and all these child loggers can have custom configuration. See Javadoc for: https://www.slf4j.org/api/org/slf4j/ILoggerFactory.html#getLogger(java.lang.String) – dominik Jan 03 '19 at 12:50
  • how to assert the message been logged the logger? – Chandresh Mishra Feb 12 '21 at 14:18
7

Just, if somebody would looking for a concrete example of programmatic configuration.

Here I setup the charset of ConsoleAppender:

LoggerContext lc = (LoggerContext) LoggerFactory.getILoggerFactory();
ConsoleAppender<ILoggingEvent> appender =
    (ConsoleAppender) lc.getLogger("appconsole").getAppender("STDOUT");
LayoutWrappingEncoder<ILoggingEvent> enc = 
    (LayoutWrappingEncoder<ILoggingEvent>) appender.getEncoder();
enc.setCharset(Charset.forName("utf-8"));

And my logback.xml:

<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
    <encoder>
        <charset>866</charset>
        <pattern>[%level] %msg%n</pattern>
    </encoder>
</appender>

<logger name="appconsole">
    <appender-ref ref="STDOUT" />
</logger>

Why I need programmaticaly configure a logger? Because, I packaging my app (Spring Boot) into a jar file. Consequently Logback.xml file is appear to be hide inside a jar. Though, it is not to be convenient to unpackage and change it. And I do not need any logback.xml file beside my app.jar. I have only app.yaml file which contains all configuration properties for app.

aldoWan
  • 93
  • 1
  • 6
Yan Pak
  • 1,767
  • 2
  • 19
  • 15
2

In the current versions (logback=1.3.0-alpha16,slf4j=2.0.0-alpha7) you can use SPI to build your configuration programmatically.

You need a text file in your jar with the name: ch.qos.logback.classic.spi.Configurator located in META-INF/services within your jar for Logback to pick it up. The file should contain the full qualified class name of your class that does the initialization.

Then create a class that does the initialization like so:

package example

import java.nio.charset.Charset;

import ch.qos.logback.classic.Level;
import ch.qos.logback.classic.Logger;
import ch.qos.logback.classic.LoggerContext;
import ch.qos.logback.classic.PatternLayout;
import ch.qos.logback.classic.spi.Configurator;
import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.core.ConsoleAppender;
import ch.qos.logback.core.encoder.LayoutWrappingEncoder;
import ch.qos.logback.core.spi.ContextAwareBase;

public class ProgrammaticLoggingConfigurator extends ContextAwareBase implements Configurator {
  
  @Override
  public void configure(LoggerContext loggerContext) {

    PatternLayout pattern = new PatternLayout();
    pattern.setContext(loggerContext);
    pattern.setPattern("[%level] %msg%n");
    pattern.start();

    LayoutWrappingEncoder<ILoggingEvent> encoder = new LayoutWrappingEncoder<ILoggingEvent>();
    encoder.setContext(loggerContext);
    encoder.setCharset(Charset.forName("utf-8"));
    encoder.setLayout(pattern);

    ConsoleAppender<ILoggingEvent> consoleAppender = new ConsoleAppender<>();
    consoleAppender.setContext(loggerContext);
    consoleAppender.setName("console");
    consoleAppender.setEncoder(encoder);
    consoleAppender.start();

    Logger log = loggerContext.getLogger(Logger.ROOT_LOGGER_NAME);
    log.setAdditive(false);
    log.setLevel(Level.DEBUG);
    log.addAppender(consoleAppender);
  }
}

So this example should have a file called ch.qos.logback.classic.spi.Configurator with the content: example.ProgrammaticLoggingConfigurator for it to be picked up.

Ivo Limmen
  • 3,095
  • 1
  • 26
  • 23
0

Not allowed to comment (yet?), I'd just like to add three tips;

  • regarding the caveats above, if you have problems, just add a call to

    StatusPrinter.print(context);
    

    after everything has been configured, that is, after having added your appenders the root/"Main" appender: it will tell you what is wrong.

  • I like very much to separate logging-levels in different files; when looking for errors I begin with looking in the error file and so on, having them set up like

tot_[app name].log   : Level.INFO
deb_[app name].log   : Level.DEBUG
err_[app name].log   : Level.ERROR

routing by means of a simple private filter class such as

    private static class ThresholdLoggerFilter extends Filter<ILoggingEvent> {

        private final Level level;

        private ThresholdLoggerFilter(Level level){
            this.level = level;
        }

        @Override
        public FilterReply decide(ILoggingEvent event) {
            if (event.getLevel().isGreaterOrEqual(level)) {
                return FilterReply.NEUTRAL;
            } else {
                return FilterReply.DENY;
            }
        }
    }

and then just call myFilter.start() and myAppender.addFilter(myFilter);.

  • Lastly, putting it together, I usually want to be able to change log levels dynamically having the setup implement some simple interface like

    public interface LoggingService {
        void setRootLogLevel(Level level);
    }
    

keeping the root logging level in some property-file which is monitored so that whenever there is some valid input there, I just call this service implemented like

    @Override
    public void setRootLogLevel(Level level) {
        if (context != null && context.isStarted()) {
        ((Logger) LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME)).setLevel(level);
        }
    }

with my new root logger level.

Ola Aronsson
  • 411
  • 4
  • 7
0

Catch here is that we need to typecast it to ch.qos.logback.classic.Logger and then appenders can be set programmatically.

    import ch.qos.logback.classic.Logger;
    import org.slf4j.LoggerFactory;
    Logger log = (Logger) LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME);
            log.addAppender(mockAppender);
Vikky
  • 1,123
  • 14
  • 16