Create LoggingSystem class to configure logging

Add LoggingSystem class that can be used to configure various logging
systems in a consistent way. Mostly the code is migrated from the
LoggingApplicationContextInitializer.
This commit is contained in:
Phillip Webb 2013-07-12 16:40:15 -07:00
parent ae20d389c4
commit a324beadac
9 changed files with 266 additions and 165 deletions

View File

@ -22,17 +22,14 @@ import java.util.Map;
import org.apache.commons.logging.Log; import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory; import org.apache.commons.logging.LogFactory;
import org.springframework.bootstrap.logging.JavaLoggerConfigurer; import org.springframework.bootstrap.SpringApplication;
import org.springframework.bootstrap.logging.LogbackConfigurer; import org.springframework.bootstrap.SpringApplicationInitializer;
import org.springframework.context.ApplicationContext; import org.springframework.bootstrap.logging.LoggingSystem;
import org.springframework.context.ApplicationContextInitializer; import org.springframework.context.ApplicationContextInitializer;
import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.Ordered; import org.springframework.core.Ordered;
import org.springframework.core.env.ConfigurableEnvironment; import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.Environment; import org.springframework.core.env.Environment;
import org.springframework.core.io.ClassPathResource;
import org.springframework.util.ClassUtils;
import org.springframework.util.Log4jConfigurer;
import org.springframework.util.ResourceUtils; import org.springframework.util.ResourceUtils;
/** /**
@ -68,7 +65,8 @@ import org.springframework.util.ResourceUtils;
* @author Phillip Webb * @author Phillip Webb
*/ */
public class LoggingApplicationContextInitializer implements public class LoggingApplicationContextInitializer implements
ApplicationContextInitializer<ConfigurableApplicationContext>, Ordered { ApplicationContextInitializer<ConfigurableApplicationContext>,
SpringApplicationInitializer, Ordered {
private static final Map<String, String> ENVIRONMENT_SYSTEM_PROPERTY_MAPPING; private static final Map<String, String> ENVIRONMENT_SYSTEM_PROPERTY_MAPPING;
static { static {
@ -78,8 +76,16 @@ public class LoggingApplicationContextInitializer implements
ENVIRONMENT_SYSTEM_PROPERTY_MAPPING.put("PID", "PID"); ENVIRONMENT_SYSTEM_PROPERTY_MAPPING.put("PID", "PID");
} }
private final Log logger = LogFactory.getLog(getClass());
private int order = Integer.MIN_VALUE + 11; private int order = Integer.MIN_VALUE + 11;
@Override
public void initialize(SpringApplication springApplication) {
LoggingSystem.get(springApplication.getClass().getClassLoader())
.beforeInitialize();
}
/** /**
* Initialize the logging system according to preferences expressed through the * Initialize the logging system according to preferences expressed through the
* {@link Environment} and the classpath. * {@link Environment} and the classpath.
@ -102,7 +108,23 @@ public class LoggingApplicationContextInitializer implements
} }
LoggingSystem system = LoggingSystem.get(applicationContext.getClassLoader()); LoggingSystem system = LoggingSystem.get(applicationContext.getClassLoader());
system.init(applicationContext);
// User specified configuration
if (environment.containsProperty("logging.config")) {
String value = environment.getProperty("logging.config");
try {
ResourceUtils.getURL(value).openStream().close();
system.initialize(value);
return;
}
catch (Exception ex) {
// Swallow exception and continue
}
this.logger.warn("Logging environment value '" + value
+ "' cannot be opened and will be ignored");
}
system.initialize();
} }
private String getPid() { private String getPid() {
@ -122,113 +144,4 @@ public class LoggingApplicationContextInitializer implements
return this.order; return this.order;
} }
private static enum LoggingSystem {
/**
* Log4J
*/
LOG4J("org.apache.log4j.PropertyConfigurator", "log4j.xml", "log4j.properties") {
@Override
protected void doInit(ApplicationContext applicationContext,
String configLocation) throws Exception {
Log4jConfigurer.initLogging(configLocation);
}
},
/**
* Logback
*/
LOGBACK("ch.qos.logback.core.Appender", "logback.xml") {
@Override
protected void doInit(ApplicationContext applicationContext,
String configLocation) throws Exception {
LogbackConfigurer.initLogging(configLocation);
}
},
/**
* Java Util Logging
*/
JAVA(null, "logging.properties") {
@Override
protected void doInit(ApplicationContext applicationContext,
String configLocation) throws Exception {
JavaLoggerConfigurer.initLogging(configLocation);
}
};
private final Log logger = LogFactory
.getLog(LoggingApplicationContextInitializer.class);
private final String className;
private final String[] paths;
private LoggingSystem(String className, String... paths) {
this.className = className;
this.paths = paths;
}
public void init(ApplicationContext applicationContext) {
String configLocation = getConfigLocation(applicationContext);
try {
doInit(applicationContext, configLocation);
}
catch (Exception ex) {
throw new IllegalStateException("Cannot initialize logging from "
+ configLocation, ex);
}
}
protected abstract void doInit(ApplicationContext applicationContext,
String configLocation) throws Exception;
private String getConfigLocation(ApplicationContext applicationContext) {
Environment environment = applicationContext.getEnvironment();
ClassLoader classLoader = applicationContext.getClassLoader();
// User specified config
if (environment.containsProperty("logging.config")) {
String value = environment.getProperty("logging.config");
try {
ResourceUtils.getURL(value).openStream().close();
return value;
}
catch (Exception ex) {
// Swallow exception and continue
}
this.logger.warn("Logging environment value '" + value
+ "' cannot be opened and will be ignored");
}
// Common patterns
for (String path : this.paths) {
ClassPathResource resource = new ClassPathResource(path, classLoader);
if (resource.exists()) {
return "classpath:" + path;
}
}
// Fallback to the default
String defaultPath = ClassUtils.getPackageName(JavaLoggerConfigurer.class);
defaultPath = defaultPath.replace(".", "/");
defaultPath = defaultPath + "/" + this.paths[this.paths.length - 1];
return "classpath:" + defaultPath;
}
public static LoggingSystem get(ClassLoader classLoader) {
for (LoggingSystem loggingSystem : values()) {
String className = loggingSystem.className;
if (className == null || ClassUtils.isPresent(className, classLoader)) {
return loggingSystem;
}
}
return JAVA;
}
}
} }

