From 1947c0fe4239bccebfffca7580382101525a9b63 Mon Sep 17 00:00:00 2001 From: Mateusz Rzeszutek Date: Thu, 6 Jul 2023 14:40:52 +0200 Subject: [PATCH] Add support for schemaUrls auto-computed from `AttributesExtrator`s (#8864) --- .../api/instrumenter/InstrumenterBuilder.java | 37 +++++++++ .../api/internal/SchemaUrlProvider.java | 22 ++++++ .../api/instrumenter/InstrumenterTest.java | 76 ++++++++++++++++++- 3 files changed, 134 insertions(+), 1 deletion(-) create mode 100644 instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/internal/SchemaUrlProvider.java diff --git a/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/InstrumenterBuilder.java b/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/InstrumenterBuilder.java index 25dbd6ebeb2a..ff1f5d08539a 100644 --- a/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/InstrumenterBuilder.java +++ b/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/InstrumenterBuilder.java @@ -6,6 +6,7 @@ package io.opentelemetry.instrumentation.api.instrumenter; import static java.util.Objects.requireNonNull; +import static java.util.logging.Level.WARNING; import com.google.errorprone.annotations.CanIgnoreReturnValue; import io.opentelemetry.api.OpenTelemetry; @@ -22,11 +23,13 @@ import io.opentelemetry.instrumentation.api.internal.EmbeddedInstrumentationProperties; import io.opentelemetry.instrumentation.api.internal.InstrumenterBuilderAccess; import io.opentelemetry.instrumentation.api.internal.InstrumenterUtil; +import io.opentelemetry.instrumentation.api.internal.SchemaUrlProvider; import io.opentelemetry.instrumentation.api.internal.SpanKey; import io.opentelemetry.instrumentation.api.internal.SpanKeyProvider; import java.util.ArrayList; import java.util.List; import java.util.Set; +import java.util.logging.Logger; import java.util.stream.Collectors; import java.util.stream.Stream; import javax.annotation.Nullable; @@ -39,6 +42,8 @@ */ public final class InstrumenterBuilder { + private static final Logger logger = Logger.getLogger(InstrumenterBuilder.class.getName()); + private static final SpanSuppressionStrategy spanSuppressionStrategy = SpanSuppressionStrategy.fromConfig( ConfigPropertiesUtil.getString( @@ -285,6 +290,7 @@ Tracer buildTracer() { if (instrumentationVersion != null) { tracerBuilder.setInstrumentationVersion(instrumentationVersion); } + String schemaUrl = getSchemaUrl(); if (schemaUrl != null) { tracerBuilder.setSchemaUrl(schemaUrl); } @@ -305,6 +311,7 @@ List buildOperationListeners() { if (instrumentationVersion != null) { meterBuilder.setInstrumentationVersion(instrumentationVersion); } + String schemaUrl = getSchemaUrl(); if (schemaUrl != null) { meterBuilder.setSchemaUrl(schemaUrl); } @@ -316,6 +323,36 @@ List buildOperationListeners() { return listeners; } + @Nullable + private String getSchemaUrl() { + // url set explicitly overrides url computed using attributes extractors + if (schemaUrl != null) { + return schemaUrl; + } + Set computedSchemaUrls = + attributesExtractors.stream() + .filter(SchemaUrlProvider.class::isInstance) + .map(SchemaUrlProvider.class::cast) + .flatMap( + provider -> { + String url = provider.internalGetSchemaUrl(); + return url == null ? Stream.of() : Stream.of(url); + }) + .collect(Collectors.toSet()); + switch (computedSchemaUrls.size()) { + case 0: + return null; + case 1: + return computedSchemaUrls.iterator().next(); + default: + logger.log( + WARNING, + "Multiple schemaUrls were detected: {0}. The built Instrumenter will have no schemaUrl assigned.", + computedSchemaUrls); + return null; + } + } + SpanSuppressor buildSpanSuppressor() { return spanSuppressionStrategy.create(getSpanKeysFromAttributesExtractors()); } diff --git a/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/internal/SchemaUrlProvider.java b/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/internal/SchemaUrlProvider.java new file mode 100644 index 000000000000..d60878294abb --- /dev/null +++ b/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/internal/SchemaUrlProvider.java @@ -0,0 +1,22 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.api.internal; + +import io.opentelemetry.instrumentation.api.instrumenter.AttributesExtractor; +import javax.annotation.Nullable; + +/** + * Returns the OpenTelemetry schema URL associated with the {@link AttributesExtractor} that + * implements this interface. + * + *

This class is internal and is hence not for public use. Its APIs are unstable and can change + * at any time. + */ +public interface SchemaUrlProvider { + + @Nullable + String internalGetSchemaUrl(); +} diff --git a/instrumentation-api/src/test/java/io/opentelemetry/instrumentation/api/instrumenter/InstrumenterTest.java b/instrumentation-api/src/test/java/io/opentelemetry/instrumentation/api/instrumenter/InstrumenterTest.java index 3774c19a9fd2..fb9ae8622d8d 100644 --- a/instrumentation-api/src/test/java/io/opentelemetry/instrumentation/api/instrumenter/InstrumenterTest.java +++ b/instrumentation-api/src/test/java/io/opentelemetry/instrumentation/api/instrumenter/InstrumenterTest.java @@ -25,6 +25,7 @@ import io.opentelemetry.context.Context; import io.opentelemetry.context.ContextKey; import io.opentelemetry.context.propagation.TextMapGetter; +import io.opentelemetry.instrumentation.api.internal.SchemaUrlProvider; import io.opentelemetry.instrumentation.api.internal.SpanKey; import io.opentelemetry.instrumentation.api.internal.SpanKeyProvider; import io.opentelemetry.sdk.common.InstrumentationScopeInfo; @@ -113,6 +114,30 @@ public void onEnd( } } + static class AttributesExtractorWithSchemaUrl + implements AttributesExtractor, Map>, SchemaUrlProvider { + + @Override + public void onStart( + AttributesBuilder attributes, Context parentContext, Map request) { + attributes.put("key", "value"); + } + + @Override + public void onEnd( + AttributesBuilder attributes, + Context context, + Map request, + @Nullable Map response, + @Nullable Throwable error) {} + + @Nullable + @Override + public String internalGetSchemaUrl() { + return "schemaUrl from extractor"; + } + } + static class LinksExtractor implements SpanLinksExtractor> { @Override @@ -583,11 +608,60 @@ void instrumentationVersion_custom() { } @Test - void schemaUrl() { + void schemaUrl_setExplicitly() { + Instrumenter, Map> instrumenter = + Instrumenter., Map>builder( + otelTesting.getOpenTelemetry(), "test", name -> "span") + .setSchemaUrl("https://opentelemetry.io/schemas/1.0.0") + .buildInstrumenter(); + + Context context = instrumenter.start(Context.root(), emptyMap()); + assertThat(Span.fromContext(context)).isNotNull(); + + instrumenter.end(context, emptyMap(), emptyMap(), null); + + InstrumentationScopeInfo expectedLibraryInfo = + InstrumentationScopeInfo.builder("test") + .setSchemaUrl("https://opentelemetry.io/schemas/1.0.0") + .build(); + otelTesting + .assertTraces() + .hasTracesSatisfyingExactly( + trace -> + trace.hasSpansSatisfyingExactly( + span -> span.hasName("span").hasInstrumentationScopeInfo(expectedLibraryInfo))); + } + + @Test + void schemaUrl_computedFromExtractors() { + Instrumenter, Map> instrumenter = + Instrumenter., Map>builder( + otelTesting.getOpenTelemetry(), "test", name -> "span") + .addAttributesExtractor(new AttributesExtractorWithSchemaUrl()) + .buildInstrumenter(); + + Context context = instrumenter.start(Context.root(), emptyMap()); + assertThat(Span.fromContext(context)).isNotNull(); + + instrumenter.end(context, emptyMap(), emptyMap(), null); + + InstrumentationScopeInfo expectedLibraryInfo = + InstrumentationScopeInfo.builder("test").setSchemaUrl("schemaUrl from extractor").build(); + otelTesting + .assertTraces() + .hasTracesSatisfyingExactly( + trace -> + trace.hasSpansSatisfyingExactly( + span -> span.hasName("span").hasInstrumentationScopeInfo(expectedLibraryInfo))); + } + + @Test + void schemaUrl_schemaSetExplicitlyOverridesSchemaComputedFromExtractors() { Instrumenter, Map> instrumenter = Instrumenter., Map>builder( otelTesting.getOpenTelemetry(), "test", name -> "span") .setSchemaUrl("https://opentelemetry.io/schemas/1.0.0") + .addAttributesExtractor(new AttributesExtractorWithSchemaUrl()) .buildInstrumenter(); Context context = instrumenter.start(Context.root(), emptyMap());