From 6831cc96e068679fd6c4f8718d5ca50fac260b5a Mon Sep 17 00:00:00 2001 From: Jack Berg Date: Fri, 5 Apr 2024 16:14:36 -0500 Subject: [PATCH] Add additional API incubator docs --- api/incubator/README.md | 45 +++++- api/incubator/build.gradle.kts | 5 +- .../incubator/events/EventApiUsageTest.java | 82 +++++++++++ .../logs/ExtendedLogsBridgeApiUsageTest.java | 74 ++++++++++ .../metrics/ExtendedMetricsApiUsageTest.java | 136 ++++++++++++++++++ .../ExtendedContextPropagatorsUsageTest.java | 76 ++++++++++ dependencyManagement/build.gradle.kts | 3 +- 7 files changed, 415 insertions(+), 6 deletions(-) create mode 100644 api/incubator/src/test/java/io/opentelemetry/api/incubator/events/EventApiUsageTest.java create mode 100644 api/incubator/src/test/java/io/opentelemetry/api/incubator/logs/ExtendedLogsBridgeApiUsageTest.java create mode 100644 api/incubator/src/test/java/io/opentelemetry/api/incubator/metrics/ExtendedMetricsApiUsageTest.java create mode 100644 api/incubator/src/test/java/io/opentelemetry/api/incubator/propagation/ExtendedContextPropagatorsUsageTest.java diff --git a/api/incubator/README.md b/api/incubator/README.md index 64264d01681..68769849e5c 100644 --- a/api/incubator/README.md +++ b/api/incubator/README.md @@ -1,11 +1,48 @@ -# ExtendedTracer +# API Incubator -Utility methods to make it easier to use the OpenTelemetry tracer. +Experimental APIs, including Event API, extended Log Bridge APIs, extended Metrics APIs, extended ContextPropagator APIs, and extended Trace APIs. + +## Event API + +Features: + +* Event API for producing log records according to [Event Semantic Conventions](https://opentelemetry.io/docs/specs/semconv/general/events/). + +See [EventApiUsageTest](./src/test/java/io/opentelemetry/api/incubator/events/EventApiUsageTest.java). + +## Extended Log Bridge API + +Features: + +* Set AnyValue log record body with arbitrarily complex data + +See [ExtendedLogsBridgeApiUsageTest](./src/test/java/io/opentelemetry/api/incubator/logs/ExtendedLogsBridgeApiUsageTest.java). + +## Extended Metrics APIs -## Usage Examples +Features: + +* Synchronous gauge instrument +* Attributes advice + +See [ExtendedMetricsApiUsageTest](./src/test/java/io/opentelemetry/api/incubator/metrics/ExtendedMetricsApiUsageTest.java). + +## Extended ContextPropagator APIs + +Features: + +* Simplified injection / extraction of context + +See [ExtendedContextPropagatorsUsageTest](./src/test/java/io/opentelemetry/api/incubator/propagation/ExtendedContextPropagatorsUsageTest.java). + +## ExtendedTracer + +Utility methods to make it easier to use the OpenTelemetry tracer. Here are some examples how the utility methods can help reduce boilerplate code. +TODO: translate examples to test to ensure no java compilation issues. + ### Tracing a function Before: @@ -145,7 +182,7 @@ String transactionId = extendedTracer.spanBuilder("checkout_cart") .startAndCall(() -> processCheckout(cartId)); ``` -## Exception handling +### Exception handling `ExtendedTracer` re-throws exceptions without modification. This means you can catch exceptions around `ExtendedTracer` calls and handle them as you would without `ExtendedTracer`. diff --git a/api/incubator/build.gradle.kts b/api/incubator/build.gradle.kts index b6f726654a3..05d2b80382c 100644 --- a/api/incubator/build.gradle.kts +++ b/api/incubator/build.gradle.kts @@ -14,6 +14,9 @@ dependencies { annotationProcessor("com.google.auto.value:auto-value") - testImplementation("io.opentelemetry.semconv:opentelemetry-semconv:1.21.0-alpha") testImplementation(project(":sdk:testing")) + + testImplementation("io.opentelemetry.semconv:opentelemetry-semconv") + + testImplementation("com.google.guava:guava") } diff --git a/api/incubator/src/test/java/io/opentelemetry/api/incubator/events/EventApiUsageTest.java b/api/incubator/src/test/java/io/opentelemetry/api/incubator/events/EventApiUsageTest.java new file mode 100644 index 00000000000..4b074ae5b13 --- /dev/null +++ b/api/incubator/src/test/java/io/opentelemetry/api/incubator/events/EventApiUsageTest.java @@ -0,0 +1,82 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.api.incubator.events; + +import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.assertThat; + +import com.google.common.collect.ImmutableMap; +import io.opentelemetry.api.common.Attributes; +import io.opentelemetry.api.incubator.logs.AnyValue; +import io.opentelemetry.sdk.logs.SdkLoggerProvider; +import io.opentelemetry.sdk.logs.export.SimpleLogRecordProcessor; +import io.opentelemetry.sdk.logs.internal.AnyValueBody; +import io.opentelemetry.sdk.logs.internal.SdkEventLoggerProvider; +import io.opentelemetry.sdk.resources.Resource; +import io.opentelemetry.sdk.testing.exporter.InMemoryLogRecordExporter; +import java.util.concurrent.TimeUnit; +import org.junit.jupiter.api.Test; + +/** Demonstrating usage of Event API. */ +class EventApiUsageTest { + + @Test + void eventApiUsage() { + // Setup SdkEventLoggerProvider, which delegates to SdkLoggerProvider + InMemoryLogRecordExporter exporter = InMemoryLogRecordExporter.create(); + SdkLoggerProvider loggerProvider = + SdkLoggerProvider.builder() + // Default resource used for demonstration purposes + .setResource(Resource.getDefault()) + // Simple processor w/ in-memory exporter used for demonstration purposes + .addLogRecordProcessor(SimpleLogRecordProcessor.create(exporter)) + .build(); + EventLoggerProvider eventLoggerProvider = SdkEventLoggerProvider.create(loggerProvider); + + // Get an EventLogger for a scope + EventLogger eventLogger = eventLoggerProvider.get("org.foo.my-scope"); + + // Emit an event + eventLogger + .builder("org.foo.my-event") + // Add fields to the payload. The API has helpers for adding field values which are + // primitives or arrays of primitives, but you can also add a field with type AnyValue, + // allowing for arbitrarily complex payloads. + .put("key1", "value1") + .put( + "key2", + AnyValue.of( + ImmutableMap.of( + "childKey1", AnyValue.of("value2"), "childKey2", AnyValue.of("value3")))) + // Optionally set other fields, including timestamp, severity, context, and attributes + // (attributes provide additional details about the event which are not part of the well + // defined payload) + .emit(); + + // Events manifest as log records with an event.name attribute, and with the payload fields in + // the AnyValue log record body + loggerProvider.forceFlush().join(10, TimeUnit.SECONDS); + assertThat(exporter.getFinishedLogRecordItems()) + .satisfiesExactly( + logData -> { + assertThat(logData) + .hasAttributes( + Attributes.builder().put("event.name", "org.foo.my-event").build()); + assertThat(((AnyValueBody) logData.getBody()).asAnyValue()) + .isEqualTo( + AnyValue.of( + ImmutableMap.of( + "key1", + AnyValue.of("value1"), + "key2", + AnyValue.of( + ImmutableMap.of( + "childKey1", + AnyValue.of("value2"), + "childKey2", + AnyValue.of("value3")))))); + }); + } +} diff --git a/api/incubator/src/test/java/io/opentelemetry/api/incubator/logs/ExtendedLogsBridgeApiUsageTest.java b/api/incubator/src/test/java/io/opentelemetry/api/incubator/logs/ExtendedLogsBridgeApiUsageTest.java new file mode 100644 index 00000000000..7a1f82e64d0 --- /dev/null +++ b/api/incubator/src/test/java/io/opentelemetry/api/incubator/logs/ExtendedLogsBridgeApiUsageTest.java @@ -0,0 +1,74 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.api.incubator.logs; + +import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.assertThat; + +import com.google.common.collect.ImmutableMap; +import io.opentelemetry.api.logs.Logger; +import io.opentelemetry.sdk.logs.SdkLoggerProvider; +import io.opentelemetry.sdk.logs.export.SimpleLogRecordProcessor; +import io.opentelemetry.sdk.logs.internal.AnyValueBody; +import io.opentelemetry.sdk.resources.Resource; +import io.opentelemetry.sdk.testing.exporter.InMemoryLogRecordExporter; +import java.util.concurrent.TimeUnit; +import org.junit.jupiter.api.Test; + +/** Demonstrating usage of extended Logs Bridge API. */ +class ExtendedLogsBridgeApiUsageTest { + + @Test + void extendedLogRecordBuilderUsage() { + // Setup SdkLoggerProvider + InMemoryLogRecordExporter exporter = InMemoryLogRecordExporter.create(); + SdkLoggerProvider loggerProvider = + SdkLoggerProvider.builder() + // Default resource used for demonstration purposes + .setResource(Resource.getDefault()) + // Simple processor w/ in-memory exporter used for demonstration purposes + .addLogRecordProcessor(SimpleLogRecordProcessor.create(exporter)) + .build(); + + // Get a Logger for a scope + Logger logger = loggerProvider.get("org.foo.my-scope"); + + // Cast to ExtendedLogRecordBuilder, and emit a log + ((ExtendedLogRecordBuilder) logger.logRecordBuilder()) + // ...can set AnyValue log record body, allowing for arbitrarily complex data + .setBody( + AnyValue.of( + ImmutableMap.of( + "key1", + AnyValue.of("value1"), + "key2", + AnyValue.of( + ImmutableMap.of( + "childKey1", + AnyValue.of("value2"), + "childKey2", + AnyValue.of("value3")))))) + .emit(); + + // SDK can access AnyValue body by casting to AnyValueBody + loggerProvider.forceFlush().join(10, TimeUnit.SECONDS); + assertThat(exporter.getFinishedLogRecordItems()) + .satisfiesExactly( + logData -> + assertThat(((AnyValueBody) logData.getBody()).asAnyValue()) + .isEqualTo( + AnyValue.of( + ImmutableMap.of( + "key1", + AnyValue.of("value1"), + "key2", + AnyValue.of( + ImmutableMap.of( + "childKey1", + AnyValue.of("value2"), + "childKey2", + AnyValue.of("value3"))))))); + } +} diff --git a/api/incubator/src/test/java/io/opentelemetry/api/incubator/metrics/ExtendedMetricsApiUsageTest.java b/api/incubator/src/test/java/io/opentelemetry/api/incubator/metrics/ExtendedMetricsApiUsageTest.java new file mode 100644 index 00000000000..ea219c1cc70 --- /dev/null +++ b/api/incubator/src/test/java/io/opentelemetry/api/incubator/metrics/ExtendedMetricsApiUsageTest.java @@ -0,0 +1,136 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.api.incubator.metrics; + +import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.assertThat; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableSet; +import io.opentelemetry.api.common.AttributeKey; +import io.opentelemetry.api.common.Attributes; +import io.opentelemetry.api.metrics.LongCounter; +import io.opentelemetry.api.metrics.Meter; +import io.opentelemetry.sdk.metrics.InstrumentSelector; +import io.opentelemetry.sdk.metrics.SdkMeterProvider; +import io.opentelemetry.sdk.metrics.View; +import io.opentelemetry.sdk.resources.Resource; +import io.opentelemetry.sdk.testing.exporter.InMemoryMetricReader; +import org.junit.jupiter.api.Test; + +/** Demonstrating usage of extended Metrics API. */ +class ExtendedMetricsApiUsageTest { + + @Test + void synchronousGaugeUsage() { + // Setup SdkMeterProvider + InMemoryMetricReader reader = InMemoryMetricReader.create(); + SdkMeterProvider meterProvider = + SdkMeterProvider.builder() + // Default resource used for demonstration purposes + .setResource(Resource.getDefault()) + // In-memory reader used for demonstration purposes + .registerMetricReader(reader) + .build(); + + // Get a Meter for a scope + Meter meter = meterProvider.get("org.foo.my-scope"); + + // Cast GaugeBuilder to ExtendedDoubleGaugeBuilder + DoubleGauge gauge = ((ExtendedDoubleGaugeBuilder) meter.gaugeBuilder("my-gauge")).build(); + + // Call set synchronously to set the value + gauge.set(1.0, Attributes.builder().put("key", "value1").build()); + gauge.set(2.0, Attributes.builder().put("key", "value2").build()); + + assertThat(reader.collectAllMetrics()) + .satisfiesExactly( + metricData -> + assertThat(metricData) + .hasName("my-gauge") + .hasDoubleGaugeSatisfying( + gaugeAssert -> + gaugeAssert.hasPointsSatisfying( + point -> + point + .hasValue(1.0) + .hasAttributes( + Attributes.builder().put("key", "value1").build()), + point -> + point + .hasValue(2.0) + .hasAttributes( + Attributes.builder().put("key", "value2").build())))); + } + + @Test + void attributesAdvice() { + // Setup SdkMeterProvider + InMemoryMetricReader reader = InMemoryMetricReader.create(); + SdkMeterProvider meterProvider = + SdkMeterProvider.builder() + // Default resource used for demonstration purposes + .setResource(Resource.getDefault()) + // In-memory reader used for demonstration purposes + .registerMetricReader(reader) + // Register a view which indicates that for counter1, attributes key1, key2 should be + // retained + .registerView( + InstrumentSelector.builder().setName("counter1").build(), + View.builder().setAttributeFilter(ImmutableSet.of("key1", "key2")).build()) + .build(); + + // Get a Meter for a scope + Meter meter = meterProvider.get("org.foo.my-scope"); + + // To apply attribute advice, cast the instrument builder to appropriate + // Extended{Instrument}Builder, and call setAttributeAdvice + // Here we create counter1 and counter2, both configured to only retain attribute key1. counter1 + // has a view configured which overrides this and retains key1, key2. + LongCounter counter1 = + ((ExtendedLongCounterBuilder) meter.counterBuilder("counter1")) + .setAttributesAdvice(ImmutableList.of(AttributeKey.stringKey("key1"))) + .build(); + LongCounter counter2 = + ((ExtendedLongCounterBuilder) meter.counterBuilder("counter2")) + .setAttributesAdvice(ImmutableList.of(AttributeKey.stringKey("key1"))) + .build(); + + // Record data with attribute key1, key2 + counter1.add(1, Attributes.builder().put("key1", "value1").put("key2", "value2").build()); + counter2.add(1, Attributes.builder().put("key1", "value1").put("key2", "value2").build()); + + // Verify that counter1 has both key1, key2 since view overrides the attribute advice + // Verify that counter2 only has key1, since attribute advice causes key2 to be dropped by + // default + assertThat(reader.collectAllMetrics()) + .satisfiesExactlyInAnyOrder( + metricData -> + assertThat(metricData) + .hasName("counter1") + .hasLongSumSatisfying( + sumAssert -> + sumAssert.hasPointsSatisfying( + point -> + point + .hasValue(1L) + .hasAttributes( + Attributes.builder() + .put("key1", "value1") + .put("key2", "value2") + .build()))), + metricData -> + assertThat(metricData) + .hasName("counter2") + .hasLongSumSatisfying( + sumAssert -> + sumAssert.hasPointsSatisfying( + point -> + point + .hasValue(1L) + .hasAttributes( + Attributes.builder().put("key1", "value1").build())))); + } +} diff --git a/api/incubator/src/test/java/io/opentelemetry/api/incubator/propagation/ExtendedContextPropagatorsUsageTest.java b/api/incubator/src/test/java/io/opentelemetry/api/incubator/propagation/ExtendedContextPropagatorsUsageTest.java new file mode 100644 index 00000000000..d3bacb21483 --- /dev/null +++ b/api/incubator/src/test/java/io/opentelemetry/api/incubator/propagation/ExtendedContextPropagatorsUsageTest.java @@ -0,0 +1,76 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.api.incubator.propagation; + +import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.assertThat; + +import com.google.common.collect.ImmutableMap; +import io.opentelemetry.api.trace.Span; +import io.opentelemetry.api.trace.SpanContext; +import io.opentelemetry.api.trace.TraceFlags; +import io.opentelemetry.api.trace.TraceState; +import io.opentelemetry.api.trace.Tracer; +import io.opentelemetry.api.trace.propagation.W3CTraceContextPropagator; +import io.opentelemetry.context.Context; +import io.opentelemetry.context.Scope; +import io.opentelemetry.context.propagation.ContextPropagators; +import io.opentelemetry.context.propagation.TextMapPropagator; +import io.opentelemetry.sdk.trace.SdkTracerProvider; +import io.opentelemetry.sdk.trace.samplers.Sampler; +import java.util.Map; +import org.junit.jupiter.api.Test; + +/** Demonstrating usage of extended ContextPropagators API. */ +class ExtendedContextPropagatorsUsageTest { + + @Test + void getTextMapPropagationContextUsage() { + // Setup Propagators + ContextPropagators propagators = + ContextPropagators.create( + TextMapPropagator.composite(W3CTraceContextPropagator.getInstance())); + + // Setup SdkTracerProvider + SdkTracerProvider tracerProvider = + SdkTracerProvider.builder().setSampler(Sampler.alwaysOn()).build(); + + // Get a Tracer for a scope + Tracer tracer = tracerProvider.get("org.foo.my-scope"); + + try (Scope scope = tracer.spanBuilder("span name").startSpan().makeCurrent()) { + // Simplify context injection by getting a text map of the key/value pairs to inject + Map textMap = + ExtendedContextPropagators.getTextMapPropagationContext(propagators); + // Assert textmap contains the "traceparent" field as injected by W3CTraceContextPropagator + assertThat(textMap) + .hasEntrySatisfying("traceparent", value -> assertThat(value).isNotEmpty()); + } + } + + @Test + void extractTextMapPropagationContextUsage() { + // Setup Propagators + ContextPropagators propagators = + ContextPropagators.create( + TextMapPropagator.composite(W3CTraceContextPropagator.getInstance())); + + // Setup map with context key/value pairs + Map contextCarrier = + ImmutableMap.of("traceparent", "00-713bde54561be5ded62545d0e7369d4a-3c3a5ddefce9c1e1-01"); + + // Extract context from the carrier map + Context context = + ExtendedContextPropagators.extractTextMapPropagationContext(contextCarrier, propagators); + // Assert SpanContext is properly extracted from the W3cTraceContextPropagator + assertThat(Span.fromContext(context).getSpanContext()) + .isEqualTo( + SpanContext.createFromRemoteParent( + "713bde54561be5ded62545d0e7369d4a", + "3c3a5ddefce9c1e1", + TraceFlags.getSampled(), + TraceState.getDefault())); + } +} diff --git a/dependencyManagement/build.gradle.kts b/dependencyManagement/build.gradle.kts index acc690a6673..5ad03624ce5 100644 --- a/dependencyManagement/build.gradle.kts +++ b/dependencyManagement/build.gradle.kts @@ -65,8 +65,9 @@ val DEPENDENCIES = listOf( "eu.rekawek.toxiproxy:toxiproxy-java:2.1.7", "io.github.netmikey.logunit:logunit-jul:2.0.0", "io.jaegertracing:jaeger-client:1.8.1", - "io.opentelemetry.proto:opentelemetry-proto:1.1.0-alpha", "io.opentelemetry.contrib:opentelemetry-aws-xray-propagator:1.29.0-alpha", + "io.opentelemetry.semconv:opentelemetry-semconv:1.21.0-alpha", + "io.opentelemetry.proto:opentelemetry-proto:1.1.0-alpha", "io.opentracing:opentracing-api:0.33.0", "io.opentracing:opentracing-noop:0.33.0", "io.prometheus:prometheus-metrics-exporter-httpserver:1.2.1",