From 279115cc0a43f0ff6a9adbeb094602f7b133d31c Mon Sep 17 00:00:00 2001 From: Jonatan Ivanov Date: Fri, 25 Feb 2022 15:01:43 -0800 Subject: [PATCH 1/2] Add observe method to the Observation for Runnable and Supplier --- .../instrument/observation/Observation.java | 28 ++++++ .../ObservationRegistryCompatibilityKit.java | 93 +++++++++++++++++++ 2 files changed, 121 insertions(+) diff --git a/micrometer-api/src/main/java/io/micrometer/api/instrument/observation/Observation.java b/micrometer-api/src/main/java/io/micrometer/api/instrument/observation/Observation.java index 6d2f305727..b34f1ea4c8 100644 --- a/micrometer-api/src/main/java/io/micrometer/api/instrument/observation/Observation.java +++ b/micrometer-api/src/main/java/io/micrometer/api/instrument/observation/Observation.java @@ -200,6 +200,34 @@ default Observation highCardinalityTag(String key, String value) { */ Scope openScope(); + default void observe(Runnable runnable) { + this.start(); + try (Scope scope = openScope()) { + runnable.run(); + } + catch (Exception exception) { + this.error(exception); + throw exception; + } + finally { + this.stop(); + } + } + + default T observe(Supplier supplier) { + this.start(); + try (Scope scope = openScope()) { + return supplier.get(); + } + catch (Exception exception) { + this.error(exception); + throw exception; + } + finally { + this.stop(); + } + } + /** * Wraps the given action in scope. * diff --git a/micrometer-test/src/main/java/io/micrometer/core/tck/ObservationRegistryCompatibilityKit.java b/micrometer-test/src/main/java/io/micrometer/core/tck/ObservationRegistryCompatibilityKit.java index 7d459979fb..5dd78e501f 100644 --- a/micrometer-test/src/main/java/io/micrometer/core/tck/ObservationRegistryCompatibilityKit.java +++ b/micrometer-test/src/main/java/io/micrometer/core/tck/ObservationRegistryCompatibilityKit.java @@ -35,6 +35,7 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.isA; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.when; @@ -90,6 +91,98 @@ void observeWithHandlers() { observation.stop(); } + @Test + void runnableShouldBeObserved() { + ObservationHandler handler = mock(ObservationHandler.class); + when(handler.supportsContext(isA(Observation.Context.class))).thenReturn(true); + registry.observationConfig().observationHandler(handler); + Observation observation = Observation.createNotStarted("myObservation", registry); + + observation.observe((Runnable) () -> assertThat(registry.getCurrentObservation()).isSameAs(observation)); + assertThat(registry.getCurrentObservation()).isNull(); + + verify(handler).supportsContext(isA(Observation.Context.class)); + verify(handler).onStart(isA(Observation.Context.class)); + verify(handler).onScopeOpened(isA(Observation.Context.class)); + verify(handler).onScopeClosed(isA(Observation.Context.class)); + verify(handler, times(0)).onError(isA(Observation.Context.class)); + verify(handler).onStop(isA(Observation.Context.class)); + } + + @Test + void runnableThrowingErrorShouldBeObserved() { + ObservationHandler handler = mock(ObservationHandler.class); + when(handler.supportsContext(isA(Observation.Context.class))).thenReturn(true); + registry.observationConfig().observationHandler(handler); + Observation observation = Observation.createNotStarted("myObservation", registry); + + assertThatThrownBy(() -> + observation.observe((Runnable) () -> { + assertThat(registry.getCurrentObservation()).isSameAs(observation); + throw new RuntimeException("simulated"); + }) + ).isInstanceOf(RuntimeException.class) + .hasMessage("simulated") + .hasNoCause(); + + assertThat(registry.getCurrentObservation()).isNull(); + + verify(handler).supportsContext(isA(Observation.Context.class)); + verify(handler).onStart(isA(Observation.Context.class)); + verify(handler).onScopeOpened(isA(Observation.Context.class)); + verify(handler).onScopeClosed(isA(Observation.Context.class)); + verify(handler).onError(isA(Observation.Context.class)); + verify(handler).onStop(isA(Observation.Context.class)); + } + + @Test + void supplierShouldBeObserved() { + ObservationHandler handler = mock(ObservationHandler.class); + when(handler.supportsContext(isA(Observation.Context.class))).thenReturn(true); + registry.observationConfig().observationHandler(handler); + Observation observation = Observation.createNotStarted("myObservation", registry); + + String result = observation.observe((Supplier) () -> { + assertThat(registry.getCurrentObservation()).isSameAs(observation); + return "test"; + }); + assertThat(result).isEqualTo("test"); + assertThat(registry.getCurrentObservation()).isNull(); + + verify(handler).supportsContext(isA(Observation.Context.class)); + verify(handler).onStart(isA(Observation.Context.class)); + verify(handler).onScopeOpened(isA(Observation.Context.class)); + verify(handler).onScopeClosed(isA(Observation.Context.class)); + verify(handler, times(0)).onError(isA(Observation.Context.class)); + verify(handler).onStop(isA(Observation.Context.class)); + } + + @Test + void supplierThrowingErrorShouldBeObserved() { + ObservationHandler handler = mock(ObservationHandler.class); + when(handler.supportsContext(isA(Observation.Context.class))).thenReturn(true); + registry.observationConfig().observationHandler(handler); + Observation observation = Observation.createNotStarted("myObservation", registry); + + assertThatThrownBy(() -> + observation.observe((Supplier) () -> { + assertThat(registry.getCurrentObservation()).isSameAs(observation); + throw new RuntimeException("simulated"); + }) + ).isInstanceOf(RuntimeException.class) + .hasMessage("simulated") + .hasNoCause(); + + assertThat(registry.getCurrentObservation()).isNull(); + + verify(handler).supportsContext(isA(Observation.Context.class)); + verify(handler).onStart(isA(Observation.Context.class)); + verify(handler).onScopeOpened(isA(Observation.Context.class)); + verify(handler).onScopeClosed(isA(Observation.Context.class)); + verify(handler).onError(isA(Observation.Context.class)); + verify(handler).onStop(isA(Observation.Context.class)); + } + @Test void runnableShouldBeScoped() { Observation observation = Observation.start("myObservation", registry); From 961dcde01af1e5ec4ea86ffb1baaa8e258e003cf Mon Sep 17 00:00:00 2001 From: Jonatan Ivanov Date: Mon, 28 Feb 2022 10:55:16 -0800 Subject: [PATCH 2/2] Add javadoc to new methods --- .../instrument/observation/Observation.java | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/micrometer-api/src/main/java/io/micrometer/api/instrument/observation/Observation.java b/micrometer-api/src/main/java/io/micrometer/api/instrument/observation/Observation.java index b34f1ea4c8..bf746e6304 100644 --- a/micrometer-api/src/main/java/io/micrometer/api/instrument/observation/Observation.java +++ b/micrometer-api/src/main/java/io/micrometer/api/instrument/observation/Observation.java @@ -200,6 +200,17 @@ default Observation highCardinalityTag(String key, String value) { */ Scope openScope(); + /** + * Observes the passed {@link Runnable}, this means the followings: + * - Starts the {@code Observation} + * - Opens a {@code Scope} + * - Calls {@link Runnable#run()} + * - Closes the {@code Scope} + * - Signals the error to the {@code Observation} if any + * - Stops the {@code Observation} + * + * @param runnable the {@link Runnable} to run + */ default void observe(Runnable runnable) { this.start(); try (Scope scope = openScope()) { @@ -214,6 +225,19 @@ default void observe(Runnable runnable) { } } + /** + * Observes the passed {@link Supplier}, this means the followings: + * - Starts the {@code Observation} + * - Opens a {@code Scope} + * - Calls {@link Supplier#get()} + * - Closes the {@code Scope} + * - Signals the error to the {@code Observation} if any + * - Stops the {@code Observation} + * + * @param supplier the {@link Supplier} to call + * @param the type parameter of the {@link Supplier} + * @return the result from {@link Supplier#get()} + */ default T observe(Supplier supplier) { this.start(); try (Scope scope = openScope()) {