How to use Groovy for Logback Configuration

How to use Groovy for Logback Configuration

4 Comments

Last Updated on October 21, 2024 by jt

Logback is designed to be faster and have a smaller memory footprint than the other logging frameworks around. If you are new to Logback, you should checkout my introductory post on Logback: Logback Introduction: An Enterprise Logging Framework.

Logback supports configuration through XML and Groovy. I explained XML configuration in my previous post, Logback Configuration: using XML. We’ll use similar configuration options for Logback, but this time in Groovy.

Because of its simplicity and flexibility, Groovy is an excellent tool for configuring Logback. Groovy is intuitive and has an easy-to-learn syntax. Even if you aren’t familiar with it, you should still easily understand, read, and write groovy configurations for Logback.

Creating a Logger

We will start by creating an application logger. As I mentioned in my earlier post here, for a Spring Boot application, we don’t need any additional Logback dependencies in our Maven POM. Groovy support is already there. We just need to start using it. Let’s start with creating a class and test for our example.

LogbackConfigGroovy.java

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
package guru.springframework.blog.logbackgroovy;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class LogbackConfigGroovy {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
public void performTask(){
logger.info("This is an info message.");
logger.warn("This is a warn message.");
logger.error("This is an error message.");
logger.debug("This is a debug message.");
}
}
package guru.springframework.blog.logbackgroovy; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class LogbackConfigGroovy { private final Logger logger = LoggerFactory.getLogger(this.getClass()); public void performTask(){ logger.info("This is an info message."); logger.warn("This is a warn message."); logger.error("This is an error message."); logger.debug("This is a debug message."); } }
package guru.springframework.blog.logbackgroovy;

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

public class LogbackConfigGroovy {
    private final Logger logger = LoggerFactory.getLogger(this.getClass());
    public void performTask(){
        logger.info("This is an info message.");
        logger.warn("This is a warn message.");
        logger.error("This is an error message.");
        logger.debug("This is a debug message.");
    }
}

Our test class uses JUnit to unit test the

LogbackConfigGroovy
LogbackConfigGroovy class.

LogbackConfigGroovyTest.java

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
package guru.springframework.blog.logbackgroovy;
import org.junit.Test;
public class LogbackConfigGroovyTest {
@Test
public void testPerformTask() throws Exception {
LogbackConfigGroovy logbackConfigGroovy=new LogbackConfigGroovy();
logbackConfigGroovy.performTask();
}
}
package guru.springframework.blog.logbackgroovy; import org.junit.Test; public class LogbackConfigGroovyTest { @Test public void testPerformTask() throws Exception { LogbackConfigGroovy logbackConfigGroovy=new LogbackConfigGroovy(); logbackConfigGroovy.performTask(); } }
package guru.springframework.blog.logbackgroovy;

import org.junit.Test;

public class LogbackConfigGroovyTest {

    @Test
    public void testPerformTask() throws Exception {
        LogbackConfigGroovy logbackConfigGroovy=new LogbackConfigGroovy();
        logbackConfigGroovy.performTask();
    }
}

The Groovy Configuration File

When Logback starts, it searches for a

logback.groovy
logback.groovy file in the classpath to configure itself. If you store the file in a different location outside the classpath, you will need to use the
logback.configurationFile
logback.configurationFile system property to point to the location, like this.

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
-DLogback.configurationFile=/path/to/logback.groovy
-DLogback.configurationFile=/path/to/logback.groovy
-DLogback.configurationFile=/path/to/logback.groovy

In a

logback.groovy
logback.groovy file, you can enable auto-scan using the
scan()
scan() method. With auto-scan enabled, Logback scans for changes in the configuration file. For any changes, Logback automatically reconfigure itself with them. By default, Logback scans for changes once every minute. You can specify a different scanning period by passing a scan period, with a value specified in units of milliseconds, seconds, minutes or hours as parameter to
scan()
scan(). For example,
scan("30 seconds")
scan("30 seconds") tells Logback to scan
Logback.groovy
Logback.groovy after every 30 seconds.

