diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/logback/LogbackLoggingSystem.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/logback/LogbackLoggingSystem.java index 9e4a68b54671..73000d9e1c60 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/logback/LogbackLoggingSystem.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/logback/LogbackLoggingSystem.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2017 the original author or authors. + * Copyright 2012-2018 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. @@ -16,6 +16,7 @@ package org.springframework.boot.logging.logback; +import java.lang.management.ManagementFactory; import java.net.URL; import java.security.CodeSource; import java.security.ProtectionDomain; @@ -23,8 +24,13 @@ import java.util.List; import java.util.Set; +import javax.management.MBeanServer; +import javax.management.ObjectName; + import ch.qos.logback.classic.Level; import ch.qos.logback.classic.LoggerContext; +import ch.qos.logback.classic.jmx.JMXConfigurator; +import ch.qos.logback.classic.jmx.MBeanUtil; import ch.qos.logback.classic.joran.JoranConfigurator; import ch.qos.logback.classic.jul.LevelChangePropagator; import ch.qos.logback.classic.turbo.TurboFilter; @@ -56,6 +62,7 @@ * @author Dave Syer * @author Andy Wilkinson * @author Ben Hale + * @author Vedran Pavic */ public class LogbackLoggingSystem extends Slf4JLoggingSystem { @@ -126,6 +133,7 @@ protected void loadDefaults(LoggingInitializationContext initializationContext, LogFile logFile) { LoggerContext context = getLoggerContext(); stopAndReset(context); + registerJmxConfigurator(context, initializationContext, null, logFile); LogbackConfigurator configurator = new LogbackConfigurator(context); Environment environment = initializationContext.getEnvironment(); context.putProperty(LoggingSystemProperties.LOG_LEVEL_PATTERN, @@ -145,6 +153,7 @@ protected void loadConfiguration(LoggingInitializationContext initializationCont super.loadConfiguration(initializationContext, location, logFile); LoggerContext loggerContext = getLoggerContext(); stopAndReset(loggerContext); + registerJmxConfigurator(loggerContext, initializationContext, location, logFile); try { configureByResourceUrl(initializationContext, loggerContext, ResourceUtils.getURL(location)); @@ -196,6 +205,28 @@ private void addLevelChangePropagator(LoggerContext loggerContext) { loggerContext.addListener(levelChangePropagator); } + private void registerJmxConfigurator(LoggerContext loggerContext, + LoggingInitializationContext initializationContext, String configLocation, + LogFile logFile) { + String objectNameAsStr = MBeanUtil.getObjectNameFor(loggerContext.getName(), + JMXConfigurator.class); + ObjectName objectName = MBeanUtil.string2ObjectName(loggerContext, this, + objectNameAsStr); + MBeanServer server = ManagementFactory.getPlatformMBeanServer(); + if (!MBeanUtil.isRegistered(server, objectName)) { + LoggingSystemJMXConfigurator jmxConfigurator = new LoggingSystemJMXConfigurator( + loggerContext, server, objectName, initializationContext, + configLocation, logFile); + try { + server.registerMBean(jmxConfigurator, objectName); + } + catch (Exception e) { + getLogger(LogbackLoggingSystem.class.getName()) + .error("Failed to create mbean", e); + } + } + } + @Override public void cleanUp() { LoggerContext context = getLoggerContext(); @@ -317,4 +348,33 @@ public void run() { } + private class LoggingSystemJMXConfigurator extends JMXConfigurator { + + private LoggingInitializationContext initializationContext; + + private String configLocation; + + private LogFile logFile; + + LoggingSystemJMXConfigurator(LoggerContext loggerContext, MBeanServer server, + ObjectName objectName, LoggingInitializationContext initializationContext, + String configLocation, LogFile logFile) { + super(loggerContext, server, objectName); + this.initializationContext = initializationContext; + this.configLocation = configLocation; + this.logFile = logFile; + } + + @Override + public void reloadDefaultConfiguration() { + initialize(this.initializationContext, this.configLocation, this.logFile); + } + + @Override + public void reloadByFileName(String fileName) { + initialize(this.initializationContext, fileName, this.logFile); + } + + } + } diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/logging/logback/LogbackLoggingSystemTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/logging/logback/LogbackLoggingSystemTests.java index b8b7eff22636..8c5b26219ad2 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/logging/logback/LogbackLoggingSystemTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/logging/logback/LogbackLoggingSystemTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2017 the original author or authors. + * Copyright 2012-2018 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. @@ -18,11 +18,14 @@ import java.io.File; import java.io.FileReader; +import java.lang.management.ManagementFactory; import java.util.EnumSet; import java.util.List; import java.util.logging.Handler; import java.util.logging.LogManager; +import javax.management.ObjectName; + import ch.qos.logback.classic.Level; import ch.qos.logback.classic.Logger; import ch.qos.logback.classic.LoggerContext; @@ -487,6 +490,43 @@ public void testDateformatPatternProperty() { .containsPattern("\\d{4}-\\d{2}\\-\\d{2}T\\d{2}:\\d{2}:\\d{2}"); } + @Test + public void testDefaultJmxConfigurator() throws Exception { + this.loggingSystem.beforeInitialize(); + assertThat(ManagementFactory.getPlatformMBeanServer().queryMBeans( + new ObjectName("ch.qos.logback.classic:Name=default,*"), null)) + .hasSize(1); + } + + @Test + public void testExplicitJmxConfigurator() throws Exception { + this.loggingSystem.beforeInitialize(); + this.loggingSystem.initialize(this.initializationContext, + "classpath:logback-jmxconfigurator.xml", null); + assertThat(ManagementFactory.getPlatformMBeanServer().queryMBeans( + new ObjectName("ch.qos.logback.classic:Name=default,*"), null)) + .hasSize(1); + } + + @Test + public void testShutdownUnregistersJmxConfigurator() throws Exception { + this.loggingSystem.beforeInitialize(); + this.loggingSystem.getShutdownHandler().run(); + assertThat(ManagementFactory.getPlatformMBeanServer().queryMBeans( + new ObjectName("ch.qos.logback.classic:Name=default,*"), null)).isEmpty(); + } + + @Test + public void testTwoLoggingSystemsYieldSingleJmxConfigurator() throws Exception { + this.loggingSystem.beforeInitialize(); + LogbackLoggingSystem anotherLoggingSystem = new LogbackLoggingSystem( + getClass().getClassLoader()); + anotherLoggingSystem.beforeInitialize(); + assertThat(ManagementFactory.getPlatformMBeanServer().queryMBeans( + new ObjectName("ch.qos.logback.classic:Name=default,*"), null)) + .hasSize(1); + } + private static Logger getRootLogger() { ILoggerFactory factory = StaticLoggerBinder.getSingleton().getLoggerFactory(); LoggerContext context = (LoggerContext) factory; diff --git a/spring-boot-project/spring-boot/src/test/resources/logback-jmxconfigurator.xml b/spring-boot-project/spring-boot/src/test/resources/logback-jmxconfigurator.xml new file mode 100644 index 000000000000..2e5aa0a9273d --- /dev/null +++ b/spring-boot-project/spring-boot/src/test/resources/logback-jmxconfigurator.xml @@ -0,0 +1,6 @@ + + + + + +