diff --git a/extensions/micrometer/runtime/src/main/java/io/quarkus/micrometer/runtime/binder/websockets/WebSocketMetricConstants.java b/extensions/micrometer/runtime/src/main/java/io/quarkus/micrometer/runtime/binder/websockets/WebSocketMetricConstants.java index b5282de4f6f2e..388d7e184cb56 100644 --- a/extensions/micrometer/runtime/src/main/java/io/quarkus/micrometer/runtime/binder/websockets/WebSocketMetricConstants.java +++ b/extensions/micrometer/runtime/src/main/java/io/quarkus/micrometer/runtime/binder/websockets/WebSocketMetricConstants.java @@ -17,14 +17,14 @@ private WebSocketMetricConstants() { public static final String SERVER_CONNECTION_OPENED = "quarkus.websockets.server.connections.opened"; /** - * Counts number of times that opening of a WebSocket server connection resulted in error. + * Counts number of times that opening of a WebSocket server connection resulted in error, therefore closing the connection. */ public static final String SERVER_CONNECTION_ON_OPEN_ERROR = "quarkus.websockets.server.connections.onopen.errors"; /** - * Counts number of times that opening of a WebSocket client connection resulted in error. + * Counts number of times that opening of a WebSocket client connection resulted in error, therefore closing the connection. */ - public static final String CLIENT_CONNECTION_OPENED_ERROR = "quarkus.websockets.client.connections.opened.errors"; + public static final String CLIENT_CONNECTION_ON_OPEN_ERROR = "quarkus.websockets.client.connections.onopen.errors"; /** * Counts all the WebSockets client closed connections. diff --git a/extensions/micrometer/runtime/src/main/java/io/quarkus/micrometer/runtime/binder/websockets/WebSocketMetricsInterceptorProducerImpl.java b/extensions/micrometer/runtime/src/main/java/io/quarkus/micrometer/runtime/binder/websockets/WebSocketMetricsInterceptorProducerImpl.java index 06aecf2f7dac0..9edf0dadd1919 100644 --- a/extensions/micrometer/runtime/src/main/java/io/quarkus/micrometer/runtime/binder/websockets/WebSocketMetricsInterceptorProducerImpl.java +++ b/extensions/micrometer/runtime/src/main/java/io/quarkus/micrometer/runtime/binder/websockets/WebSocketMetricsInterceptorProducerImpl.java @@ -43,12 +43,12 @@ public WebSocketMetricsInterceptor createServerMetricsInterceptor() { .builder(WebSocketMetricConstants.SERVER_CONNECTION_OPENED) .description("Number of opened server connections.") .withRegistry(meterRegistry); - final Meter.MeterProvider connectionOpeningFailedCounter = Counter + final Meter.MeterProvider connectionOnOpenErrorsCounter = Counter .builder(WebSocketMetricConstants.SERVER_CONNECTION_ON_OPEN_ERROR) .description("Number of failures occurred when opening server connection failed.") .withRegistry(meterRegistry); return new WebSocketMetricsInterceptorImpl(messagesCounter, bytesCounter, closedConnectionCounter, serverErrorsCounter, - connectionOpenCounter, connectionOpeningFailedCounter); + connectionOpenCounter, connectionOnOpenErrorsCounter); } @Override @@ -73,12 +73,12 @@ public WebSocketMetricsInterceptor createClientMetricsInterceptor() { .builder(WebSocketMetricConstants.CLIENT_CONNECTION_OPENED) .description("Number of opened client connections.") .withRegistry(meterRegistry); - final Meter.MeterProvider connectionOpeningFailedCounter = Counter - .builder(WebSocketMetricConstants.CLIENT_CONNECTION_OPENED_ERROR) + final Meter.MeterProvider connectionOnOpenErrorsCounter = Counter + .builder(WebSocketMetricConstants.CLIENT_CONNECTION_ON_OPEN_ERROR) .description("Number of failures occurred when opening client connection failed.") .withRegistry(meterRegistry); return new WebSocketMetricsInterceptorImpl(messagesCounter, bytesCounter, closedConnectionCounter, clientErrorsCounter, - connectionOpenCounter, connectionOpeningFailedCounter); + connectionOpenCounter, connectionOnOpenErrorsCounter); } private static final class WebSocketMetricsInterceptorImpl implements WebSocketMetricsInterceptor { @@ -88,18 +88,18 @@ private static final class WebSocketMetricsInterceptorImpl implements WebSocketM private final Meter.MeterProvider closedConnectionCounter; private final Meter.MeterProvider errorsCounter; private final Meter.MeterProvider connectionOpenCounter; - private final Meter.MeterProvider connectionOpeningFailedCounter; + private final Meter.MeterProvider connectionOnOpenErrorsCounter; private WebSocketMetricsInterceptorImpl(Meter.MeterProvider messagesCounter, Meter.MeterProvider bytesCounter, Meter.MeterProvider closedConnectionCounter, Meter.MeterProvider errorsCounter, Meter.MeterProvider connectionOpenCounter, - Meter.MeterProvider connectionOpeningFailedCounter) { + Meter.MeterProvider connectionOnOpenErrorsCounter) { this.messagesCounter = messagesCounter; this.bytesCounter = bytesCounter; this.closedConnectionCounter = closedConnectionCounter; this.errorsCounter = errorsCounter; this.connectionOpenCounter = connectionOpenCounter; - this.connectionOpeningFailedCounter = connectionOpeningFailedCounter; + this.connectionOnOpenErrorsCounter = connectionOnOpenErrorsCounter; } @Override @@ -126,7 +126,7 @@ public void onConnectionOpened(String route) { @Override public void onConnectionOpeningFailed(String route) { - connectionOpeningFailedCounter.withTag(URI_TAG_KEY, route).increment(); + connectionOnOpenErrorsCounter.withTag(URI_TAG_KEY, route).increment(); } @Override diff --git a/extensions/websockets-next/deployment/src/test/java/io/quarkus/websockets/next/test/telemetry/MicrometerWebSocketsStandaloneTest.java b/extensions/websockets-next/deployment/src/test/java/io/quarkus/websockets/next/test/telemetry/MicrometerWebSocketsStandaloneTest.java new file mode 100644 index 0000000000000..e474c01eb5653 --- /dev/null +++ b/extensions/websockets-next/deployment/src/test/java/io/quarkus/websockets/next/test/telemetry/MicrometerWebSocketsStandaloneTest.java @@ -0,0 +1,145 @@ +package io.quarkus.websockets.next.test.telemetry; + +import static io.quarkus.websockets.next.test.telemetry.MetricsAsserter.assertServerConnectionClosedTotal; +import static io.quarkus.websockets.next.test.telemetry.MetricsAsserter.assertServerConnectionOpenedTotal; +import static io.quarkus.websockets.next.test.telemetry.MetricsAsserter.assertServerConnectionOpeningFailedTotal; +import static io.quarkus.websockets.next.test.telemetry.MetricsAsserter.assertServerErrorTotal; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.net.URI; +import java.time.Duration; +import java.util.List; + +import jakarta.inject.Inject; + +import org.awaitility.Awaitility; +import org.jboss.shrinkwrap.api.asset.StringAsset; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.builder.Version; +import io.quarkus.logging.Log; +import io.quarkus.maven.dependency.Dependency; +import io.quarkus.test.QuarkusUnitTest; +import io.quarkus.test.common.http.TestHTTPResource; +import io.quarkus.websockets.next.OnError; +import io.quarkus.websockets.next.OnOpen; +import io.quarkus.websockets.next.WebSocket; +import io.quarkus.websockets.next.WebSocketClient; +import io.quarkus.websockets.next.WebSocketClientConnection; +import io.quarkus.websockets.next.WebSocketConnector; +import io.restassured.RestAssured; +import io.restassured.response.ValidatableResponse; + +public class MicrometerWebSocketsStandaloneTest { + + @RegisterExtension + public static final QuarkusUnitTest test = new QuarkusUnitTest() + .withApplicationRoot(root -> root + .addClasses(MetricsAsserter.class) + .addAsResource(new StringAsset(""" + bounce-endpoint.prefix-responses=true + quarkus.websockets-next.server.metrics.enabled=true + quarkus.websockets-next.client.metrics.enabled=true + """), "application.properties")) + .setForcedDependencies( + List.of(Dependency.of("io.quarkus", "quarkus-micrometer-registry-prometheus-deployment", + Version.getVersion()))); + + @Inject + WebSocketConnector onConnectErrorClient; + + @Inject + WebSocketConnector onConnectErrorHandlerClient; + + @TestHTTPResource("/") + URI baseUri; + + static ValidatableResponse getMetrics() { + return RestAssured.given().get("/q/metrics").then().statusCode(200); + } + + @Test + public void testServerEndpoint_OnConnectionError() { + WebSocketClientConnection connection = onConnectErrorClient.baseUri(baseUri).connectAndAwait(); + Awaitility.await().atMost(Duration.ofSeconds(2)).untilAsserted(() -> { + // Connection closing is async and takes some time + assertFalse(connection.isOpen(), + "Runtime exception happened on server side and connection is still open"); + }); + + Awaitility.await().atMost(Duration.ofSeconds(5)).untilAsserted(() -> { + getMetrics() + .body(assertServerConnectionOpeningFailedTotal("/on-connect-error", 1)) + .body(assertServerErrorTotal("/on-connect-error", 1)) + .body(assertServerConnectionOpenedTotal("/on-connect-error", 1)) + .body(assertServerConnectionClosedTotal("/on-connect-error", 1)); + }); + } + + @Test + public void testServerEndpoint_OnConnectionErrorHandler() { + WebSocketClientConnection connection = onConnectErrorHandlerClient.baseUri(baseUri).connectAndAwait(); + Awaitility.await().atMost(Duration.ofSeconds(2)).untilAsserted(() -> { + // Connection closing will never close because there is an OnError handler. + assertTrue(connection.isOpen(), "Should never close"); + }); + + Awaitility.await().atMost(Duration.ofSeconds(5)).untilAsserted(() -> { + getMetrics() + .body(assertServerConnectionOpeningFailedTotal("/with-on-connect-error-handler", 0)) + .body(assertServerErrorTotal("/with-on-connect-error-handler", 1)) + .body(assertServerConnectionOpenedTotal("/with-on-connect-error-handler", 1)) + .body(assertServerConnectionClosedTotal("/with-on-connect-error-handler", 0)); + + }); + } + + // ----------------------- SERVERS ------------------------------- + + @WebSocket(path = "/on-connect-error") + public static class OnConnectErrorEndpoint { + + @OnOpen() + public void onOpen() { + Log.info("onOpen throwing exception"); + throw new RuntimeException("Crafted exception - Websocket failed to open"); + } + } + + @WebSocket(path = "/with-on-connect-error-handler") + public static class OnConnectErrorHandlerEndpoint { + + @OnOpen() + public void onOpen() { + Log.info("onOpen throwing exception"); + throw new RuntimeException("Crafted exception - Websocket failed to open"); + } + + @OnError + public void onError(Exception failure) { + Log.warnv("Error handled: {0}", failure.getMessage()); + } + } + + // ------------------------- CLIENTS ------------------------------- + + @WebSocketClient(path = "/on-connect-error") + public static class ErroneousClient_OnConnectError { + + @OnOpen() + public void onOpen() { + Log.info("client onOpen"); + } + } + + @WebSocketClient(path = "/with-on-connect-error-handler") + public static class ErroneousClient_OnConnectErrorHandler { + + @OnOpen() + public void onOpen() { + Log.info("client onOpen"); + } + } +}