In

logback.groovy
logback.groovy, you can define one or more properties, like this.

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
def LOG_PATH = "logs"
def LOG_ARCHIVE = "${LOG_PATH}/archive"
def LOG_PATH = "logs" def LOG_ARCHIVE = "${LOG_PATH}/archive"
def LOG_PATH = "logs"
def LOG_ARCHIVE = "${LOG_PATH}/archive"

Configuration code after a property declaration can refer the property with the

${property_name}
${property_name} syntax, as shown in the second line of the code above. If you’re familiar with Spring, you’ll find this syntax similar to SpEL.

Console and File Appenders

You declare one or more appenders with the

appender()
appender() method. This method takes the name of the appender being configured as its first mandatory argument. The second mandatory argument is the class of the appender to instantiate. An optional third element is a closure, an anonymous block containing further configuration instructions.

The code to create a Logback console and a file appender, is this.

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
appender("Console-Appender", ConsoleAppender) {
encoder(PatternLayoutEncoder) {
pattern = "%msg%n"
}
}
appender("File-Appender", FileAppender) {
file = "${LOG_PATH}/logfile.log"
encoder(PatternLayoutEncoder) {
pattern = "%msg%n"
outputPatternAsHeader = true
}
}
appender("Console-Appender", ConsoleAppender) { encoder(PatternLayoutEncoder) { pattern = "%msg%n" } } appender("File-Appender", FileAppender) { file = "${LOG_PATH}/logfile.log" encoder(PatternLayoutEncoder) { pattern = "%msg%n" outputPatternAsHeader = true } }
appender("Console-Appender", ConsoleAppender) {
    encoder(PatternLayoutEncoder) {
        pattern = "%msg%n"
    }
}
appender("File-Appender", FileAppender) {
    file = "${LOG_PATH}/logfile.log"
    encoder(PatternLayoutEncoder) {
        pattern = "%msg%n"
        outputPatternAsHeader = true
    }
}

In the code above:

  • Line 1: We called the
    appender()
    appender() method passing
    Console-Appender
    Console-Appender as the appender name and
    ConsoleAppender
    ConsoleAppender as the appender implementation class.
  • Line 2: We injected an encoder of type
    PatternLayoutEncoder
    PatternLayoutEncoder into the appender.
  • Line 3: We set the
    pattern
    pattern property of the encoder to the
    %msg%n
    %msg%n conversion pattern. A conversion pattern is composed of literal text and format control expressions called conversion specifiers. You can learn more about pattern layout and conversion specifiers here.
  • Line 6 – Line 12: We created a file appender named
    File-Appender
    File-Appender of the
    FileAppender
    FileAppender type, and set the
    file
    file property to a log file. We injected an encoder into the appender and set the
    pattern
    pattern and
    outputPatternAsHeader
    outputPatternAsHeader properties of the encoder. The
    outputPatternAsHeader
    outputPatternAsHeader property, when set to
    true
    true, inserts the pattern used for the log output at the top of log files.

We will now configure an application-specific logger along with the root logger to use the console and file appenders.

You use the

logger()
logger() method to configure a logger. This method accepts the following parameters.

  • A string specifying the name of the logger.
  • One of the
    OFF
    OFF,
    ERROR
    ERROR,
    WARN
    WARN,
    INFO
    INFO,
    DEBUG
    DEBUG,
    TRACE
    TRACE, or
    ALL
    ALL fields of the Level class to represent the level of the designated logger.
  • An optional
    List
    List containing one or more appenders to be attached to the logger.
  • An optional
    Boolean
    Boolean value indicating additivity. The default value is
    true
    true. We will come to additivity a bit later.

