diff --git a/docs/contributing/using-instrumenter-api.md b/docs/contributing/using-instrumenter-api.md index 779c5cd4f773..61a71fd6fe58 100644 --- a/docs/contributing/using-instrumenter-api.md +++ b/docs/contributing/using-instrumenter-api.md @@ -116,6 +116,31 @@ The `builder()` method accepts three arguments: An `Instrumenter` can be built from several smaller components. The following subsections describe all interfaces that can be used to customize an `Instrumenter`. +### Set the instrumentation version and OpenTelemetry schema URL + +By setting the instrumentation library version, you let users identify which version of your +instrumentation produced the telemetry. Make sure you always provide the version to +the `Instrumenter`. You can do this in two ways: + +* By calling the `setInstrumentationVersion()` method on the `InstrumenterBuilder`. +* By making sure that the JAR file with your instrumentation library contains a properties file in + the `META-INF/io/opentelemetry/instrumentation/` directory. You must name the file + `${instrumentationName}.properties`, where `${instrumentationName}` is the name of the + instrumentation library passed to the `Instrumenter#builder()` method. The file must contain a + single property, `version`. For example: + + ```properties + # META-INF/io/opentelemetry/instrumentation/my-instrumentation.properties + version=1.2.3 + ``` + + The `Instrumenter` automatically detects the properties file and determines the instrumentation + version based on its name. + +If the `Instrumenter` adheres to a specific OpenTelemetry schema, you can set the schema URL using +the `setSchemaUrl()` method on the `InstrumenterBuilder`. To learn more about the OpenTelemetry +schemas [see the Overview](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/schemas/overview.md). + ### Name the spans using the `SpanNameExtractor` A `SpanNameExtractor` is a simple functional interface that accepts the `REQUEST` type and returns diff --git a/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/Instrumenter.java b/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/Instrumenter.java index b69bed631c53..8139fb610a3b 100644 --- a/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/Instrumenter.java +++ b/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/Instrumenter.java @@ -13,7 +13,6 @@ import io.opentelemetry.api.trace.StatusCode; import io.opentelemetry.api.trace.Tracer; import io.opentelemetry.context.Context; -import io.opentelemetry.instrumentation.api.internal.EmbeddedInstrumentationProperties; import io.opentelemetry.instrumentation.api.internal.SupportabilityMetrics; import java.time.Instant; import java.util.ArrayList; @@ -59,11 +58,7 @@ public static InstrumenterBuilder builder OpenTelemetry openTelemetry, String instrumentationName, SpanNameExtractor spanNameExtractor) { - return new InstrumenterBuilder<>( - openTelemetry, - instrumentationName, - EmbeddedInstrumentationProperties.findVersion(instrumentationName), - spanNameExtractor); + return new InstrumenterBuilder<>(openTelemetry, instrumentationName, spanNameExtractor); } /** @@ -82,15 +77,19 @@ public static InstrumenterBuilder builder * io.opentelemetry.apache-httpclient-4.0}. This way, if there are different instrumentations for * different library versions it's easy to find out which instrumentations produced the telemetry * data. + * + * @deprecated Use the {@link InstrumenterBuilder#setInstrumentationVersion(String)} method + * instead. */ - // TODO: add a setInstrumentationVersion method to the builder instead + @Deprecated public static InstrumenterBuilder builder( OpenTelemetry openTelemetry, String instrumentationName, String instrumentationVersion, SpanNameExtractor spanNameExtractor) { - return new InstrumenterBuilder<>( - openTelemetry, instrumentationName, instrumentationVersion, spanNameExtractor); + return Instrumenter.builder( + openTelemetry, instrumentationName, spanNameExtractor) + .setInstrumentationVersion(instrumentationVersion); } private static final SupportabilityMetrics supportability = SupportabilityMetrics.instance(); @@ -112,19 +111,18 @@ public static InstrumenterBuilder builder Instrumenter(InstrumenterBuilder builder) { this.instrumentationName = builder.instrumentationName; - this.tracer = - builder.openTelemetry.getTracer(instrumentationName, builder.instrumentationVersion); + this.tracer = builder.buildTracer(); this.spanNameExtractor = builder.spanNameExtractor; this.spanKindExtractor = builder.spanKindExtractor; this.spanStatusExtractor = builder.spanStatusExtractor; this.spanLinksExtractors = new ArrayList<>(builder.spanLinksExtractors); this.attributesExtractors = new ArrayList<>(builder.attributesExtractors); this.contextCustomizers = new ArrayList<>(builder.contextCustomizers); - this.requestListeners = new ArrayList<>(builder.requestListeners); + this.requestListeners = builder.buildRequestListeners(); this.errorCauseExtractor = builder.errorCauseExtractor; this.timeExtractor = builder.timeExtractor; this.enabled = builder.enabled; - this.spanSuppressionStrategy = builder.getSpanSuppressionStrategy(); + this.spanSuppressionStrategy = builder.buildSpanSuppressionStrategy(); } /** 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 ea9d6ffb2792..405344b0338a 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 @@ -9,12 +9,16 @@ import io.opentelemetry.api.OpenTelemetry; import io.opentelemetry.api.metrics.Meter; +import io.opentelemetry.api.metrics.MeterBuilder; import io.opentelemetry.api.trace.SpanKind; import io.opentelemetry.api.trace.StatusCode; +import io.opentelemetry.api.trace.Tracer; +import io.opentelemetry.api.trace.TracerBuilder; import io.opentelemetry.context.Context; import io.opentelemetry.context.propagation.TextMapGetter; import io.opentelemetry.context.propagation.TextMapSetter; import io.opentelemetry.instrumentation.api.config.Config; +import io.opentelemetry.instrumentation.api.internal.EmbeddedInstrumentationProperties; import io.opentelemetry.instrumentation.api.internal.SpanKey; import io.opentelemetry.instrumentation.api.internal.SpanKeyProvider; import java.util.ArrayList; @@ -38,17 +42,18 @@ public final class InstrumenterBuilder { .getBoolean("otel.instrumentation.experimental.outgoing-span-suppression-by-type", false); final OpenTelemetry openTelemetry; - final Meter meter; final String instrumentationName; - final String instrumentationVersion; final SpanNameExtractor spanNameExtractor; final List> spanLinksExtractors = new ArrayList<>(); final List> attributesExtractors = new ArrayList<>(); final List> contextCustomizers = new ArrayList<>(); - final List requestListeners = new ArrayList<>(); + private final List requestListeners = new ArrayList<>(); + private final List requestMetrics = new ArrayList<>(); + @Nullable private String instrumentationVersion; + @Nullable private String schemaUrl = null; SpanKindExtractor spanKindExtractor = SpanKindExtractor.alwaysInternal(); SpanStatusExtractor spanStatusExtractor = SpanStatusExtractor.getDefault(); @@ -61,13 +66,34 @@ public final class InstrumenterBuilder { InstrumenterBuilder( OpenTelemetry openTelemetry, String instrumentationName, - String instrumentationVersion, SpanNameExtractor spanNameExtractor) { this.openTelemetry = openTelemetry; - this.meter = openTelemetry.getMeterProvider().get(instrumentationName); this.instrumentationName = instrumentationName; - this.instrumentationVersion = instrumentationVersion; this.spanNameExtractor = spanNameExtractor; + this.instrumentationVersion = + EmbeddedInstrumentationProperties.findVersion(instrumentationName); + } + + /** + * Sets the instrumentation version that'll be associated with all telemetry produced by this + * {@link Instrumenter}. + * + * @param instrumentationVersion is the version of the instrumentation library, not the version of + * the instrument*ed* library. + */ + public InstrumenterBuilder setInstrumentationVersion( + String instrumentationVersion) { + this.instrumentationVersion = instrumentationVersion; + return this; + } + + /** + * Sets the OpenTelemetry schema URL that'll be associated with all telemetry produced by this + * {@link Instrumenter}. + */ + public InstrumenterBuilder setSchemaUrl(String schemaUrl) { + this.schemaUrl = schemaUrl; + return this; } /** @@ -127,7 +153,7 @@ public InstrumenterBuilder addRequestListener(RequestListener /** Adds a {@link RequestMetrics} whose metrics will be recorded for request start and end. */ public InstrumenterBuilder addRequestMetrics(RequestMetrics factory) { - requestListeners.add(factory.create(meter)); + requestMetrics.add(factory); return this; } @@ -274,7 +300,44 @@ private Instrumenter newInstrumenter( return constructor.create(this); } - SpanSuppressionStrategy getSpanSuppressionStrategy() { + Tracer buildTracer() { + TracerBuilder tracerBuilder = + openTelemetry.getTracerProvider().tracerBuilder(instrumentationName); + if (instrumentationVersion != null) { + tracerBuilder.setInstrumentationVersion(instrumentationVersion); + } + if (schemaUrl != null) { + tracerBuilder.setSchemaUrl(schemaUrl); + } + return tracerBuilder.build(); + } + + List buildRequestListeners() { + // just copy the listeners list if there are no metrics registered + if (requestMetrics.isEmpty()) { + return new ArrayList<>(requestListeners); + } + + List listeners = + new ArrayList<>(requestListeners.size() + requestMetrics.size()); + listeners.addAll(requestListeners); + + MeterBuilder meterBuilder = openTelemetry.getMeterProvider().meterBuilder(instrumentationName); + if (instrumentationVersion != null) { + meterBuilder.setInstrumentationVersion(instrumentationVersion); + } + if (schemaUrl != null) { + meterBuilder.setSchemaUrl(schemaUrl); + } + Meter meter = meterBuilder.build(); + for (RequestMetrics factory : requestMetrics) { + listeners.add(factory.create(meter)); + } + + return listeners; + } + + SpanSuppressionStrategy buildSpanSuppressionStrategy() { Set spanKeys = getSpanKeysFromAttributesExtractors(); if (enableSpanSuppressionByType) { return SpanSuppressionStrategy.from(spanKeys); 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 bb0eb3afc097..e98caa5b2cdc 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 @@ -749,10 +749,11 @@ void instrumentationVersion_default() { @Test void instrumentationVersion_custom() { - InstrumenterBuilder, Map> builder = - Instrumenter.builder(otelTesting.getOpenTelemetry(), "test", "1.0", name -> "span"); - - Instrumenter, Map> instrumenter = builder.newInstrumenter(); + Instrumenter, Map> instrumenter = + Instrumenter., Map>builder( + otelTesting.getOpenTelemetry(), "test", name -> "span") + .setInstrumentationVersion("1.0") + .newInstrumenter(); Context context = instrumenter.start(Context.root(), Collections.emptyMap()); assertThat(Span.fromContext(context)).isNotNull(); @@ -770,6 +771,30 @@ void instrumentationVersion_custom() { InstrumentationLibraryInfo.create("test", "1.0")))); } + @Test + void schemaUrl() { + Instrumenter, Map> instrumenter = + Instrumenter., Map>builder( + otelTesting.getOpenTelemetry(), "test", name -> "span") + .setSchemaUrl("https://opentelemetry.io/schemas/1.0.0") + .newInstrumenter(); + + Context context = instrumenter.start(Context.root(), Collections.emptyMap()); + assertThat(Span.fromContext(context)).isNotNull(); + + instrumenter.end(context, Collections.emptyMap(), Collections.emptyMap(), null); + + InstrumentationLibraryInfo expectedLibraryInfo = + InstrumentationLibraryInfo.create("test", null, "https://opentelemetry.io/schemas/1.0.0"); + otelTesting + .assertTraces() + .hasTracesSatisfyingExactly( + trace -> + trace.hasSpansSatisfyingExactly( + span -> + span.hasName("span").hasInstrumentationLibraryInfo(expectedLibraryInfo))); + } + private static void validateInstrumentationTypeSpanPresent(SpanKey spanKey, Context context) { Span span = Span.fromContext(context);