Skip to content

Commit e4a486b

Browse files
committed
Add HttpServer#metrics(boolean, Function<String, String>, Function<String, String>)
Add HttpServer#metrics(boolean, Supplier<? extends ChannelMetricsRecorder>, Function<String, String>, Function<String, String>)
1 parent aebdd91 commit e4a486b

File tree

5 files changed

+164
-23
lines changed

5 files changed

+164
-23
lines changed

reactor-netty-http/src/main/java/reactor/netty/http/server/AbstractHttpServerMetricsHandler.java

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -59,9 +59,13 @@ abstract class AbstractHttpServerMetricsHandler extends ChannelDuplexHandler {
5959

6060
long dataSentTime;
6161

62+
final Function<String, String> methodTagValue;
6263
final Function<String, String> uriTagValue;
6364

64-
protected AbstractHttpServerMetricsHandler(@Nullable Function<String, String> uriTagValue) {
65+
protected AbstractHttpServerMetricsHandler(
66+
@Nullable Function<String, String> methodTagValue,
67+
@Nullable Function<String, String> uriTagValue) {
68+
this.methodTagValue = methodTagValue == null ? Function.identity() : methodTagValue;
6569
this.uriTagValue = uriTagValue;
6670
}
6771

@@ -72,6 +76,7 @@ protected AbstractHttpServerMetricsHandler(AbstractHttpServerMetricsHandler copy
7276
this.dataReceivedTime = copy.dataReceivedTime;
7377
this.dataSent = copy.dataSent;
7478
this.dataSentTime = copy.dataSentTime;
79+
this.methodTagValue = copy.methodTagValue;
7580
this.uriTagValue = copy.uriTagValue;
7681
}
7782

@@ -140,7 +145,7 @@ public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise)
140145
HttpServerOperations ops = (HttpServerOperations) channelOps;
141146
try {
142147
recordWrite(ops, uriTagValue == null ? ops.path : uriTagValue.apply(ops.path),
143-
ops.method().name(), ops.status().codeAsText().toString());
148+
methodTagValue.apply(ops.method().name()), ops.status().codeAsText().toString());
144149
}
145150
catch (RuntimeException e) {
146151
// Allow request-response exchange to continue, unaffected by metrics problem
@@ -191,7 +196,7 @@ public void channelRead(ChannelHandlerContext ctx, Object msg) {
191196
ChannelOperations<?, ?> channelOps = ChannelOperations.get(ctx.channel());
192197
if (channelOps instanceof HttpServerOperations) {
193198
HttpServerOperations ops = (HttpServerOperations) channelOps;
194-
recordRead(ops, uriTagValue == null ? ops.path : uriTagValue.apply(ops.path), ops.method().name());
199+
recordRead(ops, uriTagValue == null ? ops.path : uriTagValue.apply(ops.path), methodTagValue.apply(ops.method().name()));
195200
}
196201

197202
dataReceived = 0;

reactor-netty-http/src/main/java/reactor/netty/http/server/ContextAwareHttpServerMetricsHandler.java

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2021-2022 VMware, Inc. or its affiliates, All Rights Reserved.
2+
* Copyright (c) 2021-2023 VMware, Inc. or its affiliates, All Rights Reserved.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -29,9 +29,11 @@ final class ContextAwareHttpServerMetricsHandler extends AbstractHttpServerMetri
2929

3030
final ContextAwareHttpServerMetricsRecorder recorder;
3131

32-
ContextAwareHttpServerMetricsHandler(ContextAwareHttpServerMetricsRecorder recorder,
32+
ContextAwareHttpServerMetricsHandler(
33+
ContextAwareHttpServerMetricsRecorder recorder,
34+
@Nullable Function<String, String> methodTagValue,
3335
@Nullable Function<String, String> uriTagValue) {
34-
super(uriTagValue);
36+
super(methodTagValue, uriTagValue);
3537
this.recorder = recorder;
3638
}
3739

reactor-netty-http/src/main/java/reactor/netty/http/server/HttpServer.java

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -624,6 +624,73 @@ else if (configuration().metricsRecorder() != null) {
624624
}
625625
}
626626

627+
/**
628+
* Whether to enable metrics to be collected and registered in Micrometer's
629+
* {@link io.micrometer.core.instrument.Metrics#globalRegistry globalRegistry}
630+
* under the name {@link reactor.netty.Metrics#HTTP_SERVER_PREFIX}.
631+
* <p>{@code uriTagValue} function receives the actual uri and returns the uri tag value
632+
* that will be used for the metrics with {@link reactor.netty.Metrics#URI} tag.
633+
* For example instead of using the actual uri {@code "/users/1"} as uri tag value, templated uri
634+
* {@code "/users/{id}"} can be used.
635+
* <p><strong>Note:</strong>
636+
* It is strongly recommended to provide template-like form for the URIs. Without a conversion to a template-like form,
637+
* each distinct URI leads to the creation of a distinct tag, which takes a lot of memory for the metrics.
638+
* <p><strong>Note:</strong>
639+
* It is strongly recommended applications to configure an upper limit for the number of the URI tags.
640+
* For example:
641+
* <pre class="code">
642+
* Metrics.globalRegistry
643+
* .config()
644+
* .meterFilter(MeterFilter.maximumAllowableTags(HTTP_SERVER_PREFIX, URI, 100, MeterFilter.deny()));
645+
* </pre>
646+
* <p>{@code methodTagValue} function receives the actual method name and returns the method tag value
647+
* that will be used for the metrics with {@link reactor.netty.Metrics#METHOD} tag.
648+
* <p>By default metrics are not enabled.
649+
*
650+
* @param enable true enables metrics collection; false disables it
651+
* @param uriTagValue a function that receives the actual uri and returns the uri tag value
652+
* that will be used for the metrics with {@link reactor.netty.Metrics#URI} tag
653+
* @param methodTagValue a function that receives the actual method name and returns the method tag value
654+
* that will be used for the metrics with {@link reactor.netty.Metrics#METHOD} tag
655+
* @return a new {@link HttpServer}
656+
* @since 1.0.39
657+
*/
658+
public final HttpServer metrics(boolean enable, Function<String, String> uriTagValue, Function<String, String> methodTagValue) {
659+
if (enable) {
660+
Objects.requireNonNull(methodTagValue, "methodTagValue");
661+
if (!Metrics.isInstrumentationAvailable()) {
662+
throw new UnsupportedOperationException(
663+
"To enable metrics, you must add the dependency `io.micrometer:micrometer-core`" +
664+
" to the class path first");
665+
}
666+
if (uriTagValue == Function.<String>identity()) {
667+
log.debug("Metrics are enabled with [uriTagValue=Function#identity]. " +
668+
"It is strongly recommended to provide template-like form for the URIs. " +
669+
"Without a conversion to a template-like form, each distinct URI leads " +
670+
"to the creation of a distinct tag, which takes a lot of memory for the metrics.");
671+
}
672+
if (methodTagValue == Function.<String>identity()) {
673+
log.debug("Metrics are enabled with [methodTagValue=Function#identity]. " +
674+
"It is strongly recommended to provide a function for transforming the method names.");
675+
}
676+
HttpServer dup = duplicate();
677+
dup.configuration().metricsRecorder(() -> configuration().defaultMetricsRecorder());
678+
dup.configuration().methodTagValue = methodTagValue;
679+
dup.configuration().uriTagValue = uriTagValue;
680+
return dup;
681+
}
682+
else if (configuration().metricsRecorder() != null) {
683+
HttpServer dup = duplicate();
684+
dup.configuration().metricsRecorder(null);
685+
dup.configuration().methodTagValue = null;
686+
dup.configuration().uriTagValue = null;
687+
return dup;
688+
}
689+
else {
690+
return this;
691+
}
692+
}
693+
627694
@Override
628695
public final HttpServer metrics(boolean enable, Supplier<? extends ChannelMetricsRecorder> recorder) {
629696
return super.metrics(enable, recorder);
@@ -663,6 +730,49 @@ else if (configuration().metricsRecorder() != null) {
663730
}
664731
}
665732

733+
/**
734+
* Specifies whether the metrics are enabled on the {@link HttpServer}.
735+
* All generated metrics are provided to the specified recorder which is only
736+
* instantiated if metrics are being enabled (the instantiation is not lazy,
737+
* but happens immediately, while configuring the {@link HttpServer}).
738+
* <p>{@code uriValue} function receives the actual uri and returns the uri value
739+
* that will be used when the metrics are propagated to the recorder.
740+
* For example instead of using the actual uri {@code "/users/1"} as uri value, templated uri
741+
* {@code "/users/{id}"} can be used.
742+
* <p>{@code methodValue} function receives the actual method name and returns the method value
743+
* that will be used when the metrics are propagated to the recorder.
744+
*
745+
* @param enable true enables metrics collection; false disables it
746+
* @param recorder a supplier for the metrics recorder that receives the collected metrics
747+
* @param uriValue a function that receives the actual uri and returns the uri value
748+
* that will be used when the metrics are propagated to the recorder.
749+
* @param methodValue a function that receives the actual method name and returns the method value
750+
* that will be used when the metrics are propagated to the recorder.
751+
* @return a new {@link HttpServer}
752+
* @since 1.0.39
753+
*/
754+
public final HttpServer metrics(boolean enable, Supplier<? extends ChannelMetricsRecorder> recorder,
755+
Function<String, String> uriValue, Function<String, String> methodValue) {
756+
if (enable) {
757+
Objects.requireNonNull(methodValue, "methodTagValue");
758+
HttpServer dup = duplicate();
759+
dup.configuration().metricsRecorder(recorder);
760+
dup.configuration().methodTagValue = methodValue;
761+
dup.configuration().uriTagValue = uriValue;
762+
return dup;
763+
}
764+
else if (configuration().metricsRecorder() != null) {
765+
HttpServer dup = duplicate();
766+
dup.configuration().metricsRecorder(null);
767+
dup.configuration().methodTagValue = null;
768+
dup.configuration().uriTagValue = null;
769+
return dup;
770+
}
771+
else {
772+
return this;
773+
}
774+
}
775+
666776
/**
667777
* Removes any previously applied SSL configuration customization.
668778
*

0 commit comments

Comments
 (0)