-
Notifications
You must be signed in to change notification settings - Fork 1.9k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Micrometer metrics tags extension #1322
Changes from 6 commits
7dafd58
4f4d029
61a585b
603b155
be02d21
cdc3cbb
05589ba
5df8f6a
74c93a7
ff7d86a
cb15f9c
3f5d6a1
f96229e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,5 @@ | ||
/** | ||
* Copyright 2012-2020 The Feign Authors | ||
* Copyright 2012-2021 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 | ||
|
@@ -16,14 +16,17 @@ | |
|
||
import java.io.IOException; | ||
import java.lang.reflect.Type; | ||
import java.util.ArrayList; | ||
import java.util.Collections; | ||
import java.util.List; | ||
import java.util.Optional; | ||
import feign.FeignException; | ||
import feign.Request; | ||
import feign.RequestTemplate; | ||
import feign.Response; | ||
import feign.codec.DecodeException; | ||
import feign.codec.Decoder; | ||
import io.micrometer.core.instrument.MeterRegistry; | ||
import io.micrometer.core.instrument.Tag; | ||
import io.micrometer.core.instrument.*; | ||
|
||
/** | ||
* Warp feign {@link Decoder} with metrics. | ||
|
@@ -46,41 +49,81 @@ public MeteredDecoder(Decoder decoder, MeterRegistry meterRegistry, MetricName m | |
|
||
@Override | ||
public Object decode(Response response, Type type) | ||
throws IOException, DecodeException, FeignException { | ||
final RequestTemplate template = response.request().requestTemplate(); | ||
throws IOException, FeignException { | ||
final Optional<MeteredBody> body = Optional.ofNullable(response.body()) | ||
.map(MeteredBody::new); | ||
|
||
Response meteredResponse = body.map(b -> response.toBuilder().body(b).build()) | ||
.orElse(response); | ||
|
||
Object decoded; | ||
|
||
final Timer.Sample sample = Timer.start(meterRegistry); | ||
try { | ||
decoded = meterRegistry | ||
.timer(metricName.name(), | ||
metricName.tag(template.methodMetadata(), template.feignTarget())) | ||
.recordCallable(() -> decoder.decode(meteredResponse, type)); | ||
decoded = decoder.decode(meteredResponse, type); | ||
final Timer timer = createSuccessTimer(response, type); | ||
sample.stop(timer); | ||
} catch (IOException | RuntimeException e) { | ||
meterRegistry.counter( | ||
metricName.name("error_count"), | ||
metricName.tag(template.methodMetadata(), template.feignTarget()) | ||
.and(Tag.of("exception_name", e.getClass().getSimpleName()))) | ||
.count(); | ||
sample.stop(createExceptionTimer(response, type, e)); | ||
createExceptionCounter(response, type, e).count(); | ||
throw e; | ||
} catch (Exception e) { | ||
meterRegistry.counter( | ||
metricName.name("error_count"), | ||
metricName.tag(template.methodMetadata(), template.feignTarget()) | ||
.and(Tag.of("exception_name", e.getClass().getSimpleName()))) | ||
.count(); | ||
sample.stop(createExceptionTimer(response, type, e)); | ||
createExceptionCounter(response, type, e).count(); | ||
throw new IOException(e); | ||
} | ||
|
||
body.ifPresent(b -> meterRegistry.summary( | ||
metricName.name("response_size"), | ||
metricName.tag(template.methodMetadata(), template.feignTarget())).record(b.count())); | ||
body.ifPresent(b -> createSummary(response, type).record(b.count())); | ||
|
||
return decoded; | ||
} | ||
|
||
private Timer createSuccessTimer(Response response, Type type) { | ||
final List<Tag> successTags = extraSuccessTags(response, type); | ||
final Tag[] tags = successTags.toArray(new Tag[] {}); | ||
final RequestTemplate template = response.request().requestTemplate(); | ||
final Tags allTags = metricName.tag(template.methodMetadata(), template.feignTarget(), tags); | ||
return meterRegistry.timer(metricName.name(), allTags); | ||
} | ||
|
||
private Timer createExceptionTimer(Response response, Type type, Exception exception) { | ||
final List<Tag> exceptionTags = extraExceptionTags(response, type, exception); | ||
final Tag[] tags = exceptionTags.toArray(new Tag[] {}); | ||
final RequestTemplate template = response.request().requestTemplate(); | ||
final Tags allTags = metricName.tag(template.methodMetadata(), template.feignTarget(), tags); | ||
return meterRegistry.timer(metricName.name("exception"), allTags); | ||
} | ||
|
||
private Counter createExceptionCounter(Response response, Type type, Exception exception) { | ||
final List<Tag> exceptionTags = extraExceptionTags(response, type, exception); | ||
final Tag[] tags = exceptionTags.toArray(new Tag[] {}); | ||
final RequestTemplate template = response.request().requestTemplate(); | ||
final Tags allTags = metricName.tag(template.methodMetadata(), template.feignTarget(), tags); | ||
return meterRegistry.counter(metricName.name("error_count"), allTags); | ||
} | ||
|
||
private DistributionSummary createSummary(Response response, Type type) { | ||
final List<Tag> successTags = extraSummaryTags(response, type); | ||
final Tag[] tags = successTags.toArray(new Tag[] {}); | ||
final RequestTemplate template = response.request().requestTemplate(); | ||
final Tags allTags = metricName.tag(template.methodMetadata(), template.feignTarget(), tags); | ||
return meterRegistry.summary(metricName.name("response_size"), allTags); | ||
} | ||
|
||
protected List<Tag> extraSuccessTags(Response response, Type type) { | ||
return Collections.emptyList(); | ||
} | ||
|
||
protected List<Tag> extraExceptionTags(Response response, Type type, Exception exception) { | ||
final RequestTemplate template = response.request().requestTemplate(); | ||
final List<Tag> result = new ArrayList<>(); | ||
result.add(Tag.of("uri", template.path())); | ||
result.add(Tag.of("exception_name", exception.getClass().getSimpleName())); | ||
return result; | ||
} | ||
|
||
protected List<Tag> extraSummaryTags(Response response, Type type) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I assume you are using this for some internal metrics (I would be curious to know what you are going =] ) I would prefer to see code to live in the We would have a few methods, one called What do you think? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I extracted tags resolution to a separate extendable class. However, seems like depending on where we create tags (i.e. client, encoder/decoder, method handler) we have really different parameters to build tags upon. I added defaultTags method, which might be helpful for customization in general. |
||
return Collections.emptyList(); | ||
} | ||
|
||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
it would be nice not to change import ordering... but, don't worry too much about it =)