diff --git a/jetty-jmx/src/main/config/etc/jetty-jmx.xml b/jetty-jmx/src/main/config/etc/jetty-jmx.xml
index 052fa7cc506..5aadbb66a70 100644
--- a/jetty-jmx/src/main/config/etc/jetty-jmx.xml
+++ b/jetty-jmx/src/main/config/etc/jetty-jmx.xml
@@ -18,6 +18,12 @@
+
+
+
+
+
+
diff --git a/jetty-slf4j-impl/src/main/java/module-info.java b/jetty-slf4j-impl/src/main/java/module-info.java
index d73adeeea2c..40372a04ed8 100644
--- a/jetty-slf4j-impl/src/main/java/module-info.java
+++ b/jetty-slf4j-impl/src/main/java/module-info.java
@@ -18,6 +18,7 @@ module org.eclipse.jetty.logging
{
exports org.eclipse.jetty.logging;
+ requires transitive java.management;
requires transitive org.slf4j;
provides SLF4JServiceProvider with JettyLoggingServiceProvider;
diff --git a/jetty-slf4j-impl/src/main/java/org/eclipse/jetty/logging/JettyLoggerFactory.java b/jetty-slf4j-impl/src/main/java/org/eclipse/jetty/logging/JettyLoggerFactory.java
index 8da26287815..6556a491a8a 100644
--- a/jetty-slf4j-impl/src/main/java/org/eclipse/jetty/logging/JettyLoggerFactory.java
+++ b/jetty-slf4j-impl/src/main/java/org/eclipse/jetty/logging/JettyLoggerFactory.java
@@ -19,15 +19,28 @@ import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.function.Consumer;
import java.util.function.Function;
+import javax.management.Attribute;
+import javax.management.AttributeList;
+import javax.management.AttributeNotFoundException;
+import javax.management.DynamicMBean;
+import javax.management.MBeanAttributeInfo;
+import javax.management.MBeanConstructorInfo;
+import javax.management.MBeanException;
+import javax.management.MBeanInfo;
+import javax.management.MBeanNotificationInfo;
+import javax.management.MBeanOperationInfo;
+import javax.management.MBeanParameterInfo;
+import javax.management.ReflectionException;
import org.slf4j.ILoggerFactory;
import org.slf4j.Logger;
-public class JettyLoggerFactory implements ILoggerFactory, JettyLoggerFactoryMBean
+public class JettyLoggerFactory implements ILoggerFactory, DynamicMBean
{
private final JettyLoggerConfiguration configuration;
private final JettyLogger rootLogger;
private final ConcurrentMap loggerMap;
+ private MBeanInfo mBeanInfo;
public JettyLoggerFactory(JettyLoggerConfiguration config)
{
@@ -129,20 +142,17 @@ public class JettyLoggerFactory implements ILoggerFactory, JettyLoggerFactoryMBe
return nameFunction.apply(Logger.ROOT_LOGGER_NAME);
}
- @Override
public String[] getLoggerNames()
{
TreeSet names = new TreeSet<>(loggerMap.keySet());
return names.toArray(new String[0]);
}
- @Override
public int getLoggerCount()
{
return loggerMap.size();
}
- @Override
public String getLoggerLevel(String loggerName)
{
return walkParentLoggerNames(loggerName, key ->
@@ -154,7 +164,6 @@ public class JettyLoggerFactory implements ILoggerFactory, JettyLoggerFactoryMBe
});
}
- @Override
public boolean setLoggerLevel(String loggerName, String levelName)
{
JettyLevel level = JettyLoggerConfiguration.toJettyLevel(loggerName, levelName);
@@ -166,4 +175,161 @@ public class JettyLoggerFactory implements ILoggerFactory, JettyLoggerFactoryMBe
jettyLogger.setLevel(level);
return true;
}
+
+ @Override
+ public Object getAttribute(String name) throws AttributeNotFoundException
+ {
+ Objects.requireNonNull(name, "Attribute Name");
+
+ switch (name)
+ {
+ case "LoggerNames":
+ return getLoggerNames();
+ case "LoggerCount":
+ return getLoggerCount();
+ default:
+ throw new AttributeNotFoundException("Cannot find " + name + " attribute in " + this.getClass().getName());
+ }
+ }
+
+ @Override
+ public void setAttribute(Attribute attribute) throws AttributeNotFoundException
+ {
+ Objects.requireNonNull(attribute, "attribute");
+ String name = attribute.getName();
+ // No attributes are writable
+ throw new AttributeNotFoundException("Cannot set attribute " + name + " because it is read-only");
+ }
+
+ @Override
+ public AttributeList getAttributes(String[] attributeNames)
+ {
+ Objects.requireNonNull(attributeNames, "attributeNames[]");
+
+ AttributeList ret = new AttributeList();
+ if (attributeNames.length == 0)
+ return ret;
+
+ for (String name : attributeNames)
+ {
+ try
+ {
+ Object value = getAttribute(name);
+ ret.add(new Attribute(name, value));
+ }
+ catch (Exception e)
+ {
+ // nothing much we can do, this method has no throwables, and we cannot use logging here.
+ e.printStackTrace();
+ }
+ }
+ return ret;
+ }
+
+ @Override
+ public AttributeList setAttributes(AttributeList attributes)
+ {
+ Objects.requireNonNull(attributes, "attributes");
+
+ AttributeList ret = new AttributeList();
+
+ if (attributes.isEmpty())
+ return ret;
+
+ for (Attribute attr : attributes.asList())
+ {
+ try
+ {
+ setAttribute(attr);
+ String name = attr.getName();
+ Object value = getAttribute(name);
+ ret.add(new Attribute(name, value));
+ }
+ catch (Exception e)
+ {
+ // nothing much we can do, this method has no throwables, and we cannot use logging here.
+ e.printStackTrace();
+ }
+ }
+ return ret;
+ }
+
+ @Override
+ public Object invoke(String actionName, Object[] params, String[] signature) throws MBeanException, ReflectionException
+ {
+ Objects.requireNonNull(actionName, "Action Name");
+
+ switch (actionName)
+ {
+ case "setLoggerLevel":
+ {
+ String loggerName = (String)params[0];
+ String level = (String)params[1];
+ return setLoggerLevel(loggerName, level);
+ }
+ case "getLoggerLevel":
+ {
+ String loggerName = (String)params[0];
+ return getLoggerLevel(loggerName);
+ }
+ default:
+ throw new ReflectionException(
+ new NoSuchMethodException(actionName),
+ "Cannot find the operation " + actionName + " in " + this.getClass().getName());
+ }
+ }
+
+ @Override
+ public MBeanInfo getMBeanInfo()
+ {
+ if (mBeanInfo == null)
+ {
+ MBeanAttributeInfo[] attrs = new MBeanAttributeInfo[2];
+
+ attrs[0] = new MBeanAttributeInfo(
+ "LoggerCount",
+ "java.lang.Integer",
+ "Count of Registered Loggers by Name.",
+ true,
+ false,
+ false);
+ attrs[1] = new MBeanAttributeInfo(
+ "LoggerNames",
+ "java.lang.String[]",
+ "List of Registered Loggers by Name.",
+ true,
+ false,
+ false);
+
+ MBeanOperationInfo[] operations = new MBeanOperationInfo[]{
+ new MBeanOperationInfo(
+ "setLoggerLevel",
+ "Set the logging level at the named logger",
+ new MBeanParameterInfo[]{
+ new MBeanParameterInfo("loggerName", "java.lang.String", "The name of the logger"),
+ new MBeanParameterInfo("level", "java.lang.String", "The name of the level [DEBUG, INFO, WARN, ERROR]")
+ },
+ "boolean",
+ MBeanOperationInfo.ACTION
+ ),
+ new MBeanOperationInfo(
+ "getLoggerLevel",
+ "Get the logging level at the named logger",
+ new MBeanParameterInfo[]{
+ new MBeanParameterInfo("loggerName", "java.lang.String", "The name of the logger")
+ },
+ "java.lang.String",
+ MBeanOperationInfo.INFO
+ )
+ };
+
+ mBeanInfo = new MBeanInfo(this.getClass().getName(),
+ "Jetty Slf4J Logger Factory",
+ attrs,
+ new MBeanConstructorInfo[0],
+ operations,
+ new MBeanNotificationInfo[0]);
+ }
+ return mBeanInfo;
+ }
}
diff --git a/jetty-slf4j-impl/src/main/java/org/eclipse/jetty/logging/JettyLoggerFactoryMBean.java b/jetty-slf4j-impl/src/main/java/org/eclipse/jetty/logging/JettyLoggerFactoryMBean.java
deleted file mode 100644
index 6e9bab31fbb..00000000000
--- a/jetty-slf4j-impl/src/main/java/org/eclipse/jetty/logging/JettyLoggerFactoryMBean.java
+++ /dev/null
@@ -1,25 +0,0 @@
-//
-// ========================================================================
-// Copyright (c) 1995-2021 Mort Bay Consulting Pty Ltd and others.
-//
-// This program and the accompanying materials are made available under the
-// terms of the Eclipse Public License v. 2.0 which is available at
-// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
-// which is available at https://www.apache.org/licenses/LICENSE-2.0.
-//
-// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
-// ========================================================================
-//
-
-package org.eclipse.jetty.logging;
-
-public interface JettyLoggerFactoryMBean
-{
- int getLoggerCount();
-
- String[] getLoggerNames();
-
- boolean setLoggerLevel(String loggerName, String levelName);
-
- String getLoggerLevel(String loggerName);
-}
diff --git a/jetty-slf4j-impl/src/test/java/org/eclipse/jetty/logging/JMXTest.java b/jetty-slf4j-impl/src/test/java/org/eclipse/jetty/logging/JMXTest.java
index 7a154ccc9e8..ab950e95e5f 100644
--- a/jetty-slf4j-impl/src/test/java/org/eclipse/jetty/logging/JMXTest.java
+++ b/jetty-slf4j-impl/src/test/java/org/eclipse/jetty/logging/JMXTest.java
@@ -18,13 +18,17 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Locale;
-import java.util.Properties;
-import javax.management.JMX;
+import java.util.stream.Stream;
+import javax.management.MBeanAttributeInfo;
+import javax.management.MBeanInfo;
import javax.management.MBeanServer;
import javax.management.ObjectName;
import org.junit.jupiter.api.Test;
+import org.slf4j.LoggerFactory;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.is;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
@@ -35,34 +39,56 @@ public class JMXTest
{
MBeanServer mbeanServer = ManagementFactory.getPlatformMBeanServer();
- Properties props = new Properties();
- JettyLoggerConfiguration config = new JettyLoggerConfiguration(props);
- JettyLoggerFactory loggerFactory = new JettyLoggerFactory(config);
-
ObjectName objectName = ObjectName.getInstance("org.eclipse.jetty.logging", "type", JettyLoggerFactory.class.getSimpleName().toLowerCase(Locale.ENGLISH));
- mbeanServer.registerMBean(loggerFactory, objectName);
+ mbeanServer.registerMBean(LoggerFactory.getILoggerFactory(), objectName);
- JettyLoggerFactoryMBean mbean = JMX.newMBeanProxy(mbeanServer, objectName, JettyLoggerFactoryMBean.class);
+ // Verify MBeanInfo
+ MBeanInfo beanInfo = mbeanServer.getMBeanInfo(objectName);
+
+ MBeanAttributeInfo[] attributeInfos = beanInfo.getAttributes();
+ assertThat("MBeanAttributeInfo count", attributeInfos.length, is(2));
+
+ MBeanAttributeInfo attr = Stream.of(attributeInfos).filter((a) -> a.getName().equals("LoggerNames")).findFirst().orElseThrow();
+ assertThat("attr", attr.getDescription(), is("List of Registered Loggers by Name."));
+
+ // Do some MBean attribute testing
+ int loggerCount;
// Only the root logger.
- assertEquals(1, mbean.getLoggerCount());
+ loggerCount = (int)mbeanServer.getAttribute(objectName, "LoggerCount");
+ assertEquals(1, loggerCount);
+ JettyLoggerFactory loggerFactory = (JettyLoggerFactory)LoggerFactory.getILoggerFactory();
JettyLogger child = loggerFactory.getJettyLogger("org.eclipse.jetty.logging");
JettyLogger parent = loggerFactory.getJettyLogger("org.eclipse.jetty");
- assertEquals(3, mbean.getLoggerCount());
+ loggerCount = (int)mbeanServer.getAttribute(objectName, "LoggerCount");
+ assertEquals(3, loggerCount);
- // Names are sorted.
+ // Names from JMX are sorted, so lets sort our expected list too.
List expected = new ArrayList<>(Arrays.asList(JettyLogger.ROOT_LOGGER_NAME, parent.getName(), child.getName()));
expected.sort(String::compareTo);
- String[] loggerNames = mbean.getLoggerNames();
+ String[] loggerNames = (String[])mbeanServer.getAttribute(objectName, "LoggerNames");
assertEquals(expected, Arrays.asList(loggerNames));
+ // Do some MBean invoker testing
+ String operationName;
+ String[] signature;
+ Object[] params;
+
// Setting the parent level should propagate to the children.
parent.setLevel(JettyLevel.DEBUG);
- assertEquals(parent.getLevel().toString(), mbean.getLoggerLevel(child.getName()));
+ operationName = "getLoggerLevel";
+ signature = new String[]{String.class.getName()};
+ params = new Object[]{child.getName()};
+ String levelName = (String)mbeanServer.invoke(objectName, operationName, params, signature);
+ assertEquals(parent.getLevel().toString(), levelName);
// Setting the level via JMX affects the logger.
- assertTrue(mbean.setLoggerLevel(child.getName(), "INFO"));
+ operationName = "setLoggerLevel";
+ signature = new String[]{String.class.getName(), String.class.getName()};
+ params = new Object[]{child.getName(), "INFO"};
+ boolean result = (boolean)mbeanServer.invoke(objectName, operationName, params, signature);
+ assertTrue(result);
assertEquals(JettyLevel.INFO, child.getLevel());
}
}