diff --git a/instrumentation-api-incubator/javaagent-testing/build.gradle.kts b/instrumentation-api-incubator/javaagent-testing/build.gradle.kts new file mode 100644 index 000000000000..492d58de9b66 --- /dev/null +++ b/instrumentation-api-incubator/javaagent-testing/build.gradle.kts @@ -0,0 +1,29 @@ +plugins { + id("otel.javaagent-testing") +} + +dependencies { + testInstrumentation(project(":instrumentation:http-url-connection:javaagent")) + + testImplementation(project(":testing-common")) +} + +tasks { + test { + jvmArgs("-Dotel.instrumentation.http.client.emit-experimental-telemetry=true") + jvmArgs("-Dotel.instrumentation.http.client.url-template-rules=http://localhost:.*/hello/.*,/hello/*") + } + + val declarativeConfigTest by registering(Test::class) { + testClassesDirs = sourceSets.test.get().output.classesDirs + classpath = sourceSets.test.get().runtimeClasspath + + jvmArgs( + "-Dotel.experimental.config.file=$projectDir/src/test/resources/declarative-config.yaml" + ) + } + + check { + dependsOn(declarativeConfigTest) + } +} diff --git a/instrumentation-api-incubator/javaagent-testing/src/test/java/io/opentelemetry/instrumentation/api/incubator/semconv/http/HttpClientUrlTemplateCustomizerTest.java b/instrumentation-api-incubator/javaagent-testing/src/test/java/io/opentelemetry/instrumentation/api/incubator/semconv/http/HttpClientUrlTemplateCustomizerTest.java new file mode 100644 index 000000000000..eff3beafe79a --- /dev/null +++ b/instrumentation-api-incubator/javaagent-testing/src/test/java/io/opentelemetry/instrumentation/api/incubator/semconv/http/HttpClientUrlTemplateCustomizerTest.java @@ -0,0 +1,64 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.api.incubator.semconv.http; + +import static io.opentelemetry.semconv.incubating.UrlIncubatingAttributes.URL_TEMPLATE; +import static org.assertj.core.api.Assertions.assertThat; + +import io.opentelemetry.api.trace.SpanKind; +import io.opentelemetry.instrumentation.testing.junit.AgentInstrumentationExtension; +import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; +import io.opentelemetry.instrumentation.testing.junit.http.HttpClientTestServer; +import io.opentelemetry.sdk.trace.data.StatusData; +import java.net.HttpURLConnection; +import java.net.URI; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +class HttpClientUrlTemplateCustomizerTest { + private static HttpClientTestServer server; + + @RegisterExtension + static InstrumentationExtension testing = AgentInstrumentationExtension.create(); + + @BeforeAll + static void setUp() { + server = new HttpClientTestServer(testing.getOpenTelemetry()); + server.start(); + } + + @AfterAll + static void tearDown() { + server.stop(); + } + + @Test + void test() throws Exception { + URI uri = URI.create("http://localhost:" + server.httpPort() + "/hello/world"); + HttpURLConnection connection = (HttpURLConnection) uri.toURL().openConnection(); + connection.getInputStream().close(); + int responseCode = connection.getResponseCode(); + connection.disconnect(); + + assertThat(responseCode).isEqualTo(200); + + testing.waitAndAssertTraces( + trace -> + trace.hasSpansSatisfyingExactly( + span -> + span.hasName("GET /hello/*") + .hasNoParent() + .hasKind(SpanKind.CLIENT) + .hasAttribute(URL_TEMPLATE, "/hello/*") + .hasStatus(StatusData.unset()), + span -> + span.hasName("test-http-server") + .hasParent(trace.getSpan(0)) + .hasKind(SpanKind.SERVER))); + } +} diff --git a/instrumentation-api-incubator/javaagent-testing/src/test/resources/declarative-config.yaml b/instrumentation-api-incubator/javaagent-testing/src/test/resources/declarative-config.yaml new file mode 100644 index 000000000000..8c6c7dbe60ef --- /dev/null +++ b/instrumentation-api-incubator/javaagent-testing/src/test/resources/declarative-config.yaml @@ -0,0 +1,11 @@ +file_format: "1.0-rc.1" +propagator: + composite_list: "tracecontext" +instrumentation/development: + java: + http: + client: + emit_experimental_telemetry: true + url_template_rules: + - pattern: "http://localhost:.*/hello/.*" + template: "/hello/*" diff --git a/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/builder/internal/DefaultHttpClientInstrumenterBuilder.java b/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/builder/internal/DefaultHttpClientInstrumenterBuilder.java index 4f40eac327b7..f15578321819 100644 --- a/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/builder/internal/DefaultHttpClientInstrumenterBuilder.java +++ b/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/builder/internal/DefaultHttpClientInstrumenterBuilder.java @@ -11,11 +11,10 @@ import io.opentelemetry.context.Context; import io.opentelemetry.context.propagation.TextMapSetter; import io.opentelemetry.instrumentation.api.incubator.config.internal.CommonConfig; -import io.opentelemetry.instrumentation.api.incubator.semconv.http.HttpClientExperimentalAttributesGetter; import io.opentelemetry.instrumentation.api.incubator.semconv.http.HttpClientExperimentalMetrics; import io.opentelemetry.instrumentation.api.incubator.semconv.http.HttpClientPeerServiceAttributesExtractor; -import io.opentelemetry.instrumentation.api.incubator.semconv.http.HttpClientUrlTemplate; import io.opentelemetry.instrumentation.api.incubator.semconv.http.HttpExperimentalAttributesExtractor; +import io.opentelemetry.instrumentation.api.incubator.semconv.http.internal.HttpClientUrlTemplateUtil; import io.opentelemetry.instrumentation.api.incubator.semconv.net.PeerServiceResolver; import io.opentelemetry.instrumentation.api.instrumenter.AttributesExtractor; import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter; @@ -36,7 +35,6 @@ import java.util.List; import java.util.Objects; import java.util.function.Consumer; -import java.util.function.Function; import java.util.function.Supplier; import java.util.function.UnaryOperator; import javax.annotation.Nullable; @@ -221,19 +219,11 @@ public DefaultHttpClientInstrumenterBuilder setBuilderCustomi public Instrumenter build() { if (emitExperimentalHttpClientTelemetry) { - Function urlTemplateExtractorFunction = unused -> null; - if (attributesGetter instanceof HttpClientExperimentalAttributesGetter) { - HttpClientExperimentalAttributesGetter experimentalAttributesGetter = - (HttpClientExperimentalAttributesGetter) attributesGetter; - urlTemplateExtractorFunction = experimentalAttributesGetter::getUrlTemplate; - } - Function urlTemplateExtractor = urlTemplateExtractorFunction; Experimental.setUrlTemplateExtractor( httpSpanNameExtractorBuilder, - request -> { - String urlTemplate = HttpClientUrlTemplate.get(Context.current()); - return urlTemplate != null ? urlTemplate : urlTemplateExtractor.apply(request); - }); + request -> + HttpClientUrlTemplateUtil.getUrlTemplate( + Context.current(), request, attributesGetter)); } SpanNameExtractor spanNameExtractor = spanNameExtractorTransformer.apply(httpSpanNameExtractorBuilder.build()); diff --git a/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/http/HttpClientUrlTemplateCustomizer.java b/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/http/HttpClientUrlTemplateCustomizer.java new file mode 100644 index 000000000000..351ac56ac6e4 --- /dev/null +++ b/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/http/HttpClientUrlTemplateCustomizer.java @@ -0,0 +1,28 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.api.incubator.semconv.http; + +import io.opentelemetry.instrumentation.api.semconv.http.HttpClientAttributesGetter; +import javax.annotation.Nullable; + +/** A service provider interface (SPI) for customizing http client url template. */ +public interface HttpClientUrlTemplateCustomizer { + + /** + * Customize url template for given request. Typically, the customizer will extract full url from + * the request and apply some logic (e.g. regex matching) to generate url template. The customizer + * can choose to override existing url template or skip customization when a url template is + * already set. + * + * @param urlTemplate existing url template, can be null + * @param request current request + * @param getter request attributes getter + * @return customized url template, or null + */ + @Nullable + String customize( + @Nullable String urlTemplate, REQUEST request, HttpClientAttributesGetter getter); +} diff --git a/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/http/HttpExperimentalAttributesExtractor.java b/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/http/HttpExperimentalAttributesExtractor.java index 4ee1fe09a319..d025e089e95e 100644 --- a/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/http/HttpExperimentalAttributesExtractor.java +++ b/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/http/HttpExperimentalAttributesExtractor.java @@ -10,6 +10,7 @@ import io.opentelemetry.api.common.AttributeKey; import io.opentelemetry.api.common.AttributesBuilder; import io.opentelemetry.context.Context; +import io.opentelemetry.instrumentation.api.incubator.semconv.http.internal.HttpClientUrlTemplateUtil; import io.opentelemetry.instrumentation.api.instrumenter.AttributesExtractor; import io.opentelemetry.instrumentation.api.semconv.http.HttpClientAttributesGetter; import io.opentelemetry.instrumentation.api.semconv.http.HttpCommonAttributesGetter; @@ -47,7 +48,15 @@ private HttpExperimentalAttributesExtractor( } @Override - public void onStart(AttributesBuilder attributes, Context parentContext, REQUEST request) {} + public void onStart(AttributesBuilder attributes, Context parentContext, REQUEST request) { + if (getter instanceof HttpClientAttributesGetter) { + HttpClientAttributesGetter clientGetter = + (HttpClientAttributesGetter) getter; + String urlTemplate = + HttpClientUrlTemplateUtil.getUrlTemplate(parentContext, request, clientGetter); + internalSet(attributes, URL_TEMPLATE, urlTemplate); + } + } @Override public void onEnd( @@ -64,15 +73,6 @@ public void onEnd( Long responseBodySize = responseBodySize(request, response); internalSet(attributes, HTTP_RESPONSE_BODY_SIZE, responseBodySize); } - - String urlTemplate = HttpClientUrlTemplate.get(context); - if (urlTemplate != null) { - internalSet(attributes, URL_TEMPLATE, urlTemplate); - } else if (getter instanceof HttpClientExperimentalAttributesGetter) { - HttpClientExperimentalAttributesGetter experimentalGetter = - (HttpClientExperimentalAttributesGetter) getter; - internalSet(attributes, URL_TEMPLATE, experimentalGetter.getUrlTemplate(request)); - } } @Nullable diff --git a/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/http/internal/HttpClientUrlTemplateUtil.java b/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/http/internal/HttpClientUrlTemplateUtil.java new file mode 100644 index 000000000000..f6f6630035c6 --- /dev/null +++ b/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/http/internal/HttpClientUrlTemplateUtil.java @@ -0,0 +1,75 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.api.incubator.semconv.http.internal; + +import io.opentelemetry.context.Context; +import io.opentelemetry.instrumentation.api.incubator.semconv.http.HttpClientExperimentalAttributesGetter; +import io.opentelemetry.instrumentation.api.incubator.semconv.http.HttpClientUrlTemplate; +import io.opentelemetry.instrumentation.api.incubator.semconv.http.HttpClientUrlTemplateCustomizer; +import io.opentelemetry.instrumentation.api.internal.InstrumenterContext; +import io.opentelemetry.instrumentation.api.internal.ServiceLoaderUtil; +import io.opentelemetry.instrumentation.api.semconv.http.HttpClientAttributesGetter; +import java.util.ArrayList; +import java.util.List; +import javax.annotation.Nullable; + +/** + * This class is internal and is hence not for public use. Its APIs are unstable and can change at + * any time. + */ +public final class HttpClientUrlTemplateUtil { + + private static final List customizers = new ArrayList<>(); + + static { + for (HttpClientUrlTemplateCustomizer customizer : + ServiceLoaderUtil.load(HttpClientUrlTemplateCustomizer.class)) { + customizers.add(customizer); + } + } + + @Nullable + public static String getUrlTemplate( + Context context, REQUEST request, HttpClientAttributesGetter getter) { + // first, try to get url template from context + String urlTemplate = HttpClientUrlTemplate.get(context); + if (urlTemplate == null && getter instanceof HttpClientExperimentalAttributesGetter) { + HttpClientExperimentalAttributesGetter experimentalGetter = + (HttpClientExperimentalAttributesGetter) getter; + // next, try to get url template from getter + urlTemplate = experimentalGetter.getUrlTemplate(request); + } + + return customizeUrlTemplate(urlTemplate, request, getter); + } + + @Nullable + private static String customizeUrlTemplate( + @Nullable String urlTemplate, + REQUEST request, + HttpClientAttributesGetter getter) { + if (customizers.isEmpty()) { + return urlTemplate; + } + + // we cache the computation in InstrumenterContext because url template is used by both + // HttpSpanNameExtractor and HttpExperimentalAttributesExtractor + return InstrumenterContext.computeIfAbsent( + "url.template", + unused -> { + for (HttpClientUrlTemplateCustomizer customizer : customizers) { + String result = customizer.customize(urlTemplate, request, getter); + if (result != null) { + return result; + } + } + + return urlTemplate; + }); + } + + private HttpClientUrlTemplateUtil() {} +} diff --git a/instrumentation-api-incubator/src/test/java/io/opentelemetry/instrumentation/api/incubator/semconv/http/HttpExperimentalAttributesExtractorTest.java b/instrumentation-api-incubator/src/test/java/io/opentelemetry/instrumentation/api/incubator/semconv/http/HttpExperimentalAttributesExtractorTest.java index b4f4fa886829..9ec4fe24d8f2 100644 --- a/instrumentation-api-incubator/src/test/java/io/opentelemetry/instrumentation/api/incubator/semconv/http/HttpExperimentalAttributesExtractorTest.java +++ b/instrumentation-api-incubator/src/test/java/io/opentelemetry/instrumentation/api/incubator/semconv/http/HttpExperimentalAttributesExtractorTest.java @@ -60,7 +60,7 @@ void runTest( AttributesBuilder attributes = Attributes.builder(); extractor.onStart(attributes, Context.root(), "request"); - assertThat(attributes.build()).isEmpty(); + assertThat(attributes.build().asMap()).containsExactlyInAnyOrderEntriesOf(expected); extractor.onEnd(attributes, Context.root(), "request", "response", null); Map, Object> expectedAttributes = new HashMap<>(expected); diff --git a/javaagent-tooling/src/main/java/io/opentelemetry/javaagent/tooling/instrumentation/http/RegexUrlTemplateCustomizer.java b/javaagent-tooling/src/main/java/io/opentelemetry/javaagent/tooling/instrumentation/http/RegexUrlTemplateCustomizer.java new file mode 100644 index 000000000000..fa00e2b3520a --- /dev/null +++ b/javaagent-tooling/src/main/java/io/opentelemetry/javaagent/tooling/instrumentation/http/RegexUrlTemplateCustomizer.java @@ -0,0 +1,46 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.tooling.instrumentation.http; + +import static io.opentelemetry.javaagent.tooling.instrumentation.http.UrlTemplateRules.getRules; + +import com.google.auto.service.AutoService; +import io.opentelemetry.instrumentation.api.incubator.semconv.http.HttpClientUrlTemplateCustomizer; +import io.opentelemetry.instrumentation.api.semconv.http.HttpClientAttributesGetter; +import io.opentelemetry.javaagent.tooling.instrumentation.http.UrlTemplateRules.Rule; +import java.util.regex.Pattern; +import javax.annotation.Nullable; + +@AutoService(HttpClientUrlTemplateCustomizer.class) +public final class RegexUrlTemplateCustomizer implements HttpClientUrlTemplateCustomizer { + + @Override + @Nullable + public String customize( + @Nullable String urlTemplate, + REQUEST request, + HttpClientAttributesGetter getter) { + String url = getter.getUrlFull(request); + if (url == null) { + return null; + } + + for (Rule rule : getRules()) { + if (urlTemplate != null && !rule.getOverride()) { + continue; + } + + Pattern pattern = rule.getPattern(); + // to generate the url template, we apply the regex replacement on the full url + String result = pattern.matcher(url).replaceFirst(rule.getReplacement()); + if (!url.equals(result)) { + return result; + } + } + + return null; + } +} diff --git a/javaagent-tooling/src/main/java/io/opentelemetry/javaagent/tooling/instrumentation/http/RegexUrlTemplateCustomizerInitializer.java b/javaagent-tooling/src/main/java/io/opentelemetry/javaagent/tooling/instrumentation/http/RegexUrlTemplateCustomizerInitializer.java new file mode 100644 index 000000000000..e234920e553a --- /dev/null +++ b/javaagent-tooling/src/main/java/io/opentelemetry/javaagent/tooling/instrumentation/http/RegexUrlTemplateCustomizerInitializer.java @@ -0,0 +1,106 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.tooling.instrumentation.http; + +import static io.opentelemetry.api.incubator.config.DeclarativeConfigProperties.empty; +import static java.util.Collections.emptyList; +import static java.util.logging.Level.WARNING; + +import com.google.auto.service.AutoService; +import io.opentelemetry.api.incubator.config.DeclarativeConfigProperties; +import io.opentelemetry.instrumentation.api.incubator.config.internal.InstrumentationConfig; +import io.opentelemetry.javaagent.bootstrap.internal.AgentInstrumentationConfig; +import io.opentelemetry.javaagent.tooling.BeforeAgentListener; +import io.opentelemetry.sdk.autoconfigure.AutoConfiguredOpenTelemetrySdk; +import java.util.logging.Logger; +import java.util.regex.Pattern; +import java.util.regex.PatternSyntaxException; + +@AutoService(BeforeAgentListener.class) +public final class RegexUrlTemplateCustomizerInitializer implements BeforeAgentListener { + private static final Logger logger = + Logger.getLogger(RegexUrlTemplateCustomizerInitializer.class.getName()); + + @Override + public void beforeAgent(AutoConfiguredOpenTelemetrySdk autoConfiguredOpenTelemetrySdk) { + InstrumentationConfig config = AgentInstrumentationConfig.get(); + // url template is emitted only when http client experimental telemetry is enabled + boolean urlTemplateEnabled = + config.getBoolean("otel.instrumentation.http.client.emit-experimental-telemetry", false); + if (!urlTemplateEnabled) { + return; + } + if (config.isDeclarative()) { + DeclarativeConfigProperties configuration = + config.getDeclarativeConfig("http").getStructured("client", empty()); + configuration + .getStructuredList("url_template_rules", emptyList()) + .forEach( + rule -> { + String patternString = rule.getString("pattern", ""); + String template = rule.getString("template", ""); + if (patternString.isEmpty() || template.isEmpty()) { + return; + } + boolean override = rule.getBoolean("override", false); + Pattern pattern = toPattern(patternString); + if (pattern != null) { + UrlTemplateRules.addRule(pattern, template, override); + } + }); + } else { + String rules = config.getString("otel.instrumentation.http.client.url-template-rules"); + if (rules != null && !rules.isEmpty()) { + parse(rules); + } + } + } + + // visible for testing + static void parse(String rules) { + // We are expecting a semicolon-separated list of rules in the form + // pattern,replacement[,override] + // Where pattern is a regex, replacement is the url template to use when the pattern matches, + // override is an optional boolean (default false) indicating whether this rule should override + // an existing url template. The pattern should match the entire url. + for (String rule : rules.split(";")) { + String[] parts = rule.split(","); + if (parts.length != 2 && parts.length != 3) { + logger.log( + WARNING, "Invalid http client url template customization rule \"" + rule + "\"."); + continue; + } + + Pattern pattern = toPattern(parts[0].trim()); + if (pattern == null) { + continue; + } + UrlTemplateRules.addRule( + pattern, parts[1].trim(), parts.length == 3 && Boolean.parseBoolean(parts[2].trim())); + } + } + + private static Pattern toPattern(String patternString) { + try { + // ensure that pattern matches the whole url + if (!patternString.startsWith("^")) { + patternString = "^" + patternString; + } + if (!patternString.endsWith("$")) { + patternString = patternString + "$"; + } + return Pattern.compile(patternString); + } catch (PatternSyntaxException exception) { + logger.log( + WARNING, + "Invalid pattern in http client url template customization rule \"" + + patternString + + "\".", + exception); + return null; + } + } +} diff --git a/javaagent-tooling/src/main/java/io/opentelemetry/javaagent/tooling/instrumentation/http/UrlTemplateRules.java b/javaagent-tooling/src/main/java/io/opentelemetry/javaagent/tooling/instrumentation/http/UrlTemplateRules.java new file mode 100644 index 000000000000..66071bec0a84 --- /dev/null +++ b/javaagent-tooling/src/main/java/io/opentelemetry/javaagent/tooling/instrumentation/http/UrlTemplateRules.java @@ -0,0 +1,57 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.tooling.instrumentation.http; + +import static java.util.logging.Level.FINE; + +import java.util.ArrayList; +import java.util.List; +import java.util.logging.Logger; +import java.util.regex.Pattern; + +final class UrlTemplateRules { + private static final Logger logger = Logger.getLogger(UrlTemplateRules.class.getName()); + private static final List rules = new ArrayList<>(); + + static List getRules() { + return rules; + } + + static void addRule(Pattern pattern, String replacement, boolean override) { + logger.log( + FINE, + "Adding http client url template customization rule: pattern=\"{0}\", replacement=\"{1}\", override={2}.", + new Object[] {pattern, replacement, override}); + + rules.add(new Rule(pattern, replacement, override)); + } + + static final class Rule { + private final Pattern pattern; + private final String replacement; + private final boolean override; + + Rule(Pattern pattern, String replacement, boolean override) { + this.pattern = pattern; + this.replacement = replacement; + this.override = override; + } + + Pattern getPattern() { + return pattern; + } + + String getReplacement() { + return replacement; + } + + boolean getOverride() { + return override; + } + } + + private UrlTemplateRules() {} +} diff --git a/javaagent-tooling/src/test/java/io/opentelemetry/javaagent/tooling/instrumentation/http/RegexUrlTemplateParserTest.java b/javaagent-tooling/src/test/java/io/opentelemetry/javaagent/tooling/instrumentation/http/RegexUrlTemplateParserTest.java new file mode 100644 index 000000000000..bcb1a02a69f6 --- /dev/null +++ b/javaagent-tooling/src/test/java/io/opentelemetry/javaagent/tooling/instrumentation/http/RegexUrlTemplateParserTest.java @@ -0,0 +1,63 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.tooling.instrumentation.http; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; + +class RegexUrlTemplateParserTest { + + @AfterEach + void reset() { + UrlTemplateRules.getRules().clear(); + } + + @ParameterizedTest + @CsvSource( + value = { + "a", "a,", "a;b", "a,;b", "(a,b", + }, + delimiter = '|') + void invalid(String rules) { + RegexUrlTemplateCustomizerInitializer.parse(rules); + assertThat(UrlTemplateRules.getRules()).isEmpty(); + } + + @Test + void parse() { + RegexUrlTemplateCustomizerInitializer.parse( + "pattern1,replacement1;" + + "pattern2,replacement2,false;" + + "pattern3,replacement3,true;" + + " pattern4 , replacement4 , true ;"); + assertThat(UrlTemplateRules.getRules()) + .satisfiesExactly( + rule -> { + assertThat(rule.getPattern().pattern()).isEqualTo("^pattern1$"); + assertThat(rule.getReplacement()).isEqualTo("replacement1"); + assertThat(rule.getOverride()).isFalse(); + }, + rule -> { + assertThat(rule.getPattern().pattern()).isEqualTo("^pattern2$"); + assertThat(rule.getReplacement()).isEqualTo("replacement2"); + assertThat(rule.getOverride()).isFalse(); + }, + rule -> { + assertThat(rule.getPattern().pattern()).isEqualTo("^pattern3$"); + assertThat(rule.getReplacement()).isEqualTo("replacement3"); + assertThat(rule.getOverride()).isTrue(); + }, + rule -> { + assertThat(rule.getPattern().pattern()).isEqualTo("^pattern4$"); + assertThat(rule.getReplacement()).isEqualTo("replacement4"); + assertThat(rule.getOverride()).isTrue(); + }); + } +} diff --git a/settings.gradle.kts b/settings.gradle.kts index d97ed912157c..887ca65a29c2 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -127,6 +127,7 @@ include(":bom") include(":bom-alpha") include(":instrumentation-api") include(":instrumentation-api-incubator") +include(":instrumentation-api-incubator:javaagent-testing") include(":instrumentation-annotations") include(":instrumentation-annotations-support") include(":instrumentation-annotations-support-testing")