The code to configure the loggers is this.

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
logger("guru.springframework.blog.Logbackgroovy", INFO, ["File-Appender"])
root(INFO, ["Console-Appender"])
logger("guru.springframework.blog.Logbackgroovy", INFO, ["File-Appender"]) root(INFO, ["Console-Appender"])
logger("guru.springframework.blog.Logbackgroovy", INFO, ["File-Appender"])
root(INFO, ["Console-Appender"])

In the code above, we configured all loggers of the

guru.springframework.blog.Logbackgroovy
guru.springframework.blog.Logbackgroovy package and its sub-packages to log
INFO
INFO and higher level messages to the configured
File-Appender
File-Appender. We also configured the root logger to log
INFO
INFO and higher level messages to the configured
Console-Appender
Console-Appender.

The output on running the test class,

LogbackConfigGroovyTest
LogbackConfigGroovyTest is this.
Output of Console and File Appender

Rolling File Appender

The rolling file appender supports writing to a file and rolls the file over according to one of your pre-defined policies. To learn more about the rolling file appender and its policies, refer to the Logback manual.

The code to configure a rolling file appender is this.

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
appender("RollingFile-Appender", RollingFileAppender) {
file = "${LOG_PATH}/rollingfile.log"
rollingPolicy(TimeBasedRollingPolicy) {
fileNamePattern = "${LOG_ARCHIVE}/rollingfile.log%d{yyyy-MM-dd}.log"
maxHistory = 30
totalSizeCap = "1KB"
}
encoder(PatternLayoutEncoder) {
pattern = "%msg%n"
}
}
appender("RollingFile-Appender", RollingFileAppender) { file = "${LOG_PATH}/rollingfile.log" rollingPolicy(TimeBasedRollingPolicy) { fileNamePattern = "${LOG_ARCHIVE}/rollingfile.log%d{yyyy-MM-dd}.log" maxHistory = 30 totalSizeCap = "1KB" } encoder(PatternLayoutEncoder) { pattern = "%msg%n" } }
appender("RollingFile-Appender", RollingFileAppender) {
    file = "${LOG_PATH}/rollingfile.log"
    rollingPolicy(TimeBasedRollingPolicy) {
        fileNamePattern = "${LOG_ARCHIVE}/rollingfile.log%d{yyyy-MM-dd}.log"
        maxHistory = 30
        totalSizeCap = "1KB"
    }
    encoder(PatternLayoutEncoder) {
        pattern = "%msg%n"
    }
}

in the code above:

  • Line 1: We called the
    appender()
    appender() method passing
    RollingFile-Appender
    RollingFile-Appender as the appender name and
    RollingFileAppender
    RollingFileAppender as the appender class.
  • Line 2: We set the
    file
    file property of the appender to specify the rolling file.
  • Line 3: We injected a time-based rolling policy of type
    TimeBasedRollingPolicy
    TimeBasedRollingPolicy into the appender. A time-based rolling policy performs a rollover once the date/time pattern is no longer applies to the active log file.
  • Line 4 – Line 6: We set the
    fileNamePattern
    fileNamePattern,
    maxHistory
    maxHistory, and
    totalSizeCap
    totalSizeCap properties of the policy. The
    fileNamePattern
    fileNamePattern property defines a file name pattern for archived log files. The rollover period is inferred from the value of
    fileNamePattern
    fileNamePattern, which in the code example is set for daily rolling. The
    maxHistory
    maxHistory property sets the maximum number of archive files to keep, before deleting older files asynchronously. The
    totalSizeCap
    totalSizeCap element sets the total size of all archive files. Oldest archives are deleted asynchronously when the total size cap is exceeded.

To use the rolling file appender, add the appender name in the list passed to the

logger()
logger() method, like this.

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
logger("guru.springframework.blog.Logbackgroovy", INFO, ["File-Appender", "RollingFile-Appender"])
logger("guru.springframework.blog.Logbackgroovy", INFO, ["File-Appender", "RollingFile-Appender"])
logger("guru.springframework.blog.Logbackgroovy", INFO, ["File-Appender", "RollingFile-Appender"])

