From 67bd321bfcf62a32deb658fd7619245e774606fa Mon Sep 17 00:00:00 2001 From: Joakim Erdfelt Date: Thu, 30 Apr 2020 14:51:42 -0500 Subject: [PATCH] Issue #4830 - Enabling JMX on jetty-slf4j-impl Signed-off-by: Joakim Erdfelt --- .../src/main/java/module-info.java | 2 + .../eclipse/jetty/logging/JettyLogger.java | 38 +------- .../logging/JettyLoggerConfiguration.java | 74 +++------------ .../jetty/logging/JettyLoggerFactory.java | 77 ++++++++++++++-- .../logging/JettyLoggerFactoryMBean.java | 31 +++++++ .../jetty/logging/JettyLoggingJmx.java | 50 +++++++++++ .../logging/JettyLoggingServiceProvider.java | 2 + .../org/eclipse/jetty/logging/LevelUtils.java | 90 +++++++++++++++++++ .../jetty/logging/JettyLoggerTest.java | 2 +- .../eclipse/jetty/logging/JmxExperiment.java | 46 ++++++++++ 10 files changed, 305 insertions(+), 107 deletions(-) create mode 100644 jetty-slf4j-impl/src/main/java/org/eclipse/jetty/logging/JettyLoggerFactoryMBean.java create mode 100644 jetty-slf4j-impl/src/main/java/org/eclipse/jetty/logging/JettyLoggingJmx.java create mode 100644 jetty-slf4j-impl/src/main/java/org/eclipse/jetty/logging/LevelUtils.java create mode 100644 jetty-slf4j-impl/src/test/java/org/eclipse/jetty/logging/JmxExperiment.java diff --git a/jetty-slf4j-impl/src/main/java/module-info.java b/jetty-slf4j-impl/src/main/java/module-info.java index 0bfb99cd437..3cfbbf20391 100644 --- a/jetty-slf4j-impl/src/main/java/module-info.java +++ b/jetty-slf4j-impl/src/main/java/module-info.java @@ -25,5 +25,7 @@ module org.eclipse.jetty.logging requires transitive org.slf4j; + requires static java.management; + provides SLF4JServiceProvider with JettyLoggingServiceProvider; } diff --git a/jetty-slf4j-impl/src/main/java/org/eclipse/jetty/logging/JettyLogger.java b/jetty-slf4j-impl/src/main/java/org/eclipse/jetty/logging/JettyLogger.java index 90e1a639b5b..54ab43069e8 100644 --- a/jetty-slf4j-impl/src/main/java/org/eclipse/jetty/logging/JettyLogger.java +++ b/jetty-slf4j-impl/src/main/java/org/eclipse/jetty/logging/JettyLogger.java @@ -247,7 +247,7 @@ public class JettyLogger implements LocationAwareLogger, Logger { long timestamp = System.currentTimeMillis(); String threadName = Thread.currentThread().getName(); - getAppender().emit(this, intToLevel(levelInt), timestamp, threadName, throwable, message, argArray); + getAppender().emit(this, LevelUtils.intToLevel(levelInt), timestamp, threadName, throwable, message, argArray); } } @@ -636,43 +636,9 @@ public class JettyLogger implements LocationAwareLogger, Logger getAppender().emit(this, level, timestamp, threadName, throwable, msg); } - public static Level intToLevel(int level) - { - if (level >= JettyLogger.OFF) - return Level.ERROR; - if (level >= Level.ERROR.toInt()) - return Level.ERROR; - if (level >= Level.WARN.toInt()) - return Level.WARN; - if (level >= Level.INFO.toInt()) - return Level.INFO; - if (level >= Level.DEBUG.toInt()) - return Level.DEBUG; - if (level >= Level.TRACE.toInt()) - return Level.TRACE; - return Level.TRACE; // everything else - } - - public static String levelToString(int level) - { - if (level >= JettyLogger.OFF) - return "OFF"; - if (level >= Level.ERROR.toInt()) - return "ERROR"; - if (level >= Level.WARN.toInt()) - return "WARN"; - if (level >= Level.INFO.toInt()) - return "INFO"; - if (level >= Level.DEBUG.toInt()) - return "DEBUG"; - if (level >= Level.TRACE.toInt()) - return "TRACE"; - return "OFF"; // everything else - } - @Override public String toString() { - return String.format("%s:%s:LEVEL=%s", JettyLogger.class.getSimpleName(), name, levelToString(level)); + return String.format("%s:%s:LEVEL=%s", JettyLogger.class.getSimpleName(), name, LevelUtils.levelToString(level)); } } diff --git a/jetty-slf4j-impl/src/main/java/org/eclipse/jetty/logging/JettyLoggerConfiguration.java b/jetty-slf4j-impl/src/main/java/org/eclipse/jetty/logging/JettyLoggerConfiguration.java index 2864430fee2..547e54e82f3 100644 --- a/jetty-slf4j-impl/src/main/java/org/eclipse/jetty/logging/JettyLoggerConfiguration.java +++ b/jetty-slf4j-impl/src/main/java/org/eclipse/jetty/logging/JettyLoggerConfiguration.java @@ -26,7 +26,6 @@ import java.security.PrivilegedAction; import java.util.Locale; import java.util.Properties; import java.util.TimeZone; -import java.util.function.Function; import org.slf4j.event.Level; @@ -82,7 +81,7 @@ public class JettyLoggerConfiguration startName = startName.substring(0, startName.length() - SUFFIX_STACKS.length()); } - Boolean hideStacks = walkParentLoggerNames(startName, (key) -> + Boolean hideStacks = JettyLoggerFactory.walkParentLoggerNames(startName, (key) -> { String stacksBool = properties.getProperty(key + SUFFIX_STACKS); if (stacksBool != null) @@ -124,12 +123,12 @@ public class JettyLoggerConfiguration startName = startName.substring(0, startName.length() - SUFFIX_LEVEL.length()); } - Integer level = walkParentLoggerNames(startName, (key) -> + Integer level = JettyLoggerFactory.walkParentLoggerNames(startName, (key) -> { - String levelStr = properties.getProperty(key + SUFFIX_LEVEL); - if (levelStr != null) + String levelStr1 = properties.getProperty(key + SUFFIX_LEVEL); + if (levelStr1 != null) { - return getLevelInt(key, levelStr); + return LevelUtils.getLevelInt(key, levelStr1); } return null; }); @@ -140,7 +139,7 @@ public class JettyLoggerConfiguration String levelStr = properties.getProperty("log" + SUFFIX_LEVEL); if (levelStr != null) { - level = getLevelInt("log", levelStr); + level = LevelUtils.getLevelInt("log", levelStr); } } @@ -193,6 +192,11 @@ public class JettyLoggerConfiguration }); } + public String getString(String key, String defValue) + { + return properties.getProperty(key, defValue); + } + public boolean getBoolean(String key, boolean defValue) { String val = properties.getProperty(key, Boolean.toString(defValue)); @@ -216,36 +220,6 @@ public class JettyLoggerConfiguration } } - private Integer getLevelInt(String levelSegment, String levelStr) - { - if (levelStr == null) - { - return null; - } - - String levelName = levelStr.trim().toUpperCase(Locale.ENGLISH); - switch (levelName) - { - case "ALL": - return JettyLogger.ALL; - case "TRACE": - return Level.TRACE.toInt(); - case "DEBUG": - return Level.DEBUG.toInt(); - case "INFO": - return Level.INFO.toInt(); - case "WARN": - return Level.WARN.toInt(); - case "ERROR": - return Level.ERROR.toInt(); - case "OFF": - return JettyLogger.OFF; - default: - System.err.println("Unknown JettyLogger/Slf4J Level [" + levelSegment + "]=[" + levelName + "], expecting only [ALL, TRACE, DEBUG, INFO, WARN, ERROR, OFF] as values."); - return null; - } - } - private URL getResource(ClassLoader loader, String resourceName) { if (loader == null) @@ -303,30 +277,4 @@ public class JettyLoggerConfiguration } return null; } - - private T walkParentLoggerNames(String startName, Function nameFunction) - { - String nameSegment = startName; - - // Checking with FQCN first, then each package segment from longest to shortest. - while ((nameSegment != null) && (nameSegment.length() > 0)) - { - T ret = nameFunction.apply(nameSegment); - if (ret != null) - return ret; - - // Trim and try again. - int idx = nameSegment.lastIndexOf('.'); - if (idx >= 0) - { - nameSegment = nameSegment.substring(0, idx); - } - else - { - nameSegment = null; - } - } - - return null; - } } 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 83d39cf71c1..f607ada4790 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 @@ -22,16 +22,16 @@ import java.util.Objects; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.function.Consumer; +import java.util.function.Function; import org.slf4j.ILoggerFactory; import org.slf4j.Logger; -public class JettyLoggerFactory implements ILoggerFactory +public class JettyLoggerFactory implements ILoggerFactory, JettyLoggerFactoryMBean { - private static final String ROOT_LOGGER_NAME = ""; private final JettyLoggerConfiguration configuration; private final JettyLogger rootLogger; - private ConcurrentMap loggerMap; + private final ConcurrentMap loggerMap; public JettyLoggerFactory(JettyLoggerConfiguration config) { @@ -41,9 +41,9 @@ public class JettyLoggerFactory implements ILoggerFactory StdErrAppender appender = new StdErrAppender(configuration); - rootLogger = new JettyLogger(this, ROOT_LOGGER_NAME, appender); - loggerMap.put(ROOT_LOGGER_NAME, rootLogger); - rootLogger.setLevel(configuration.getLevel(ROOT_LOGGER_NAME)); + rootLogger = new JettyLogger(this, Logger.ROOT_LOGGER_NAME, appender); + loggerMap.put(Logger.ROOT_LOGGER_NAME, rootLogger); + rootLogger.setLevel(configuration.getLevel(Logger.ROOT_LOGGER_NAME)); } /** @@ -54,7 +54,7 @@ public class JettyLoggerFactory implements ILoggerFactory */ public JettyLogger getJettyLogger(String name) { - if (name.equals(ROOT_LOGGER_NAME)) + if (name.equals(Logger.ROOT_LOGGER_NAME)) { return getRootLogger(); } @@ -179,4 +179,67 @@ public class JettyLoggerFactory implements ILoggerFactory return dense.toString(); } + + public static T walkParentLoggerNames(String startName, Function nameFunction) + { + String nameSegment = startName; + + // Checking with FQCN first, then each package segment from longest to shortest. + while ((nameSegment != null) && (nameSegment.length() > 0)) + { + T ret = nameFunction.apply(nameSegment); + if (ret != null) + return ret; + + // Trim and try again. + int idx = nameSegment.lastIndexOf('.'); + if (idx >= 0) + { + nameSegment = nameSegment.substring(0, idx); + } + else + { + nameSegment = null; + } + } + + return null; + } + + @Override + public String[] getLoggerNames() + { + return loggerMap.keySet().toArray(new String[0]); + } + + @Override + public int getLoggerCount() + { + return loggerMap.size(); + } + + @Override + public String getLoggerLevel(String loggerName) + { + return walkParentLoggerNames(loggerName, (key) -> + { + JettyLogger logger = loggerMap.get(key); + if (key != null) + { + return LevelUtils.levelToString(logger.getLevel()); + } + return null; + }); + } + + @Override + public void setLoggerLevel(String loggerName, String levelName) + { + Integer levelInt = LevelUtils.getLevelInt(loggerName, levelName); + if (levelInt != null) + { + JettyLogger jettyLogger = getJettyLogger(loggerName); + jettyLogger.setLevel(levelInt); + } + } } 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 new file mode 100644 index 00000000000..59c2da9f0a2 --- /dev/null +++ b/jetty-slf4j-impl/src/main/java/org/eclipse/jetty/logging/JettyLoggerFactoryMBean.java @@ -0,0 +1,31 @@ +// +// ======================================================================== +// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others. +// +// This program and the accompanying materials are made available under +// the terms of the Eclipse Public License 2.0 which is available at +// https://www.eclipse.org/legal/epl-2.0 +// +// This Source Code may also be made available under the following +// Secondary Licenses when the conditions for such availability set +// forth in the Eclipse Public License, v. 2.0 are satisfied: +// the Apache License v2.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; + +@SuppressWarnings("unused") +public interface JettyLoggerFactoryMBean +{ + int getLoggerCount(); + + String[] getLoggerNames(); + + void setLoggerLevel(String loggerName, String levelName); + + String getLoggerLevel(String loggerName); +} diff --git a/jetty-slf4j-impl/src/main/java/org/eclipse/jetty/logging/JettyLoggingJmx.java b/jetty-slf4j-impl/src/main/java/org/eclipse/jetty/logging/JettyLoggingJmx.java new file mode 100644 index 00000000000..f7aeac3a80e --- /dev/null +++ b/jetty-slf4j-impl/src/main/java/org/eclipse/jetty/logging/JettyLoggingJmx.java @@ -0,0 +1,50 @@ +// +// ======================================================================== +// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others. +// +// This program and the accompanying materials are made available under +// the terms of the Eclipse Public License 2.0 which is available at +// https://www.eclipse.org/legal/epl-2.0 +// +// This Source Code may also be made available under the following +// Secondary Licenses when the conditions for such availability set +// forth in the Eclipse Public License, v. 2.0 are satisfied: +// the Apache License v2.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; + +import java.lang.management.ManagementFactory; +import javax.management.MBeanServer; +import javax.management.ObjectName; + +public class JettyLoggingJmx +{ + public static void initialize(JettyLoggerConfiguration config, JettyLoggerFactory loggerFactory) + { + if (!config.getBoolean("org.eclipse.jetty.logging.jmx", false)) + { + loggerFactory.getJettyLogger(JettyLoggingJmx.class.getName()).debug("JMX not enabled"); + return; + } + + try + { + MBeanServer mbs = ManagementFactory.getPlatformMBeanServer(); + String contextName = config.getString("org.eclipse.jetty.logging.jmx.contextName", "default"); + + ObjectName objName = new ObjectName(JettyLoggerFactory.class.getName() + ":name=" + contextName); + mbs.registerMBean(loggerFactory, objName); + } + catch (Throwable cause) + { + JettyLogger logger = loggerFactory.getJettyLogger(JettyLoggingJmx.class.getName()); + logger.warn("java.management not available."); + logger.debug("java.management is not available", cause); + } + } +} diff --git a/jetty-slf4j-impl/src/main/java/org/eclipse/jetty/logging/JettyLoggingServiceProvider.java b/jetty-slf4j-impl/src/main/java/org/eclipse/jetty/logging/JettyLoggingServiceProvider.java index 01ad473162a..61e7ec47f40 100644 --- a/jetty-slf4j-impl/src/main/java/org/eclipse/jetty/logging/JettyLoggingServiceProvider.java +++ b/jetty-slf4j-impl/src/main/java/org/eclipse/jetty/logging/JettyLoggingServiceProvider.java @@ -45,6 +45,8 @@ public class JettyLoggingServiceProvider implements SLF4JServiceProvider loggerFactory = new JettyLoggerFactory(config); markerFactory = new BasicMarkerFactory(); mdcAdapter = new NOPMDCAdapter(); // TODO: Provide Jetty Implementation? + + JettyLoggingJmx.initialize(config, loggerFactory); } public JettyLoggerFactory getJettyLoggerFactory() diff --git a/jetty-slf4j-impl/src/main/java/org/eclipse/jetty/logging/LevelUtils.java b/jetty-slf4j-impl/src/main/java/org/eclipse/jetty/logging/LevelUtils.java new file mode 100644 index 00000000000..e78abf4817b --- /dev/null +++ b/jetty-slf4j-impl/src/main/java/org/eclipse/jetty/logging/LevelUtils.java @@ -0,0 +1,90 @@ +// +// ======================================================================== +// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others. +// +// This program and the accompanying materials are made available under +// the terms of the Eclipse Public License 2.0 which is available at +// https://www.eclipse.org/legal/epl-2.0 +// +// This Source Code may also be made available under the following +// Secondary Licenses when the conditions for such availability set +// forth in the Eclipse Public License, v. 2.0 are satisfied: +// the Apache License v2.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; + +import java.util.Locale; + +import org.slf4j.event.Level; + +public class LevelUtils +{ + public static Integer getLevelInt(String loggerName, String levelStr) + { + if (levelStr == null) + { + return null; + } + + String levelName = levelStr.trim().toUpperCase(Locale.ENGLISH); + switch (levelName) + { + case "ALL": + return JettyLogger.ALL; + case "TRACE": + return Level.TRACE.toInt(); + case "DEBUG": + return Level.DEBUG.toInt(); + case "INFO": + return Level.INFO.toInt(); + case "WARN": + return Level.WARN.toInt(); + case "ERROR": + return Level.ERROR.toInt(); + case "OFF": + return JettyLogger.OFF; + default: + System.err.println("Unknown JettyLogger/Slf4J Level [" + loggerName + "]=[" + levelName + "], expecting only [ALL, TRACE, DEBUG, INFO, WARN, ERROR, OFF] as values."); + return null; + } + } + + public static Level intToLevel(int level) + { + if (level >= JettyLogger.OFF) + return Level.ERROR; + if (level >= Level.ERROR.toInt()) + return Level.ERROR; + if (level >= Level.WARN.toInt()) + return Level.WARN; + if (level >= Level.INFO.toInt()) + return Level.INFO; + if (level >= Level.DEBUG.toInt()) + return Level.DEBUG; + if (level >= Level.TRACE.toInt()) + return Level.TRACE; + return Level.TRACE; // everything else + } + + public static String levelToString(int level) + { + if (level >= JettyLogger.OFF) + return "OFF"; + if (level >= Level.ERROR.toInt()) + return "ERROR"; + if (level >= Level.WARN.toInt()) + return "WARN"; + if (level >= Level.INFO.toInt()) + return "INFO"; + if (level >= Level.DEBUG.toInt()) + return "DEBUG"; + if (level >= Level.TRACE.toInt()) + return "TRACE"; + return "OFF"; // everything else + } +} diff --git a/jetty-slf4j-impl/src/test/java/org/eclipse/jetty/logging/JettyLoggerTest.java b/jetty-slf4j-impl/src/test/java/org/eclipse/jetty/logging/JettyLoggerTest.java index 86b6fd6db8d..58b0c660573 100644 --- a/jetty-slf4j-impl/src/test/java/org/eclipse/jetty/logging/JettyLoggerTest.java +++ b/jetty-slf4j-impl/src/test/java/org/eclipse/jetty/logging/JettyLoggerTest.java @@ -609,6 +609,6 @@ public class JettyLoggerTest private void assertLevel(JettyLogger log, Level expectedLevel) { assertThat("Log[" + log.getName() + "].level", - JettyLogger.levelToString(log.getLevel()), is(expectedLevel.toString())); + LevelUtils.levelToString(log.getLevel()), is(expectedLevel.toString())); } } diff --git a/jetty-slf4j-impl/src/test/java/org/eclipse/jetty/logging/JmxExperiment.java b/jetty-slf4j-impl/src/test/java/org/eclipse/jetty/logging/JmxExperiment.java new file mode 100644 index 00000000000..26e12297d23 --- /dev/null +++ b/jetty-slf4j-impl/src/test/java/org/eclipse/jetty/logging/JmxExperiment.java @@ -0,0 +1,46 @@ +// +// ======================================================================== +// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others. +// +// This program and the accompanying materials are made available under +// the terms of the Eclipse Public License 2.0 which is available at +// https://www.eclipse.org/legal/epl-2.0 +// +// This Source Code may also be made available under the following +// Secondary Licenses when the conditions for such availability set +// forth in the Eclipse Public License, v. 2.0 are satisfied: +// the Apache License v2.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; + +import java.util.Properties; + +public class JmxExperiment +{ + public static void main(String[] args) + { + try + { + Properties props = new Properties(); + props.setProperty("org.eclipse.jetty.logging.jmx", "true"); + props.setProperty("org.eclipse.jetty.logging.jmx.contextName", "Main"); + props.setProperty("org.eclipse.jetty.logging.LEVEL", "DEBUG"); + JettyLoggerConfiguration config = new JettyLoggerConfiguration(props); + JettyLoggerFactory loggerFactory = new JettyLoggerFactory(config); + + JettyLoggingJmx.initialize(config, loggerFactory); + + loggerFactory.getJettyLogger(JmxExperiment.class.getName()).info("Waiting for key-press"); + System.in.read(); + } + catch (Exception e) + { + e.printStackTrace(); + } + } +}