diff --git a/docs/apidiffs/current_vs_latest/opentelemetry-sdk-testing.txt b/docs/apidiffs/current_vs_latest/opentelemetry-sdk-testing.txt index 988fe184f0f..4d359472772 100644 --- a/docs/apidiffs/current_vs_latest/opentelemetry-sdk-testing.txt +++ b/docs/apidiffs/current_vs_latest/opentelemetry-sdk-testing.txt @@ -2,6 +2,17 @@ Comparing source compatibility of opentelemetry-sdk-testing-1.42.0-SNAPSHOT.jar *** MODIFIED CLASS: PUBLIC FINAL io.opentelemetry.sdk.testing.assertj.LogRecordDataAssert (not serializable) === CLASS FILE FORMAT VERSION: 52.0 <- 52.0 +++ NEW METHOD: PUBLIC(+) io.opentelemetry.sdk.testing.assertj.LogRecordDataAssert hasBody(io.opentelemetry.api.common.Value) + +++ NEW METHOD: PUBLIC(+) io.opentelemetry.sdk.testing.assertj.LogRecordDataAssert hasBodyField(java.lang.String, java.lang.String) + +++ NEW METHOD: PUBLIC(+) io.opentelemetry.sdk.testing.assertj.LogRecordDataAssert hasBodyField(java.lang.String, long) + +++ NEW METHOD: PUBLIC(+) io.opentelemetry.sdk.testing.assertj.LogRecordDataAssert hasBodyField(java.lang.String, double) + +++ NEW METHOD: PUBLIC(+) io.opentelemetry.sdk.testing.assertj.LogRecordDataAssert hasBodyField(java.lang.String, boolean) + +++ NEW METHOD: PUBLIC(+) io.opentelemetry.sdk.testing.assertj.LogRecordDataAssert hasBodyField(java.lang.String, java.lang.String[]) + +++ NEW METHOD: PUBLIC(+) io.opentelemetry.sdk.testing.assertj.LogRecordDataAssert hasBodyField(java.lang.String, long[]) + +++ NEW METHOD: PUBLIC(+) io.opentelemetry.sdk.testing.assertj.LogRecordDataAssert hasBodyField(java.lang.String, double[]) + +++ NEW METHOD: PUBLIC(+) io.opentelemetry.sdk.testing.assertj.LogRecordDataAssert hasBodyField(java.lang.String, boolean[]) + +++ NEW METHOD: PUBLIC(+) io.opentelemetry.sdk.testing.assertj.LogRecordDataAssert hasBodyField(java.lang.String, io.opentelemetry.api.common.Value) + +++ NEW METHOD: PUBLIC(+) io.opentelemetry.sdk.testing.assertj.LogRecordDataAssert hasBodyField(io.opentelemetry.api.common.AttributeKey, java.lang.Object) + GENERIC TEMPLATES: +++ T:java.lang.Object **** MODIFIED CLASS: PUBLIC ABSTRACT io.opentelemetry.sdk.testing.logs.TestLogRecordData (not serializable) === CLASS FILE FORMAT VERSION: 52.0 <- 52.0 +++ NEW METHOD: PUBLIC(+) io.opentelemetry.sdk.logs.data.Body getBody() diff --git a/sdk/testing/build.gradle.kts b/sdk/testing/build.gradle.kts index a9f2534aafd..4d3f3aa4ec8 100644 --- a/sdk/testing/build.gradle.kts +++ b/sdk/testing/build.gradle.kts @@ -8,6 +8,7 @@ otelJava.moduleName.set("io.opentelemetry.sdk.testing") dependencies { api(project(":api:all")) + api(project(":api:incubator")) api(project(":sdk:all")) compileOnly("org.assertj:assertj-core") diff --git a/sdk/testing/src/main/java/io/opentelemetry/sdk/testing/assertj/LogRecordDataAssert.java b/sdk/testing/src/main/java/io/opentelemetry/sdk/testing/assertj/LogRecordDataAssert.java index 11031aa9ef4..b717ecedad9 100644 --- a/sdk/testing/src/main/java/io/opentelemetry/sdk/testing/assertj/LogRecordDataAssert.java +++ b/sdk/testing/src/main/java/io/opentelemetry/sdk/testing/assertj/LogRecordDataAssert.java @@ -5,18 +5,25 @@ package io.opentelemetry.sdk.testing.assertj; +import static io.opentelemetry.api.common.ValueType.KEY_VALUE_LIST; import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.assertThat; +import static java.util.stream.Collectors.toList; +import static org.junit.Assert.assertNotNull; import io.opentelemetry.api.common.AttributeKey; import io.opentelemetry.api.common.Attributes; import io.opentelemetry.api.common.AttributesBuilder; +import io.opentelemetry.api.common.KeyValue; import io.opentelemetry.api.common.Value; +import io.opentelemetry.api.common.ValueType; import io.opentelemetry.api.logs.Severity; import io.opentelemetry.api.trace.SpanContext; import io.opentelemetry.sdk.common.InstrumentationScopeInfo; import io.opentelemetry.sdk.logs.data.LogRecordData; import io.opentelemetry.sdk.resources.Resource; +import java.util.ArrayList; import java.util.Arrays; +import java.util.List; import java.util.Map; import java.util.Objects; import java.util.function.Consumer; @@ -167,6 +174,138 @@ public LogRecordDataAssert hasBody(@Nullable Value body) { return this; } + /** + * Asserts the log has a body of type {@link ValueType#KEY_VALUE_LIST}, containing a field with + * the given {@code key} and String {@code value}. + */ + public LogRecordDataAssert hasBodyField(String key, String value) { + return hasBodyField(key, Value.of(value)); + } + + /** + * Asserts the log has a body of type {@link ValueType#KEY_VALUE_LIST}, containing a field with + * the given {@code key} and long {@code value}. + */ + public LogRecordDataAssert hasBodyField(String key, long value) { + return hasBodyField(key, Value.of(value)); + } + + /** + * Asserts the log has a body of type {@link ValueType#KEY_VALUE_LIST}, containing a field with + * the given {@code key} and double {@code value}. + */ + public LogRecordDataAssert hasBodyField(String key, double value) { + return hasBodyField(key, Value.of(value)); + } + + /** + * Asserts the log has a body of type {@link ValueType#KEY_VALUE_LIST}, containing a field with + * the given {@code key} and boolean {@code value}. + */ + public LogRecordDataAssert hasBodyField(String key, boolean value) { + return hasBodyField(key, Value.of(value)); + } + + /** + * Asserts the log has a body of type {@link ValueType#KEY_VALUE_LIST}, containing a field with + * the given {@code key} and list of String {@code value}s. + */ + public LogRecordDataAssert hasBodyField(String key, String... value) { + List> values = new ArrayList<>(value.length); + for (String val : value) { + values.add(Value.of(val)); + } + return hasBodyField(key, Value.of(values)); + } + + /** + * Asserts the log has a body of type {@link ValueType#KEY_VALUE_LIST}, containing a field with + * the given {@code key} and list of long {@code value}s. + */ + public LogRecordDataAssert hasBodyField(String key, long... value) { + List> values = new ArrayList<>(value.length); + for (long val : value) { + values.add(Value.of(val)); + } + return hasBodyField(key, Value.of(values)); + } + + /** + * Asserts the log has a body of type {@link ValueType#KEY_VALUE_LIST}, containing a field with + * the given {@code key} and list of double {@code value}s. + */ + public LogRecordDataAssert hasBodyField(String key, double... value) { + List> values = new ArrayList<>(value.length); + for (double val : value) { + values.add(Value.of(val)); + } + return hasBodyField(key, Value.of(values)); + } + + /** + * Asserts the log has a body of type {@link ValueType#KEY_VALUE_LIST}, containing a field with + * the given {@code key} and list of boolean {@code value}s. + */ + public LogRecordDataAssert hasBodyField(String key, boolean... value) { + List> values = new ArrayList<>(value.length); + for (boolean val : value) { + values.add(Value.of(val)); + } + return hasBodyField(key, Value.of(values)); + } + + /** + * Asserts the log has a body of type {@link ValueType#KEY_VALUE_LIST}, containing a field with + * the given {@code key} and {@code value}. + */ + @SuppressWarnings({"unchecked"}) + public LogRecordDataAssert hasBodyField(String key, Value value) { + isNotNull(); + Value bodyValue = actual.getBodyValue(); + assertNotNull( + "Body was not expected to be null.", bodyValue); // Can't use assertj or nullaway complains + assertThat(bodyValue.getType()).isEqualTo(KEY_VALUE_LIST); + Value> body = (Value>) bodyValue; + List payload = body.getValue(); + KeyValue expected = KeyValue.of(key, value); + assertThat(payload).contains(expected); + return this; + } + + /** + * Asserts the log has a body of type {@link ValueType#KEY_VALUE_LIST}, containing a field with + * the given attribute {@code key} and {@code value}. + */ + @SuppressWarnings({"unchecked"}) + public LogRecordDataAssert hasBodyField(AttributeKey key, T value) { + switch (key.getType()) { + case STRING: + return hasBodyField(key.getKey(), (String) value); + case BOOLEAN: + return hasBodyField(key.getKey(), (boolean) value); + case LONG: + return hasBodyField(key.getKey(), (long) value); + case DOUBLE: + return hasBodyField(key.getKey(), (double) value); + case STRING_ARRAY: + return hasBodyField( + key.getKey(), + Value.of(((List) value).stream().map(Value::of).collect(toList()))); + case BOOLEAN_ARRAY: + return hasBodyField( + key.getKey(), + Value.of(((List) value).stream().map(Value::of).collect(toList()))); + case LONG_ARRAY: + return hasBodyField( + key.getKey(), Value.of(((List) value).stream().map(Value::of).collect(toList()))); + case DOUBLE_ARRAY: + return hasBodyField( + key.getKey(), + Value.of(((List) value).stream().map(Value::of).collect(toList()))); + } + return this; + } + /** Asserts the log has the given attributes. */ public LogRecordDataAssert hasAttributes(Attributes attributes) { isNotNull(); diff --git a/sdk/testing/src/test/java/io/opentelemetry/sdk/testing/assertj/LogAssertionsTest.java b/sdk/testing/src/test/java/io/opentelemetry/sdk/testing/assertj/LogAssertionsTest.java index 15da07a5214..849ba25b90f 100644 --- a/sdk/testing/src/test/java/io/opentelemetry/sdk/testing/assertj/LogAssertionsTest.java +++ b/sdk/testing/src/test/java/io/opentelemetry/sdk/testing/assertj/LogAssertionsTest.java @@ -5,6 +5,14 @@ package io.opentelemetry.sdk.testing.assertj; +import static io.opentelemetry.api.common.AttributeKey.booleanArrayKey; +import static io.opentelemetry.api.common.AttributeKey.booleanKey; +import static io.opentelemetry.api.common.AttributeKey.doubleArrayKey; +import static io.opentelemetry.api.common.AttributeKey.doubleKey; +import static io.opentelemetry.api.common.AttributeKey.longArrayKey; +import static io.opentelemetry.api.common.AttributeKey.longKey; +import static io.opentelemetry.api.common.AttributeKey.stringArrayKey; +import static io.opentelemetry.api.common.AttributeKey.stringKey; import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.assertThat; import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.attributeEntry; import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.equalTo; @@ -13,15 +21,22 @@ import io.opentelemetry.api.common.AttributeKey; import io.opentelemetry.api.common.Attributes; +import io.opentelemetry.api.common.Value; +import io.opentelemetry.api.incubator.events.EventLogger; import io.opentelemetry.api.logs.Severity; import io.opentelemetry.api.trace.SpanContext; import io.opentelemetry.api.trace.TraceFlags; import io.opentelemetry.api.trace.TraceState; import io.opentelemetry.sdk.common.InstrumentationScopeInfo; +import io.opentelemetry.sdk.logs.SdkLoggerProvider; import io.opentelemetry.sdk.logs.data.LogRecordData; +import io.opentelemetry.sdk.logs.export.SimpleLogRecordProcessor; +import io.opentelemetry.sdk.logs.internal.SdkEventLoggerProvider; import io.opentelemetry.sdk.resources.Resource; +import io.opentelemetry.sdk.testing.exporter.InMemoryLogRecordExporter; import io.opentelemetry.sdk.testing.logs.TestLogRecordData; import java.util.Arrays; +import java.util.List; import java.util.concurrent.TimeUnit; import org.junit.jupiter.api.Test; @@ -33,7 +48,7 @@ public class LogAssertionsTest { private static final String TRACE_ID = "00000000000000010000000000000002"; private static final String SPAN_ID = "0000000000000003"; - private static final AttributeKey DOG = AttributeKey.stringKey("dog"); + private static final AttributeKey DOG = stringKey("dog"); private static final Attributes ATTRIBUTES = Attributes.builder() .put("bear", "mya") @@ -79,7 +94,7 @@ void passing() { attributes -> assertThat(attributes) .hasSize(2) - .containsEntry(AttributeKey.stringKey("dog"), "bark") + .containsEntry(stringKey("dog"), "bark") .hasEntrySatisfying(DOG, value -> assertThat(value).hasSize(4)) .hasEntrySatisfying( AttributeKey.booleanKey("dog is cute"), @@ -118,14 +133,13 @@ void passing() { attributes -> OpenTelemetryAssertions.assertThat(attributes) .hasSize(8) - .containsEntry(AttributeKey.stringKey("bear"), "mya") - .hasEntrySatisfying( - AttributeKey.stringKey("bear"), value -> assertThat(value).hasSize(3)) + .containsEntry(stringKey("bear"), "mya") + .hasEntrySatisfying(stringKey("bear"), value -> assertThat(value).hasSize(3)) .containsEntry("bear", "mya") .containsEntry("warm", true) .containsEntry("temperature", 30) - .containsEntry(AttributeKey.longKey("temperature"), 30L) - .containsEntry(AttributeKey.longKey("temperature"), 30) + .containsEntry(longKey("temperature"), 30L) + .containsEntry(longKey("temperature"), 30) .containsEntry("length", 1.2) .containsEntry("colors", "red", "blue") .containsEntryWithStringValuesOf("colors", Arrays.asList("red", "blue")) @@ -135,7 +149,7 @@ void passing() { .containsEntryWithLongValuesOf("scores", Arrays.asList(0L, 1L)) .containsEntry("coins", 0.01, 0.05, 0.1) .containsEntryWithDoubleValuesOf("coins", Arrays.asList(0.01, 0.05, 0.1)) - .containsKey(AttributeKey.stringKey("bear")) + .containsKey(stringKey("bear")) .containsKey("bear") .containsOnly( attributeEntry("bear", "mya"), @@ -147,12 +161,12 @@ void passing() { attributeEntry("scores", 0L, 1L), attributeEntry("coins", 0.01, 0.05, 0.1))) .hasAttributesSatisfying( - equalTo(AttributeKey.stringKey("bear"), "mya"), + equalTo(stringKey("bear"), "mya"), equalTo(AttributeKey.booleanArrayKey("conditions"), Arrays.asList(false, true))) .hasAttributesSatisfyingExactly( - equalTo(AttributeKey.stringKey("bear"), "mya"), + equalTo(stringKey("bear"), "mya"), equalTo(AttributeKey.booleanKey("warm"), true), - equalTo(AttributeKey.longKey("temperature"), 30L), + equalTo(longKey("temperature"), 30L), equalTo(AttributeKey.doubleKey("length"), 1.2), equalTo(AttributeKey.stringArrayKey("colors"), Arrays.asList("red", "blue")), equalTo(AttributeKey.booleanArrayKey("conditions"), Arrays.asList(false, true)), @@ -228,7 +242,7 @@ void failure() { .hasAttributesSatisfying( attributes -> OpenTelemetryAssertions.assertThat(attributes) - .containsKey(AttributeKey.stringKey("cat")))) + .containsKey(stringKey("cat")))) .isInstanceOf(AssertionError.class); assertThatThrownBy( () -> @@ -256,24 +270,71 @@ void failure() { attributes -> OpenTelemetryAssertions.assertThat(attributes) .hasEntrySatisfying( - AttributeKey.stringKey("bear"), - value -> assertThat(value).hasSize(2)))) + stringKey("bear"), value -> assertThat(value).hasSize(2)))) .isInstanceOf(AssertionError.class); assertThatThrownBy( - () -> - assertThat(LOG_DATA) - .hasAttributesSatisfying(equalTo(AttributeKey.stringKey("bear"), "moo"))) + () -> assertThat(LOG_DATA).hasAttributesSatisfying(equalTo(stringKey("bear"), "moo"))) .isInstanceOf(AssertionError.class); assertThatThrownBy( () -> assertThat(LOG_DATA) .hasAttributesSatisfyingExactly( - equalTo(AttributeKey.stringKey("bear"), "mya"), + equalTo(stringKey("bear"), "mya"), equalTo(AttributeKey.booleanKey("warm"), true), - equalTo(AttributeKey.longKey("temperature"), 30L), + equalTo(longKey("temperature"), 30L), equalTo(AttributeKey.doubleKey("length"), 1.2))) .isInstanceOf(AssertionError.class); assertThatThrownBy(() -> assertThat(LOG_DATA).hasTotalAttributeCount(11)) .isInstanceOf(AssertionError.class); } + + @Test + void eventBodyAssertions() { + InMemoryLogRecordExporter exporter = InMemoryLogRecordExporter.create(); + SdkLoggerProvider loggerProvider = + SdkLoggerProvider.builder() + .addLogRecordProcessor(SimpleLogRecordProcessor.create(exporter)) + .build(); + EventLogger eventLogger = SdkEventLoggerProvider.create(loggerProvider).get("test.test"); + eventLogger + .builder("foo") + .put("foostr", "bar") + .put("foobool", true) + .put("foolong", 12L) + .put("foodbl", 12.0) + .put("foostra", "bar", "baz", "buzz") + .put("foolonga", 9, 0, 2, 1, 0) + .put("foodbla", 9.1, 0.2, 2.3, 1.4, 0.5) + .put("fooboola", true, true, true, false) + .put("fooany", Value.of("grim")) + .put(stringKey("ak_str"), "bar") + .put(booleanKey("ak_bool"), true) + .put(longKey("ak_long"), 12L) + .put(doubleKey("ak_dbl"), 12.0) + .put(stringArrayKey("ak_stra"), Arrays.asList("bar", "baz", "buzz")) + .put(longArrayKey("ak_longa"), Arrays.asList(9L, 0L, 2L, 1L, 0L)) + .put(doubleArrayKey("ak_dbla"), Arrays.asList(9.1, 0.2, 2.3, 1.4, 0.5)) + .put(booleanArrayKey("ak_boola"), Arrays.asList(true, true, true, false)) + .emit(); + List logs = exporter.getFinishedLogRecordItems(); + assertThat(logs).hasSize(1); + assertThat(logs.get(0)) + .hasBodyField("foostr", "bar") + .hasBodyField("foobool", true) + .hasBodyField("foolong", 12L) + .hasBodyField("foodbl", 12.0) + .hasBodyField("foostra", "bar", "baz", "buzz") + .hasBodyField("foolonga", 9, 0, 2, 1, 0) + .hasBodyField("foodbla", 9.1, 0.2, 2.3, 1.4, 0.5) + .hasBodyField("fooboola", true, true, true, false) + .hasBodyField("fooany", Value.of("grim")) + .hasBodyField(stringKey("ak_str"), "bar") + .hasBodyField(booleanKey("ak_bool"), true) + .hasBodyField(longKey("ak_long"), 12L) + .hasBodyField(doubleKey("ak_dbl"), 12.0) + .hasBodyField(stringArrayKey("ak_stra"), Arrays.asList("bar", "baz", "buzz")) + .hasBodyField(longArrayKey("ak_longa"), Arrays.asList(9L, 0L, 2L, 1L, 0L)) + .hasBodyField(doubleArrayKey("ak_dbla"), Arrays.asList(9.1, 0.2, 2.3, 1.4, 0.5)) + .hasBodyField(booleanArrayKey("ak_boola"), Arrays.asList(true, true, true, false)); + } }