At this point, if you run the test class, a rolling log file named

rollingfile.log
rollingfile.log is created under
logs
logs. To simulate a rollover, you can set the system clock one day ahead and run the test class again. A new
rollingfile.log
rollingfile.log is created under
logs
logs and the previous file is archived in the
logs/archive
logs/archive folder.
Output of Rolling File Appender

Async Appender

An async appender runs in a separate thread to decouple the logging overhead from the thread executing your code. To make an appender async, first call the

appender()
appender() method passing a name for the async appender and an
AsyncAppender
AsyncAppender object. Then, inject the appender to invoke asynchronously, like this.

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
appender("Async-Appender", AsyncAppender) {
appenderRef("RollingFile-Appender")
}
appender("Async-Appender", AsyncAppender) { appenderRef("RollingFile-Appender") }
appender("Async-Appender", AsyncAppender) {
    appenderRef("RollingFile-Appender")
}

The code above makes the

RollingFile-Appender
RollingFile-Appender appender asynchronous.

Once you define an async appender, you can use it in a logger like any other appender, as shown.

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
logger("guru.springframework.blog.Logbackgroovy", INFO, ["File-Appender", "Async-Appender"])
logger("guru.springframework.blog.Logbackgroovy", INFO, ["File-Appender", "Async-Appender"])
logger("guru.springframework.blog.Logbackgroovy", INFO, ["File-Appender", "Async-Appender"])

The complete code of the

logback.groovy
logback.groovy file is this.

Logback.groovy

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
import ch.qos.logback.classic.AsyncAppender
import ch.qos.logback.classic.PatternLayout
import static ch.qos.logback.classic.Level.INFO
scan("30 seconds")
def LOG_PATH = "logs"
def LOG_ARCHIVE = "${LOG_PATH}/archive"
appender("Console-Appender", ConsoleAppender) {
encoder(PatternLayoutEncoder) {
pattern = "%msg%n"
}
}
appender("File-Appender", FileAppender) {
file = "${LOG_PATH}/logfile.log"
encoder(PatternLayoutEncoder) {
pattern = "%msg%n"
outputPatternAsHeader = true
}
}
appender("RollingFile-Appender", RollingFileAppender) {
file = "${LOG_PATH}/rollingfile.log"
rollingPolicy(TimeBasedRollingPolicy) {
fileNamePattern = "${LOG_ARCHIVE}/rollingfile.log%d{yyyy-MM-dd}.log"
maxHistory = 30
totalSizeCap = "1KB"
}
encoder(PatternLayoutEncoder) {
pattern = "%msg%n"
}
}
appender("Async-Appender", AsyncAppender) {
appenderRef("RollingFile-Appender")
}
logger("guru.springframework.blog.logbackgroovy", INFO, ["Console-Appender", "File-Appender", "Async-Appender"], false)
root(INFO, ["Console-Appender"])
import ch.qos.logback.classic.AsyncAppender import ch.qos.logback.classic.PatternLayout import static ch.qos.logback.classic.Level.INFO scan("30 seconds") def LOG_PATH = "logs" def LOG_ARCHIVE = "${LOG_PATH}/archive" appender("Console-Appender", ConsoleAppender) { encoder(PatternLayoutEncoder) { pattern = "%msg%n" } } appender("File-Appender", FileAppender) { file = "${LOG_PATH}/logfile.log" encoder(PatternLayoutEncoder) { pattern = "%msg%n" outputPatternAsHeader = true } } appender("RollingFile-Appender", RollingFileAppender) { file = "${LOG_PATH}/rollingfile.log" rollingPolicy(TimeBasedRollingPolicy) { fileNamePattern = "${LOG_ARCHIVE}/rollingfile.log%d{yyyy-MM-dd}.log" maxHistory = 30 totalSizeCap = "1KB" } encoder(PatternLayoutEncoder) { pattern = "%msg%n" } } appender("Async-Appender", AsyncAppender) { appenderRef("RollingFile-Appender") } logger("guru.springframework.blog.logbackgroovy", INFO, ["Console-Appender", "File-Appender", "Async-Appender"], false) root(INFO, ["Console-Appender"])
import ch.qos.logback.classic.AsyncAppender
import ch.qos.logback.classic.PatternLayout
import static ch.qos.logback.classic.Level.INFO

