Skip to content

support logger group when refresh by the /refresh endpoint #948

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.ListableBeanFactory;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanDefinitionHolder;
Expand All @@ -41,6 +42,8 @@
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.context.properties.bind.Bindable;
import org.springframework.boot.context.properties.bind.Binder;
import org.springframework.boot.logging.LoggerGroups;
import org.springframework.boot.logging.LoggingSystem;
import org.springframework.cloud.context.refresh.ConfigDataContextRefresher;
import org.springframework.cloud.context.refresh.ContextRefresher;
import org.springframework.cloud.context.refresh.LegacyContextRefresher;
Expand Down Expand Up @@ -98,8 +101,11 @@ public static RefreshScope refreshScope() {

@Bean
@ConditionalOnMissingBean
public static LoggingRebinder loggingRebinder() {
return new LoggingRebinder();
public static LoggingRebinder loggingRebinder(ObjectProvider<LoggingSystem> loggingSystem,
ObjectProvider<LoggerGroups> loggerGroups) {
return new LoggingRebinder(
loggingSystem.getIfAvailable(() -> LoggingSystem.get(LoggingSystem.class.getClassLoader())),
loggerGroups.getIfAvailable(LoggerGroups::new));
}

@Bean
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@

/**
* @author Dave Syer
* @author Haibo Wang
*
*/
@Configuration(proxyBeanMethods = false)
Expand Down Expand Up @@ -149,7 +150,8 @@ private void reinitializeLoggingSystem(ConfigurableEnvironment environment, Stri
}

private void setLogLevels(ConfigurableApplicationContext applicationContext, ConfigurableEnvironment environment) {
LoggingRebinder rebinder = new LoggingRebinder();
LoggingSystem system = LoggingSystem.get(LoggingSystem.class.getClassLoader());
LoggingRebinder rebinder = new LoggingRebinder(system, null);
rebinder.setEnvironment(environment);
// We can't fire the event in the ApplicationContext here (too early), but we can
// create our own listener and poke it (it doesn't need the key changes)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,11 @@

package org.springframework.cloud.logging;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Map.Entry;
Expand All @@ -27,28 +31,52 @@
import org.springframework.boot.context.properties.bind.Bindable;
import org.springframework.boot.context.properties.bind.Binder;
import org.springframework.boot.logging.LogLevel;
import org.springframework.boot.logging.LoggerGroup;
import org.springframework.boot.logging.LoggerGroups;
import org.springframework.boot.logging.LoggingSystem;
import org.springframework.cloud.context.environment.EnvironmentChangeEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.context.EnvironmentAware;
import org.springframework.core.ResolvableType;
import org.springframework.core.env.Environment;
import org.springframework.util.Assert;
import org.springframework.util.MultiValueMap;
import org.springframework.util.StringUtils;

/**
* Listener that looks for {@link EnvironmentChangeEvent} and rebinds logger levels if any
* changed.
*
* @author Dave Syer
* @author Olga Maciaszek-Sharma
* @author Haibo Wang
*
*/
public class LoggingRebinder implements ApplicationListener<EnvironmentChangeEvent>, EnvironmentAware {

private static Bindable<Map<String, List<String>>> STRING_STRINGS_MAP = Bindable
.of(ResolvableType.forClassWithGenerics(MultiValueMap.class, String.class, String.class).asMap());

private static final Bindable<Map<String, String>> STRING_STRING_MAP = Bindable.mapOf(String.class, String.class);

private final Log logger = LogFactory.getLog(getClass());

private Environment environment;

final private static List<String> SPRING_LOGGING_GROUP_NAMES = Arrays.asList("web", "sql");

private ArrayList<String> rootLoggersList = new ArrayList<>();

final private LoggingSystem loggingSystem;

final private LoggerGroups loggerGroups;

public LoggingRebinder(LoggingSystem loggingSystem, LoggerGroups loggerGroups) {
Assert.notNull(loggingSystem, "LoggingSystem must not be null");
this.loggingSystem = loggingSystem;
this.loggerGroups = loggerGroups;
}

@Override
public void setEnvironment(Environment environment) {
this.environment = environment;
Expand All @@ -59,31 +87,143 @@ public void onApplicationEvent(EnvironmentChangeEvent event) {
if (this.environment == null) {
return;
}
LoggingSystem system = LoggingSystem.get(LoggingSystem.class.getClassLoader());
setLogLevels(system, this.environment);

if (this.loggerGroups != null) {
List<String> deletedLoggerList = updateLoggerGroups(loggingSystem, environment, loggerGroups);

for (String logger : deletedLoggerList) {
if (!rootLoggersList.contains(logger)) {
rootLoggersList.add(logger);
}
}
}

setDefaultLogLevel(rootLoggersList, loggingSystem, environment);
setLogLevels(loggingSystem, environment, loggerGroups);
}

/**
* Merges the definition of a logger group from the configuration and returns the
* loggers that have been removed from the logger group.
* @param loggingSystem the logging system
* @param environment the environment
* @param loggerGroups the logger groups
* @return A list of logger that have been removed from the log group
*/
protected List<String> updateLoggerGroups(LoggingSystem loggingSystem, Environment environment,
LoggerGroups loggerGroups) {
Map<String, List<String>> loggerGroupsMap = Binder.get(environment).bind("logging.group", STRING_STRINGS_MAP)
.orElseGet(Collections::emptyMap);

ArrayList<String> deletedList = new ArrayList<String>();

// The deleted logger group
Map<String, List<String>> deletedGroup = new HashMap<String, List<String>>();
for (LoggerGroup loggerGroup : loggerGroups) {
if (!loggerGroupsMap.containsKey(loggerGroup.getName())
&& !SPRING_LOGGING_GROUP_NAMES.contains(loggerGroup.getName())) {
for (String loggerName : loggerGroup.getMembers()) {
if (!deletedList.contains(loggerName)) {
deletedList.add(loggerName);
}
}
deletedGroup.put(loggerGroup.getName(), Collections.emptyList());
}
}
loggerGroups.putAll(deletedGroup);

for (Entry<String, List<String>> entry : loggerGroupsMap.entrySet()) {
LoggerGroup loggerGroup = loggerGroups.get(entry.getKey());
if (loggerGroup != null) {
for (String loggerName : loggerGroup.getMembers()) {
if (!entry.getValue().contains(loggerName)) {
if (!deletedList.contains(loggerName)) {
deletedList.add(loggerName);
}
}
}
}
}
loggerGroups.putAll(loggerGroupsMap);

return deletedList;
}

protected void setLogLevels(LoggingSystem system, Environment environment) {
protected void setLogLevels(LoggingSystem system, Environment environment, LoggerGroups loggerGroups) {
Map<String, String> levels = Binder.get(environment).bind("logging.level", STRING_STRING_MAP)
.orElseGet(Collections::emptyMap);
for (Entry<String, String> entry : levels.entrySet()) {
setLogLevel(system, environment, entry.getKey(), entry.getValue().toString());
setLogLevel(loggingSystem, environment, loggerGroups, entry.getKey(), entry.getValue());
}
}

private void setLogLevel(LoggingSystem system, Environment environment, String name, String level) {
private void setLogLevel(LoggingSystem system, Environment environment, LoggerGroups loggerGroups, String name,
String level) {
try {
if (name.equalsIgnoreCase("root")) {
level = environment.resolvePlaceholders(level);
LogLevel logLevel = resolveLogLevel(level);

if (loggerGroups != null) {
LoggerGroup loggerGroup = loggerGroups.get(name);
if (loggerGroup != null && loggerGroup.hasMembers()) {
loggerGroup.configureLogLevel(logLevel, this::setLogLevel);
}
}

if ("root".equalsIgnoreCase(name)) {
name = null;
}
level = environment.resolvePlaceholders(level);
system.setLogLevel(name, resolveLogLevel(level));

setLogLevel(name, logLevel);
}
catch (RuntimeException ex) {
this.logger.error("Cannot set level: " + level + " for '" + name + "'");
}
}

private void setLogLevel(String name, LogLevel logLevel) {
loggingSystem.setLogLevel(name, logLevel);
}

private void setDefaultLogLevel(List<String> loggerNames, LoggingSystem loggingSystem, Environment environment) {
if (loggerNames.isEmpty()) {
return;
}

LogLevel logLevel = determineLogLevel(environment);
for (String loggerName : loggerNames) {
setLogLevel(loggerName, logLevel);
}
}

private LogLevel determineLogLevel(Environment environment) {
LogLevel logLevel = LogLevel.INFO;
String level = environment.getProperty("logging.level.root");
if (StringUtils.hasLength(level)) {
logLevel = resolveLogLevel(level);
}
else {
Log log = LogFactory.getLog(LoggingSystem.ROOT_LOGGER_NAME);
if (log.isTraceEnabled()) {
logLevel = LogLevel.TRACE;
}
else if (log.isDebugEnabled()) {
logLevel = LogLevel.DEBUG;
}
else if (log.isInfoEnabled()) {
logLevel = LogLevel.INFO;
}
else if (log.isErrorEnabled()) {
logLevel = LogLevel.ERROR;
}
else if (log.isFatalEnabled()) {
logLevel = LogLevel.FATAL;
}
}

return logLevel;
}

private LogLevel resolveLogLevel(String level) {
String trimmedLevel = level.trim();
if ("false".equalsIgnoreCase(trimmedLevel)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,21 @@
package org.springframework.cloud.logging;

import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import ch.qos.logback.classic.Level;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import org.springframework.boot.logging.LogLevel;
import org.springframework.boot.logging.LoggerGroups;
import org.springframework.boot.logging.LoggingSystem;
import org.springframework.boot.test.util.TestPropertyValues;
import org.springframework.cloud.context.environment.EnvironmentChangeEvent;
Expand All @@ -35,13 +42,22 @@
/**
* @author Dave Syer
* @author Olga Maciaszek-Sharma
* @author Haibo Wang
*
*/
public class LoggingRebinderTests {

private LoggingRebinder rebinder = new LoggingRebinder();
private LoggingRebinder rebinder = null;

private Logger logger = LoggerFactory.getLogger("org.springframework.web");
private final Logger logger = LoggerFactory.getLogger("org.springframework.web");

private LoggingSystem loggingSystem;

@Before
public void init() {
loggingSystem = LoggingSystem.get(getClass().getClassLoader());
rebinder = new LoggingRebinder(loggingSystem, null);
}

@After
public void reset() {
Expand Down Expand Up @@ -82,4 +98,52 @@ public void logLevelFalseResolvedToOff() {
then(Level.OFF).isEqualTo((logger.getLevel()));
}

@Test
public void logLevelsChangedByLoggerGroup() {
Logger testLogger = LoggerFactory.getLogger("my-app01");
then(testLogger.isTraceEnabled()).isFalse();
StandardEnvironment environment = new StandardEnvironment();
TestPropertyValues.of("logging.level.app=trace", "logging.group.app=my-app01").applyTo(environment);

LoggingRebinder rebinder = new LoggingRebinder(loggingSystem, new LoggerGroups());
rebinder.setEnvironment(environment);

HashSet<String> changeKeys = new HashSet<>();
changeKeys.add("spring.level.app");
changeKeys.add("spring.group.app");
rebinder.onApplicationEvent(new EnvironmentChangeEvent(environment, changeKeys));

then(testLogger.isTraceEnabled()).isTrue();
}

@Test
public void logLevelsChangedByGroupRemovedAndRootLevelChanged() {
LoggerGroups loggerGroups = new LoggerGroups();
HashMap<String, List<String>> groupsMap = new HashMap<>();
groupsMap.put("app", Stream.of("my-app01").collect(Collectors.toList()));

loggerGroups.putAll(groupsMap);
loggerGroups.get("app").configureLogLevel(LogLevel.TRACE,
(name, logLevel) -> loggingSystem.setLogLevel(name, logLevel));

Logger testLogger = LoggerFactory.getLogger("my-app01");
then(testLogger.isTraceEnabled()).isTrue();

// first, we removed logger from app logger group
StandardEnvironment environment = new StandardEnvironment();
TestPropertyValues.of("logging.level.app=trace").applyTo(environment);
LoggingRebinder rebinder = new LoggingRebinder(loggingSystem, loggerGroups);
rebinder.setEnvironment(environment);

HashSet<String> changeKeys = new HashSet<>();
rebinder.onApplicationEvent(new EnvironmentChangeEvent(environment, changeKeys));
// the default root logger level is Info
then(testLogger.isInfoEnabled()).isTrue();

// then, we changed the root logger level to error
TestPropertyValues.of("logging.level.root=error").applyTo(environment);
rebinder.onApplicationEvent(new EnvironmentChangeEvent(environment, changeKeys));
then(testLogger.isErrorEnabled()).isTrue();
}

}