-
Notifications
You must be signed in to change notification settings - Fork 38.1k
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
Instrument webmvc #27936
Closed
jonatan-ivanov
wants to merge
11
commits into
spring-projects:main
from
jonatan-ivanov:instrumentation-webmvc
Closed
Instrument webmvc #27936
Changes from all commits
Commits
Show all changes
11 commits
Select commit
Hold shift + click to select a range
35f3db9
Add micrometer-core (optional)
jonatan-ivanov d2b8ff1
Add micrometer support from Spring Boot
jonatan-ivanov 4763224
Fix the build and fit the code to Framework 6
jonatan-ivanov 96d4315
WebMvcMetricsFilter -> WebMvcObservabilityFilter
jonatan-ivanov 5232ab8
Add HttpServerHandlerContext to instrumentation so that tracing can g…
jonatan-ivanov 4f904a7
Add demo to show it in action
jonatan-ivanov 7ed0fea
Checkstyle <3
jonatan-ivanov 8cccc1f
Add scope handling
jonatan-ivanov d16169d
Back to micrometer snapshots for bugfixes and improvements
jonatan-ivanov bc649af
Fix build failure: micrometer-tracing-test -> micrometer-tracing-inte…
jonatan-ivanov 7bff894
Fixing things after the Micrometer API split
jonatan-ivanov File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
114 changes: 114 additions & 0 deletions
114
spring-core/src/main/java/org/springframework/core/observability/AutoTimer.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,114 @@ | ||
/* | ||
* Copyright 2012-2022 the original author or 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 | ||
* | ||
* https://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 org.springframework.core.observability; | ||
|
||
import java.util.Set; | ||
import java.util.function.Consumer; | ||
import java.util.function.Supplier; | ||
|
||
import io.micrometer.api.annotation.Timed; | ||
import io.micrometer.api.instrument.Timer; | ||
import io.micrometer.api.instrument.Timer.Builder; | ||
|
||
import org.springframework.util.CollectionUtils; | ||
|
||
/** | ||
* Strategy that can be used to apply {@link Timer Timers} automatically instead of using | ||
* {@link Timed @Timed}. | ||
* | ||
* @author Tadaya Tsuyukubo | ||
* @author Stephane Nicoll | ||
* @author Phillip Webb | ||
* @since 6.0.0 | ||
*/ | ||
@FunctionalInterface | ||
public interface AutoTimer { | ||
|
||
/** | ||
* An {@link AutoTimer} implementation that is enabled but applies no additional | ||
* customizations. | ||
*/ | ||
AutoTimer ENABLED = builder -> { | ||
}; | ||
|
||
/** | ||
* An {@link AutoTimer} implementation that is disabled and will not record metrics. | ||
*/ | ||
AutoTimer DISABLED = new AutoTimer() { | ||
|
||
@Override | ||
public boolean isEnabled() { | ||
return false; | ||
} | ||
|
||
@Override | ||
public void apply(Builder builder) { | ||
throw new IllegalStateException("AutoTimer is disabled"); | ||
} | ||
|
||
}; | ||
|
||
/** | ||
* Return if the auto-timer is enabled and metrics should be recorded. | ||
* @return if the auto-timer is enabled | ||
*/ | ||
default boolean isEnabled() { | ||
return true; | ||
} | ||
|
||
/** | ||
* Factory method to create a new {@link Builder Timer.Builder} with auto-timer | ||
* settings {@link #apply(Builder) applied}. | ||
* @param name the name of the timer | ||
* @return a new builder instance with auto-settings applied | ||
*/ | ||
default Builder builder(String name) { | ||
return builder(() -> Timer.builder(name)); | ||
} | ||
|
||
/** | ||
* Factory method to create a new {@link Builder Timer.Builder} with auto-timer | ||
* settings {@link #apply(Builder) applied}. | ||
* @param supplier the builder supplier | ||
* @return a new builder instance with auto-settings applied | ||
*/ | ||
default Builder builder(Supplier<Builder> supplier) { | ||
Builder builder = supplier.get(); | ||
apply(builder); | ||
return builder; | ||
} | ||
|
||
/** | ||
* Called to apply any auto-timer settings to the given {@link Builder Timer.Builder}. | ||
* @param builder the builder to apply settings to | ||
*/ | ||
void apply(Builder builder); | ||
|
||
static void apply(AutoTimer autoTimer, String metricName, Set<Timed> annotations, Consumer<Builder> action) { | ||
if (!CollectionUtils.isEmpty(annotations)) { | ||
for (Timed annotation : annotations) { | ||
action.accept(Timer.builder(annotation, metricName)); | ||
} | ||
} | ||
else { | ||
if (autoTimer != null && autoTimer.isEnabled()) { | ||
action.accept(autoTimer.builder(metricName)); | ||
} | ||
} | ||
} | ||
|
||
} |
74 changes: 74 additions & 0 deletions
74
...ore/src/main/java/org/springframework/core/observability/annotation/TimedAnnotations.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,74 @@ | ||
/* | ||
* Copyright 2012-2022 the original author or 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 | ||
* | ||
* https://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 org.springframework.core.observability.annotation; | ||
|
||
import java.lang.reflect.AnnotatedElement; | ||
import java.lang.reflect.Method; | ||
import java.util.Collections; | ||
import java.util.Map; | ||
import java.util.Set; | ||
|
||
import io.micrometer.api.annotation.Timed; | ||
|
||
import org.springframework.core.annotation.MergedAnnotationCollectors; | ||
import org.springframework.core.annotation.MergedAnnotations; | ||
import org.springframework.util.ConcurrentReferenceHashMap; | ||
|
||
/** | ||
* Utility used to obtain {@link Timed @Timed} annotations from bean methods. | ||
* | ||
* @author Phillip Webb | ||
* @since 6.0.0 | ||
*/ | ||
public final class TimedAnnotations { | ||
|
||
private static final Map<AnnotatedElement, Set<Timed>> cache = new ConcurrentReferenceHashMap<>(); | ||
|
||
private TimedAnnotations() { | ||
} | ||
|
||
/** | ||
* Return {@link Timed} annotations that should be used for the given {@code method} | ||
* and {@code type}. | ||
* @param method the source method | ||
* @param type the source type | ||
* @return the {@link Timed} annotations to use or an empty set | ||
*/ | ||
public static Set<Timed> get(Method method, Class<?> type) { | ||
Set<Timed> methodAnnotations = findTimedAnnotations(method); | ||
if (!methodAnnotations.isEmpty()) { | ||
return methodAnnotations; | ||
} | ||
return findTimedAnnotations(type); | ||
} | ||
|
||
private static Set<Timed> findTimedAnnotations(AnnotatedElement element) { | ||
if (element == null) { | ||
return Collections.emptySet(); | ||
} | ||
Set<Timed> result = cache.get(element); | ||
if (result != null) { | ||
return result; | ||
} | ||
MergedAnnotations annotations = MergedAnnotations.from(element); | ||
result = (!annotations.isPresent(Timed.class)) ? Collections.emptySet() | ||
: annotations.stream(Timed.class).collect(MergedAnnotationCollectors.toAnnotationSet()); | ||
cache.put(element, result); | ||
return result; | ||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
133 changes: 133 additions & 0 deletions
133
...test/src/test/java/org/springframework/test/web/servlet/ObservabilityPlaygroundTests.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,133 @@ | ||
/* | ||
* Copyright 2002-2022 the original author or 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 | ||
* | ||
* https://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 org.springframework.test.web.servlet; | ||
|
||
import java.time.Duration; | ||
import java.util.function.BiConsumer; | ||
|
||
import io.micrometer.api.annotation.Timed; | ||
import io.micrometer.api.instrument.MeterRegistry; | ||
import io.micrometer.api.instrument.Timer; | ||
import io.micrometer.api.instrument.TimerRecordingHandler; | ||
import io.micrometer.api.instrument.simple.SimpleMeterRegistry; | ||
import io.micrometer.api.instrument.transport.http.context.HttpServerHandlerContext; | ||
import io.micrometer.tracing.Tracer; | ||
import io.micrometer.tracing.test.SampleTestRunner; | ||
|
||
import org.springframework.http.ResponseEntity; | ||
import org.springframework.web.bind.annotation.GetMapping; | ||
import org.springframework.web.bind.annotation.PathVariable; | ||
import org.springframework.web.bind.annotation.RestController; | ||
import org.springframework.web.servlet.observability.DefaultWebMvcTagsProvider; | ||
import org.springframework.web.servlet.observability.WebMvcObservabilityFilter; | ||
|
||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; | ||
import static org.springframework.test.web.servlet.setup.MockMvcBuilders.standaloneSetup; | ||
|
||
/** | ||
* Just a demo to try MVC instrumentation out with Zipkin, will be deleted later. | ||
*/ | ||
public class ObservabilityPlaygroundTests extends SampleTestRunner { | ||
private final SimpleMeterRegistry meterRegistry; | ||
private final MockMvc mockMvc; | ||
|
||
public ObservabilityPlaygroundTests() { | ||
this.meterRegistry = new SimpleMeterRegistry(); | ||
this.meterRegistry.config().timerRecordingHandler(new TestTimerRecordingHandler()); | ||
this.mockMvc = standaloneSetup(new TestController()) | ||
.addFilters(new WebMvcObservabilityFilter(this.meterRegistry, new DefaultWebMvcTagsProvider(), "http.server.rq", null)) | ||
.build(); | ||
} | ||
|
||
@Override | ||
protected MeterRegistry getMeterRegistry() { | ||
return this.meterRegistry; | ||
} | ||
|
||
@Override | ||
protected SampleRunnerConfig getSampleRunnerConfig() { | ||
return SampleRunnerConfig.builder().build(); | ||
} | ||
|
||
@Override | ||
public BiConsumer<Tracer, MeterRegistry> yourCode() { | ||
return ((tracer, registry) -> { | ||
try { | ||
mockMvc.perform(get("/")); | ||
mockMvc.perform(get("/api/people/12345")); | ||
mockMvc.perform(get("/oops")); | ||
} | ||
catch (Exception e) { | ||
e.printStackTrace(); | ||
} | ||
|
||
System.out.println(meterRegistry.getMetersAsString()); | ||
}); | ||
} | ||
|
||
@Timed | ||
@RestController | ||
static class TestController { | ||
@GetMapping("/") | ||
String hello() { | ||
return "hello"; | ||
} | ||
|
||
@GetMapping("/api/people/{id}") | ||
String personById(@PathVariable String id) { | ||
return id; | ||
} | ||
|
||
@GetMapping("/oops") | ||
ResponseEntity<String> oops() { | ||
return ResponseEntity.badRequest().body("oops"); | ||
} | ||
} | ||
|
||
static class TestTimerRecordingHandler implements TimerRecordingHandler<HttpServerHandlerContext> { | ||
@Override | ||
public void onStart(Timer.Sample sample, HttpServerHandlerContext context) { | ||
System.out.println(sample + " started " + context); | ||
} | ||
|
||
@Override | ||
public void onError(Timer.Sample sample, HttpServerHandlerContext context, Throwable throwable) { | ||
System.out.println(sample + " failed " + context); | ||
} | ||
|
||
@Override | ||
public void onStop(Timer.Sample sample, HttpServerHandlerContext context, Timer timer, Duration duration) { | ||
System.out.println(sample + " stopped " + context); | ||
} | ||
|
||
@Override | ||
public void onScopeOpened(Timer.Sample sample, HttpServerHandlerContext context) { | ||
System.out.println(sample + " scope opened " + context); | ||
} | ||
|
||
@Override | ||
public void onScopeClosed(Timer.Sample sample, HttpServerHandlerContext context) { | ||
System.out.println(sample + " scope closed " + context); | ||
} | ||
|
||
@Override | ||
public boolean supportsContext(Timer.HandlerContext context) { | ||
return context instanceof HttpServerHandlerContext; | ||
} | ||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
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.
Would this class be better in
io.micrometer
? Other thanCollectionUtils
there's no direct Spring connection.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.
This is something we need to figure out. I think based on what we have right now, it would be but we need to check if we can/want to provide a different disable mechanism (for
Meter
s this mechanism is provided byMeterFilter
s but that won't work in this use-case).