scan("30 seconds")
def LOG_PATH = "logs"
def LOG_ARCHIVE = "${LOG_PATH}/archive"
appender("Console-Appender", ConsoleAppender) {
    encoder(PatternLayoutEncoder) {
        pattern = "%msg%n"
    }
}
appender("File-Appender", FileAppender) {
    file = "${LOG_PATH}/logfile.log"
    encoder(PatternLayoutEncoder) {
        pattern = "%msg%n"
        outputPatternAsHeader = true
    }
}
appender("RollingFile-Appender", RollingFileAppender) {
    file = "${LOG_PATH}/rollingfile.log"
    rollingPolicy(TimeBasedRollingPolicy) {
        fileNamePattern = "${LOG_ARCHIVE}/rollingfile.log%d{yyyy-MM-dd}.log"
        maxHistory = 30
        totalSizeCap = "1KB"
    }
    encoder(PatternLayoutEncoder) {
        pattern = "%msg%n"
    }
}
appender("Async-Appender", AsyncAppender) {
    appenderRef("RollingFile-Appender")
}
logger("guru.springframework.blog.logbackgroovy", INFO, ["Console-Appender", "File-Appender", "Async-Appender"], false)
root(INFO, ["Console-Appender"])

In the configuration code above, observe Line 34. I included the console appender and passed a

false
false parameter to
logger()
logger(). I did this to disable additivity. With additivity disabled, Logback will use
Console-Appender
Console-Appender of the application logger instead of the one configured for the root logger. Review the Logback manual for more information on additivity. If you have noticed in the Groovy code, we intentionally skipped a number of import statements. To reduce unnecessary boilerplate code, Groovy includes several common types and packages by default.

Summary

If you work with both XML and Groovy to configure Logback, you will find the Groovy syntax less verbose, and so more readable. Also, the Groovy syntax being a super-set of Java syntax is more intuitive for Java developers. On the other hand, XML with the years of industry backing is more popular and have a larger user base. You’ll find better IDE support when using XML, since it can be validated against the structure of a XML schema. Groovy doesn’t have this fallback. So while you may be writing syntaxually correct Groovy code, it may fail to configure Logback in the way in you intended.

Personally, I feel you should not lock yourself into XML or Groovy. XML will provide you highly structured configuration. Groovy gives you freedom to do things programmatically, which you cannot do in a structured XML document. Most of the time, XML configuration will be just fine. But when you have a complex use case for your logging requirements, Groovy is a great tool you can use to configure Logback.

About jt

    You May Also Like

    4 comments on “How to use Groovy for Logback Configuration

    1. October 25, 2016 at 3:12 pm

      There has been a discussion on a Stack Overflow question (http://stackoverflow.com/questions/40161937) about whether it’s possible to use a string literal such as “1KB” for the totalSizeCap property of a rolling file appender, as you have done above. It didn’t work for me, and it seems it didn’t work for another couple of developers. Would it be possible to clarify? Thanks.

      Reply
    2. January 25, 2024 at 7:11 am

      Logback have stopped supporting logback.groovy
      “Groovy Given that Groovy is a full-fledged language, we have dropped support for logback.groovy in order to protect the innocent. However, groovy support was picked up at virtualdogbert/logback-groovy-config by Tucker Pelletier.”
      ref: https://logback.qos.ch/manual/configuration.html

      Reply

    Leave a Reply

    Your email address will not be published. Required fields are marked *

    This site uses Akismet to reduce spam. Learn how your comment data is processed.