1
votes

I have a grails application, deployed to tomcat, that is using log4j. I would like to be able to update /webapps/WEB-INF/classes/log4j.properties in tomcat, and then have the application dynamically pickup the changes without requiring a restart. I haven't had any luck finding a good way to do this. What I have figured out is:

I can retrieve a file as a stream:

Thread.currentThread().getContextClassLoader().getResourceAsStream("log4j.properties")

Unfortunately this is the class loaded during startup, and I'm not sure if/how I can force it to update from the actual file. If that worked I could read each property and use:

Logger.rootLogger.loggerRepository.getLogger(<key>).level = <new log level>

I've seen things about LogManager.resetConfiguration() but that doesn't seem to help either.

Also, this is how I setup log4j in resources.groovy

beans = {
    // Setting up external configuration for log4j
    log4jConfigurer(org.springframework.beans.factory.config.MethodInvokingFactoryBean) {
        targetClass = "org.springframework.util.Log4jConfigurer"
        targetMethod = "initLogging"
        arguments = ["classpath:log4j.properties"]
    }
}

I'm not interested in using the configureAndWatch approach as I've read about its vulnerabilities.

I see that there are XML and JSON properties you can use for log4j, and I'm just using a plain *.properties file, and I'm not sure if that is part of the problem.

Any tips would be greatly appreciated, thanks in advance!

1
Is the external-config-reload plugin any help? -- I've never used it though, so this is more of a question rather than an endorsement ;-) - tim_yates
I had tried using this, but the grails.plugins.reloadConfig.interval property didn't seem to refresh the properties when expected. Although I admit that it's very possible I had something else misconfigured with the plugin. - mnd
Is this with the properties inside the webapp folder? Have you tried putting the properties file outside of the webapp? - tim_yates

1 Answers

0
votes

The following is what I did to get around this issue, it required setUseCaches(false), to make sure that cached versions of a file (from the classloader) were not used.

def fileInputStream = null
// The method below is used to ensure that the file is read from disk every time, rather than using the previously loaded file from the ClassLoader
def classLoader = Thread.currentThread().getContextClassLoader()
def resource = classLoader.getResource("log4j.properties")
def connection = resource.openConnection()
connection.setUseCaches(false)
// Warning: do not read or log the fileInputStream, because it will close the connection
fileInputStream = (FileInputStream)connection.getInputStream().in
// Use the Properties object to prevent errors on comments (#)
def props = new Properties()
props.load(fileInputStream)
def config = new ConfigSlurper().parse(props)

Then I can reference logger properties using:

config.log4j.logger.each {
    Logger.rootLogger.loggerRepository.getLogger(it.key).level = Level.toLevel(it.value)
}

You can also make checks to see what the existing log levels are and provide output to state which logging has actually changed.