From e852dada20147b88688e8055018527155157c5f9 Mon Sep 17 00:00:00 2001 From: Sylvain Juge <763082+SylvainJuge@users.noreply.github.com> Date: Thu, 20 Nov 2025 21:54:34 +0100 Subject: [PATCH 01/11] jmx-metrics: allow any resource path for classpath resources rules --- .../jmx/JmxMetricInsightInstaller.java | 4 +++- .../jmx/JmxTelemetryBuilder.java | 24 ++++++++++++++----- .../instrumentation/jmx/JmxTelemetryTest.java | 10 +++++--- 3 files changed, 28 insertions(+), 10 deletions(-) diff --git a/instrumentation/jmx-metrics/javaagent/src/main/java/io/opentelemetry/instrumentation/javaagent/jmx/JmxMetricInsightInstaller.java b/instrumentation/jmx-metrics/javaagent/src/main/java/io/opentelemetry/instrumentation/javaagent/jmx/JmxMetricInsightInstaller.java index a24e6c9db49b..711dce912485 100644 --- a/instrumentation/jmx-metrics/javaagent/src/main/java/io/opentelemetry/instrumentation/javaagent/jmx/JmxMetricInsightInstaller.java +++ b/instrumentation/jmx-metrics/javaagent/src/main/java/io/opentelemetry/instrumentation/javaagent/jmx/JmxMetricInsightInstaller.java @@ -35,7 +35,9 @@ public void afterAgent(AutoConfiguredOpenTelemetrySdk autoConfiguredSdk) { try { config.getList("otel.jmx.config").stream().map(Paths::get).forEach(jmx::addCustomRules); - config.getList("otel.jmx.target.system").forEach(jmx::addClassPathRules); + config.getList("otel.jmx.target.system").stream() + .map(v -> String.format("jmx/rules/%s.yaml", v)) + .forEach(jmx::addClassPathResourceRules); } catch (RuntimeException e) { // for now only log JMX errors as they do not prevent agent startup logger.log(Level.SEVERE, "Error while loading JMX configuration", e); diff --git a/instrumentation/jmx-metrics/library/src/main/java/io/opentelemetry/instrumentation/jmx/JmxTelemetryBuilder.java b/instrumentation/jmx-metrics/library/src/main/java/io/opentelemetry/instrumentation/jmx/JmxTelemetryBuilder.java index 8f1c5a4d51f0..ecda4cccd2c3 100644 --- a/instrumentation/jmx-metrics/library/src/main/java/io/opentelemetry/instrumentation/jmx/JmxTelemetryBuilder.java +++ b/instrumentation/jmx-metrics/library/src/main/java/io/opentelemetry/instrumentation/jmx/JmxTelemetryBuilder.java @@ -57,18 +57,30 @@ public JmxTelemetryBuilder beanDiscoveryDelay(Duration delay) { */ @CanIgnoreReturnValue public JmxTelemetryBuilder addClassPathRules(String target) { - String yamlResource = String.format("jmx/rules/%s.yaml", target); + return addClassPathResourceRules(String.format("jmx/rules/%s.yaml", target)); + } + + /** + * Adds built-in JMX rules from classpath resource + * + * @param resourcePath relative path of the classpath resource yaml from classpath root, must not + * start with '/' + * @return builder instance + * @throws IllegalArgumentException when classpath resource does not exist or can't be parsed + */ + @CanIgnoreReturnValue + public JmxTelemetryBuilder addClassPathResourceRules(String resourcePath) { try (InputStream inputStream = - JmxTelemetryBuilder.class.getClassLoader().getResourceAsStream(yamlResource)) { + JmxTelemetryBuilder.class.getClassLoader().getResourceAsStream(resourcePath)) { if (inputStream == null) { - throw new IllegalArgumentException("JMX rules not found in classpath: " + yamlResource); + throw new IllegalArgumentException("JMX rules not found in classpath: " + resourcePath); } - logger.log(FINE, "Adding JMX config from classpath for {0}", yamlResource); + logger.log(FINE, "Adding JMX config from classpath for {0}", resourcePath); RuleParser parserInstance = RuleParser.get(); - parserInstance.addMetricDefsTo(metricConfiguration, inputStream, target); + parserInstance.addMetricDefsTo(metricConfiguration, inputStream, resourcePath); } catch (Exception e) { throw new IllegalArgumentException( - "Unable to load JMX rules from classpath: " + yamlResource, e); + "Unable to load JMX rules from classpath: " + resourcePath, e); } return this; } diff --git a/instrumentation/jmx-metrics/library/src/test/java/io/opentelemetry/instrumentation/jmx/JmxTelemetryTest.java b/instrumentation/jmx-metrics/library/src/test/java/io/opentelemetry/instrumentation/jmx/JmxTelemetryTest.java index e840b91b1be3..f92fea4f149e 100644 --- a/instrumentation/jmx-metrics/library/src/test/java/io/opentelemetry/instrumentation/jmx/JmxTelemetryTest.java +++ b/instrumentation/jmx-metrics/library/src/test/java/io/opentelemetry/instrumentation/jmx/JmxTelemetryTest.java @@ -27,20 +27,24 @@ void createDefault() { @Test void missingClasspathTarget() { JmxTelemetryBuilder builder = JmxTelemetry.builder(OpenTelemetry.noop()); - assertThatThrownBy(() -> builder.addClassPathRules("should-not-exist")) + assertThatThrownBy(() -> builder.addClassPathResourceRules("should-not-exist")) .isInstanceOf(IllegalArgumentException.class); } @Test void invalidClasspathTarget() { JmxTelemetryBuilder builder = JmxTelemetry.builder(OpenTelemetry.noop()); - assertThatThrownBy(() -> builder.addClassPathRules("invalid")) + assertThatThrownBy(() -> builder.addClassPathResourceRules("rules/jmx/invalid.yaml")) .isInstanceOf(IllegalArgumentException.class); } @Test void knownClassPathTarget() { - JmxTelemetry.builder(OpenTelemetry.noop()).addClassPathRules("jvm").build(); + JmxTelemetry jmxtelemetry = + JmxTelemetry.builder(OpenTelemetry.noop()) + .addClassPathResourceRules("rules/jmx/jvm.yaml") + .build(); + assertThat(jmxtelemetry).isNotNull(); } @Test From d688fc46f98c29f4d58bac1bea0788ab64293654 Mon Sep 17 00:00:00 2001 From: Sylvain Juge <763082+SylvainJuge@users.noreply.github.com> Date: Thu, 20 Nov 2025 21:59:36 +0100 Subject: [PATCH 02/11] deprecate old method --- .../opentelemetry/instrumentation/jmx/JmxTelemetryBuilder.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/instrumentation/jmx-metrics/library/src/main/java/io/opentelemetry/instrumentation/jmx/JmxTelemetryBuilder.java b/instrumentation/jmx-metrics/library/src/main/java/io/opentelemetry/instrumentation/jmx/JmxTelemetryBuilder.java index ecda4cccd2c3..e40bfe13656a 100644 --- a/instrumentation/jmx-metrics/library/src/main/java/io/opentelemetry/instrumentation/jmx/JmxTelemetryBuilder.java +++ b/instrumentation/jmx-metrics/library/src/main/java/io/opentelemetry/instrumentation/jmx/JmxTelemetryBuilder.java @@ -54,7 +54,9 @@ public JmxTelemetryBuilder beanDiscoveryDelay(Duration delay) { * @param target name of target in /jmx/rules/{target}.yaml classpath resource * @return builder instance * @throws IllegalArgumentException when classpath resource does not exist or can't be parsed + * @deprecated use {@link #addClassPathResourceRules(String)} instead */ + @Deprecated @CanIgnoreReturnValue public JmxTelemetryBuilder addClassPathRules(String target) { return addClassPathResourceRules(String.format("jmx/rules/%s.yaml", target)); From 2514661208159bf46c00dc731a8dc976bccdbf9a Mon Sep 17 00:00:00 2001 From: Sylvain Juge <763082+SylvainJuge@users.noreply.github.com> Date: Thu, 20 Nov 2025 22:50:04 +0100 Subject: [PATCH 03/11] fix misleading exception messages --- .../jmx/JmxTelemetryBuilder.java | 21 +++++++++++-------- .../instrumentation/jmx/JmxTelemetryTest.java | 12 +++++++---- 2 files changed, 20 insertions(+), 13 deletions(-) diff --git a/instrumentation/jmx-metrics/library/src/main/java/io/opentelemetry/instrumentation/jmx/JmxTelemetryBuilder.java b/instrumentation/jmx-metrics/library/src/main/java/io/opentelemetry/instrumentation/jmx/JmxTelemetryBuilder.java index e40bfe13656a..238a66393da3 100644 --- a/instrumentation/jmx-metrics/library/src/main/java/io/opentelemetry/instrumentation/jmx/JmxTelemetryBuilder.java +++ b/instrumentation/jmx-metrics/library/src/main/java/io/opentelemetry/instrumentation/jmx/JmxTelemetryBuilder.java @@ -49,14 +49,13 @@ public JmxTelemetryBuilder beanDiscoveryDelay(Duration delay) { } /** - * Adds built-in JMX rules from classpath resource + * Adds built-in JMX rules from classpath resource. * * @param target name of target in /jmx/rules/{target}.yaml classpath resource * @return builder instance * @throws IllegalArgumentException when classpath resource does not exist or can't be parsed - * @deprecated use {@link #addClassPathResourceRules(String)} instead */ - @Deprecated + // TODO: deprecate this method after 2.23.0 release in favor of addClassPathResourceRules @CanIgnoreReturnValue public JmxTelemetryBuilder addClassPathRules(String target) { return addClassPathResourceRules(String.format("jmx/rules/%s.yaml", target)); @@ -72,18 +71,22 @@ public JmxTelemetryBuilder addClassPathRules(String target) { */ @CanIgnoreReturnValue public JmxTelemetryBuilder addClassPathResourceRules(String resourcePath) { + boolean found = false; try (InputStream inputStream = JmxTelemetryBuilder.class.getClassLoader().getResourceAsStream(resourcePath)) { - if (inputStream == null) { - throw new IllegalArgumentException("JMX rules not found in classpath: " + resourcePath); + if (inputStream != null) { + found = true; + logger.log(FINE, "Adding JMX config from classpath for {0}", resourcePath); + RuleParser parserInstance = RuleParser.get(); + parserInstance.addMetricDefsTo(metricConfiguration, inputStream, resourcePath); } - logger.log(FINE, "Adding JMX config from classpath for {0}", resourcePath); - RuleParser parserInstance = RuleParser.get(); - parserInstance.addMetricDefsTo(metricConfiguration, inputStream, resourcePath); - } catch (Exception e) { + } catch (RuntimeException | IOException e) { throw new IllegalArgumentException( "Unable to load JMX rules from classpath: " + resourcePath, e); } + if (!found) { + throw new IllegalArgumentException("JMX rules not found in classpath: " + resourcePath); + } return this; } diff --git a/instrumentation/jmx-metrics/library/src/test/java/io/opentelemetry/instrumentation/jmx/JmxTelemetryTest.java b/instrumentation/jmx-metrics/library/src/test/java/io/opentelemetry/instrumentation/jmx/JmxTelemetryTest.java index f92fea4f149e..11df1ab75f5e 100644 --- a/instrumentation/jmx-metrics/library/src/test/java/io/opentelemetry/instrumentation/jmx/JmxTelemetryTest.java +++ b/instrumentation/jmx-metrics/library/src/test/java/io/opentelemetry/instrumentation/jmx/JmxTelemetryTest.java @@ -28,21 +28,25 @@ void createDefault() { void missingClasspathTarget() { JmxTelemetryBuilder builder = JmxTelemetry.builder(OpenTelemetry.noop()); assertThatThrownBy(() -> builder.addClassPathResourceRules("should-not-exist")) - .isInstanceOf(IllegalArgumentException.class); + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("not found") + .hasMessageContaining("should-not-exist"); } @Test void invalidClasspathTarget() { JmxTelemetryBuilder builder = JmxTelemetry.builder(OpenTelemetry.noop()); - assertThatThrownBy(() -> builder.addClassPathResourceRules("rules/jmx/invalid.yaml")) - .isInstanceOf(IllegalArgumentException.class); + assertThatThrownBy(() -> builder.addClassPathResourceRules("jmx/rules/invalid.yaml")) + .isInstanceOf(IllegalArgumentException.class) + .describedAs("must have an exception message including the invalid resource path") + .hasMessageContaining("jmx/rules/invalid.yaml"); } @Test void knownClassPathTarget() { JmxTelemetry jmxtelemetry = JmxTelemetry.builder(OpenTelemetry.noop()) - .addClassPathResourceRules("rules/jmx/jvm.yaml") + .addClassPathResourceRules("jmx/rules/jvm.yaml") .build(); assertThat(jmxtelemetry).isNotNull(); } From 04bf7dbddcafdef0d23e60829decd99b60725b1e Mon Sep 17 00:00:00 2001 From: Sylvain Juge <763082+SylvainJuge@users.noreply.github.com> Date: Fri, 21 Nov 2025 10:12:18 +0100 Subject: [PATCH 04/11] switch to a more generic InputStream for adding rules --- .../jmx/JmxMetricInsightInstaller.java | 27 +++++++++-- .../jmx/JmxMetricInsightInstallerTest.java | 6 ++- .../jmx/JmxTelemetryBuilder.java | 47 +++++++++---------- .../instrumentation/jmx/yaml/RuleParser.java | 11 +++-- .../instrumentation/jmx/JmxTelemetryTest.java | 30 +++++++----- 5 files changed, 73 insertions(+), 48 deletions(-) diff --git a/instrumentation/jmx-metrics/javaagent/src/main/java/io/opentelemetry/instrumentation/javaagent/jmx/JmxMetricInsightInstaller.java b/instrumentation/jmx-metrics/javaagent/src/main/java/io/opentelemetry/instrumentation/javaagent/jmx/JmxMetricInsightInstaller.java index 711dce912485..93112ca3f762 100644 --- a/instrumentation/jmx-metrics/javaagent/src/main/java/io/opentelemetry/instrumentation/javaagent/jmx/JmxMetricInsightInstaller.java +++ b/instrumentation/jmx-metrics/javaagent/src/main/java/io/opentelemetry/instrumentation/javaagent/jmx/JmxMetricInsightInstaller.java @@ -13,6 +13,10 @@ import io.opentelemetry.sdk.autoconfigure.AutoConfiguredOpenTelemetrySdk; import io.opentelemetry.sdk.autoconfigure.internal.AutoConfigureUtil; import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties; +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Files; +import java.nio.file.Path; import java.nio.file.Paths; import java.time.Duration; import java.util.logging.Level; @@ -32,12 +36,9 @@ public void afterAgent(AutoConfiguredOpenTelemetrySdk autoConfiguredSdk) { JmxTelemetryBuilder jmx = JmxTelemetry.builder(GlobalOpenTelemetry.get()) .beanDiscoveryDelay(beanDiscoveryDelay(config)); - try { - config.getList("otel.jmx.config").stream().map(Paths::get).forEach(jmx::addCustomRules); - config.getList("otel.jmx.target.system").stream() - .map(v -> String.format("jmx/rules/%s.yaml", v)) - .forEach(jmx::addClassPathResourceRules); + config.getList("otel.jmx.config").forEach(file -> addCustomRules(file, jmx)); + config.getList("otel.jmx.target.system").forEach(target -> addClasspathRules(target, jmx)); } catch (RuntimeException e) { // for now only log JMX errors as they do not prevent agent startup logger.log(Level.SEVERE, "Error while loading JMX configuration", e); @@ -47,6 +48,22 @@ public void afterAgent(AutoConfiguredOpenTelemetrySdk autoConfiguredSdk) { } } + private static void addCustomRules(String path, JmxTelemetryBuilder jmx) { + Path filePath = Paths.get(path); + try (InputStream input = Files.newInputStream(filePath)) { + jmx.addRules(input, filePath.toString()); + } catch (IOException e) { + throw new IllegalArgumentException("Unable to load JMX rules from " + filePath, e); + } + } + + private static void addClasspathRules(String target, JmxTelemetryBuilder builder) { + ClassLoader classLoader = JmxTelemetryBuilder.class.getClassLoader(); + String resource = String.format("jmx/rules/%s.yaml", target); + InputStream input = classLoader.getResourceAsStream(resource); + builder.addRules(input, "classpath " + resource); + } + private static Duration beanDiscoveryDelay(ConfigProperties configProperties) { Duration discoveryDelay = configProperties.getDuration("otel.jmx.discovery.delay"); if (discoveryDelay != null) { diff --git a/instrumentation/jmx-metrics/javaagent/src/test/java/io/opentelemetry/instrumentation/javaagent/jmx/JmxMetricInsightInstallerTest.java b/instrumentation/jmx-metrics/javaagent/src/test/java/io/opentelemetry/instrumentation/javaagent/jmx/JmxMetricInsightInstallerTest.java index aa823292fb65..7cf95457c7c0 100644 --- a/instrumentation/jmx-metrics/javaagent/src/test/java/io/opentelemetry/instrumentation/javaagent/jmx/JmxMetricInsightInstallerTest.java +++ b/instrumentation/jmx-metrics/javaagent/src/test/java/io/opentelemetry/instrumentation/javaagent/jmx/JmxMetricInsightInstallerTest.java @@ -10,6 +10,8 @@ import io.opentelemetry.api.OpenTelemetry; import io.opentelemetry.instrumentation.jmx.JmxTelemetry; import io.opentelemetry.instrumentation.jmx.yaml.RuleParser; +import java.io.InputStream; +import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.util.Arrays; @@ -41,7 +43,9 @@ void testToVerifyExistingRulesAreValid() throws Exception { assertThat(filePath).isRegularFile(); // loading rules from direct file access - JmxTelemetry.builder(OpenTelemetry.noop()).addCustomRules(filePath); + try (InputStream fileInput = Files.newInputStream(filePath)) { + JmxTelemetry.builder(OpenTelemetry.noop()).addRules(fileInput, filePath.toString()); + } } } } diff --git a/instrumentation/jmx-metrics/library/src/main/java/io/opentelemetry/instrumentation/jmx/JmxTelemetryBuilder.java b/instrumentation/jmx-metrics/library/src/main/java/io/opentelemetry/instrumentation/jmx/JmxTelemetryBuilder.java index 238a66393da3..2721a01266f1 100644 --- a/instrumentation/jmx-metrics/library/src/main/java/io/opentelemetry/instrumentation/jmx/JmxTelemetryBuilder.java +++ b/instrumentation/jmx-metrics/library/src/main/java/io/opentelemetry/instrumentation/jmx/JmxTelemetryBuilder.java @@ -55,38 +55,34 @@ public JmxTelemetryBuilder beanDiscoveryDelay(Duration delay) { * @return builder instance * @throws IllegalArgumentException when classpath resource does not exist or can't be parsed */ - // TODO: deprecate this method after 2.23.0 release in favor of addClassPathResourceRules + // TODO: deprecate this method after 2.23.0 release in favor of addRules @CanIgnoreReturnValue public JmxTelemetryBuilder addClassPathRules(String target) { - return addClassPathResourceRules(String.format("jmx/rules/%s.yaml", target)); + String resourcePath = String.format("jmx/rules/%s.yaml", target); + ClassLoader classLoader = JmxTelemetryBuilder.class.getClassLoader(); + try (InputStream inputStream = classLoader.getResourceAsStream(resourcePath)) { + return addRules(inputStream, resourcePath); + } catch (IOException e) { + throw new IllegalArgumentException( + "Unable to load JMX rules from resource " + resourcePath, e); + } } /** - * Adds built-in JMX rules from classpath resource + * Adds JMX rules from input stream * - * @param resourcePath relative path of the classpath resource yaml from classpath root, must not - * start with '/' - * @return builder instance - * @throws IllegalArgumentException when classpath resource does not exist or can't be parsed + * @param input input to read rules from + * @param description input description, used for user-friendly logs and parsing error messages + * @throws IllegalArgumentException when input is {@literal null} */ @CanIgnoreReturnValue - public JmxTelemetryBuilder addClassPathResourceRules(String resourcePath) { - boolean found = false; - try (InputStream inputStream = - JmxTelemetryBuilder.class.getClassLoader().getResourceAsStream(resourcePath)) { - if (inputStream != null) { - found = true; - logger.log(FINE, "Adding JMX config from classpath for {0}", resourcePath); - RuleParser parserInstance = RuleParser.get(); - parserInstance.addMetricDefsTo(metricConfiguration, inputStream, resourcePath); - } - } catch (RuntimeException | IOException e) { - throw new IllegalArgumentException( - "Unable to load JMX rules from classpath: " + resourcePath, e); - } - if (!found) { - throw new IllegalArgumentException("JMX rules not found in classpath: " + resourcePath); + public JmxTelemetryBuilder addRules(InputStream input, String description) { + if (input == null) { + throw new IllegalArgumentException("JMX rules not found for " + description); } + logger.log(FINE, "Adding JMX config from {0}", description); + RuleParser parserInstance = RuleParser.get(); + parserInstance.addMetricDefsTo(metricConfiguration, input, description); return this; } @@ -97,16 +93,15 @@ public JmxTelemetryBuilder addClassPathResourceRules(String resourcePath) { * @return builder instance * @throws IllegalArgumentException when classpath resource does not exist or can't be parsed */ + // TODO: deprecate this method after 2.23.0 release in favor of addRules @CanIgnoreReturnValue public JmxTelemetryBuilder addCustomRules(Path path) { logger.log(FINE, "Adding JMX config from file: {0}", path); - RuleParser parserInstance = RuleParser.get(); try (InputStream inputStream = Files.newInputStream(path)) { - parserInstance.addMetricDefsTo(metricConfiguration, inputStream, path.toString()); + return addRules(inputStream, path.toString()); } catch (IOException e) { throw new IllegalArgumentException("Unable to load JMX rules in path: " + path, e); } - return this; } public JmxTelemetry build() { diff --git a/instrumentation/jmx-metrics/library/src/main/java/io/opentelemetry/instrumentation/jmx/yaml/RuleParser.java b/instrumentation/jmx-metrics/library/src/main/java/io/opentelemetry/instrumentation/jmx/yaml/RuleParser.java index 1fc7f13dd78a..4e4840db5cbc 100644 --- a/instrumentation/jmx-metrics/library/src/main/java/io/opentelemetry/instrumentation/jmx/yaml/RuleParser.java +++ b/instrumentation/jmx-metrics/library/src/main/java/io/opentelemetry/instrumentation/jmx/yaml/RuleParser.java @@ -162,13 +162,16 @@ private static void failOnExtraKeys(Map yaml) { * * @param conf the metric configuration * @param is the InputStream with the YAML rules - * @param id identifier of the YAML ruleset, such as a filename + * @param description description of the YAML ruleset like file name or classpath resource * @throws IllegalArgumentException when unable to parse YAML */ - public void addMetricDefsTo(MetricConfiguration conf, InputStream is, String id) { + public void addMetricDefsTo(MetricConfiguration conf, InputStream is, String description) { try { JmxConfig config = loadConfig(is); - logger.log(INFO, "{0}: found {1} metric rules", new Object[] {id, config.getRules().size()}); + logger.log( + INFO, + "{0}: found {1} metric rules", + new Object[] {description, config.getRules().size()}); config.addMetricDefsTo(conf); } catch (Exception exception) { // It is essential that the parser exception is made visible to the user. @@ -176,7 +179,7 @@ public void addMetricDefsTo(MetricConfiguration conf, InputStream is, String id) String msg = String.format( "Failed to parse YAML rules from %s: %s %s", - id, rootCause(exception), exception.getMessage()); + description, rootCause(exception), exception.getMessage()); throw new IllegalArgumentException(msg, exception); } } diff --git a/instrumentation/jmx-metrics/library/src/test/java/io/opentelemetry/instrumentation/jmx/JmxTelemetryTest.java b/instrumentation/jmx-metrics/library/src/test/java/io/opentelemetry/instrumentation/jmx/JmxTelemetryTest.java index 11df1ab75f5e..b6e6f4f93772 100644 --- a/instrumentation/jmx-metrics/library/src/test/java/io/opentelemetry/instrumentation/jmx/JmxTelemetryTest.java +++ b/instrumentation/jmx-metrics/library/src/test/java/io/opentelemetry/instrumentation/jmx/JmxTelemetryTest.java @@ -9,6 +9,7 @@ import static org.assertj.core.api.Assertions.assertThatThrownBy; import io.opentelemetry.api.OpenTelemetry; +import java.io.InputStream; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; @@ -27,37 +28,42 @@ void createDefault() { @Test void missingClasspathTarget() { JmxTelemetryBuilder builder = JmxTelemetry.builder(OpenTelemetry.noop()); - assertThatThrownBy(() -> builder.addClassPathResourceRules("should-not-exist")) + assertThatThrownBy(() -> builder.addRules(null, "something is missing")) .isInstanceOf(IllegalArgumentException.class) .hasMessageContaining("not found") - .hasMessageContaining("should-not-exist"); + .hasMessageContaining("something is missing"); } @Test void invalidClasspathTarget() { JmxTelemetryBuilder builder = JmxTelemetry.builder(OpenTelemetry.noop()); - assertThatThrownBy(() -> builder.addClassPathResourceRules("jmx/rules/invalid.yaml")) + assertThatThrownBy(() -> addClasspathRules(builder, "jmx/rules/invalid.yaml")) .isInstanceOf(IllegalArgumentException.class) .describedAs("must have an exception message including the invalid resource path") .hasMessageContaining("jmx/rules/invalid.yaml"); } @Test - void knownClassPathTarget() { - JmxTelemetry jmxtelemetry = - JmxTelemetry.builder(OpenTelemetry.noop()) - .addClassPathResourceRules("jmx/rules/jvm.yaml") - .build(); - assertThat(jmxtelemetry).isNotNull(); + void knownValidYaml() { + JmxTelemetryBuilder jmxtelemetry = JmxTelemetry.builder(OpenTelemetry.noop()); + addClasspathRules(jmxtelemetry, "jmx/rules/jvm.yaml"); + assertThat(jmxtelemetry.build()).isNotNull(); + } + + private static void addClasspathRules(JmxTelemetryBuilder builder, String path) { + InputStream input = JmxTelemetryTest.class.getClassLoader().getResourceAsStream(path); + builder.addRules(input, path); } @Test - void invalidExternalYaml(@TempDir Path dir) throws Exception { + void tryInvalidYaml(@TempDir Path dir) throws Exception { Path invalid = Files.createTempFile(dir, "invalid", ".yaml"); Files.write(invalid, ":this !is /not YAML".getBytes(StandardCharsets.UTF_8)); JmxTelemetryBuilder builder = JmxTelemetry.builder(OpenTelemetry.noop()); - assertThatThrownBy(() -> builder.addCustomRules(invalid)) - .isInstanceOf(IllegalArgumentException.class); + try (InputStream input = Files.newInputStream(invalid)) { + assertThatThrownBy(() -> builder.addRules(input, invalid.toString())) + .isInstanceOf(IllegalArgumentException.class); + } } @Test From eb0555c19429d92f9a1a715c9708e6c49b77945f Mon Sep 17 00:00:00 2001 From: Sylvain Juge <763082+SylvainJuge@users.noreply.github.com> Date: Fri, 21 Nov 2025 11:23:44 +0100 Subject: [PATCH 05/11] further simplify API --- .../jmx/JmxMetricInsightInstaller.java | 15 +----------- .../jmx/JmxTelemetryBuilder.java | 23 ++++++++++++++----- .../instrumentation/jmx/JmxTelemetryTest.java | 9 ++++---- 3 files changed, 22 insertions(+), 25 deletions(-) diff --git a/instrumentation/jmx-metrics/javaagent/src/main/java/io/opentelemetry/instrumentation/javaagent/jmx/JmxMetricInsightInstaller.java b/instrumentation/jmx-metrics/javaagent/src/main/java/io/opentelemetry/instrumentation/javaagent/jmx/JmxMetricInsightInstaller.java index 93112ca3f762..84b6f90a79d3 100644 --- a/instrumentation/jmx-metrics/javaagent/src/main/java/io/opentelemetry/instrumentation/javaagent/jmx/JmxMetricInsightInstaller.java +++ b/instrumentation/jmx-metrics/javaagent/src/main/java/io/opentelemetry/instrumentation/javaagent/jmx/JmxMetricInsightInstaller.java @@ -13,10 +13,7 @@ import io.opentelemetry.sdk.autoconfigure.AutoConfiguredOpenTelemetrySdk; import io.opentelemetry.sdk.autoconfigure.internal.AutoConfigureUtil; import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties; -import java.io.IOException; import java.io.InputStream; -import java.nio.file.Files; -import java.nio.file.Path; import java.nio.file.Paths; import java.time.Duration; import java.util.logging.Level; @@ -37,26 +34,16 @@ public void afterAgent(AutoConfiguredOpenTelemetrySdk autoConfiguredSdk) { JmxTelemetry.builder(GlobalOpenTelemetry.get()) .beanDiscoveryDelay(beanDiscoveryDelay(config)); try { - config.getList("otel.jmx.config").forEach(file -> addCustomRules(file, jmx)); + config.getList("otel.jmx.config").stream().map(Paths::get).forEach(jmx::addRules); config.getList("otel.jmx.target.system").forEach(target -> addClasspathRules(target, jmx)); } catch (RuntimeException e) { // for now only log JMX errors as they do not prevent agent startup logger.log(Level.SEVERE, "Error while loading JMX configuration", e); } - jmx.build().start(); } } - private static void addCustomRules(String path, JmxTelemetryBuilder jmx) { - Path filePath = Paths.get(path); - try (InputStream input = Files.newInputStream(filePath)) { - jmx.addRules(input, filePath.toString()); - } catch (IOException e) { - throw new IllegalArgumentException("Unable to load JMX rules from " + filePath, e); - } - } - private static void addClasspathRules(String target, JmxTelemetryBuilder builder) { ClassLoader classLoader = JmxTelemetryBuilder.class.getClassLoader(); String resource = String.format("jmx/rules/%s.yaml", target); diff --git a/instrumentation/jmx-metrics/library/src/main/java/io/opentelemetry/instrumentation/jmx/JmxTelemetryBuilder.java b/instrumentation/jmx-metrics/library/src/main/java/io/opentelemetry/instrumentation/jmx/JmxTelemetryBuilder.java index 2721a01266f1..46bd813e84ae 100644 --- a/instrumentation/jmx-metrics/library/src/main/java/io/opentelemetry/instrumentation/jmx/JmxTelemetryBuilder.java +++ b/instrumentation/jmx-metrics/library/src/main/java/io/opentelemetry/instrumentation/jmx/JmxTelemetryBuilder.java @@ -86,6 +86,22 @@ public JmxTelemetryBuilder addRules(InputStream input, String description) { return this; } + /** + * Adds JMX rules from file system path + * + * @param path path to yaml file + * @return builder instance + * @throws IllegalArgumentException in case of parsing errors or when file does not exist + */ + @CanIgnoreReturnValue + public JmxTelemetryBuilder addRules(Path path) { + try (InputStream inputStream = Files.newInputStream(path)) { + return addRules(inputStream, "file " + path); + } catch (IOException e) { + throw new IllegalArgumentException("Unable to load JMX rules from: " + path, e); + } + } + /** * Adds custom JMX rules from file system path * @@ -96,12 +112,7 @@ public JmxTelemetryBuilder addRules(InputStream input, String description) { // TODO: deprecate this method after 2.23.0 release in favor of addRules @CanIgnoreReturnValue public JmxTelemetryBuilder addCustomRules(Path path) { - logger.log(FINE, "Adding JMX config from file: {0}", path); - try (InputStream inputStream = Files.newInputStream(path)) { - return addRules(inputStream, path.toString()); - } catch (IOException e) { - throw new IllegalArgumentException("Unable to load JMX rules in path: " + path, e); - } + return addRules(path); } public JmxTelemetry build() { diff --git a/instrumentation/jmx-metrics/library/src/test/java/io/opentelemetry/instrumentation/jmx/JmxTelemetryTest.java b/instrumentation/jmx-metrics/library/src/test/java/io/opentelemetry/instrumentation/jmx/JmxTelemetryTest.java index b6e6f4f93772..ab613bc52250 100644 --- a/instrumentation/jmx-metrics/library/src/test/java/io/opentelemetry/instrumentation/jmx/JmxTelemetryTest.java +++ b/instrumentation/jmx-metrics/library/src/test/java/io/opentelemetry/instrumentation/jmx/JmxTelemetryTest.java @@ -56,14 +56,13 @@ private static void addClasspathRules(JmxTelemetryBuilder builder, String path) } @Test - void tryInvalidYaml(@TempDir Path dir) throws Exception { + void invalidExternalYaml(@TempDir Path dir) throws Exception { Path invalid = Files.createTempFile(dir, "invalid", ".yaml"); Files.write(invalid, ":this !is /not YAML".getBytes(StandardCharsets.UTF_8)); JmxTelemetryBuilder builder = JmxTelemetry.builder(OpenTelemetry.noop()); - try (InputStream input = Files.newInputStream(invalid)) { - assertThatThrownBy(() -> builder.addRules(input, invalid.toString())) - .isInstanceOf(IllegalArgumentException.class); - } + assertThatThrownBy(() -> builder.addCustomRules(invalid)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining(invalid.toString()); } @Test From 549ff19b0589cc607b1299353c83b4f4af3a1f1e Mon Sep 17 00:00:00 2001 From: Sylvain Juge <763082+SylvainJuge@users.noreply.github.com> Date: Tue, 25 Nov 2025 15:11:41 +0100 Subject: [PATCH 06/11] avoid using future deprecated methods --- .../javaagent/jmx/JmxMetricInsightInstallerTest.java | 4 +--- .../opentelemetry/instrumentation/jmx/JmxTelemetryTest.java | 2 +- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/instrumentation/jmx-metrics/javaagent/src/test/java/io/opentelemetry/instrumentation/javaagent/jmx/JmxMetricInsightInstallerTest.java b/instrumentation/jmx-metrics/javaagent/src/test/java/io/opentelemetry/instrumentation/javaagent/jmx/JmxMetricInsightInstallerTest.java index 7cf95457c7c0..f6152f0c3323 100644 --- a/instrumentation/jmx-metrics/javaagent/src/test/java/io/opentelemetry/instrumentation/javaagent/jmx/JmxMetricInsightInstallerTest.java +++ b/instrumentation/jmx-metrics/javaagent/src/test/java/io/opentelemetry/instrumentation/javaagent/jmx/JmxMetricInsightInstallerTest.java @@ -43,9 +43,7 @@ void testToVerifyExistingRulesAreValid() throws Exception { assertThat(filePath).isRegularFile(); // loading rules from direct file access - try (InputStream fileInput = Files.newInputStream(filePath)) { - JmxTelemetry.builder(OpenTelemetry.noop()).addRules(fileInput, filePath.toString()); - } + JmxTelemetry.builder(OpenTelemetry.noop()).addRules(filePath); } } } diff --git a/instrumentation/jmx-metrics/library/src/test/java/io/opentelemetry/instrumentation/jmx/JmxTelemetryTest.java b/instrumentation/jmx-metrics/library/src/test/java/io/opentelemetry/instrumentation/jmx/JmxTelemetryTest.java index ab613bc52250..bf1671eb4e33 100644 --- a/instrumentation/jmx-metrics/library/src/test/java/io/opentelemetry/instrumentation/jmx/JmxTelemetryTest.java +++ b/instrumentation/jmx-metrics/library/src/test/java/io/opentelemetry/instrumentation/jmx/JmxTelemetryTest.java @@ -60,7 +60,7 @@ void invalidExternalYaml(@TempDir Path dir) throws Exception { Path invalid = Files.createTempFile(dir, "invalid", ".yaml"); Files.write(invalid, ":this !is /not YAML".getBytes(StandardCharsets.UTF_8)); JmxTelemetryBuilder builder = JmxTelemetry.builder(OpenTelemetry.noop()); - assertThatThrownBy(() -> builder.addCustomRules(invalid)) + assertThatThrownBy(() -> builder.addRules(invalid)) .isInstanceOf(IllegalArgumentException.class) .hasMessageContaining(invalid.toString()); } From 0c6e261612ed6f5029d1e784d43596f047171f28 Mon Sep 17 00:00:00 2001 From: Sylvain Juge <763082+SylvainJuge@users.noreply.github.com> Date: Tue, 25 Nov 2025 15:12:03 +0100 Subject: [PATCH 07/11] update readme with the new API --- instrumentation/jmx-metrics/library/README.md | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/instrumentation/jmx-metrics/library/README.md b/instrumentation/jmx-metrics/library/README.md index 2803f10d60cb..941d6e7d0d55 100644 --- a/instrumentation/jmx-metrics/library/README.md +++ b/instrumentation/jmx-metrics/library/README.md @@ -32,21 +32,24 @@ implementation("io.opentelemetry.instrumentation:opentelemetry-jmx-metrics:OPENT ```java import io.opentelemetry.api.OpenTelemetry; +import io.opentelemetry.api.GlobalOpenTelemetry; import io.opentelemetry.instrumentation.jmx.JmxTelemetry; import io.opentelemetry.instrumentation.jmx.JmxTelemetryBuilder; +import java.time.Duration; + // Get an OpenTelemetry instance -OpenTelemetry openTelemetry = ... +OpenTelemetry openTelemetry = GlobalOpenTelemetry.get(); JmxTelemetry jmxTelemetry = JmxTelemetry.builder(openTelemetry) // Configure included metrics (optional) - .addClasspathRules("tomcat") - .addClasspathRules("jetty") + .addRules(JmxTelemetry.class.getClassLoader().getResourceAsStream("jmx/rules/jetty.yaml"), "jetty") + .addRules(JmxTelemetry.class.getClassLoader().getResourceAsStream("jmx/rules/tomcat.yaml"), "tomcat") // Configure custom metrics (optional) - .addCustomRules("/path/to/custom-jmx.yaml") + .addRules(Paths.get("/path/to/custom-jmx.yaml")) // delay bean discovery by 5 seconds - .beanDiscoveryDelay(5000) + .beanDiscoveryDelay(Duration.ofSeconds(5)) .build(); -jmxTelemetry.startLocal(); +jmxTelemetry.start(); ``` From 5ff7e246c62aadb194ca09bc7da94adebee44dab Mon Sep 17 00:00:00 2001 From: Sylvain Juge <763082+SylvainJuge@users.noreply.github.com> Date: Tue, 25 Nov 2025 15:21:07 +0100 Subject: [PATCH 08/11] spotless --- .../javaagent/jmx/JmxMetricInsightInstallerTest.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/instrumentation/jmx-metrics/javaagent/src/test/java/io/opentelemetry/instrumentation/javaagent/jmx/JmxMetricInsightInstallerTest.java b/instrumentation/jmx-metrics/javaagent/src/test/java/io/opentelemetry/instrumentation/javaagent/jmx/JmxMetricInsightInstallerTest.java index f6152f0c3323..2d55adc4ac12 100644 --- a/instrumentation/jmx-metrics/javaagent/src/test/java/io/opentelemetry/instrumentation/javaagent/jmx/JmxMetricInsightInstallerTest.java +++ b/instrumentation/jmx-metrics/javaagent/src/test/java/io/opentelemetry/instrumentation/javaagent/jmx/JmxMetricInsightInstallerTest.java @@ -10,8 +10,6 @@ import io.opentelemetry.api.OpenTelemetry; import io.opentelemetry.instrumentation.jmx.JmxTelemetry; import io.opentelemetry.instrumentation.jmx.yaml.RuleParser; -import java.io.InputStream; -import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.util.Arrays; From 0abd94797ce8e6f91c668388e5d490807a1e3e95 Mon Sep 17 00:00:00 2001 From: Sylvain Juge <763082+SylvainJuge@users.noreply.github.com> Date: Fri, 28 Nov 2025 13:58:29 +0100 Subject: [PATCH 09/11] post-review changes --- .../jmx/JmxMetricInsightInstaller.java | 31 ++++++++++++++----- .../jmx/JmxTelemetryBuilder.java | 16 +++++----- .../instrumentation/jmx/yaml/RuleParser.java | 13 +++----- .../instrumentation/jmx/JmxTelemetryTest.java | 12 +++---- 4 files changed, 41 insertions(+), 31 deletions(-) diff --git a/instrumentation/jmx-metrics/javaagent/src/main/java/io/opentelemetry/instrumentation/javaagent/jmx/JmxMetricInsightInstaller.java b/instrumentation/jmx-metrics/javaagent/src/main/java/io/opentelemetry/instrumentation/javaagent/jmx/JmxMetricInsightInstaller.java index 84b6f90a79d3..d6d1a19e8736 100644 --- a/instrumentation/jmx-metrics/javaagent/src/main/java/io/opentelemetry/instrumentation/javaagent/jmx/JmxMetricInsightInstaller.java +++ b/instrumentation/jmx-metrics/javaagent/src/main/java/io/opentelemetry/instrumentation/javaagent/jmx/JmxMetricInsightInstaller.java @@ -14,6 +14,7 @@ import io.opentelemetry.sdk.autoconfigure.internal.AutoConfigureUtil; import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties; import java.io.InputStream; +import java.nio.file.Path; import java.nio.file.Paths; import java.time.Duration; import java.util.logging.Level; @@ -33,22 +34,36 @@ public void afterAgent(AutoConfiguredOpenTelemetrySdk autoConfiguredSdk) { JmxTelemetryBuilder jmx = JmxTelemetry.builder(GlobalOpenTelemetry.get()) .beanDiscoveryDelay(beanDiscoveryDelay(config)); - try { - config.getList("otel.jmx.config").stream().map(Paths::get).forEach(jmx::addRules); - config.getList("otel.jmx.target.system").forEach(target -> addClasspathRules(target, jmx)); - } catch (RuntimeException e) { - // for now only log JMX errors as they do not prevent agent startup - logger.log(Level.SEVERE, "Error while loading JMX configuration", e); - } + + config.getList("otel.jmx.config").stream() + .map(Paths::get) + .forEach(path -> addFileRules(path, jmx)); + config.getList("otel.jmx.target.system").forEach(target -> addClasspathRules(target, jmx)); + jmx.build().start(); } } + private static void addFileRules(Path path, JmxTelemetryBuilder builder) { + try { + builder.addRules(path); + } catch (RuntimeException e) { + // for now only log JMX errors as they do not prevent agent startup + logger.log(Level.SEVERE, "Error while loading JMX configuration from " + path, e); + } + } + private static void addClasspathRules(String target, JmxTelemetryBuilder builder) { ClassLoader classLoader = JmxTelemetryBuilder.class.getClassLoader(); String resource = String.format("jmx/rules/%s.yaml", target); InputStream input = classLoader.getResourceAsStream(resource); - builder.addRules(input, "classpath " + resource); + try { + builder.addRules(input); + } catch (RuntimeException e) { + // for now only log JMX errors as they do not prevent agent startup + logger.log( + Level.SEVERE, "Error while loading JMX configuration from classpath " + resource, e); + } } private static Duration beanDiscoveryDelay(ConfigProperties configProperties) { diff --git a/instrumentation/jmx-metrics/library/src/main/java/io/opentelemetry/instrumentation/jmx/JmxTelemetryBuilder.java b/instrumentation/jmx-metrics/library/src/main/java/io/opentelemetry/instrumentation/jmx/JmxTelemetryBuilder.java index 46bd813e84ae..f4e66f023c6a 100644 --- a/instrumentation/jmx-metrics/library/src/main/java/io/opentelemetry/instrumentation/jmx/JmxTelemetryBuilder.java +++ b/instrumentation/jmx-metrics/library/src/main/java/io/opentelemetry/instrumentation/jmx/JmxTelemetryBuilder.java @@ -60,8 +60,9 @@ public JmxTelemetryBuilder beanDiscoveryDelay(Duration delay) { public JmxTelemetryBuilder addClassPathRules(String target) { String resourcePath = String.format("jmx/rules/%s.yaml", target); ClassLoader classLoader = JmxTelemetryBuilder.class.getClassLoader(); + logger.log(FINE, "Adding JMX config from classpath {0}", resourcePath); try (InputStream inputStream = classLoader.getResourceAsStream(resourcePath)) { - return addRules(inputStream, resourcePath); + return addRules(inputStream); } catch (IOException e) { throw new IllegalArgumentException( "Unable to load JMX rules from resource " + resourcePath, e); @@ -72,17 +73,15 @@ public JmxTelemetryBuilder addClassPathRules(String target) { * Adds JMX rules from input stream * * @param input input to read rules from - * @param description input description, used for user-friendly logs and parsing error messages - * @throws IllegalArgumentException when input is {@literal null} + * @throws IllegalArgumentException when input is {@literal null} or can't be parsed */ @CanIgnoreReturnValue - public JmxTelemetryBuilder addRules(InputStream input, String description) { + public JmxTelemetryBuilder addRules(InputStream input) { if (input == null) { - throw new IllegalArgumentException("JMX rules not found for " + description); + throw new IllegalArgumentException("missing JMX rules"); } - logger.log(FINE, "Adding JMX config from {0}", description); RuleParser parserInstance = RuleParser.get(); - parserInstance.addMetricDefsTo(metricConfiguration, input, description); + parserInstance.addMetricDefsTo(metricConfiguration, input); return this; } @@ -96,7 +95,8 @@ public JmxTelemetryBuilder addRules(InputStream input, String description) { @CanIgnoreReturnValue public JmxTelemetryBuilder addRules(Path path) { try (InputStream inputStream = Files.newInputStream(path)) { - return addRules(inputStream, "file " + path); + logger.log(FINE, "Adding JMX config from file {0}", path); + return addRules(inputStream); } catch (IOException e) { throw new IllegalArgumentException("Unable to load JMX rules from: " + path, e); } diff --git a/instrumentation/jmx-metrics/library/src/main/java/io/opentelemetry/instrumentation/jmx/yaml/RuleParser.java b/instrumentation/jmx-metrics/library/src/main/java/io/opentelemetry/instrumentation/jmx/yaml/RuleParser.java index 4e4840db5cbc..e22710555c8f 100644 --- a/instrumentation/jmx-metrics/library/src/main/java/io/opentelemetry/instrumentation/jmx/yaml/RuleParser.java +++ b/instrumentation/jmx-metrics/library/src/main/java/io/opentelemetry/instrumentation/jmx/yaml/RuleParser.java @@ -6,7 +6,7 @@ package io.opentelemetry.instrumentation.jmx.yaml; import static java.util.Collections.emptyList; -import static java.util.logging.Level.INFO; +import static java.util.logging.Level.FINE; import io.opentelemetry.instrumentation.jmx.engine.MetricConfiguration; import java.io.InputStream; @@ -162,24 +162,19 @@ private static void failOnExtraKeys(Map yaml) { * * @param conf the metric configuration * @param is the InputStream with the YAML rules - * @param description description of the YAML ruleset like file name or classpath resource * @throws IllegalArgumentException when unable to parse YAML */ - public void addMetricDefsTo(MetricConfiguration conf, InputStream is, String description) { + public void addMetricDefsTo(MetricConfiguration conf, InputStream is) { try { JmxConfig config = loadConfig(is); - logger.log( - INFO, - "{0}: found {1} metric rules", - new Object[] {description, config.getRules().size()}); + logger.log(FINE, "found {1} metric rules", config.getRules().size()); config.addMetricDefsTo(conf); } catch (Exception exception) { // It is essential that the parser exception is made visible to the user. // It contains contextual information about any syntax issues found by the parser. String msg = String.format( - "Failed to parse YAML rules from %s: %s %s", - description, rootCause(exception), exception.getMessage()); + "Failed to parse YAML rules : %s %s", rootCause(exception), exception.getMessage()); throw new IllegalArgumentException(msg, exception); } } diff --git a/instrumentation/jmx-metrics/library/src/test/java/io/opentelemetry/instrumentation/jmx/JmxTelemetryTest.java b/instrumentation/jmx-metrics/library/src/test/java/io/opentelemetry/instrumentation/jmx/JmxTelemetryTest.java index bf1671eb4e33..9205c3a38ce5 100644 --- a/instrumentation/jmx-metrics/library/src/test/java/io/opentelemetry/instrumentation/jmx/JmxTelemetryTest.java +++ b/instrumentation/jmx-metrics/library/src/test/java/io/opentelemetry/instrumentation/jmx/JmxTelemetryTest.java @@ -26,12 +26,12 @@ void createDefault() { } @Test - void missingClasspathTarget() { + void throwsExceptionOnNullInput() { JmxTelemetryBuilder builder = JmxTelemetry.builder(OpenTelemetry.noop()); - assertThatThrownBy(() -> builder.addRules(null, "something is missing")) - .isInstanceOf(IllegalArgumentException.class) - .hasMessageContaining("not found") - .hasMessageContaining("something is missing"); + assertThatThrownBy(() -> builder.addRules((InputStream) null)) + .isInstanceOf(IllegalArgumentException.class); + assertThatThrownBy(() -> builder.addRules((Path) null)) + .isInstanceOf(IllegalArgumentException.class); } @Test @@ -52,7 +52,7 @@ void knownValidYaml() { private static void addClasspathRules(JmxTelemetryBuilder builder, String path) { InputStream input = JmxTelemetryTest.class.getClassLoader().getResourceAsStream(path); - builder.addRules(input, path); + builder.addRules(input); } @Test From 4404db3063a88b3ebf2e88c45d6e36255439f66d Mon Sep 17 00:00:00 2001 From: Sylvain Juge <763082+SylvainJuge@users.noreply.github.com> Date: Fri, 28 Nov 2025 16:33:27 +0100 Subject: [PATCH 10/11] update comment about JMX configuration errors --- .../javaagent/jmx/JmxMetricInsightInstaller.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/instrumentation/jmx-metrics/javaagent/src/main/java/io/opentelemetry/instrumentation/javaagent/jmx/JmxMetricInsightInstaller.java b/instrumentation/jmx-metrics/javaagent/src/main/java/io/opentelemetry/instrumentation/javaagent/jmx/JmxMetricInsightInstaller.java index d6d1a19e8736..4cff1ad4e26d 100644 --- a/instrumentation/jmx-metrics/javaagent/src/main/java/io/opentelemetry/instrumentation/javaagent/jmx/JmxMetricInsightInstaller.java +++ b/instrumentation/jmx-metrics/javaagent/src/main/java/io/opentelemetry/instrumentation/javaagent/jmx/JmxMetricInsightInstaller.java @@ -48,7 +48,7 @@ private static void addFileRules(Path path, JmxTelemetryBuilder builder) { try { builder.addRules(path); } catch (RuntimeException e) { - // for now only log JMX errors as they do not prevent agent startup + // for now only log JMX metric configuration errors as they do not prevent agent startup logger.log(Level.SEVERE, "Error while loading JMX configuration from " + path, e); } } @@ -60,7 +60,7 @@ private static void addClasspathRules(String target, JmxTelemetryBuilder builder try { builder.addRules(input); } catch (RuntimeException e) { - // for now only log JMX errors as they do not prevent agent startup + // for now only log JMX metric configuration errors as they do not prevent agent startup logger.log( Level.SEVERE, "Error while loading JMX configuration from classpath " + resource, e); } From 80d76dcf64f1ac8979b8a7022d26ed0704be8b35 Mon Sep 17 00:00:00 2001 From: Sylvain Juge <763082+SylvainJuge@users.noreply.github.com> Date: Fri, 28 Nov 2025 17:23:33 +0100 Subject: [PATCH 11/11] fix and simplify tests --- .../instrumentation/jmx/JmxTelemetryBuilder.java | 3 +++ .../instrumentation/jmx/JmxTelemetryTest.java | 7 ++----- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/instrumentation/jmx-metrics/library/src/main/java/io/opentelemetry/instrumentation/jmx/JmxTelemetryBuilder.java b/instrumentation/jmx-metrics/library/src/main/java/io/opentelemetry/instrumentation/jmx/JmxTelemetryBuilder.java index f4e66f023c6a..4f9ddc720bd9 100644 --- a/instrumentation/jmx-metrics/library/src/main/java/io/opentelemetry/instrumentation/jmx/JmxTelemetryBuilder.java +++ b/instrumentation/jmx-metrics/library/src/main/java/io/opentelemetry/instrumentation/jmx/JmxTelemetryBuilder.java @@ -94,6 +94,9 @@ public JmxTelemetryBuilder addRules(InputStream input) { */ @CanIgnoreReturnValue public JmxTelemetryBuilder addRules(Path path) { + if (path == null) { + throw new IllegalArgumentException("missing JMX rules"); + } try (InputStream inputStream = Files.newInputStream(path)) { logger.log(FINE, "Adding JMX config from file {0}", path); return addRules(inputStream); diff --git a/instrumentation/jmx-metrics/library/src/test/java/io/opentelemetry/instrumentation/jmx/JmxTelemetryTest.java b/instrumentation/jmx-metrics/library/src/test/java/io/opentelemetry/instrumentation/jmx/JmxTelemetryTest.java index 9205c3a38ce5..749d0737a5c5 100644 --- a/instrumentation/jmx-metrics/library/src/test/java/io/opentelemetry/instrumentation/jmx/JmxTelemetryTest.java +++ b/instrumentation/jmx-metrics/library/src/test/java/io/opentelemetry/instrumentation/jmx/JmxTelemetryTest.java @@ -38,9 +38,7 @@ void throwsExceptionOnNullInput() { void invalidClasspathTarget() { JmxTelemetryBuilder builder = JmxTelemetry.builder(OpenTelemetry.noop()); assertThatThrownBy(() -> addClasspathRules(builder, "jmx/rules/invalid.yaml")) - .isInstanceOf(IllegalArgumentException.class) - .describedAs("must have an exception message including the invalid resource path") - .hasMessageContaining("jmx/rules/invalid.yaml"); + .isInstanceOf(IllegalArgumentException.class); } @Test @@ -61,8 +59,7 @@ void invalidExternalYaml(@TempDir Path dir) throws Exception { Files.write(invalid, ":this !is /not YAML".getBytes(StandardCharsets.UTF_8)); JmxTelemetryBuilder builder = JmxTelemetry.builder(OpenTelemetry.noop()); assertThatThrownBy(() -> builder.addRules(invalid)) - .isInstanceOf(IllegalArgumentException.class) - .hasMessageContaining(invalid.toString()); + .isInstanceOf(IllegalArgumentException.class); } @Test