diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/jmx/EndpointMBeanExporter.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/jmx/EndpointMBeanExporter.java index 102eeaa3ab..a1416ae1ac 100644 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/jmx/EndpointMBeanExporter.java +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/jmx/EndpointMBeanExporter.java @@ -35,6 +35,7 @@ import org.springframework.beans.BeansException; import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.ListableBeanFactory; import org.springframework.boot.actuate.endpoint.Endpoint; +import org.springframework.boot.actuate.endpoint.LoggersEndpoint; import org.springframework.boot.actuate.endpoint.ShutdownEndpoint; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; @@ -58,6 +59,7 @@ import org.springframework.util.ObjectUtils; * * @author Christian Dupuis * @author Andy Wilkinson + * @author Vedran Pavic */ public class EndpointMBeanExporter extends MBeanExporter implements SmartLifecycle, ApplicationContextAware { @@ -191,6 +193,9 @@ public class EndpointMBeanExporter extends MBeanExporter if (endpoint instanceof ShutdownEndpoint) { return new ShutdownEndpointMBean(beanName, endpoint, this.objectMapper); } + if (endpoint instanceof LoggersEndpoint) { + return new LoggersEndpointMBean(beanName, endpoint, this.objectMapper); + } return new DataEndpointMBean(beanName, endpoint, this.objectMapper); } diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/jmx/LoggersEndpointMBean.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/jmx/LoggersEndpointMBean.java new file mode 100644 index 0000000000..b95aea460b --- /dev/null +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/jmx/LoggersEndpointMBean.java @@ -0,0 +1,66 @@ +/* + * Copyright 2012-2016 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.actuate.endpoint.jmx; + +import com.fasterxml.jackson.databind.ObjectMapper; + +import org.springframework.boot.actuate.endpoint.Endpoint; +import org.springframework.boot.actuate.endpoint.LoggersEndpoint; +import org.springframework.boot.actuate.endpoint.mvc.MvcEndpoint; +import org.springframework.boot.logging.LogLevel; +import org.springframework.jmx.export.annotation.ManagedAttribute; +import org.springframework.jmx.export.annotation.ManagedOperation; +import org.springframework.jmx.export.annotation.ManagedResource; +import org.springframework.util.Assert; + +/** + * Adapter to expose {@link LoggersEndpoint} as an {@link MvcEndpoint}. + * + * @author Vedran Pavic + * @since 1.5.0 + */ +@ManagedResource +public class LoggersEndpointMBean extends EndpointMBean { + + public LoggersEndpointMBean(String beanName, Endpoint endpoint, + ObjectMapper objectMapper) { + super(beanName, endpoint, objectMapper); + } + + @ManagedAttribute(description = "Get log levels for all known loggers") + public Object getLoggers() { + return convert(getEndpoint().invoke()); + } + + @ManagedOperation(description = "Get log level for a given logger") + public Object getLogger(String loggerName) { + return convert(getEndpoint().invoke(loggerName)); + } + + @ManagedOperation(description = "Set log level for a given logger") + public void setLogLevel(String loggerName, String logLevel) { + Assert.notNull(logLevel, "LogLevel must not be null"); + LogLevel level = LogLevel.valueOf(logLevel.toUpperCase()); + getEndpoint().setLogLevel(loggerName, level); + } + + @Override + public LoggersEndpoint getEndpoint() { + return (LoggersEndpoint) super.getEndpoint(); + } + +} diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/jmx/EndpointMBeanExporterTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/jmx/EndpointMBeanExporterTests.java index c50fff0165..981459dcd2 100644 --- a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/jmx/EndpointMBeanExporterTests.java +++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/jmx/EndpointMBeanExporterTests.java @@ -26,18 +26,23 @@ import java.util.List; import java.util.Map; import java.util.Properties; +import javax.management.MBeanException; import javax.management.MBeanInfo; import javax.management.MalformedObjectNameException; import javax.management.ObjectName; import com.fasterxml.jackson.databind.ObjectMapper; import org.junit.After; +import org.junit.Rule; import org.junit.Test; +import org.junit.rules.ExpectedException; import org.springframework.beans.MutablePropertyValues; import org.springframework.beans.factory.config.ConstructorArgumentValues; import org.springframework.beans.factory.support.RootBeanDefinition; import org.springframework.boot.actuate.endpoint.AbstractEndpoint; +import org.springframework.boot.actuate.endpoint.LoggersEndpoint; +import org.springframework.boot.logging.logback.LogbackLoggingSystem; import org.springframework.context.ApplicationContext; import org.springframework.context.support.GenericApplicationContext; import org.springframework.jmx.export.MBeanExporter; @@ -45,15 +50,21 @@ import org.springframework.jmx.support.ObjectNameManager; import org.springframework.util.ObjectUtils; import static org.assertj.core.api.Assertions.assertThat; +import static org.hamcrest.Matchers.containsString; +import static org.junit.internal.matchers.ThrowableMessageMatcher.hasMessage; /** * Tests for {@link EndpointMBeanExporter} * * @author Christian Dupuis * @author Andy Wilkinson + * @author Stephane Nicoll */ public class EndpointMBeanExporterTests { + @Rule + public ExpectedException thrown = ExpectedException.none(); + GenericApplicationContext context = null; @After @@ -253,6 +264,42 @@ public class EndpointMBeanExporterTests { assertThat(((List) response).get(0)).isInstanceOf(Long.class); } + @Test + public void loggerEndpointLowerCaseLogLevel() throws Exception { + MBeanExporter mbeanExporter = registerLoggersEndpoint(); + Object response = mbeanExporter.getServer().invoke( + getObjectName("loggersEndpoint", this.context), "setLogLevel", + new Object[]{"com.example", "trace"}, + new String[]{String.class.getName(), String.class.getName()}); + assertThat(response).isNull(); + } + + @Test + public void loggerEndpointUnknownLogLevel() throws Exception { + MBeanExporter mbeanExporter = registerLoggersEndpoint(); + this.thrown.expect(MBeanException.class); + this.thrown.expectCause(hasMessage(containsString("No enum constant"))); + this.thrown.expectCause(hasMessage(containsString("LogLevel.INVALID"))); + mbeanExporter.getServer().invoke( + getObjectName("loggersEndpoint", this.context), "setLogLevel", + new Object[]{"com.example", "invalid"}, + new String[]{String.class.getName(), String.class.getName()}); + } + + private MBeanExporter registerLoggersEndpoint() { + this.context = new GenericApplicationContext(); + this.context.registerBeanDefinition("endpointMbeanExporter", + new RootBeanDefinition(EndpointMBeanExporter.class)); + RootBeanDefinition bd = new RootBeanDefinition(LoggersEndpoint.class); + ConstructorArgumentValues values = new ConstructorArgumentValues(); + values.addIndexedArgumentValue(0, + new LogbackLoggingSystem(getClass().getClassLoader())); + bd.setConstructorArgumentValues(values); + this.context.registerBeanDefinition("loggersEndpoint", bd); + this.context.refresh(); + return this.context.getBean(EndpointMBeanExporter.class); + } + private ObjectName getObjectName(String beanKey, GenericApplicationContext context) throws MalformedObjectNameException { return getObjectName("org.springframework.boot", beanKey, false, context);