From 62277a9df6bc396de2fe4ff35f766c468ec24e9a Mon Sep 17 00:00:00 2001 From: chao-chang-paypay <52404151+chao-chang-paypay@users.noreply.github.com> Date: Sun, 18 Dec 2022 17:46:22 +0900 Subject: [PATCH] set root cause as the exception_name in micrometer tags (#1883) * set root cause as the exception_name in micrometer tags * set root cause as the exception_name in other metrics system * set root cause as the exception_name in other metrics system * Update ExceptionUtilsTest.java * reformat * reformat * add root_cause_name instead of changing exception_name Co-authored-by: Marvin Froeder --- .../main/java/feign/utils/ExceptionUtils.java | 44 +++++++++++++++++++ .../java/feign/utils/ExceptionUtilsTest.java | 41 +++++++++++++++++ .../feign/metrics4/BaseMeteredClient.java | 5 +++ .../feign/metrics5/BaseMeteredClient.java | 5 +++ .../java/feign/metrics5/MeteredDecoder.java | 3 ++ .../MeteredInvocationHandleFactory.java | 6 ++- .../micrometer/FeignMetricTagResolver.java | 4 ++ .../java/feign/micrometer/MeteredDecoder.java | 4 +- .../micrometer/AbstractMetricsTestBase.java | 3 +- 9 files changed, 111 insertions(+), 4 deletions(-) create mode 100644 core/src/main/java/feign/utils/ExceptionUtils.java create mode 100644 core/src/test/java/feign/utils/ExceptionUtilsTest.java diff --git a/core/src/main/java/feign/utils/ExceptionUtils.java b/core/src/main/java/feign/utils/ExceptionUtils.java new file mode 100644 index 000000000..54f03c4bd --- /dev/null +++ b/core/src/main/java/feign/utils/ExceptionUtils.java @@ -0,0 +1,44 @@ +/* + * Copyright 2012-2022 The Feign Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ +package feign.utils; + +import java.util.HashSet; +import java.util.Set; + +public class ExceptionUtils { + /** + * Introspects the {@link Throwable} to obtain the root cause. + * + *

+ * This method walks through the exception chain to the last element, "root" of the tree, using + * {@link Throwable#getCause()}, and returns that exception. + * + * @param throwable the throwable to get the root cause for, may be null + * @return the root cause of the {@link Throwable}, {@code null} if null throwable input + */ + public static Throwable getRootCause(Throwable throwable) { + if (throwable == null) { + return null; + } + Throwable rootCause = throwable; + // this is to avoid infinite loops for recursive cases + final Set seenThrowables = new HashSet<>(); + seenThrowables.add(rootCause); + while ((rootCause.getCause() != null && !seenThrowables.contains(rootCause.getCause()))) { + seenThrowables.add(rootCause.getCause()); + rootCause = rootCause.getCause(); + } + return rootCause; + } +} diff --git a/core/src/test/java/feign/utils/ExceptionUtilsTest.java b/core/src/test/java/feign/utils/ExceptionUtilsTest.java new file mode 100644 index 000000000..2958521a5 --- /dev/null +++ b/core/src/test/java/feign/utils/ExceptionUtilsTest.java @@ -0,0 +1,41 @@ +/* + * Copyright 2012-2022 The Feign Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ +package feign.utils; + +import org.junit.Test; +import static org.assertj.core.api.Assertions.assertThat; + +public class ExceptionUtilsTest { + @Test + public void rootCauseOfNullIsNull() { + Throwable e = null; + Throwable rootCause = ExceptionUtils.getRootCause(e); + assertThat(rootCause).isNull(); + } + + @Test + public void rootCauseIsSelf() { + Throwable e = new Exception(); + Throwable rootCause = ExceptionUtils.getRootCause(e); + assertThat(rootCause).isSameAs(e); + } + + @Test + public void rootCauseIsDifferent() { + Throwable rootCause = new Exception(); + Throwable e = new Exception(rootCause); + Throwable actualRootCause = ExceptionUtils.getRootCause(e); + assertThat(actualRootCause).isSameAs(rootCause); + } +} diff --git a/dropwizard-metrics4/src/main/java/feign/metrics4/BaseMeteredClient.java b/dropwizard-metrics4/src/main/java/feign/metrics4/BaseMeteredClient.java index bc2f1f3f2..d148eb55d 100644 --- a/dropwizard-metrics4/src/main/java/feign/metrics4/BaseMeteredClient.java +++ b/dropwizard-metrics4/src/main/java/feign/metrics4/BaseMeteredClient.java @@ -18,6 +18,7 @@ import feign.FeignException; import feign.RequestTemplate; import feign.Response; +import feign.utils.ExceptionUtils; class BaseMeteredClient { @@ -65,6 +66,8 @@ protected void recordFailure(RequestTemplate template, FeignException e) { httpResponseCode(template), "exception_name", e.getClass().getSimpleName(), + "root_cause_name", + ExceptionUtils.getRootCause(e).getClass().getSimpleName(), "status_group", e.status() / 100 + "xx", "http_status", @@ -82,6 +85,8 @@ protected void recordFailure(RequestTemplate template, Exception e) { httpResponseCode(template), "exception_name", e.getClass().getSimpleName(), + "root_cause_name", + ExceptionUtils.getRootCause(e).getClass().getSimpleName(), "uri", template.methodMetadata().template().path()), metricSuppliers.meters()) diff --git a/dropwizard-metrics5/src/main/java/feign/metrics5/BaseMeteredClient.java b/dropwizard-metrics5/src/main/java/feign/metrics5/BaseMeteredClient.java index 33d43f50b..966ce5df7 100644 --- a/dropwizard-metrics5/src/main/java/feign/metrics5/BaseMeteredClient.java +++ b/dropwizard-metrics5/src/main/java/feign/metrics5/BaseMeteredClient.java @@ -17,6 +17,7 @@ import feign.FeignException; import feign.RequestTemplate; import feign.Response; +import feign.utils.ExceptionUtils; import io.dropwizard.metrics5.MetricName; import io.dropwizard.metrics5.MetricRegistry; import io.dropwizard.metrics5.Timer; @@ -60,6 +61,8 @@ protected void recordFailure(RequestTemplate template, FeignException e) { .counter( httpResponseCode(template) .tagged("exception_name", e.getClass().getSimpleName()) + .tagged("root_cause_name", + ExceptionUtils.getRootCause(e).getClass().getSimpleName()) .tagged("http_status", String.valueOf(e.status())) .tagged("status_group", e.status() / 100 + "xx") .tagged("uri", template.methodMetadata().template().path())) @@ -71,6 +74,8 @@ protected void recordFailure(RequestTemplate template, Exception e) { .counter( httpResponseCode(template) .tagged("exception_name", e.getClass().getSimpleName()) + .tagged("root_cause_name", + ExceptionUtils.getRootCause(e).getClass().getSimpleName()) .tagged("uri", template.methodMetadata().template().path())) .inc(); } diff --git a/dropwizard-metrics5/src/main/java/feign/metrics5/MeteredDecoder.java b/dropwizard-metrics5/src/main/java/feign/metrics5/MeteredDecoder.java index 10573cf5c..eb07a3d06 100644 --- a/dropwizard-metrics5/src/main/java/feign/metrics5/MeteredDecoder.java +++ b/dropwizard-metrics5/src/main/java/feign/metrics5/MeteredDecoder.java @@ -21,6 +21,7 @@ import feign.Response; import feign.codec.DecodeException; import feign.codec.Decoder; +import feign.utils.ExceptionUtils; import io.dropwizard.metrics5.MetricRegistry; import io.dropwizard.metrics5.Timer.Context; @@ -64,6 +65,7 @@ public Object decode(Response response, Type type) metricRegistry.meter( metricName.metricName(template.methodMetadata(), template.feignTarget(), "error_count") .tagged("exception_name", e.getClass().getSimpleName()) + .tagged("root_cause_name", ExceptionUtils.getRootCause(e).getClass().getSimpleName()) .tagged("uri", template.methodMetadata().template().path()), metricSuppliers.meters()).mark(); throw e; @@ -71,6 +73,7 @@ public Object decode(Response response, Type type) metricRegistry.meter( metricName.metricName(template.methodMetadata(), template.feignTarget(), "error_count") .tagged("exception_name", e.getClass().getSimpleName()) + .tagged("root_cause_name", ExceptionUtils.getRootCause(e).getClass().getSimpleName()) .tagged("uri", template.methodMetadata().template().path()), metricSuppliers.meters()).mark(); throw new IOException(e); diff --git a/dropwizard-metrics5/src/main/java/feign/metrics5/MeteredInvocationHandleFactory.java b/dropwizard-metrics5/src/main/java/feign/metrics5/MeteredInvocationHandleFactory.java index 3fd63ea60..1876d2023 100644 --- a/dropwizard-metrics5/src/main/java/feign/metrics5/MeteredInvocationHandleFactory.java +++ b/dropwizard-metrics5/src/main/java/feign/metrics5/MeteredInvocationHandleFactory.java @@ -13,7 +13,7 @@ */ package feign.metrics5; - +import feign.utils.ExceptionUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.lang.reflect.InvocationHandler; @@ -86,7 +86,9 @@ public InvocationHandler create(Target target, Map dispat metricRegistry .meter(metricName.metricName(clientClass, method, target.url()) .resolve("exception") - .tagged("exception_name", e.getClass().getSimpleName()), + .tagged("exception_name", e.getClass().getSimpleName()) + .tagged("root_cause_name", + ExceptionUtils.getRootCause(e).getClass().getSimpleName()), metricSuppliers.meters()) .mark(); diff --git a/micrometer/src/main/java/feign/micrometer/FeignMetricTagResolver.java b/micrometer/src/main/java/feign/micrometer/FeignMetricTagResolver.java index 0dbc41e6c..bfef319dc 100644 --- a/micrometer/src/main/java/feign/micrometer/FeignMetricTagResolver.java +++ b/micrometer/src/main/java/feign/micrometer/FeignMetricTagResolver.java @@ -15,6 +15,7 @@ import feign.MethodMetadata; import feign.Target; +import feign.utils.ExceptionUtils; import io.micrometer.core.instrument.Tag; import io.micrometer.core.instrument.Tags; import java.lang.reflect.Method; @@ -50,6 +51,8 @@ public Tags tag(Class targetType, Method method, String url, Throwable e, Tag tags.add(Tag.of("host", extractHost(url))); if (e != null) { tags.add(Tag.of("exception_name", e.getClass().getSimpleName())); + tags.add( + Tag.of("root_cause_name", ExceptionUtils.getRootCause(e).getClass().getSimpleName())); } tags.addAll(Arrays.asList(extraTags)); return Tags.of(tags); @@ -72,4 +75,5 @@ private String extractHost(final String targetUrl) { ? targetUrl : targetUrl.substring(0, 20); } + } diff --git a/micrometer/src/main/java/feign/micrometer/MeteredDecoder.java b/micrometer/src/main/java/feign/micrometer/MeteredDecoder.java index 10fa9f6ae..169def5ec 100644 --- a/micrometer/src/main/java/feign/micrometer/MeteredDecoder.java +++ b/micrometer/src/main/java/feign/micrometer/MeteredDecoder.java @@ -18,6 +18,7 @@ import feign.RequestTemplate; import feign.Response; import feign.codec.Decoder; +import feign.utils.ExceptionUtils; import io.micrometer.core.instrument.*; import java.io.IOException; import java.lang.reflect.Type; @@ -95,7 +96,8 @@ protected Counter createExceptionCounter(Response response, Type type, Exception final RequestTemplate template = response.request().requestTemplate(); final Tags allTags = metricTagResolver.tag(template.methodMetadata(), template.feignTarget(), Tag.of("uri", template.methodMetadata().template().path()), - Tag.of("exception_name", e.getClass().getSimpleName())) + Tag.of("exception_name", e.getClass().getSimpleName()), + Tag.of("root_cause_name", ExceptionUtils.getRootCause(e).getClass().getSimpleName())) .and(extraTags); return meterRegistry.counter(metricName.name("error_count"), allTags); } diff --git a/micrometer/src/test/java/feign/micrometer/AbstractMetricsTestBase.java b/micrometer/src/test/java/feign/micrometer/AbstractMetricsTestBase.java index c515ddcab..1f5518de7 100644 --- a/micrometer/src/test/java/feign/micrometer/AbstractMetricsTestBase.java +++ b/micrometer/src/test/java/feign/micrometer/AbstractMetricsTestBase.java @@ -210,7 +210,8 @@ public void shouldMetricCollectionWithCustomException() { assertThat(thrown.getMessage(), equalTo("Test error")); assertThat( - getMetric("exception", "exception_name", "RuntimeException", "method", "get"), + getMetric("exception", "exception_name", "RuntimeException", "method", "get", + "root_cause_name", "RuntimeException"), notNullValue()); }