Grails 3.x – Configuring logback

Before Grails 3 logging configuration could be specified in grails-app/conf/Config.groovy, since Grails 3 it is in the file grails-app/conf/logback.groovy

In this article, we will see how can we set different log levels in our logback file and also how to use an appender.

What is an Appender?

Definition from docs – “Logback delegates the task of writing a logging event to components called appenders.”

Appenders deliver the LogEvents to their destination. In this article, we will focus on following appenders –

  • ConsoleAppender
  • RollingFileAppender
  • AsyncAppender

Logback file configurations –

After Grails 3.2.0, we see that the logback.groovy file follows the standard Logback groovy config format and only configures a single appender by default which is an instance of ConsoleAppender called STDOUT, and conditionally (in development mode) an instance of FileAppender called FULL_STACKTRACE. This may then be used by logger instances. These logger instances can target specific package names and log levels, and write to one or more appenders. You can also configure different combinations of appenders for development, production, test, and other custom environments.
Following code snippet shows the default logback.groovy file in Grails 3.2.x –

import grails.util.BuildSettings
import grails.util.environment

appender(‘STDOUT’, ConsoleAppender) {
    encoder(PatternLayoutEncoder) { 
        pattern = “%level %logger - %msg%n” 

Def targetDir = BuildSettings.TARGET_DIR // Environment specific configuration 
if (Environment.isDevelopmentMode() && targetDir != null) { 
    appender(“FULL_STACKTRACE”, FileAppender) { 
        file = “${targetDir}/stacktrace.log” 
        append = true 
        encoder(PatternLayoutEncoder) { 
            pattern = “%level %logger - %msg%n” 
    logger(“StackTrace”, ERROR, [‘FULL_STACKTRACE’], false)  
} else { 
    root(ERROR, [‘STDOUT’]) 

Appenders –

1 ConsoleAppender

Unsurprisingly, ConsoleAppender appends the logs on the console, or to be more precise on System.out or System.err. System.out is the default target. ConsoleAppender formats the events with the help of user specified encoder.

2 RollingFileAppender

RollingFileAppender supports a rolling log system and changes the logging target from one file to another when the user-specified condition is met. This helps to separate our log outputs and we can also manage space that these log files take on any system since the hard drive space is limited. To manage the space for log files, we can set a file size cap and define the max space for these log files.

Let’s look at this code snippet which configures a RollingFileAppender to split the log files day by day and only keeps 30 days of log files (deleting the older logs) –

import ch.qos.logback.core.rolling.RollingFileAppender 
import ch.qos.logback.core.rolling.TimeBasedRollingPolicy 
import ch.qos.logback.core.util.FileSize

String HOME_DIR = ‘'
appender(“ROLLING”, RollingFileAppender) { 
    encoder(PatternLayOutEncoder) { 
        Pattern = “%level %logger - %msg%n”
    rollingPolicy(TimeBasedRollingPolicy) {
        fileNamePattern = “${HOME_DIR}/logs/myApp-%d{yyyy-MM-dd_HH-mm}.log”
        maxHistory = 30 //Deletes older log files. 
        totalSizeCap = FileSize.valueOf(“2GB”) // Max size allowed for the log files on disk 

In our code snippet we have set the file size cap to 2 GB, so the total disk space used by our logs won’t exceed this value. Following code snippet show how to manage the rollover time –

// Rollover at the beginning of each month.

fileNamePattern = "/myApp-log.%d{yyyy-MM}.log" 

// Rollover at the first day of each week. First day of the week is locale dependent.

fileNamePattern = "/myApp-log.%d{yyyy-ww}.log" 

// Rollover at the top of each hour.

fileNamePattern = "/myApp-log.%d{yyyy-MM-dd_HH}.log" 

3 AsyncAppender

Since performance is a critical aspect of any enterprise service, the latency (Time required to perform some action or produce a result – should be minimum) and throughput (Number of results per unit time – should be maximum) matters a lot. AsyncAppender logs the events asynchronously. In simple terms, AsyncAppender has its own queue and does the logging in a separate thread. This will free the main thread to perform other important tasks. Note – AsyncAppender must be referenced with another appender since it acts as an event dispatcher.

More information on Appenders

Setting log levels –

There are various Log levels. These levels can be set using the logger method. The logger method takes four arguments. Let’s have a look at the signature of this method –

logger(String name, Level level, List<String> appenderNames = [], boolean additivity = null) 

About the arguments – The first argument is the name of the logger to configure. The second argument is the level of the designated logger.

logger(‘’, WARN) 

The above script will set the WARN level log for classes in ‘’ package. The third argument is optional and is of type List. We can pass comma separated list of appender names here. For example, if we want to set TRACE level log for classes in package and want to use ConsoleAppender and RollingFileAppender, then we write it as –

logger(“”, TRACE, [‘STDOUT’, ‘ROLLING’]) 

Where ROLLING and STDOUT are the names of our appenders.

The final argument controls the additivity flag. For more information visit –

This is all you need to know to configure your logback.groovy file. Enjoy!

Resources :

About CauseCode: We are a technology company specializing in Healthtech related Web and Mobile application development. We collaborate with passionate companies looking to change health and wellness tech for good. If you are a startup, enterprise or generally interested in digital health, we would love to hear from you! Let's connect at
  • Rick says:

    Is it possible to log user agent and remote IP?

    • Ankit Agrawal says:

      Yes, it is. You can use the “request” object available in Grails controllers.

      To log the user agent:
      // request.getHeader(‘User-Agent’)

      To log the remote IP:
      // request.getRemoteAddr()

      Note: If you are using a proxy server, this will be useful – request.getHeader(‘X-Forwarded-For’)

Leave a Reply

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


Do you want to get articles like these in your inbox?

Email *

Interested groups *
Technical articles