View File

@ -0,0 +1,72 @@
/*
* Copyright 2012-2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.bootstrap.logging;
import org.springframework.core.io.ClassPathResource;
import org.springframework.util.ClassUtils;
/**
* Abstract base class for {@link LoggingSystem} implementations.
*
* @author Phillip Webb
* @author Dave Syer
*/
abstract class AbstractLoggingSystem extends LoggingSystem {
private final ClassLoader classLoader;
private final String[] paths;
public AbstractLoggingSystem(ClassLoader classLoader, String... paths) {
this.classLoader = classLoader;
this.paths = paths.clone();
}
protected final ClassLoader getClassLoader() {
return this.classLoader;
}
@Override
public void beforeInitialize() {
initializeWithSensibleDefaults();
}
@Override
public void initialize() {
for (String path : this.paths) {
ClassPathResource resource = new ClassPathResource(path, this.classLoader);
if (resource.exists()) {
initialize("classpath:" + path);
return;
}
}
initializeWithSensibleDefaults();
}
protected void initializeWithSensibleDefaults() {
initialize(getPackagedConfigFile(this.paths[this.paths.length - 1]));
}
protected final String getPackagedConfigFile(String fileName) {
String defaultPath = ClassUtils.getPackageName(getClass());
defaultPath = defaultPath.replace(".", "/");
defaultPath = defaultPath + "/" + fileName;
defaultPath = "classpath:" + defaultPath;
return defaultPath;
}
}

View File

