Skip to content

Commit

Permalink
Migrate WebMvcTags to ServerRequestObservationConvention (#586)
Browse files Browse the repository at this point in the history
* Initial test case

* WIP

* Apply suggestions from code review

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>

* WIP

* Apply suggestions from code review

* WIP

* WIP

* WIP

* WIP

* WIP

* Suggestions

* WIP

* Working setup

* Apply suggestions from code review

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>

* Format and apply suggestion

* More progress

* Naming and imports

* Adopt classpathFromResources & add tomcat-embedded-core

* Fix most warnings and issues

* Fix type issues and warnings

* More edge cases

* More edge cases, extra test coverage

* Remove jetbrains notnull

* Reduce document example test case and add additional test cases for edge cases

* Apply review feedback

* Apply formatter to tests

* Reduce imports and classpath
Explicitly add micrometer-observation jar

* Use method matcher

* By default use high cardinality keys as low might cause issues with ingesting metrics/traces

* Consistently use the same version suffix for classpath resources

* Demonstrate failure on any empty method

* Fix class cast issue

---------

Co-authored-by: Tim te Beek <[email protected]>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
  • Loading branch information
3 people authored Oct 9, 2024
1 parent 0d5e2d8 commit 9d3a3f0
Show file tree
Hide file tree
Showing 8 changed files with 618 additions and 0 deletions.
7 changes: 7 additions & 0 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,11 @@ recipeDependencies {

parserClasspath("org.apache.httpcomponents.core5:httpcore5:5.1.+")
parserClasspath("org.apache.httpcomponents.client5:httpclient5:5.1.+")

parserClasspath("jakarta.servlet:jakarta.servlet-api:6.1.+")
parserClasspath("io.micrometer:micrometer-commons:1.11.+")
parserClasspath("io.micrometer:micrometer-core:1.11.+")
parserClasspath("io.micrometer:micrometer-observation:1.11.+")
}

val rewriteVersion = rewriteRecipe.rewriteVersion.get()
Expand Down Expand Up @@ -237,6 +242,7 @@ dependencies {
"testWithSpringBoot_2_7RuntimeOnly"("jakarta.xml.bind:jakarta.xml.bind-api:2.3.3")

"testWithSpringBoot_3_0RuntimeOnly"("org.springframework.boot:spring-boot-starter:3.0.+")
"testWithSpringBoot_3_0RuntimeOnly"("org.springframework.boot:spring-boot-starter-actuator:3.0.+")
"testWithSpringBoot_3_0RuntimeOnly"("org.springframework.boot:spring-boot-starter-test:3.0.+")
"testWithSpringBoot_3_0RuntimeOnly"("org.springframework:spring-context:6.0.+")
"testWithSpringBoot_3_0RuntimeOnly"("org.springframework:spring-web:6.0.+")
Expand All @@ -249,6 +255,7 @@ dependencies {
"testWithSpringBoot_3_0RuntimeOnly"("org.springframework.security:spring-security-config:6.0.+")
"testWithSpringBoot_3_0RuntimeOnly"("org.springframework.security:spring-security-web:6.0.+")
"testWithSpringBoot_3_0RuntimeOnly"("org.springframework.security:spring-security-ldap:6.0.+")
"testWithSpringBoot_3_0RuntimeOnly"("jakarta.servlet:jakarta.servlet-api:6.1.+")

"testWithSpringBoot_3_2RuntimeOnly"("org.springframework.boot:spring-boot-starter:3.2.+")
"testWithSpringBoot_3_2RuntimeOnly"("org.springframework.boot:spring-boot-starter-test:3.2.+")
Expand Down

Large diffs are not rendered by default.

Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
1 change: 1 addition & 0 deletions src/main/resources/META-INF/rewrite/spring-boot-30.yml
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ recipeList:
- org.openrewrite.java.spring.boot3.MigrateThymeleafDependencies
- org.openrewrite.java.spring.boot3.MigrateDropWizardDependencies
- org.openrewrite.java.spring.boot3.RemoveSolrAutoConfigurationExclude
- org.openrewrite.java.spring.boot3.MigrateWebMvcTagsToObservationConvention
- org.openrewrite.java.spring.batch.SpringBatch4To5Migration
- org.openrewrite.java.spring.framework.UpgradeSpringFramework_6_0
- org.openrewrite.java.spring.kafka.UpgradeSpringKafka_3_0
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,314 @@
/*
* Copyright 2024 the original author or authors.
* <p>
* 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
* <p>
* https://www.apache.org/licenses/LICENSE-2.0
* <p>
* 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.openrewrite.java.spring.boot3;

import org.junit.jupiter.api.Test;
import org.openrewrite.DocumentExample;
import org.openrewrite.java.JavaParser;
import org.openrewrite.test.RecipeSpec;
import org.openrewrite.test.RewriteTest;

import static org.openrewrite.java.Assertions.java;

class MigrateWebMvcTagsToObservationConventionTest implements RewriteTest {

@Override
public void defaults(RecipeSpec spec) {
spec.recipe(new MigrateWebMvcTagsToObservationConvention())
.parser(JavaParser.fromJavaVersion().classpath(
"micrometer-core",
"spring-boot",
"spring-context",
"spring-beans",
"spring-web",
"jakarta.servlet-api"));
}

@DocumentExample
@Test
void shouldMigrateWebMvcTagsProviderToDefaultServerRequestObservationConvention() {
//language=java
rewriteRun(
java(
"""
import io.micrometer.core.instrument.Tag;
import io.micrometer.core.instrument.Tags;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.boot.actuate.metrics.web.servlet.WebMvcTags;
import org.springframework.boot.actuate.metrics.web.servlet.WebMvcTagsProvider;
class CustomWebMvcTagsProvider implements WebMvcTagsProvider {
@Override
public Iterable<Tag> getTags(HttpServletRequest request, HttpServletResponse response, Object handler, Throwable exception) {
Tags tags = Tags.of(WebMvcTags.method(request), WebMvcTags.uri(request, response), WebMvcTags.status(response), WebMvcTags.outcome(response));
String customHeader = request.getHeader("X-Custom-Header");
if (customHeader != null) {
tags = tags.and("custom.header", customHeader);
}
return tags;
}
}
""",
"""
import io.micrometer.common.KeyValue;
import io.micrometer.common.KeyValues;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.http.server.observation.DefaultServerRequestObservationConvention;
import org.springframework.http.server.observation.ServerRequestObservationContext;
class CustomWebMvcTagsProvider extends DefaultServerRequestObservationConvention {
@Override
public KeyValues getHighCardinalityKeyValues(ServerRequestObservationContext context) {
HttpServletRequest request = context.getCarrier();
KeyValues values = super.getHighCardinalityKeyValues(context);
String customHeader = request.getHeader("X-Custom-Header");
if (customHeader != null) {
values.and(KeyValue.of("custom.header", customHeader));
}
return values;
}
}
"""
)
);
}

@Test
void shouldMigrateTags_Of() {
//language=java
rewriteRun(
java(
"""
import io.micrometer.core.instrument.Tag;
import io.micrometer.core.instrument.Tags;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.boot.actuate.metrics.web.servlet.WebMvcTags;
import org.springframework.boot.actuate.metrics.web.servlet.WebMvcTagsProvider;
class CustomWebMvcTagsProvider implements WebMvcTagsProvider {
Tags staticTags = Tags.of("a", "b", "c", "d");
Tag staticTag = Tag.of("a", "b");
@Override
public Iterable<Tag> getTags(HttpServletRequest request, HttpServletResponse response, Object handler, Throwable exception) {
Tags tags = Tags.of(WebMvcTags.method(request), WebMvcTags.uri(request, response), WebMvcTags.status(response), WebMvcTags.outcome(response));
tags = Tags.of("a", "b");
tags = Tags.of("a", "b", "c", "d");
tags = Tags.of(Tag.of("a", "b"), staticTag);
tags = Tags.of(staticTags);
return tags;
}
}
""",
"""
import io.micrometer.common.KeyValue;
import io.micrometer.common.KeyValues;
import io.micrometer.core.instrument.Tag;
import io.micrometer.core.instrument.Tags;
import org.springframework.http.server.observation.DefaultServerRequestObservationConvention;
import org.springframework.http.server.observation.ServerRequestObservationContext;
class CustomWebMvcTagsProvider extends DefaultServerRequestObservationConvention {
Tags staticTags = Tags.of("a", "b", "c", "d");
Tag staticTag = Tag.of("a", "b");
@Override
public KeyValues getHighCardinalityKeyValues(ServerRequestObservationContext context) {
KeyValues values = super.getHighCardinalityKeyValues(context);
values.and(KeyValue.of("a", "b"));
values.and(KeyValue.of("a", "b"), KeyValue.of("c", "d"));
values.and(KeyValue.of("a", "b"), KeyValue.of(staticTag.getKey(), staticTag.getValue()));
for (Tag tag : staticTags) {
values.and(KeyValue.of(tag.getKey(), tag.getValue()));
}
return values;
}
}
"""
)
);
}

@Test
void shouldMigrateTags_And() {
//language=java
rewriteRun(
java(
"""
import io.micrometer.core.instrument.Tag;
import io.micrometer.core.instrument.Tags;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.boot.actuate.metrics.web.servlet.WebMvcTags;
import org.springframework.boot.actuate.metrics.web.servlet.WebMvcTagsProvider;
class CustomWebMvcTagsProvider implements WebMvcTagsProvider {
Tags staticTags = Tags.of("a", "b", "c", "d");
Tag staticTag = Tag.of("a", "b");
@Override
public Iterable<Tag> getTags(HttpServletRequest request, HttpServletResponse response, Object handler, Throwable exception) {
Tags tags = Tags.of(WebMvcTags.method(request), WebMvcTags.uri(request, response), WebMvcTags.status(response), WebMvcTags.outcome(response));
String customHeader = request.getHeader("X-Custom-Header");
if (customHeader != null) {
tags = tags.and("custom.header", customHeader);
}
if (response.getStatus() >= 400) {
tags = tags.and("error", "true");
}
tags = tags.and("a", "b", "c", "d");
tags = Tags.and(Tag.of("a", "b"), staticTag);
tags = tags.and(staticTags);
return tags;
}
}
""",
"""
import io.micrometer.common.KeyValue;
import io.micrometer.common.KeyValues;
import io.micrometer.core.instrument.Tag;
import io.micrometer.core.instrument.Tags;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.http.server.observation.DefaultServerRequestObservationConvention;
import org.springframework.http.server.observation.ServerRequestObservationContext;
class CustomWebMvcTagsProvider extends DefaultServerRequestObservationConvention {
Tags staticTags = Tags.of("a", "b", "c", "d");
Tag staticTag = Tag.of("a", "b");
@Override
public KeyValues getHighCardinalityKeyValues(ServerRequestObservationContext context) {
HttpServletRequest request = context.getCarrier();
HttpServletResponse response = context.getResponse();
KeyValues values = super.getHighCardinalityKeyValues(context);
String customHeader = request.getHeader("X-Custom-Header");
if (customHeader != null) {
values.and(KeyValue.of("custom.header", customHeader));
}
if (response.getStatus() >= 400) {
values.and(KeyValue.of("error", "true"));
}
values.and(KeyValue.of("a", "b"), KeyValue.of("c", "d"));
values.and(KeyValue.of("a", "b"), KeyValue.of(staticTag.getKey(), staticTag.getValue()));
for (Tag tag : staticTags) {
values.and(KeyValue.of(tag.getKey(), tag.getValue()));
}
return values;
}
}
"""
)
);
}

@Test
void shouldMigrateReturnTags_Of() {
//language=java
rewriteRun(
java(
"""
import io.micrometer.core.instrument.Tag;
import io.micrometer.core.instrument.Tags;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.boot.actuate.metrics.web.servlet.WebMvcTagsProvider;
class CustomWebMvcTagsProvider implements WebMvcTagsProvider {
@Override
public Iterable<Tag> getTags(HttpServletRequest request, HttpServletResponse response, Object handler, Throwable exception) {
return Tags.of(Tag.of("a", "b"));
}
}
""",
"""
import io.micrometer.common.KeyValue;
import io.micrometer.common.KeyValues;
import org.springframework.http.server.observation.DefaultServerRequestObservationConvention;
import org.springframework.http.server.observation.ServerRequestObservationContext;
class CustomWebMvcTagsProvider extends DefaultServerRequestObservationConvention {
@Override
public KeyValues getHighCardinalityKeyValues(ServerRequestObservationContext context) {
KeyValues values = super.getHighCardinalityKeyValues(context);
values.and(KeyValue.of("a", "b"));
return values;
}
}
"""
)
);
}

@Test
void shouldNotFailOnEmptyMethod() {
//language=java
rewriteRun(
java(
"""
import io.micrometer.core.instrument.Tag;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.boot.actuate.metrics.web.servlet.WebMvcTagsProvider;
import java.util.Collections;
class CustomWebMvcTagsProvider implements WebMvcTagsProvider {
@Override
public Iterable<Tag> getTags(HttpServletRequest request, HttpServletResponse response, Object handler, Throwable exception) {
return Collections.emptyList();
}
void shouldNotFailOnEmptyMethod() {}
}
""",
"""
import io.micrometer.common.KeyValues;
import org.springframework.http.server.observation.DefaultServerRequestObservationConvention;
import org.springframework.http.server.observation.ServerRequestObservationContext;
import java.util.Collections;
class CustomWebMvcTagsProvider extends DefaultServerRequestObservationConvention {
@Override
public KeyValues getHighCardinalityKeyValues(ServerRequestObservationContext context) {
KeyValues values = super.getHighCardinalityKeyValues(context);
return values;
}
void shouldNotFailOnEmptyMethod() {}
}
"""
)
);
}
}

0 comments on commit 9d3a3f0

Please sign in to comment.