Browse Source
* Support of capabilities for AsyncFeign * Removed SyncBased interface, added todo for stageExecution, and adopted micrometer client to be async as well. * Added internal builder flag similar to 'forceDecoding' but for client enrichment * Added async client enrichment to Dropwizard5 capability * Added async client enrichment to Dropwizard5 capability + code formatting * Progress with tests; added decoder condition similar to the client one * Fixed javadoc * A different take on skipping enrichment and delagation * Switcharoo * Relaxed casting requirements and check it during execution phase * Create class to hold common Builder fields * Make sure capabilities are applied to all relevant fields Co-authored-by: Marvin Froeder <velo@users.noreply.github.com> Co-authored-by: Marvin Froeder <velo.br@gmail.com>pull/1636/head
Eduard Dudar
2 years ago
committed by
GitHub
29 changed files with 1457 additions and 840 deletions
@ -0,0 +1,19 @@
@@ -0,0 +1,19 @@
|
||||
/* |
||||
* 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; |
||||
|
||||
public interface AsyncContextSupplier<C> { |
||||
|
||||
C newContext(); |
||||
} |
@ -0,0 +1,263 @@
@@ -0,0 +1,263 @@
|
||||
/* |
||||
* 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; |
||||
|
||||
import static feign.ExceptionPropagationPolicy.NONE; |
||||
import feign.Feign.ResponseMappingDecoder; |
||||
import feign.Logger.NoOpLogger; |
||||
import feign.Request.Options; |
||||
import feign.codec.Decoder; |
||||
import feign.codec.Encoder; |
||||
import feign.codec.ErrorDecoder; |
||||
import feign.querymap.FieldQueryMapEncoder; |
||||
import java.lang.reflect.Field; |
||||
import java.lang.reflect.ParameterizedType; |
||||
import java.lang.reflect.Type; |
||||
import java.util.ArrayList; |
||||
import java.util.List; |
||||
import java.util.Objects; |
||||
import java.util.stream.Collectors; |
||||
|
||||
public abstract class BaseBuilder<B extends BaseBuilder<B>> { |
||||
|
||||
private final B thisB; |
||||
|
||||
protected final List<RequestInterceptor> requestInterceptors = |
||||
new ArrayList<>(); |
||||
protected Logger.Level logLevel = Logger.Level.NONE; |
||||
protected Contract contract = new Contract.Default(); |
||||
protected Retryer retryer = new Retryer.Default(); |
||||
protected Logger logger = new NoOpLogger(); |
||||
protected Encoder encoder = new Encoder.Default(); |
||||
protected Decoder decoder = new Decoder.Default(); |
||||
protected boolean closeAfterDecode = true; |
||||
protected QueryMapEncoder queryMapEncoder = new FieldQueryMapEncoder(); |
||||
protected ErrorDecoder errorDecoder = new ErrorDecoder.Default(); |
||||
protected Options options = new Options(); |
||||
protected InvocationHandlerFactory invocationHandlerFactory = |
||||
new InvocationHandlerFactory.Default(); |
||||
protected boolean dismiss404; |
||||
protected ExceptionPropagationPolicy propagationPolicy = NONE; |
||||
protected List<Capability> capabilities = new ArrayList<>(); |
||||
|
||||
|
||||
public BaseBuilder() { |
||||
super(); |
||||
thisB = (B) this; |
||||
} |
||||
|
||||
public B logLevel(Logger.Level logLevel) { |
||||
this.logLevel = logLevel; |
||||
return thisB; |
||||
} |
||||
|
||||
public B contract(Contract contract) { |
||||
this.contract = contract; |
||||
return thisB; |
||||
} |
||||
|
||||
public B retryer(Retryer retryer) { |
||||
this.retryer = retryer; |
||||
return thisB; |
||||
} |
||||
|
||||
public B logger(Logger logger) { |
||||
this.logger = logger; |
||||
return thisB; |
||||
} |
||||
|
||||
public B encoder(Encoder encoder) { |
||||
this.encoder = encoder; |
||||
return thisB; |
||||
} |
||||
|
||||
public B decoder(Decoder decoder) { |
||||
this.decoder = decoder; |
||||
return thisB; |
||||
} |
||||
|
||||
/** |
||||
* This flag indicates that the response should not be automatically closed upon completion of |
||||
* decoding the message. This should be set if you plan on processing the response into a |
||||
* lazy-evaluated construct, such as a {@link java.util.Iterator}. |
||||
* |
||||
* </p> |
||||
* Feign standard decoders do not have built in support for this flag. If you are using this flag, |
||||
* you MUST also use a custom Decoder, and be sure to close all resources appropriately somewhere |
||||
* in the Decoder (you can use {@link Util#ensureClosed} for convenience). |
||||
* |
||||
* @since 9.6 |
||||
* |
||||
*/ |
||||
public B doNotCloseAfterDecode() { |
||||
this.closeAfterDecode = false; |
||||
return thisB; |
||||
} |
||||
|
||||
public B queryMapEncoder(QueryMapEncoder queryMapEncoder) { |
||||
this.queryMapEncoder = queryMapEncoder; |
||||
return thisB; |
||||
} |
||||
|
||||
/** |
||||
* Allows to map the response before passing it to the decoder. |
||||
*/ |
||||
public B mapAndDecode(ResponseMapper mapper, Decoder decoder) { |
||||
this.decoder = new ResponseMappingDecoder(mapper, decoder); |
||||
return thisB; |
||||
} |
||||
|
||||
/** |
||||
* This flag indicates that the {@link #decoder(Decoder) decoder} should process responses with |
||||
* 404 status, specifically returning null or empty instead of throwing {@link FeignException}. |
||||
* |
||||
* <p/> |
||||
* All first-party (ex gson) decoders return well-known empty values defined by |
||||
* {@link Util#emptyValueOf}. To customize further, wrap an existing {@link #decoder(Decoder) |
||||
* decoder} or make your own. |
||||
* |
||||
* <p/> |
||||
* This flag only works with 404, as opposed to all or arbitrary status codes. This was an |
||||
* explicit decision: 404 -> empty is safe, common and doesn't complicate redirection, retry or |
||||
* fallback policy. If your server returns a different status for not-found, correct via a custom |
||||
* {@link #client(Client) client}. |
||||
* |
||||
* @since 11.9 |
||||
*/ |
||||
public B dismiss404() { |
||||
this.dismiss404 = true; |
||||
return thisB; |
||||
} |
||||
|
||||
|
||||
/** |
||||
* This flag indicates that the {@link #decoder(Decoder) decoder} should process responses with |
||||
* 404 status, specifically returning null or empty instead of throwing {@link FeignException}. |
||||
* |
||||
* <p/> |
||||
* All first-party (ex gson) decoders return well-known empty values defined by |
||||
* {@link Util#emptyValueOf}. To customize further, wrap an existing {@link #decoder(Decoder) |
||||
* decoder} or make your own. |
||||
* |
||||
* <p/> |
||||
* This flag only works with 404, as opposed to all or arbitrary status codes. This was an |
||||
* explicit decision: 404 -> empty is safe, common and doesn't complicate redirection, retry or |
||||
* fallback policy. If your server returns a different status for not-found, correct via a custom |
||||
* {@link #client(Client) client}. |
||||
* |
||||
* @since 8.12 |
||||
* @deprecated |
||||
*/ |
||||
@Deprecated |
||||
public B decode404() { |
||||
this.dismiss404 = true; |
||||
return thisB; |
||||
} |
||||
|
||||
|
||||
public B errorDecoder(ErrorDecoder errorDecoder) { |
||||
this.errorDecoder = errorDecoder; |
||||
return thisB; |
||||
} |
||||
|
||||
public B options(Options options) { |
||||
this.options = options; |
||||
return thisB; |
||||
} |
||||
|
||||
/** |
||||
* Adds a single request interceptor to the builder. |
||||
*/ |
||||
public B requestInterceptor(RequestInterceptor requestInterceptor) { |
||||
this.requestInterceptors.add(requestInterceptor); |
||||
return thisB; |
||||
} |
||||
|
||||
/** |
||||
* Sets the full set of request interceptors for the builder, overwriting any previous |
||||
* interceptors. |
||||
*/ |
||||
public B requestInterceptors(Iterable<RequestInterceptor> requestInterceptors) { |
||||
this.requestInterceptors.clear(); |
||||
for (RequestInterceptor requestInterceptor : requestInterceptors) { |
||||
this.requestInterceptors.add(requestInterceptor); |
||||
} |
||||
return thisB; |
||||
} |
||||
|
||||
/** |
||||
* Allows you to override how reflective dispatch works inside of Feign. |
||||
*/ |
||||
public B invocationHandlerFactory(InvocationHandlerFactory invocationHandlerFactory) { |
||||
this.invocationHandlerFactory = invocationHandlerFactory; |
||||
return thisB; |
||||
} |
||||
|
||||
public B exceptionPropagationPolicy(ExceptionPropagationPolicy propagationPolicy) { |
||||
this.propagationPolicy = propagationPolicy; |
||||
return thisB; |
||||
} |
||||
|
||||
public B addCapability(Capability capability) { |
||||
this.capabilities.add(capability); |
||||
return thisB; |
||||
} |
||||
|
||||
protected B enrich() { |
||||
if (capabilities.isEmpty()) { |
||||
return thisB; |
||||
} |
||||
|
||||
getFieldsToEnrich().forEach(field -> { |
||||
field.setAccessible(true); |
||||
try { |
||||
final Object originalValue = field.get(thisB); |
||||
final Object enriched; |
||||
if (originalValue instanceof List) { |
||||
Type ownerType = ((ParameterizedType) field.getGenericType()).getActualTypeArguments()[0]; |
||||
enriched = ((List) originalValue).stream() |
||||
.map(value -> Capability.enrich(value, (Class<?>) ownerType, capabilities)) |
||||
.collect(Collectors.toList()); |
||||
} else { |
||||
enriched = Capability.enrich(originalValue, field.getType(), capabilities); |
||||
} |
||||
field.set(thisB, enriched); |
||||
} catch (IllegalArgumentException | IllegalAccessException e) { |
||||
throw new RuntimeException("Unable to enrich field " + field, e); |
||||
} finally { |
||||
field.setAccessible(false); |
||||
} |
||||
}); |
||||
|
||||
return thisB; |
||||
} |
||||
|
||||
List<Field> getFieldsToEnrich() { |
||||
return Util.allFields(getClass()) |
||||
.stream() |
||||
// exclude anything generated by compiler
|
||||
.filter(field -> !field.isSynthetic()) |
||||
// and capabilities itself
|
||||
.filter(field -> !Objects.equals(field.getName(), "capabilities")) |
||||
// and thisB helper field
|
||||
.filter(field -> !Objects.equals(field.getName(), "thisB")) |
||||
// skip primitive types
|
||||
.filter(field -> !field.getType().isPrimitive()) |
||||
// skip enumerations
|
||||
.filter(field -> !field.getType().isEnum()) |
||||
.collect(Collectors.toList()); |
||||
} |
||||
|
||||
|
||||
} |
@ -0,0 +1,102 @@
@@ -0,0 +1,102 @@
|
||||
/* |
||||
* 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; |
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat; |
||||
import static org.junit.Assert.assertTrue; |
||||
import static org.mockito.Mockito.RETURNS_MOCKS; |
||||
import java.lang.reflect.Field; |
||||
import java.util.Arrays; |
||||
import java.util.List; |
||||
import java.util.Objects; |
||||
import java.util.stream.Collectors; |
||||
import org.junit.Test; |
||||
import org.mockito.Mockito; |
||||
|
||||
public class BaseBuilderTest { |
||||
|
||||
@Test |
||||
public void avoidDuplicationsBetweenBuilders() { |
||||
List<String> builderA = getExclusiveMethods(Feign.Builder.class); |
||||
List<String> builderB = getExclusiveMethods(AsyncFeign.AsyncBuilder.class); |
||||
|
||||
assertThat(builderA).noneMatch(m -> builderB.contains(m)); |
||||
assertThat(builderB).noneMatch(m -> builderA.contains(m)); |
||||
|
||||
} |
||||
|
||||
@Test |
||||
public void avoidDuplicationsBetweenAsyncBuilderAndBaseBuilder() { |
||||
List<String> builderA = getExclusiveMethods(BaseBuilder.class); |
||||
List<String> builderB = getExclusiveMethods(AsyncFeign.AsyncBuilder.class); |
||||
|
||||
assertThat(builderA).noneMatch(m -> builderB.contains(m)); |
||||
assertThat(builderB).noneMatch(m -> builderA.contains(m)); |
||||
|
||||
} |
||||
|
||||
@Test |
||||
public void avoidDuplicationsBetweenBuilderAndBaseBuilder() { |
||||
List<String> builderA = getExclusiveMethods(Feign.Builder.class); |
||||
List<String> builderB = getExclusiveMethods(BaseBuilder.class); |
||||
|
||||
assertThat(builderA).noneMatch(m -> builderB.contains(m)); |
||||
assertThat(builderB).noneMatch(m -> builderA.contains(m)); |
||||
|
||||
} |
||||
|
||||
private List<String> getExclusiveMethods(Class<?> clazz) { |
||||
return Arrays.stream(clazz.getDeclaredMethods()) |
||||
.filter(m -> !Objects.equals(m.getName(), "target")) |
||||
.filter(m -> !Objects.equals(m.getName(), "build")) |
||||
.filter(m -> !m.isSynthetic()) |
||||
.map(m -> m.getName() + "#" + Arrays.toString(m.getParameterTypes())) |
||||
.collect(Collectors.toList()); |
||||
} |
||||
|
||||
@Test |
||||
public void checkEnrichTouchesAllAsyncBuilderFields() |
||||
throws IllegalArgumentException, IllegalAccessException { |
||||
test(AsyncFeign.asyncBuilder().requestInterceptor(template -> { |
||||
}), 12); |
||||
} |
||||
|
||||
private void test(BaseBuilder<?> builder, int expectedFieldsCount) |
||||
throws IllegalArgumentException, IllegalAccessException { |
||||
Capability mockingCapability = Mockito.mock(Capability.class, RETURNS_MOCKS); |
||||
BaseBuilder<?> enriched = builder.addCapability(mockingCapability).enrich(); |
||||
|
||||
List<Field> fields = enriched.getFieldsToEnrich(); |
||||
assertThat(fields).hasSize(expectedFieldsCount); |
||||
|
||||
for (Field field : fields) { |
||||
field.setAccessible(true); |
||||
Object mockedValue = field.get(enriched); |
||||
if (mockedValue instanceof List) { |
||||
mockedValue = ((List<Object>) mockedValue).get(0); |
||||
} |
||||
assertTrue("Field was not enriched " + field, Mockito.mockingDetails(mockedValue) |
||||
.isMock()); |
||||
} |
||||
|
||||
} |
||||
|
||||
@Test |
||||
public void checkEnrichTouchesAllBuilderFields() |
||||
throws IllegalArgumentException, IllegalAccessException { |
||||
test(Feign.builder().requestInterceptor(template -> { |
||||
}), 11); |
||||
} |
||||
|
||||
} |
@ -0,0 +1,95 @@
@@ -0,0 +1,95 @@
|
||||
/* |
||||
* 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.metrics4; |
||||
|
||||
import com.codahale.metrics.MetricRegistry; |
||||
import com.codahale.metrics.Timer; |
||||
import feign.FeignException; |
||||
import feign.RequestTemplate; |
||||
import feign.Response; |
||||
|
||||
class BaseMeteredClient { |
||||
|
||||
protected final MetricRegistry metricRegistry; |
||||
protected final FeignMetricName metricName; |
||||
protected final MetricSuppliers metricSuppliers; |
||||
|
||||
public BaseMeteredClient( |
||||
MetricRegistry metricRegistry, FeignMetricName metricName, MetricSuppliers metricSuppliers) { |
||||
this.metricRegistry = metricRegistry; |
||||
this.metricName = metricName; |
||||
this.metricSuppliers = metricSuppliers; |
||||
} |
||||
|
||||
protected Timer.Context createTimer(RequestTemplate template) { |
||||
return metricRegistry |
||||
.timer( |
||||
MetricRegistry.name( |
||||
metricName.metricName(template.methodMetadata(), template.feignTarget()), |
||||
"uri", |
||||
template.methodMetadata().template().path()), |
||||
metricSuppliers.timers()) |
||||
.time(); |
||||
} |
||||
|
||||
protected void recordSuccess(RequestTemplate template, Response response) { |
||||
metricRegistry |
||||
.meter( |
||||
MetricRegistry.name( |
||||
httpResponseCode(template), |
||||
"status_group", |
||||
response.status() / 100 + "xx", |
||||
"http_status", |
||||
String.valueOf(response.status()), |
||||
"uri", |
||||
template.methodMetadata().template().path()), |
||||
metricSuppliers.meters()) |
||||
.mark(); |
||||
} |
||||
|
||||
protected void recordFailure(RequestTemplate template, FeignException e) { |
||||
metricRegistry |
||||
.meter( |
||||
MetricRegistry.name( |
||||
httpResponseCode(template), |
||||
"exception_name", |
||||
e.getClass().getSimpleName(), |
||||
"status_group", |
||||
e.status() / 100 + "xx", |
||||
"http_status", |
||||
String.valueOf(e.status()), |
||||
"uri", |
||||
template.methodMetadata().template().path()), |
||||
metricSuppliers.meters()) |
||||
.mark(); |
||||
} |
||||
|
||||
protected void recordFailure(RequestTemplate template, Exception e) { |
||||
metricRegistry |
||||
.meter( |
||||
MetricRegistry.name( |
||||
httpResponseCode(template), |
||||
"exception_name", |
||||
e.getClass().getSimpleName(), |
||||
"uri", |
||||
template.methodMetadata().template().path()), |
||||
metricSuppliers.meters()) |
||||
.mark(); |
||||
} |
||||
|
||||
private String httpResponseCode(RequestTemplate template) { |
||||
return metricName.metricName( |
||||
template.methodMetadata(), template.feignTarget(), "http_response_code"); |
||||
} |
||||
} |
@ -0,0 +1,63 @@
@@ -0,0 +1,63 @@
|
||||
/* |
||||
* 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.metrics4; |
||||
|
||||
import com.codahale.metrics.MetricRegistry; |
||||
import com.codahale.metrics.Timer; |
||||
import feign.AsyncClient; |
||||
import feign.FeignException; |
||||
import feign.Request; |
||||
import feign.Request.Options; |
||||
import feign.RequestTemplate; |
||||
import feign.Response; |
||||
import java.util.Optional; |
||||
import java.util.concurrent.CompletableFuture; |
||||
|
||||
/** Warp feign {@link AsyncClient} with metrics. */ |
||||
public class MeteredAsyncClient extends BaseMeteredClient implements AsyncClient<Object> { |
||||
|
||||
private final AsyncClient<Object> asyncClient; |
||||
|
||||
public MeteredAsyncClient( |
||||
AsyncClient<Object> asyncClient, |
||||
MetricRegistry metricRegistry, |
||||
MetricSuppliers metricSuppliers) { |
||||
super(metricRegistry, new FeignMetricName(AsyncClient.class), metricSuppliers); |
||||
this.asyncClient = asyncClient; |
||||
} |
||||
|
||||
@Override |
||||
public CompletableFuture<Response> execute( |
||||
Request request, |
||||
Options options, |
||||
Optional<Object> requestContext) { |
||||
final RequestTemplate template = request.requestTemplate(); |
||||
final Timer.Context timer = createTimer(template); |
||||
return asyncClient |
||||
.execute(request, options, requestContext) |
||||
.whenComplete( |
||||
(response, th) -> { |
||||
if (th == null) { |
||||
recordSuccess(template, response); |
||||
} else if (th instanceof FeignException) { |
||||
FeignException e = (FeignException) th; |
||||
recordFailure(template, e); |
||||
} else if (th instanceof Exception) { |
||||
Exception e = (Exception) th; |
||||
recordFailure(template, e); |
||||
} |
||||
}) |
||||
.whenComplete((response, th) -> timer.close()); |
||||
} |
||||
} |
@ -0,0 +1,82 @@
@@ -0,0 +1,82 @@
|
||||
/* |
||||
* 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.metrics5; |
||||
|
||||
import feign.AsyncClient; |
||||
import feign.FeignException; |
||||
import feign.RequestTemplate; |
||||
import feign.Response; |
||||
import io.dropwizard.metrics5.MetricName; |
||||
import io.dropwizard.metrics5.MetricRegistry; |
||||
import io.dropwizard.metrics5.Timer; |
||||
|
||||
public class BaseMeteredClient { |
||||
|
||||
protected final MetricRegistry metricRegistry; |
||||
protected final FeignMetricName metricName; |
||||
protected final MetricSuppliers metricSuppliers; |
||||
|
||||
public BaseMeteredClient( |
||||
MetricRegistry metricRegistry, FeignMetricName metricName, MetricSuppliers metricSuppliers) { |
||||
super(); |
||||
this.metricRegistry = metricRegistry; |
||||
this.metricName = metricName; |
||||
this.metricSuppliers = metricSuppliers; |
||||
} |
||||
|
||||
protected Timer.Context createTimer(RequestTemplate template) { |
||||
return metricRegistry |
||||
.timer( |
||||
metricName |
||||
.metricName(template.methodMetadata(), template.feignTarget()) |
||||
.tagged("uri", template.methodMetadata().template().path()), |
||||
metricSuppliers.timers()) |
||||
.time(); |
||||
} |
||||
|
||||
protected void recordSuccess(RequestTemplate template, Response response) { |
||||
metricRegistry |
||||
.counter( |
||||
httpResponseCode(template) |
||||
.tagged("http_status", String.valueOf(response.status())) |
||||
.tagged("status_group", response.status() / 100 + "xx") |
||||
.tagged("uri", template.methodMetadata().template().path())) |
||||
.inc(); |
||||
} |
||||
|
||||
protected void recordFailure(RequestTemplate template, FeignException e) { |
||||
metricRegistry |
||||
.counter( |
||||
httpResponseCode(template) |
||||
.tagged("exception_name", e.getClass().getSimpleName()) |
||||
.tagged("http_status", String.valueOf(e.status())) |
||||
.tagged("status_group", e.status() / 100 + "xx") |
||||
.tagged("uri", template.methodMetadata().template().path())) |
||||
.inc(); |
||||
} |
||||
|
||||
protected void recordFailure(RequestTemplate template, Exception e) { |
||||
metricRegistry |
||||
.counter( |
||||
httpResponseCode(template) |
||||
.tagged("exception_name", e.getClass().getSimpleName()) |
||||
.tagged("uri", template.methodMetadata().template().path())) |
||||
.inc(); |
||||
} |
||||
|
||||
private MetricName httpResponseCode(RequestTemplate template) { |
||||
return metricName.metricName( |
||||
template.methodMetadata(), template.feignTarget(), "http_response_code"); |
||||
} |
||||
} |
@ -0,0 +1,63 @@
@@ -0,0 +1,63 @@
|
||||
/* |
||||
* 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.metrics5; |
||||
|
||||
import feign.AsyncClient; |
||||
import feign.FeignException; |
||||
import feign.Request; |
||||
import feign.Request.Options; |
||||
import feign.RequestTemplate; |
||||
import feign.Response; |
||||
import io.dropwizard.metrics5.MetricRegistry; |
||||
import io.dropwizard.metrics5.Timer; |
||||
import java.util.Optional; |
||||
import java.util.concurrent.CompletableFuture; |
||||
|
||||
/** Warp feign {@link AsyncClient} with metrics. */ |
||||
public class MeteredAsyncClient extends BaseMeteredClient implements AsyncClient<Object> { |
||||
|
||||
private final AsyncClient<Object> asyncClient; |
||||
|
||||
public MeteredAsyncClient( |
||||
AsyncClient<Object> asyncClient, |
||||
MetricRegistry metricRegistry, |
||||
MetricSuppliers metricSuppliers) { |
||||
super(metricRegistry, new FeignMetricName(AsyncClient.class), metricSuppliers); |
||||
this.asyncClient = asyncClient; |
||||
} |
||||
|
||||
@Override |
||||
public CompletableFuture<Response> execute( |
||||
Request request, |
||||
Options options, |
||||
Optional<Object> requestContext) { |
||||
final RequestTemplate template = request.requestTemplate(); |
||||
final Timer.Context timer = createTimer(template); |
||||
return asyncClient |
||||
.execute(request, options, requestContext) |
||||
.whenComplete( |
||||
(response, th) -> { |
||||
if (th == null) { |
||||
recordSuccess(template, response); |
||||
} else if (th instanceof FeignException) { |
||||
FeignException e = (FeignException) th; |
||||
recordFailure(template, e); |
||||
} else if (th instanceof Exception) { |
||||
Exception e = (Exception) th; |
||||
recordFailure(template, e); |
||||
} |
||||
}) |
||||
.whenComplete((response, th) -> timer.close()); |
||||
} |
||||
} |
@ -0,0 +1,77 @@
@@ -0,0 +1,77 @@
|
||||
/* |
||||
* 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.micrometer; |
||||
|
||||
import static feign.micrometer.MetricTagResolver.EMPTY_TAGS_ARRAY; |
||||
import feign.Request; |
||||
import feign.Request.Options; |
||||
import feign.RequestTemplate; |
||||
import feign.Response; |
||||
import io.micrometer.core.instrument.MeterRegistry; |
||||
import io.micrometer.core.instrument.Tag; |
||||
import io.micrometer.core.instrument.Tags; |
||||
import io.micrometer.core.instrument.Timer; |
||||
|
||||
abstract class BaseMeteredClient { |
||||
|
||||
protected final MeterRegistry meterRegistry; |
||||
protected final MetricName metricName; |
||||
protected final MetricTagResolver metricTagResolver; |
||||
|
||||
public BaseMeteredClient( |
||||
MeterRegistry meterRegistry, MetricName metricName, MetricTagResolver metricTagResolver) { |
||||
super(); |
||||
this.meterRegistry = meterRegistry; |
||||
this.metricName = metricName; |
||||
this.metricTagResolver = metricTagResolver; |
||||
} |
||||
|
||||
protected void countResponseCode( |
||||
Request request, |
||||
Response response, |
||||
Options options, |
||||
int responseStatus, |
||||
Exception e) { |
||||
final Tag[] extraTags = extraTags(request, response, options, e); |
||||
final RequestTemplate template = request.requestTemplate(); |
||||
final Tags allTags = |
||||
metricTagResolver |
||||
.tag( |
||||
template.methodMetadata(), |
||||
template.feignTarget(), |
||||
e, |
||||
Tag.of("http_status", String.valueOf(responseStatus)), |
||||
Tag.of("status_group", responseStatus / 100 + "xx"), |
||||
Tag.of("uri", template.methodMetadata().template().path())) |
||||
.and(extraTags); |
||||
meterRegistry.counter(metricName.name("http_response_code"), allTags).increment(); |
||||
} |
||||
|
||||
protected Timer createTimer(Request request, Response response, Options options, Exception e) { |
||||
final RequestTemplate template = request.requestTemplate(); |
||||
final Tags allTags = |
||||
metricTagResolver |
||||
.tag( |
||||
template.methodMetadata(), |
||||
template.feignTarget(), |
||||
e, |
||||
Tag.of("uri", template.methodMetadata().template().path())) |
||||
.and(extraTags(request, response, options, e)); |
||||
return meterRegistry.timer(metricName.name(e), allTags); |
||||
} |
||||
|
||||
protected Tag[] extraTags(Request request, Response response, Options options, Exception e) { |
||||
return EMPTY_TAGS_ARRAY; |
||||
} |
||||
} |
@ -0,0 +1,76 @@
@@ -0,0 +1,76 @@
|
||||
/* |
||||
* 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.micrometer; |
||||
|
||||
import feign.AsyncClient; |
||||
import feign.Client; |
||||
import feign.FeignException; |
||||
import feign.Request; |
||||
import feign.Request.Options; |
||||
import feign.Response; |
||||
import io.micrometer.core.instrument.MeterRegistry; |
||||
import io.micrometer.core.instrument.Timer; |
||||
import java.util.Optional; |
||||
import java.util.concurrent.CompletableFuture; |
||||
|
||||
/** Warp feign {@link Client} with metrics. */ |
||||
public class MeteredAsyncClient extends BaseMeteredClient implements AsyncClient<Object> { |
||||
|
||||
private final AsyncClient<Object> client; |
||||
|
||||
public MeteredAsyncClient(AsyncClient<Object> client, MeterRegistry meterRegistry) { |
||||
this( |
||||
client, |
||||
meterRegistry, |
||||
new FeignMetricName(AsyncClient.class), |
||||
new FeignMetricTagResolver()); |
||||
} |
||||
|
||||
public MeteredAsyncClient( |
||||
AsyncClient<Object> client, |
||||
MeterRegistry meterRegistry, |
||||
MetricName metricName, |
||||
MetricTagResolver metricTagResolver) { |
||||
super(meterRegistry, metricName, metricTagResolver); |
||||
this.client = client; |
||||
} |
||||
|
||||
@Override |
||||
public CompletableFuture<Response> execute( |
||||
Request request, |
||||
Options options, |
||||
Optional<Object> requestContext) { |
||||
final Timer.Sample sample = Timer.start(meterRegistry); |
||||
return client |
||||
.execute(request, options, requestContext) |
||||
.whenComplete( |
||||
(response, th) -> { |
||||
Timer timer; |
||||
if (th == null) { |
||||
countResponseCode(request, response, options, response.status(), null); |
||||
timer = createTimer(request, response, options, null); |
||||
} else if (th instanceof FeignException) { |
||||
FeignException e = (FeignException) th; |
||||
timer = createTimer(request, response, options, e); |
||||
countResponseCode(request, response, options, e.status(), e); |
||||
} else if (th instanceof Exception) { |
||||
Exception e = (Exception) th; |
||||
timer = createTimer(request, response, options, e); |
||||
} else { |
||||
timer = createTimer(request, response, options, null); |
||||
} |
||||
sample.stop(timer); |
||||
}); |
||||
} |
||||
} |
Loading…
Reference in new issue