How to get liquibase to log using slf4j?

Benny Bottema picture Benny Bottema · Jan 2, 2014 · Viewed 18.6k times · Source

A lot of people are unsure how to fix logging for liquibase, either to the console or file.

Is it possible to make liquibase log to slf4j?

Answer

Benny Bottema picture Benny Bottema · Jan 2, 2014

There is, but it is a little bit obscure. Quoting Fixing liquibase logging with SLF4J and Log4J:

There's The Easy Way, by dropping in a dependency:

<!-- your own standard logging dependencies -->
<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-api</artifactId>
    <version>1.7.5</version>
</dependency>
<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-log4j12</artifactId><!-- or log4j2 or logback or whatever-->
    <version>1.7.5</version>
</dependency>

<!-- special dependency to fix liquibase's logging fetish -->
<dependency>
    <groupId>com.mattbertolini</groupId>
    <artifactId>liquibase-slf4j</artifactId>
    <version>1.2.1</version>
</dependency>

Now the first two are your everyday logging frameworks (slf4j api and log4j implementation). These are in addition to your standard log4j dependency, as all they do is route to the physical logging framework. Without log4j/logback/etc. itself, they still can't route anything.

The last one however, is an interesting one, as it provides a single class in a specific package that liquibase will scan for Logger implementations. It's open source, by Matt Bertolini, so you can find it on GitHub.

If you wish to do this yourself, there's also The Hard Way:

package liquibase.ext.logging; // this is *very* important

import liquibase.changelog.ChangeSet;
import liquibase.changelog.DatabaseChangeLog;
import liquibase.logging.core.AbstractLogger;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Liquibase finds this class by itself by doing a custom component scan (sl4fj wasn't generic enough).
 */
public class LiquibaseLogger extends AbstractLogger {
    private static final Logger LOGGER = LoggerFactory.getLogger(LiquibaseLogger.class);
    private String name = "";

    @Override
    public void setName(String name) {
        this.name = name;
    }

    @Override
    public void severe(String message) {
        LOGGER.error("{} {}", name, message);
    }

    @Override
    public void severe(String message, Throwable e) {
        LOGGER.error("{} {}", name, message, e);
    }

    @Override
    public void warning(String message) {
        LOGGER.warn("{} {}", name, message);
    }

    @Override
    public void warning(String message, Throwable e) {
        LOGGER.warn("{} {}", name, message, e);
    }

    @Override
    public void info(String message) {
        LOGGER.info("{} {}", name, message);
    }

    @Override
    public void info(String message, Throwable e) {
        LOGGER.info("{} {}", name, message, e);
    }

    @Override
    public void debug(String message) {
        LOGGER.debug("{} {}", name, message);
    }

    @Override
    public void debug(String message, Throwable e) {
        LOGGER.debug("{} {}", message, e);
    }

    @Override
    public void setLogLevel(String logLevel, String logFile) {
    }

    @Override
    public void setChangeLog(DatabaseChangeLog databaseChangeLog) {
    }

    @Override
    public void setChangeSet(ChangeSet changeSet) {
    }

    @Override
    public int getPriority() {
        return Integer.MAX_VALUE;
    }
}

This implementation works, but should only be used as an example. For example, I'm not using Liquibase's names to require a logging, but use this Logger class itself instead. Matt's versions does some null-checks as well, so that's probably a more mature implementation to use, plus it's open source.