@ -16,7 +16,6 @@
package org.springframework.bootstrap.logging; package org.springframework.bootstrap.logging;
import java.io.FileNotFoundException;
import java.util.logging.LogManager; import java.util.logging.LogManager;
import java.util.logging.Logger; import java.util.logging.Logger;
@ -24,29 +23,27 @@ import org.springframework.util.ResourceUtils;
import org.springframework.util.SystemPropertyUtils; import org.springframework.util.SystemPropertyUtils;
/** /**
* Logging initializer for {@link Logger java.util.logging}. * {@link LoggingSystem} for {@link Logger java.util.logging}.
* *
* @author Phillip Webb
* @author Dave Syer * @author Dave Syer
*/ */
public abstract class JavaLoggerConfigurer { class JavaLoggingSystem extends AbstractLoggingSystem {
/** public JavaLoggingSystem(ClassLoader classLoader) {
* Configure the logging system from the specified location (a properties file). super(classLoader, "logging.properties");
* }
* @param location the location to use to configure logging
*/ @Override
public static void initLogging(String location) throws FileNotFoundException { public void initialize(String configLocation) {
String resolvedLocation = SystemPropertyUtils.resolvePlaceholders(location); String resolvedLocation = SystemPropertyUtils.resolvePlaceholders(configLocation);
try { try {
LogManager.getLogManager().readConfiguration( LogManager.getLogManager().readConfiguration(
ResourceUtils.getURL(resolvedLocation).openStream()); ResourceUtils.getURL(resolvedLocation).openStream());
} }
catch (Exception ex) { catch (Exception ex) {
if (ex instanceof FileNotFoundException) { throw new IllegalStateException("Could not initialize logging from "
throw (FileNotFoundException) ex; + configLocation, ex);
}
throw new IllegalArgumentException("Could not initialize logging from "
+ location, ex);
} }
} }

View File

@ -0,0 +1,44 @@
/*
* Copyright 2012-2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.bootstrap.logging;
import org.springframework.util.Log4jConfigurer;
/**
* {@link LoggingSystem} for for <a href="http://logging.apache.org/log4j">log4j</a>.
*
* @author Phillip Webb
* @author Dave Syer
*/
class Log4JLoggingSystem extends AbstractLoggingSystem {
public Log4JLoggingSystem(ClassLoader classLoader) {
super(classLoader, "log4j.xml", "log4j.properties");
}
@Override
public void initialize(String configLocation) {
try {
Log4jConfigurer.initLogging(configLocation);
}
catch (Exception ex) {
throw new IllegalStateException("Could not initialize logging from "
+ configLocation, ex);
}
}
}

View File

@ -16,41 +16,43 @@
package org.springframework.bootstrap.logging; package org.springframework.bootstrap.logging;
import java.io.FileNotFoundException;
import java.net.URL; import java.net.URL;
import org.slf4j.ILoggerFactory;
import org.slf4j.impl.StaticLoggerBinder; import org.slf4j.impl.StaticLoggerBinder;
import org.springframework.util.Assert;
import org.springframework.util.ResourceUtils; import org.springframework.util.ResourceUtils;
import org.springframework.util.SystemPropertyUtils; import org.springframework.util.SystemPropertyUtils;
import ch.qos.logback.classic.LoggerContext; import ch.qos.logback.classic.LoggerContext;
import ch.qos.logback.classic.util.ContextInitializer; import ch.qos.logback.classic.util.ContextInitializer;
import ch.qos.logback.core.joran.spi.JoranException;
/** /**
* Logging initializer for <a href="http://logback.qos.ch">logback</a>. * {@link LoggingSystem} for for <a href="http://logback.qos.ch">logback</a>.
* *
* @author Phillip Webb
* @author Dave Syer * @author Dave Syer
*/ */
public abstract class LogbackConfigurer { class LogbackLoggingSystem extends AbstractLoggingSystem {
/** public LogbackLoggingSystem(ClassLoader classLoader) {
* Configure the logback system from the specified location. super(classLoader, "logback.xml");
* }
* @param location the location to use to configure logback
*/ @Override
public static void initLogging(String location) throws FileNotFoundException { public void initialize(String configLocation) {
String resolvedLocation = SystemPropertyUtils.resolvePlaceholders(location); String resolvedLocation = SystemPropertyUtils.resolvePlaceholders(configLocation);
URL url = ResourceUtils.getURL(resolvedLocation); ILoggerFactory factory = StaticLoggerBinder.getSingleton().getLoggerFactory();
LoggerContext context = (LoggerContext) StaticLoggerBinder.getSingleton() Assert.isInstanceOf(ILoggerFactory.class, factory);
.getLoggerFactory(); LoggerContext context = (LoggerContext) factory;
context.stop(); context.stop();
try { try {
URL url = ResourceUtils.getURL(resolvedLocation);
new ContextInitializer(context).configureByResource(url); new ContextInitializer(context).configureByResource(url);
} }
catch (JoranException ex) { catch (Exception ex) {
throw new IllegalArgumentException("Could not initialize logging from " throw new IllegalStateException("Could not initialize logging from "
+ location, ex); + configLocation, ex);
} }
} }

View File

@ -0,0 +1,63 @@
/*
* Copyright 2012-2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.bootstrap.logging;
import org.springframework.util.ClassUtils;
/**
* Common abstraction over logging systems.
*
* @author Phillip Webb
* @author Dave Syer
*/
public abstract class LoggingSystem {
/**
* Reset the logging system to be limit output. This method may be called before
* {@link #initialize()} to reduce logging noise until the systems has been full
* Initialized.
*/
public abstract void beforeInitialize();
/**
* Initialize the logging system using sensible defaults. This method should generally
* try to find system specific configuration on classpath before falling back to
* sensible defaults.
*/
public abstract void initialize();
/**
* Initialize the logging system from a logging configuration location.
* @param configLocation a log configuration location
*/
public abstract void initialize(String configLocation);
/**
* Detect and return the logging system in use.
* @return The logging system
*/
public static LoggingSystem get(ClassLoader classLoader) {
if (ClassUtils.isPresent("ch.qos.logback.core.Appender", classLoader)) {
return new LogbackLoggingSystem(classLoader);
}
if (ClassUtils.isPresent("org.apache.log4j.PropertyConfigurator", classLoader)) {
return new Log4JLoggingSystem(classLoader);
}
return new JavaLoggingSystem(classLoader);
}
}

View File

@ -17,7 +17,6 @@
package org.springframework.bootstrap.logging; package org.springframework.bootstrap.logging;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.io.FileNotFoundException;
import java.io.IOException; import java.io.IOException;
import java.io.PrintStream; import java.io.PrintStream;
import java.util.logging.LogManager; import java.util.logging.LogManager;
@ -30,11 +29,14 @@ import org.junit.Test;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
/** /**
* Tests for {@link JavaLoggerConfigurer}. * Tests for {@link JavaLoggingSystem}.
* *
* @author Dave Syer * @author Dave Syer
*/ */
public class JavaLoggerConfigurerTests { public class JavaLoggerSystemTests {
private JavaLoggingSystem loggingSystem = new JavaLoggingSystem(getClass()
.getClassLoader());
private PrintStream savedOutput; private PrintStream savedOutput;
@ -67,21 +69,21 @@ public class JavaLoggerConfigurerTests {
@Test @Test
public void testDefaultConfigLocation() throws Exception { public void testDefaultConfigLocation() throws Exception {
JavaLoggerConfigurer.initLogging("classpath:logging-nondefault.properties"); this.loggingSystem.initialize("classpath:logging-nondefault.properties");
this.logger.info("Hello world"); this.logger.info("Hello world");
String output = getOutput().trim(); String output = getOutput().trim();
assertTrue("Wrong output:\n" + output, output.contains("Hello world")); assertTrue("Wrong output:\n" + output, output.contains("Hello world"));
assertTrue("Wrong output:\n" + output, output.contains("INFO")); assertTrue("Wrong output:\n" + output, output.contains("INFO"));
} }
@Test(expected = FileNotFoundException.class) @Test(expected = IllegalStateException.class)
public void testNonexistentConfigLocation() throws Exception { public void testNonexistentConfigLocation() throws Exception {
JavaLoggerConfigurer.initLogging("classpath:logging-nonexistent.properties"); this.loggingSystem.initialize("classpath:logging-nonexistent.properties");
} }
@Test(expected = IllegalArgumentException.class) @Test(expected = IllegalArgumentException.class)
public void testNullConfigLocation() throws Exception { public void testNullConfigLocation() throws Exception {
JavaLoggerConfigurer.initLogging(null); this.loggingSystem.initialize(null);
} }
} }

View File

@ -17,7 +17,6 @@
package org.springframework.bootstrap.logging; package org.springframework.bootstrap.logging;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.io.FileNotFoundException;
import java.io.PrintStream; import java.io.PrintStream;
import org.apache.commons.logging.Log; import org.apache.commons.logging.Log;
@ -29,13 +28,17 @@ import org.junit.Test;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
/** /**
* Tests for {@link LogbackConfigurer}. * Tests for {@link LogbackLoggingSystem}.
* *
* @author Dave Syer * @author Dave Syer
*/ */
public class LogbackConfigurerTests { public class LogbackLoggingSystemTests {
private LogbackLoggingSystem loggingSystem = new LogbackLoggingSystem(getClass()
.getClassLoader());
private PrintStream savedOutput; private PrintStream savedOutput;
private ByteArrayOutputStream output; private ByteArrayOutputStream output;
@Before @Before
@ -59,22 +62,22 @@ public class LogbackConfigurerTests {
@Test @Test
public void testDefaultConfigLocation() throws Exception { public void testDefaultConfigLocation() throws Exception {
LogbackConfigurer.initLogging("classpath:logback-nondefault.xml"); this.loggingSystem.initialize("classpath:logback-nondefault.xml");
Log logger = LogFactory.getLog(LogbackConfigurerTests.class); Log logger = LogFactory.getLog(LogbackLoggingSystemTests.class);
logger.info("Hello world"); logger.info("Hello world");
String output = getOutput().trim(); String output = getOutput().trim();
assertTrue("Wrong output:\n" + output, output.contains("Hello world")); assertTrue("Wrong output:\n" + output, output.contains("Hello world"));
assertTrue("Wrong output:\n" + output, output.startsWith("/tmp/spring.log")); assertTrue("Wrong output:\n" + output, output.startsWith("/tmp/spring.log"));
} }
@Test(expected = FileNotFoundException.class) @Test(expected = IllegalStateException.class)
public void testNonexistentConfigLocation() throws Exception { public void testNonexistentConfigLocation() throws Exception {
LogbackConfigurer.initLogging("classpath:logback-nonexistent.xml"); this.loggingSystem.initialize("classpath:logback-nonexistent.xml");
} }
@Test(expected = IllegalArgumentException.class) @Test(expected = IllegalArgumentException.class)
public void testNullConfigLocation() throws Exception { public void testNullConfigLocation() throws Exception {
LogbackConfigurer.initLogging(null); this.loggingSystem.initialize(null);
} }
} }

View File

@ -26,7 +26,9 @@ import org.apache.commons.logging.LogFactory;
import org.apache.commons.logging.impl.SLF4JLogFactory; import org.apache.commons.logging.impl.SLF4JLogFactory;
import org.junit.After; import org.junit.After;
import org.junit.Before; import org.junit.Before;
import org.junit.Rule;
import org.junit.Test; import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import org.springframework.bootstrap.context.initializer.LoggingApplicationContextInitializer; import org.springframework.bootstrap.context.initializer.LoggingApplicationContextInitializer;
import org.springframework.context.support.GenericApplicationContext; import org.springframework.context.support.GenericApplicationContext;
import org.springframework.core.env.PropertySource; import org.springframework.core.env.PropertySource;
@ -41,6 +43,9 @@ import static org.junit.Assert.assertTrue;
*/ */
public class LoggingApplicationContextInitializerTests { public class LoggingApplicationContextInitializerTests {
@Rule
public TemporaryFolder temporaryFolder = new TemporaryFolder();
private LoggingApplicationContextInitializer initializer = new LoggingApplicationContextInitializer(); private LoggingApplicationContextInitializer initializer = new LoggingApplicationContextInitializer();
private Log logger = new SLF4JLogFactory().getInstance(getClass()); private Log logger = new SLF4JLogFactory().getInstance(getClass());