Support programmatic setting of log levels

Provide a common way to programmatically set log levels regardless of
the underlying log implementation.

Issue: #55202588
This commit is contained in:
Phillip Webb 2013-08-13 15:25:29 -07:00 committed by Phillip Webb
parent 5446a20a90
commit 5b77028f04
13 changed files with 272 additions and 20 deletions

View File

@ -39,6 +39,11 @@
<artifactId>javax.servlet-api</artifactId> <artifactId>javax.servlet-api</artifactId>
<optional>true</optional> <optional>true</optional>
</dependency> </dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<optional>true</optional>
</dependency>
<dependency> <dependency>
<groupId>org.apache.tomcat.embed</groupId> <groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-core</artifactId> <artifactId>tomcat-embed-core</artifactId>

View File

@ -0,0 +1,28 @@
/*
* 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.boot.logging;
/**
* Logging levels supported by a {@link LoggingSystem}.
*
* @author Phillip Webb
*/
public enum LogLevel {
TRACE, DEBUG, INFO, WARN, ERROR, FATAL
}

View File

@ -61,6 +61,13 @@ public abstract class LoggingSystem {
*/ */
public abstract void initialize(String configLocation); public abstract void initialize(String configLocation);
/**
* Sets the logging level for a given logger.
* @param loggerName the name of the logger to set
* @param level the log level
*/
public abstract void setLogLevel(String loggerName, LogLevel level);
/** /**
* Detect and return the logging system in use. * Detect and return the logging system in use.
* @return The logging system * @return The logging system

View File

@ -16,11 +16,17 @@
package org.springframework.boot.logging.java; package org.springframework.boot.logging.java;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.LogManager; import java.util.logging.LogManager;
import java.util.logging.Logger; import java.util.logging.Logger;
import org.springframework.boot.logging.AbstractLoggingSystem; import org.springframework.boot.logging.AbstractLoggingSystem;
import org.springframework.boot.logging.LogLevel;
import org.springframework.boot.logging.LoggingSystem; import org.springframework.boot.logging.LoggingSystem;
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;
@ -32,12 +38,25 @@ import org.springframework.util.SystemPropertyUtils;
*/ */
public class JavaLoggingSystem extends AbstractLoggingSystem { public class JavaLoggingSystem extends AbstractLoggingSystem {
private static final Map<LogLevel, Level> LEVELS;
static {
Map<LogLevel, Level> levels = new HashMap<LogLevel, Level>();
levels.put(LogLevel.TRACE, Level.FINEST);
levels.put(LogLevel.DEBUG, Level.FINE);
levels.put(LogLevel.INFO, Level.INFO);
levels.put(LogLevel.WARN, Level.WARNING);
levels.put(LogLevel.ERROR, Level.SEVERE);
levels.put(LogLevel.FATAL, Level.SEVERE);
LEVELS = Collections.unmodifiableMap(levels);
}
public JavaLoggingSystem(ClassLoader classLoader) { public JavaLoggingSystem(ClassLoader classLoader) {
super(classLoader, "logging.properties"); super(classLoader, "logging.properties");
} }
@Override @Override
public void initialize(String configLocation) { public void initialize(String configLocation) {
Assert.notNull(configLocation, "ConfigLocation must not be null");
String resolvedLocation = SystemPropertyUtils.resolvePlaceholders(configLocation); String resolvedLocation = SystemPropertyUtils.resolvePlaceholders(configLocation);
try { try {
LogManager.getLogManager().readConfiguration( LogManager.getLogManager().readConfiguration(
@ -49,4 +68,10 @@ public class JavaLoggingSystem extends AbstractLoggingSystem {
} }
} }
@Override
public void setLogLevel(String loggerName, LogLevel level) {
Assert.notNull(level, "Level must not be null");
Logger logger = Logger.getLogger(loggerName == null ? "" : loggerName);
logger.setLevel(LEVELS.get(level));
}
} }

View File

@ -16,9 +16,19 @@
package org.springframework.boot.logging.log4j; package org.springframework.boot.logging.log4j;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import org.apache.log4j.Level;
import org.apache.log4j.LogManager;
import org.apache.log4j.Logger;
import org.springframework.boot.logging.AbstractLoggingSystem; import org.springframework.boot.logging.AbstractLoggingSystem;
import org.springframework.boot.logging.LogLevel;
import org.springframework.boot.logging.LoggingSystem; import org.springframework.boot.logging.LoggingSystem;
import org.springframework.util.Assert;
import org.springframework.util.Log4jConfigurer; import org.springframework.util.Log4jConfigurer;
import org.springframework.util.StringUtils;
/** /**
* {@link LoggingSystem} for for <a href="http://logging.apache.org/log4j">log4j</a>. * {@link LoggingSystem} for for <a href="http://logging.apache.org/log4j">log4j</a>.
@ -28,12 +38,25 @@ import org.springframework.util.Log4jConfigurer;
*/ */
public class Log4JLoggingSystem extends AbstractLoggingSystem { public class Log4JLoggingSystem extends AbstractLoggingSystem {
private static final Map<LogLevel, Level> LEVELS;
static {
Map<LogLevel, Level> levels = new HashMap<LogLevel, Level>();
levels.put(LogLevel.TRACE, Level.TRACE);
levels.put(LogLevel.DEBUG, Level.DEBUG);
levels.put(LogLevel.INFO, Level.INFO);
levels.put(LogLevel.WARN, Level.WARN);
levels.put(LogLevel.ERROR, Level.ERROR);
levels.put(LogLevel.FATAL, Level.ERROR);
LEVELS = Collections.unmodifiableMap(levels);
}
public Log4JLoggingSystem(ClassLoader classLoader) { public Log4JLoggingSystem(ClassLoader classLoader) {
super(classLoader, "log4j.xml", "log4j.properties"); super(classLoader, "log4j.xml", "log4j.properties");
} }
@Override @Override
public void initialize(String configLocation) { public void initialize(String configLocation) {
Assert.notNull(configLocation, "ConfigLocation must not be null");
try { try {
Log4jConfigurer.initLogging(configLocation); Log4jConfigurer.initLogging(configLocation);
} }
@ -43,4 +66,11 @@ public class Log4JLoggingSystem extends AbstractLoggingSystem {
} }
} }
@Override
public void setLogLevel(String loggerName, LogLevel level) {
Logger logger = (StringUtils.hasLength(loggerName) ? LogManager
.getLogger(loggerName) : LogManager.getRootLogger());
logger.setLevel(LEVELS.get(level));
}
} }

View File

@ -17,17 +17,24 @@
package org.springframework.boot.logging.logback; package org.springframework.boot.logging.logback;
import java.net.URL; import java.net.URL;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import org.slf4j.ILoggerFactory; import org.slf4j.ILoggerFactory;
import org.slf4j.Logger;
import org.slf4j.bridge.SLF4JBridgeHandler; import org.slf4j.bridge.SLF4JBridgeHandler;
import org.slf4j.impl.StaticLoggerBinder; import org.slf4j.impl.StaticLoggerBinder;
import org.springframework.boot.logging.AbstractLoggingSystem; import org.springframework.boot.logging.AbstractLoggingSystem;
import org.springframework.boot.logging.LogLevel;
import org.springframework.boot.logging.LoggingSystem; import org.springframework.boot.logging.LoggingSystem;
import org.springframework.util.Assert; import org.springframework.util.Assert;
import org.springframework.util.ClassUtils; import org.springframework.util.ClassUtils;
import org.springframework.util.ResourceUtils; import org.springframework.util.ResourceUtils;
import org.springframework.util.StringUtils;
import org.springframework.util.SystemPropertyUtils; import org.springframework.util.SystemPropertyUtils;
import ch.qos.logback.classic.Level;
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;
@ -39,6 +46,18 @@ import ch.qos.logback.classic.util.ContextInitializer;
*/ */
public class LogbackLoggingSystem extends AbstractLoggingSystem { public class LogbackLoggingSystem extends AbstractLoggingSystem {
private static final Map<LogLevel, Level> LEVELS;
static {
Map<LogLevel, Level> levels = new HashMap<LogLevel, Level>();
levels.put(LogLevel.TRACE, Level.TRACE);
levels.put(LogLevel.DEBUG, Level.DEBUG);
levels.put(LogLevel.INFO, Level.INFO);
levels.put(LogLevel.WARN, Level.WARN);
levels.put(LogLevel.ERROR, Level.ERROR);
levels.put(LogLevel.FATAL, Level.ERROR);
LEVELS = Collections.unmodifiableMap(levels);
}
public LogbackLoggingSystem(ClassLoader classLoader) { public LogbackLoggingSystem(ClassLoader classLoader) {
super(classLoader, "logback.xml"); super(classLoader, "logback.xml");
} }
@ -54,6 +73,7 @@ public class LogbackLoggingSystem extends AbstractLoggingSystem {
@Override @Override
public void initialize(String configLocation) { public void initialize(String configLocation) {
Assert.notNull(configLocation, "ConfigLocation must not be null");
String resolvedLocation = SystemPropertyUtils.resolvePlaceholders(configLocation); String resolvedLocation = SystemPropertyUtils.resolvePlaceholders(configLocation);
ILoggerFactory factory = StaticLoggerBinder.getSingleton().getLoggerFactory(); ILoggerFactory factory = StaticLoggerBinder.getSingleton().getLoggerFactory();
Assert.isInstanceOf(ILoggerFactory.class, factory); Assert.isInstanceOf(ILoggerFactory.class, factory);
@ -69,4 +89,13 @@ public class LogbackLoggingSystem extends AbstractLoggingSystem {
} }
} }
@Override
public void setLogLevel(String loggerName, LogLevel level) {
ILoggerFactory factory = StaticLoggerBinder.getSingleton().getLoggerFactory();
Logger logger = factory
.getLogger(StringUtils.isEmpty(loggerName) ? Logger.ROOT_LOGGER_NAME
: loggerName);
((ch.qos.logback.classic.Logger) logger).setLevel(LEVELS.get(level));
}
} }

View File

@ -2,10 +2,11 @@ handlers = java.util.logging.FileHandler, java.util.logging.ConsoleHandler
.level = INFO .level = INFO
# File Logging # File Logging
java.util.logging.FileHandler.pattern = %t/service.log java.util.logging.FileHandler.pattern = %t/spring.log
java.util.logging.FileHandler.formatter = org.springframework.boot.logging.java.SimpleFormatter java.util.logging.FileHandler.formatter = org.springframework.boot.logging.java.SimpleFormatter
java.util.logging.FileHandler.level = INFO java.util.logging.FileHandler.level = ALL
java.util.logging.FileHandler.limit = 10485760 java.util.logging.FileHandler.limit = 10485760
java.util.logging.FileHandler.count = 10 java.util.logging.FileHandler.count = 10
java.util.logging.ConsoleHandler.formatter = org.springframework.boot.logging.java.SimpleFormatter java.util.logging.ConsoleHandler.formatter = org.springframework.boot.logging.java.SimpleFormatter
java.util.logging.ConsoleHandler.level = ALL

View File

@ -1,9 +1,8 @@
log4j.rootCategory=INFO, CONSOLE, FILE log4j.rootCategory=INFO, CONSOLE, FILE
PID=???? PID=????
catalina.base=/tmp LOG_PATH=/tmp
LOG_PATH=${catalina.base}/logs LOG_FILE=${LOG_PATH}/spring.log
LOG_FILE=${LOG_PATH}/service.log
LOG_PATTERN=[%d{yyyy-MM-dd HH:mm:ss.SSS}] service%X{context} - ${PID} %5p [%t] --- %c{1}: %m%n LOG_PATTERN=[%d{yyyy-MM-dd HH:mm:ss.SSS}] service%X{context} - ${PID} %5p [%t] --- %c{1}: %m%n
# CONSOLE is set to be a ConsoleAppender using a PatternLayout. # CONSOLE is set to be a ConsoleAppender using a PatternLayout.

View File

@ -19,15 +19,17 @@ package org.springframework.boot.logging.java;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.PrintStream; import java.io.PrintStream;
import java.util.logging.LogManager;
import org.apache.commons.logging.impl.Jdk14Logger; import org.apache.commons.logging.impl.Jdk14Logger;
import org.junit.After; import org.junit.After;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.springframework.boot.logging.java.JavaLoggingSystem; import org.springframework.boot.logging.LogLevel;
import org.springframework.util.ClassUtils; import org.springframework.util.ClassUtils;
import org.springframework.util.StringUtils;
import static org.hamcrest.Matchers.equalTo;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
/** /**
@ -48,8 +50,6 @@ public class JavaLoggerSystemTests {
@Before @Before
public void init() throws SecurityException, IOException { public void init() throws SecurityException, IOException {
LogManager.getLogManager().readConfiguration(
getClass().getResourceAsStream("/logging.properties"));
this.logger = new Jdk14Logger(getClass().getName()); this.logger = new Jdk14Logger(getClass().getName());
this.savedOutput = System.err; this.savedOutput = System.err;
this.output = new ByteArrayOutputStream(); this.output = new ByteArrayOutputStream();
@ -71,6 +71,7 @@ public class JavaLoggerSystemTests {
@Test @Test
public void testCustomFormatter() throws Exception { public void testCustomFormatter() throws Exception {
this.loggingSystem.initialize();
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"));
@ -108,4 +109,14 @@ public class JavaLoggerSystemTests {
this.loggingSystem.initialize(null); this.loggingSystem.initialize(null);
} }
@Test
public void setLevel() throws Exception {
this.loggingSystem.initialize();
this.logger.debug("Hello");
this.loggingSystem.setLogLevel("org.springframework.boot", LogLevel.DEBUG);
this.logger.debug("Hello");
assertThat(StringUtils.countOccurrencesOf(this.output.toString(), "Hello"),
equalTo(1));
}
} }

View File

@ -0,0 +1,88 @@
/*
* 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.boot.logging.log4j;
import org.apache.commons.logging.impl.Log4JLogger;
import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.springframework.boot.OutputCapture;
import org.springframework.boot.logging.LogLevel;
import org.springframework.util.StringUtils;
import static org.hamcrest.Matchers.equalTo;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
/**
* Tests for {@link Log4JLoggingSystem}.
*
* @author Phillip Webb
*/
public class Log4JLoggingSystemTests {
@Rule
public OutputCapture output = new OutputCapture();
private Log4JLoggingSystem loggingSystem = new Log4JLoggingSystem(getClass()
.getClassLoader());
private Log4JLogger logger;
@Before
public void setup() {
this.logger = new Log4JLogger(getClass().getName());
}
@After
public void clear() {
System.clearProperty("LOG_FILE");
System.clearProperty("LOG_PATH");
System.clearProperty("PID");
}
@Test
public void testNonDefaultConfigLocation() throws Exception {
this.loggingSystem.initialize("classpath:log4j-nondefault.properties");
this.logger.info("Hello world");
String output = this.output.toString().trim();
assertTrue("Wrong output:\n" + output, output.contains("Hello world"));
assertTrue("Wrong output:\n" + output, output.contains("/tmp/spring.log"));
}
@Test(expected = IllegalStateException.class)
public void testNonexistentConfigLocation() throws Exception {
this.loggingSystem.initialize("classpath:log4j-nonexistent.xml");
}
@Test(expected = IllegalArgumentException.class)
public void testNullConfigLocation() throws Exception {
this.loggingSystem.initialize(null);
}
@Test
public void setLevel() throws Exception {
this.loggingSystem.initialize();
this.logger.debug("Hello");
this.loggingSystem.setLogLevel("org.springframework.boot", LogLevel.DEBUG);
this.logger.debug("Hello");
assertThat(StringUtils.countOccurrencesOf(this.output.toString(), "Hello"),
equalTo(1));
}
}

View File

@ -17,12 +17,17 @@
package org.springframework.boot.logging.logback; package org.springframework.boot.logging.logback;
import org.apache.commons.logging.Log; import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory; import org.apache.commons.logging.impl.SLF4JLogFactory;
import org.junit.After; import org.junit.After;
import org.junit.Before;
import org.junit.Rule; import org.junit.Rule;
import org.junit.Test; import org.junit.Test;
import org.springframework.boot.OutputCapture; import org.springframework.boot.OutputCapture;
import org.springframework.boot.logging.LogLevel;
import org.springframework.util.StringUtils;
import static org.hamcrest.Matchers.equalTo;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
/** /**
@ -33,11 +38,18 @@ import static org.junit.Assert.assertTrue;
public class LogbackLoggingSystemTests { public class LogbackLoggingSystemTests {
@Rule @Rule
public OutputCapture outputCapture = new OutputCapture(); public OutputCapture output = new OutputCapture();
private LogbackLoggingSystem loggingSystem = new LogbackLoggingSystem(getClass() private LogbackLoggingSystem loggingSystem = new LogbackLoggingSystem(getClass()
.getClassLoader()); .getClassLoader());
private Log logger;
@Before
public void setup() {
this.logger = new SLF4JLogFactory().getInstance(getClass().getName());
}
@After @After
public void clear() { public void clear() {
System.clearProperty("LOG_FILE"); System.clearProperty("LOG_FILE");
@ -46,13 +58,12 @@ public class LogbackLoggingSystemTests {
} }
@Test @Test
public void testDefaultConfigLocation() throws Exception { public void testNonDefaultConfigLocation() throws Exception {
this.loggingSystem.initialize("classpath:logback-nondefault.xml"); this.loggingSystem.initialize("classpath:logback-nondefault.xml");
Log logger = LogFactory.getLog(LogbackLoggingSystemTests.class); this.logger.info("Hello world");
logger.info("Hello world"); String output = this.output.toString().trim();
String output = this.outputCapture.toString().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.contains("/tmp/spring.log"));
} }
@Test(expected = IllegalStateException.class) @Test(expected = IllegalStateException.class)
@ -65,4 +76,14 @@ public class LogbackLoggingSystemTests {
this.loggingSystem.initialize(null); this.loggingSystem.initialize(null);
} }
@Test
public void setLevel() throws Exception {
this.loggingSystem.initialize();
this.logger.debug("Hello");
this.loggingSystem.setLogLevel("org.springframework.boot", LogLevel.DEBUG);
this.logger.debug("Hello");
assertThat(StringUtils.countOccurrencesOf(this.output.toString(), "Hello"),
equalTo(1));
}
} }

View File

@ -0,0 +1,12 @@
log4j.reset=true
log4j.rootCategory=INFO, CONSOLE
PID=????
LOG_PATH=/tmp
LOG_FILE=${LOG_PATH}/spring.log
LOG_PATTERN=${LOG_FILE} %d{yyyy-MM-dd HH:mm:ss.SSS}] service%X{context} - ${PID} %5p [%t] --- %c{1}: %m%n
# CONSOLE is set to be a ConsoleAppender using a PatternLayout.
log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender
log4j.appender.CONSOLE.layout=org.apache.log4j.PatternLayout
log4j.appender.CONSOLE.layout.ConversionPattern=${LOG_PATTERN}

View File

@ -1,4 +0,0 @@
handlers= java.util.logging.ConsoleHandler
.level= INFO
java.util.logging.ConsoleHandler.level = INFO
java.util.logging.ConsoleHandler.formatter = org.springframework.boot.logging.java.SimpleFormatter