2

So, Grails already sets up datasources backed by connection pools. Is there a way to leverage those for use with DBAppender in Logback so that I don't have to create a separate parallel datasource/connection pool?

logback.groovy is somewhat external to Grails, so it doesn't accept Spring autowiring, and other tricks like grails.util.Holders.findApplication() don't appear to work.

U47
  • 193
  • 14
  • I've been fiddling with the [Spring Logback Extension](https://github.com/qos-ch/logback-extensions/wiki/Spring) but surely this is redundant? Is the issue that Grails relies on `logback.groovy` and Logback's DSL instead of instantiating it all from `application.yml` or, more likely, `application.groovy` to leverage the Application Context that Grails creates? – U47 Nov 07 '16 at 21:50

1 Answers1

3

Woof, this was a chore. Frankly, I'm a bit disillusioned with Logback. Logback creates it's own Spring ApplicationContext. So we have two separate contexts. Ugh. Also, it certainly doesn't help that the DSL that Logback uses for configuring Spring in Groovy is different than Grails'.

Since Logback gets fired up before Grails is completely started, we need to tell Logback to create some dummy appenders that will just store log messages until we fire up appenders from within Grails. We use the logback extension for Spring to do this.

build.gradle:

compile 'org.logback-extensions:logback-ext-spring:0.1.4'

logback.groovy:

import ch.qos.logback.ext.spring.DelegatingLogbackAppender

appender('DB', DelegatingLogbackAppender)
appender('STDOUT', DelegatingLogbackAppender)

resources.groovy:

import ch.qos.logback.ext.spring.ApplicationContextHolder
import ch.qos.logback.classic.encoder.PatternLayoutEncoder
import ch.qos.logback.classic.db.DBAppender
import ch.qos.logback.core.ConsoleAppender
import ch.qos.logback.core.db.DataSourceConnectionSource
import org.slf4j.LoggerFactory

beans = {
    applicationContextHolder(ApplicationContextHolder)

    loggerContext(LoggerFactory) { bean ->
        bean.factoryMethod = "getILoggerFactory"
    }

    patternLayoutEncoder(PatternLayoutEncoder) { bean ->
        bean.initMethod = 'start'
        bean.destroyMethod = 'stop'

        context = ref(loggerContext)
        pattern = "%level %logger - %msg%n"
    }

   STDOUT(ConsoleAppender) { bean ->
        bean.initMethod = 'start'
        bean.destroyMethod = 'stop'
        context = ref(loggerContext)
        encoder = ref(patternLayoutEncoder)
    }

    connectionSource(DataSourceConnectionSource) { bean ->
        bean.initMethod = 'start'
        bean.destroyMethod = 'stop'
        context = ref(loggerContext)
        dataSource = ref(dataSource)
    }

    DB(DBAppender) { bean ->
        bean.initMethod = 'start'
        bean.destroyMethod = 'stop'
        context = ref(loggerContext)
        connectionSource = ref(connectionSource)
    }
}

The ref(dataSource) in the DataSourceConnectionSource references the dataSource you have configured in application.yml or application.groovy.

Say you have multiple dataSources (or even one configured just for logback called dataSources.logging. In that case the bean reference would be dataSource_logging. The default dataSource in that case (called dataSources.dataSource bean reference is just dataSource. Took me a while to figure that one out.

All in all, i miss the days of configuring Log4j from within the Grails config file using the Grails DSL. I get that separating logging from Grails means one less thing for Graeme and the Grails team to deal with, but this was a major PITA for something that I thought would be common. ¯\_(ツ)_/¯

U47
  • 193
  • 14