From 0373dd076dda434d2bb386b309efe134b596b805 Mon Sep 17 00:00:00 2001 From: Aditya Kousik Date: Thu, 23 Apr 2026 06:55:19 -0700 Subject: [PATCH] MINOR: Add toString() to KafkaMetric for readable logging This adds a toString() that includes the metric name, group, description, and tags. The metricValueProvider is represented by its simple class name rather than its full toString() to avoid dumping internal stat state like SampledStat's samples list into logs --- .../kafka/common/metrics/KafkaMetric.java | 16 ++++++ .../kafka/common/metrics/KafkaMetricTest.java | 55 +++++++++++++++++-- 2 files changed, 65 insertions(+), 6 deletions(-) diff --git a/clients/src/main/java/org/apache/kafka/common/metrics/KafkaMetric.java b/clients/src/main/java/org/apache/kafka/common/metrics/KafkaMetric.java index a9203ead0a044..a3210261b0ff8 100644 --- a/clients/src/main/java/org/apache/kafka/common/metrics/KafkaMetric.java +++ b/clients/src/main/java/org/apache/kafka/common/metrics/KafkaMetric.java @@ -124,4 +124,20 @@ public void config(MetricConfig config) { this.config = config; } } + + /** + * Returns a human-readable representation of this metric, primarily useful for logging + * in contexts like {@link MetricsReporter#metricChange(KafkaMetric)}. + * + *

The metricValueProvider is represented by its simple class name rather than its full + * toString() to avoid dumping internal stat state (e.g. SampledStat's samples list) into + * logs, which could be verbose and is rarely useful for identifying which metric changed. + */ + @Override + public String toString() { + return "KafkaMetric [" + + "metricName=" + metricName + + ", metricValueProvider=" + metricValueProvider.getClass().getSimpleName() + + ']'; + } } diff --git a/clients/src/test/java/org/apache/kafka/common/metrics/KafkaMetricTest.java b/clients/src/test/java/org/apache/kafka/common/metrics/KafkaMetricTest.java index e3a9fb345d795..992d1bf50bcd7 100644 --- a/clients/src/test/java/org/apache/kafka/common/metrics/KafkaMetricTest.java +++ b/clients/src/test/java/org/apache/kafka/common/metrics/KafkaMetricTest.java @@ -17,6 +17,7 @@ package org.apache.kafka.common.metrics; import org.apache.kafka.common.MetricName; +import org.apache.kafka.common.metrics.stats.Avg; import org.apache.kafka.common.utils.MockTime; import org.junit.jupiter.api.Test; @@ -30,12 +31,18 @@ public class KafkaMetricTest { - private static final MetricName METRIC_NAME = new MetricName("name", "group", "description", Collections.emptyMap()); + private static final MetricName METRIC_NAME_1 = new MetricName("name", "group", "description", Collections.emptyMap()); + private static final MetricName METRIC_NAME_2 = new MetricName( + "request-latency-avg", + "consumer-fetch-manager-metrics", + "The average request latency in ms", + Collections.singletonMap("client-id", "consumer-1") + ); @Test public void testIsMeasurable() { Measurable metricValueProvider = (config, now) -> 0; - KafkaMetric metric = new KafkaMetric(new Object(), METRIC_NAME, metricValueProvider, new MetricConfig(), new MockTime()); + KafkaMetric metric = new KafkaMetric(new Object(), METRIC_NAME_1, metricValueProvider, new MetricConfig(), new MockTime()); assertTrue(metric.isMeasurable()); assertEquals(metricValueProvider, metric.measurable()); } @@ -43,7 +50,7 @@ public void testIsMeasurable() { @Test public void testIsMeasurableWithGaugeProvider() { Gauge metricValueProvider = (config, now) -> 0.0; - KafkaMetric metric = new KafkaMetric(new Object(), METRIC_NAME, metricValueProvider, new MetricConfig(), new MockTime()); + KafkaMetric metric = new KafkaMetric(new Object(), METRIC_NAME_1, metricValueProvider, new MetricConfig(), new MockTime()); assertFalse(metric.isMeasurable()); assertThrows(IllegalStateException.class, metric::measurable); } @@ -54,14 +61,14 @@ public void testMeasurableValueReturnsZeroWhenNotMeasurable() { MetricConfig config = new MetricConfig(); Gauge gauge = (c, now) -> 7; - KafkaMetric metric = new KafkaMetric(new Object(), METRIC_NAME, gauge, config, time); + KafkaMetric metric = new KafkaMetric(new Object(), METRIC_NAME_1, gauge, config, time); assertEquals(0.0d, metric.measurableValue(time.milliseconds()), 0.0d); } @Test public void testKafkaMetricAcceptsNonMeasurableNonGaugeProvider() { MetricValueProvider provider = (config, now) -> "metric value provider"; - KafkaMetric metric = new KafkaMetric(new Object(), METRIC_NAME, provider, new MetricConfig(), new MockTime()); + KafkaMetric metric = new KafkaMetric(new Object(), METRIC_NAME_1, provider, new MetricConfig(), new MockTime()); Object value = metric.metricValue(); assertEquals("metric value provider", value); @@ -70,7 +77,43 @@ public void testKafkaMetricAcceptsNonMeasurableNonGaugeProvider() { @Test public void testConstructorWithNullProvider() { assertThrows(NullPointerException.class, () -> - new KafkaMetric(new Object(), METRIC_NAME, null, new MetricConfig(), new MockTime()) + new KafkaMetric(new Object(), METRIC_NAME_1, null, new MetricConfig(), new MockTime()) ); } + + /** + * Verifies that toString produces a human-readable representation suitable for logging, + * e.g. in {@link org.apache.kafka.common.metrics.MetricsReporter#metricChange(KafkaMetric)}. + * Note that metricValueProvider may render as a lambda reference rather than a meaningful + * value, but this is still a significant improvement over the default Object.toString() + * output (e.g. "KafkaMetric@62a7d6c6"). + */ + @Test + public void testToStringWithLambdaProvider() { + Measurable metricValueProvider = (config, now) -> 0; + KafkaMetric metric = new KafkaMetric( + new Object(), METRIC_NAME_2, metricValueProvider, new MetricConfig(), new MockTime()); + + String result = metric.toString(); + assertTrue(result.startsWith( + "KafkaMetric [metricName=MetricName [name=request-latency-avg, " + + "group=consumer-fetch-manager-metrics, " + + "description=The average request latency in ms, " + + "tags={client-id=consumer-1}], " + + "metricValueProvider=")); + assertTrue(result.endsWith("]")); + } + + @Test + public void testToStringWithRealStat() { + Avg avg = new Avg(); + KafkaMetric metric = new KafkaMetric(new Object(), METRIC_NAME_2, avg, new MetricConfig(), new MockTime()); + + assertEquals("KafkaMetric [metricName=MetricName [name=request-latency-avg, " + + "group=consumer-fetch-manager-metrics, " + + "description=The average request latency in ms, " + + "tags={client-id=consumer-1}], " + + "metricValueProvider=Avg]", + metric.toString()); + } }